diff options
Diffstat (limited to 'pyload/web/app')
100 files changed, 5589 insertions, 0 deletions
| diff --git a/pyload/web/app/fonts/Abel-Regular.ttf b/pyload/web/app/fonts/Abel-Regular.ttfBinary files differ new file mode 100755 index 000000000..e37beb972 --- /dev/null +++ b/pyload/web/app/fonts/Abel-Regular.ttf diff --git a/pyload/web/app/fonts/Abel-Regular.woff b/pyload/web/app/fonts/Abel-Regular.woffBinary files differ new file mode 100644 index 000000000..ab8954389 --- /dev/null +++ b/pyload/web/app/fonts/Abel-Regular.woff diff --git a/pyload/web/app/images/default/checks_sheet.png b/pyload/web/app/images/default/checks_sheet.pngBinary files differ new file mode 100644 index 000000000..9662b8070 --- /dev/null +++ b/pyload/web/app/images/default/checks_sheet.png diff --git a/pyload/web/app/images/icon.png b/pyload/web/app/images/icon.pngBinary files differ new file mode 100644 index 000000000..1ab4ca081 --- /dev/null +++ b/pyload/web/app/images/icon.png diff --git a/pyload/web/app/index.html b/pyload/web/app/index.html new file mode 100644 index 000000000..4a4195b13 --- /dev/null +++ b/pyload/web/app/index.html @@ -0,0 +1,110 @@ +<!DOCTYPE html> +<html lang="en"> +<head> +    <meta charset="utf-8"> +    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> +    <!-- TODO: dynamic title --> +    <title>pyLoad WebUI</title> +    <meta name="description" content="pyLoad WebUI"> +    <meta name="viewport" content="width=device-width"> + +    <!-- TODO: basepath and templates --> +    <link href="styles/font.css" rel="stylesheet" type="text/css"/> +    <link href="styles/default/main.css" rel="stylesheet" type="text/css"> +    <link href="vendor/select2.css" rel="stylesheet" type="text/css"/> + + +    <!-- build:js scripts/config.js --> +    <script data-main="scripts/config" src="components/requirejs/require.js"></script> +    <!-- endbuild --> + +    <script type="text/javascript"> + +        // Use value set by templateEngine or default val +        function configValue(string, defaultValue) { +            if (string.indexOf('{{') > -1) +                return defaultValue; +            return string; +        } + +        window.dates = { +            weeks: ['week', 'weeks'], +            days: ['day', 'days'], +            hours: ['hour', 'hours'], +            minutes: ['minute', 'minutes'], +            seconds: ['second', 'seconds'] +        }; // TODO carefully when translating + +        window.hostProtocol = window.location.protocol +  '//'; +        window.hostAddress = window.location.hostname; +        window.hostPort = configValue('{{web}}', '8001'); +        // TODO +        window.pathPrefix = '/'; +        window.wsAddress = configValue('{{ws}}', 'ws://%s:7227'); + +        require(['config'], function(Config) { +            require(['default'], function(App) { +            }); +        }) +    </script> + +</head> +<body> +<div id="wrap"> +    <header> +        <div class="container-fluid"> +            <div class="row-fluid" id="header"> +                <div class="span3"> +                    <div class="logo"></div> +                    <span class="title visible-large-screen">pyLoad</span> +                </div> +            </div> +        </div> +        <div id="notification-area"></div> +        <div id="selection-area"></div> +    </header> +    <div id="content-container" class="container-fluid"> +        <div class="row-fluid" id="actionbar"> +        </div> +        <div class="row-fluid" id="content"> +        </div> +    </div> +</div> +<footer> +    <div class="container-fluid"> +        <div class="row-fluid"> +            <div class="span2 offset1"> +                <div class="copyright"> +                    © 2008-2013<br> +                    <a href="http://pyload.org/" target="_blank">The pyLoad Team</a><br> +                </div> +            </div> +            <div class="span2"> +                <h2 class="block-title">Powered by</h2> +                <hr> +                Bootstrap <br> +            </div> + +            <div class="span2"> +                <h2 class="block-title">pyLoad</h2> +                <hr> +                dsfdsf <br> +            </div> + +            <div class="span2"> +                <h2 class="block-title">Community</h2> +                <hr> +                asd <br> +            </div> + +            <div class="span2"> +                <h2 class="block-title">Development</h2> +                <hr> +                asd <br> +            </div> +        </div> +    </div> +</footer> +<div id="modal-overlay" class="hide"></div> +</body> +</html> diff --git a/pyload/web/app/scripts/app.js b/pyload/web/app/scripts/app.js new file mode 100644 index 000000000..af5c50b14 --- /dev/null +++ b/pyload/web/app/scripts/app.js @@ -0,0 +1,104 @@ +/*  + *  Global Application Object + *  Contains all necessary logic shared across views + */ +define([ + +    // Libraries. +    'jquery', +    'underscore', +    'backbone', +    'handlebars', +    'utils/animations', +    'utils/lazyRequire', +    'utils/dialogs', +    'marionette', +    'bootstrap', +    'animate' + +], function($, _, Backbone, Handlebars) { +    'use strict'; + +    Backbone.Marionette.TemplateCache.prototype.compileTemplate = function(rawTemplate) { +        return Handlebars.compile(rawTemplate); +    }; + +    // TODO: configurable root +    var App = new Backbone.Marionette.Application({ +        root: '/' +    }); + +    App.addRegions({ +        header: '#header', +        notification: '#notification-area', +        selection: '#selection-area', +        content: '#content', +        actionbar: '#actionbar' +    }); + +    App.navigate = function(url) { +        return Backbone.history.navigate(url, true); +    }; + +    App.apiUrl = function(path) { +        var url = window.hostProtocol + window.hostAddress + ':' + window.hostPort + window.pathPrefix + path; +        return url; +    }; + +    // Add Global Helper functions +    // Generates options dict that can be used for xhr requests +    App.apiRequest = function(method, data, options) { +        options || (options = {}); +        options.url = App.apiUrl('api/' + method); +        options.dataType = 'json'; + +        if (data) { +            options.type = 'POST'; +            options.data = {}; +            // Convert arguments to json +            _.keys(data).map(function(key) { +                options.data[key] = JSON.stringify(data[key]); +            }); +        } + +        return options; +    }; + +    App.setTitle = function(name) { +        var title = window.document.title; +        var newTitle; +        // page name separator +        var index = title.indexOf('-'); +        if (index >= 0) +            newTitle = name + ' - ' + title.substr(index + 2, title.length); +        else +            newTitle = name + ' - ' + title; + +        window.document.title = newTitle; +    }; + +    App.openWebSocket = function(path) { +        return new WebSocket(window.wsAddress.replace('%s', window.hostAddress) + path); +    }; + +    App.on('initialize:after', function() { +//        TODO pushState variable +        Backbone.history.start({ +            pushState: false, +            root: App.root +        }); + +        // All links should be handled by backbone +        $(document).on('click', 'a[data-nav]', function(evt) { +            var href = { prop: $(this).prop('href'), attr: $(this).attr('href') }; +            var root = location.protocol + '//' + location.host + App.root; +            if (href.prop.slice(0, root.length) === root) { +                evt.preventDefault(); +                Backbone.history.navigate(href.attr, true); +            } +        }); +    }); + +    // Returns the app object to be available to other modules through require.js. +    return App; +});
\ No newline at end of file diff --git a/pyload/web/app/scripts/collections/AccountList.js b/pyload/web/app/scripts/collections/AccountList.js new file mode 100644 index 000000000..bfc2af5a3 --- /dev/null +++ b/pyload/web/app/scripts/collections/AccountList.js @@ -0,0 +1,24 @@ +define(['jquery', 'backbone', 'underscore', 'app', 'models/Account'], function($, Backbone, _, App, Account) { +    'use strict'; + +    return Backbone.Collection.extend({ + +        model: Account, + +        comparator: function(account) { +            return account.get('plugin'); +        }, + +        initialize: function() { + +        }, + +        fetch: function(options) { +            // TODO: refresh options? +            options = App.apiRequest('getAccounts/false', null, options); +            return Backbone.Collection.prototype.fetch.call(this, options); +        } + +    }); + +});
\ No newline at end of file diff --git a/pyload/web/app/scripts/collections/FileList.js b/pyload/web/app/scripts/collections/FileList.js new file mode 100644 index 000000000..112dc5e51 --- /dev/null +++ b/pyload/web/app/scripts/collections/FileList.js @@ -0,0 +1,28 @@ +define(['jquery', 'backbone', 'underscore', 'models/File'], function($, Backbone, _, File) { +    'use strict'; + +    return Backbone.Collection.extend({ + +        model: File, + +        comparator: function(file) { +            return file.get('fileorder'); +        }, + +        isEqual: function(fileList) { +            if (this.length !== fileList.length) return false; + +            // Assuming same order would be faster in false case +            var diff = _.difference(this.models, fileList.models); + +            // If there is a difference models are unequal +            return diff.length > 0; +        }, + +        initialize: function() { + +        } + +    }); + +});
\ No newline at end of file diff --git a/pyload/web/app/scripts/collections/InteractionList.js b/pyload/web/app/scripts/collections/InteractionList.js new file mode 100644 index 000000000..24f8b9248 --- /dev/null +++ b/pyload/web/app/scripts/collections/InteractionList.js @@ -0,0 +1,49 @@ +define(['jquery', 'backbone', 'underscore', 'app', 'models/InteractionTask'], +    function($, Backbone, _, App, InteractionTask) { +        'use strict'; + +        return Backbone.Collection.extend({ + +            model: InteractionTask, + +            comparator: function(task) { +                return task.get('iid'); +            }, + +            fetch: function(options) { +                options = App.apiRequest('getInteractionTasks/0', null, options); +                var self = this; +                options.success = function(data) { +                    self.set(data); +                }; + +                return $.ajax(options); +            }, + +            toJSON: function() { +                var data = {queries: 0, notifications: 0}; + +                this.map(function(task) { +                    if (task.isNotification()) +                        data.notifications++; +                    else +                        data.queries++; +                }); + +                return data; +            }, + +            // a task is waiting for attention (no notification) +            hasTaskWaiting: function() { +                var tasks = 0; +                this.map(function(task) { +                    if (!task.isNotification()) +                        tasks++; +                }); + +                return tasks > 0; +            } + +        }); + +    });
\ No newline at end of file diff --git a/pyload/web/app/scripts/collections/PackageList.js b/pyload/web/app/scripts/collections/PackageList.js new file mode 100644 index 000000000..7bee861a4 --- /dev/null +++ b/pyload/web/app/scripts/collections/PackageList.js @@ -0,0 +1,16 @@ +define(['jquery', 'backbone', 'underscore', 'models/Package'], function($, Backbone, _, Package) { +    'use strict'; + +    return Backbone.Collection.extend({ + +        model: Package, + +        comparator: function(pack) { +            return pack.get('packageorder'); +        }, + +        initialize: function() { +        } + +    }); +});
\ No newline at end of file diff --git a/pyload/web/app/scripts/collections/ProgressList.js b/pyload/web/app/scripts/collections/ProgressList.js new file mode 100644 index 000000000..51849d8de --- /dev/null +++ b/pyload/web/app/scripts/collections/ProgressList.js @@ -0,0 +1,18 @@ +define(['jquery', 'backbone', 'underscore', 'models/Progress'], function($, Backbone, _, Progress) { +    'use strict'; + +    return Backbone.Collection.extend({ + +        model: Progress, + +        comparator: function(progress) { +            return progress.get('eta'); +        }, + +        initialize: function() { + +        } + +    }); + +});
\ No newline at end of file diff --git a/pyload/web/app/scripts/config.js b/pyload/web/app/scripts/config.js new file mode 100644 index 000000000..9d1d027d9 --- /dev/null +++ b/pyload/web/app/scripts/config.js @@ -0,0 +1,75 @@ +// Sets the require.js configuration for your application. +'use strict'; +require.config({ + +    deps: ['default'], + +    paths: { + +        jquery: '../components/jquery/jquery', +        flot: '../components/flot/jquery.flot', +        transit: '../components/jquery.transit/jquery.transit', +        animate: '../components/jquery.animate-enhanced/scripts/src/jquery.animate-enhanced', +        cookie: '../components/jquery.cookie/jquery.cookie', +        omniwindow: 'vendor/jquery.omniwindow', +        select2: '../components/select2/select2', +        bootstrap: '../components/bootstrap-assets/js/bootstrap', +        underscore: '../components/underscore/underscore', +        backbone: '../components/backbone/backbone', +        marionette: '../components/backbone.marionette/lib/backbone.marionette', +        handlebars: '../components/handlebars.js/dist/handlebars', +        jed: '../components/jed/jed', + +        // TODO: Two hbs dependencies could be replaced +        i18nprecompile: '../components/require-handlebars-plugin/hbs/i18nprecompile', +        json2: '../components/require-handlebars-plugin/hbs/json2', + +        // Plugins +//        text: '../components/requirejs-text/text', +        hbs: '../components/require-handlebars-plugin/hbs', + +        // Shortcut +        tpl: '../templates/default' +    }, + +    hbs: { +        disableI18n: true, +        helperPathCallback:       // Callback to determine the path to look for helpers +            function(name) { +                if (name === '_' || name === 'ngettext') +                    name = 'gettext'; + +                // Some helpers are accumulated into one file +                if (name.indexOf('file') === 0) +                    name = 'fileHelper'; + +                return 'helpers/' + name; +            }, +        templateExtension: 'html' +    }, + +    // Sets the configuration for your third party scripts that are not AMD compatible +    shim: { +        underscore: { +            exports: '_' +        }, + +        backbone: { +            deps: ['underscore', 'jquery'], +            exports: 'Backbone' +        }, + +        marionette: ['backbone'], +        handlebars: { +            exports: 'Handlebars' +        }, + +        flot: ['jquery'], +        transit: ['jquery'], +        cookie: ['jquery'], +        omniwindow: ['jquery'], +        select2: ['jquery'], +        bootstrap: ['jquery'], +        animate: ['jquery'] +    } +});
\ No newline at end of file diff --git a/pyload/web/app/scripts/controller.js b/pyload/web/app/scripts/controller.js new file mode 100644 index 000000000..60f604e5b --- /dev/null +++ b/pyload/web/app/scripts/controller.js @@ -0,0 +1,72 @@ +define([ +    'app', +    'backbone', +    'underscore', + +    // Views +    'views/headerView', +    'views/notificationView', +    'views/dashboard/dashboardView', +    'views/dashboard/selectionView', +    'views/dashboard/filterView', +    'views/loginView', +    'views/settings/settingsView', +    'views/accounts/accountListView' +], function( +    App, Backbone, _, HeaderView, NotificationView, DashboardView, SelectionView, FilterView, LoginView, SettingsView, AccountListView) { +    'use strict'; +    // TODO some views does not need to be loaded instantly + +    return { + +        header: function() { +            if (!App.header.currentView) { +                App.header.show(new HeaderView()); +                App.header.currentView.init(); +                App.notification.attachView(new NotificationView()); +            } +        }, + +        dashboard: function() { +            this.header(); + +            App.actionbar.show(new FilterView()); + +            // TODO: not completely visible after reattaching +            // now visible every time +            if (_.isUndefined(App.selection.currentView) || _.isNull(App.selection.currentView)) +                App.selection.attachView(new SelectionView()); + +            App.content.show(new DashboardView()); +        }, + +        login: function() { +            App.content.show(new LoginView()); +        }, + +        logout: function() { +            alert('Not implemented'); +        }, + +        settings: function() { +            this.header(); + +            var view = new SettingsView(); +            App.actionbar.show(new view.actionbar()); +            App.content.show(view); +        }, + +        accounts: function() { +            this.header(); + +            var view = new AccountListView(); +            App.actionbar.show(new view.actionbar()); +            App.content.show(view); +        }, + +        admin: function() { +            alert('Not implemented'); +        } +    }; + +}); diff --git a/pyload/web/app/scripts/default.js b/pyload/web/app/scripts/default.js new file mode 100644 index 000000000..6c5ee9afb --- /dev/null +++ b/pyload/web/app/scripts/default.js @@ -0,0 +1,30 @@ +define('default', ['backbone', 'jquery', 'app', 'router', 'models/UserSession'], +    function(Backbone, $, App, Router, UserSession) { +        'use strict'; + +        // Global ajax options +        var options = { +            statusCode: { +                401: function() { +                    console.log('Not logged in.'); +                    App.navigate('login'); +                } +            }, +            xhrFields: {withCredentials: true} +        }; + +        $.ajaxSetup(options); + +        Backbone.ajax = function() { +            Backbone.$.ajaxSetup.call(Backbone.$, options); +            return Backbone.$.ajax.apply(Backbone.$, arguments); +        }; + +        $(function() { +            App.session = new UserSession(); +            App.router = new Router(); +            App.start(); +        }); + +        return App; +    });
\ No newline at end of file diff --git a/pyload/web/app/scripts/helpers/fileHelper.js b/pyload/web/app/scripts/helpers/fileHelper.js new file mode 100644 index 000000000..156be58f0 --- /dev/null +++ b/pyload/web/app/scripts/helpers/fileHelper.js @@ -0,0 +1,55 @@ +// Helpers to render the file view +define('helpers/fileHelper', ['handlebars', 'utils/apitypes', 'helpers/formatTime'], +    function(Handlebars, Api, formatTime) { +        'use strict'; + +        function fileClass(file, options) { +            if (file.finished) +                return 'finished'; +            else if (file.failed) +                return 'failed'; +            else if (file.offline) +                return 'offline'; +            else if (file.online) +                return 'online'; +            else if (file.waiting) +                return 'waiting'; +            else if (file.downloading) +                return 'downloading'; + +            return ''; +        } + +        // TODO +        function fileIcon(media, options) { +            return 'icon-music'; +        } + +        // TODO rest of the states +        function fileStatus(file, options) { +            var s; +            var msg = file.download.statusmsg; + +            if (file.failed) { +                s = '<i class="icon-remove"></i> '; +                if (file.download.error) +                    s += file.download.error; +                else s += msg; +            } else if (file.finished) +                s = '<i class="icon-ok"></i> ' + msg; +            else if (file.downloading) +                s = '<div class="progress"><div class="bar" style="width: ' + file.progress + '%">  ' + +                    formatTime(file.eta) + '</div></div>'; +            else if (file.waiting) +                s = '<i class="icon-time"></i> ' + formatTime(file.eta); +            else +                s = msg; + +            return new Handlebars.SafeString(s); +        } + +        Handlebars.registerHelper('fileClass', fileClass); +        Handlebars.registerHelper('fileIcon', fileIcon); +        Handlebars.registerHelper('fileStatus', fileStatus); +        return fileClass; +    });
\ No newline at end of file diff --git a/pyload/web/app/scripts/helpers/formatSize.js b/pyload/web/app/scripts/helpers/formatSize.js new file mode 100644 index 000000000..3b62e74c7 --- /dev/null +++ b/pyload/web/app/scripts/helpers/formatSize.js @@ -0,0 +1,15 @@ +// Format bytes in human readable format +define('helpers/formatSize', ['handlebars'], function(Handlebars) { +    'use strict'; + +    var sizes = ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB']; +    function formatSize(bytes, options) { +        if (!bytes || bytes === 0) return '0 B'; +        var i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)), 10); +        // round to two digits +        return (bytes / Math.pow(1024, i)).toFixed(2) + ' ' + sizes[i]; +    } + +    Handlebars.registerHelper('formatSize', formatSize); +    return formatSize; +});
\ No newline at end of file diff --git a/pyload/web/app/scripts/helpers/formatTime.js b/pyload/web/app/scripts/helpers/formatTime.js new file mode 100644 index 000000000..757ff73ad --- /dev/null +++ b/pyload/web/app/scripts/helpers/formatTime.js @@ -0,0 +1,17 @@ +// Format bytes in human readable format +define('helpers/formatTime', ['handlebars', 'vendor/remaining'], function(Handlebars, Remaining) { +    'use strict'; + +    function formatTime(seconds, options) { +        if (seconds === Infinity) +            return '∞'; +        else if (!seconds || seconds <= 0) +            return '-'; + +        // TODO: digital or written string +        return Remaining.getStringDigital(seconds, window.dates); +    } + +    Handlebars.registerHelper('formatTime', formatTime); +    return formatTime; +});
\ No newline at end of file diff --git a/pyload/web/app/scripts/helpers/gettext.js b/pyload/web/app/scripts/helpers/gettext.js new file mode 100644 index 000000000..d73b5e378 --- /dev/null +++ b/pyload/web/app/scripts/helpers/gettext.js @@ -0,0 +1,16 @@ +require(['underscore', 'handlebars', 'utils/i18n'], function(_, Handlebars, i18n) { +    'use strict'; +    // These methods binds additional content directly to translated message +    function ngettext(single, plural, n) { +        return i18n.sprintf(i18n.ngettext(single, plural, n), n); +    } + +    function gettext(key, message) { +        return i18n.sprintf(i18n.gettext(key), message); +    } + +    Handlebars.registerHelper('_', gettext); +    Handlebars.registerHelper('gettext', gettext); +    Handlebars.registerHelper('ngettext', ngettext); +    return gettext; +});
\ No newline at end of file diff --git a/pyload/web/app/scripts/helpers/pluginIcon.js b/pyload/web/app/scripts/helpers/pluginIcon.js new file mode 100644 index 000000000..6b2fdc67f --- /dev/null +++ b/pyload/web/app/scripts/helpers/pluginIcon.js @@ -0,0 +1,14 @@ +// Resolves name of plugin to icon path +define('helpers/pluginIcon', ['handlebars', 'app'], function(Handlebars, App) { +    'use strict'; + +    function pluginIcon(name) { +        if (typeof name === 'object' && typeof name.get === 'function') +            name = name.get('plugin'); + +        return App.apiUrl('icons/' + name); +    } + +    Handlebars.registerHelper('pluginIcon', pluginIcon); +    return pluginIcon; +});
\ No newline at end of file diff --git a/pyload/web/app/scripts/helpers/truncate.js b/pyload/web/app/scripts/helpers/truncate.js new file mode 100644 index 000000000..fb351b776 --- /dev/null +++ b/pyload/web/app/scripts/helpers/truncate.js @@ -0,0 +1,25 @@ +require(['underscore','handlebars'], function(_, Handlebars) { +    'use strict'; + +    function truncate(fullStr, options) { +        var strLen = 30; +        if (_.isNumber(options)) +            strLen = options; + +        if (fullStr.length <= strLen) return fullStr; + +        var separator = options.separator || '…'; + +        var sepLen = separator.length, +            charsToShow = strLen - sepLen, +            frontChars = Math.ceil(charsToShow / 2), +            backChars = Math.floor(charsToShow / 2); + +        return fullStr.substr(0, frontChars) + +            separator + +            fullStr.substr(fullStr.length - backChars); +    } + +    Handlebars.registerHelper('truncate', truncate); +    return truncate; +});
\ No newline at end of file diff --git a/pyload/web/app/scripts/models/Account.js b/pyload/web/app/scripts/models/Account.js new file mode 100644 index 000000000..a2e24b056 --- /dev/null +++ b/pyload/web/app/scripts/models/Account.js @@ -0,0 +1,51 @@ +define(['jquery', 'backbone', 'underscore', 'app', 'utils/apitypes'], function($, Backbone, _, App, Api) { +    'use strict'; + +    return Backbone.Model.extend({ + +        // TODO +        // generated, not submitted +        idAttribute: 'user', + +        defaults: { +            plugin: null, +            loginname: null, +            owner: -1, +            valid: false, +            validuntil: -1, +            trafficleft: -1, +            maxtraffic: -1, +            premium: false, +            activated: false, +            shared: false, +            options: null +        }, + +        // Model Constructor +        initialize: function() { +        }, + +        // Any time a model attribute is set, this method is called +        validate: function(attrs) { + +        }, + +        save: function(options) { +            options = App.apiRequest('updateAccountInfo', {account: this.toJSON()}, options); +            return $.ajax(options); +        }, + +        destroy: function(options) { +            options = App.apiRequest('removeAccount', {account: this.toJSON()}, options); +            var self = this; +            options.success = function() { +                self.trigger('destroy', self, self.collection, options); +            }; + +            // TODO request is not dispatched +//            return Backbone.Model.prototype.destroy.call(this, options); +            return $.ajax(options); +        } +    }); + +});
\ No newline at end of file diff --git a/pyload/web/app/scripts/models/ConfigHolder.js b/pyload/web/app/scripts/models/ConfigHolder.js new file mode 100644 index 000000000..40efbc7c0 --- /dev/null +++ b/pyload/web/app/scripts/models/ConfigHolder.js @@ -0,0 +1,68 @@ +define(['jquery', 'backbone', 'underscore', 'app', './ConfigItem'], +    function($, Backbone, _, App, ConfigItem) { +        'use strict'; + +        return Backbone.Model.extend({ + +            defaults: { +                name: '', +                label: '', +                description: '', +                long_description: null, +                // simple list but no collection +                items: null, +                info: null +            }, + +            // Model Constructor +            initialize: function() { + +            }, + +            // Loads it from server by name +            fetch: function(options) { +                options = App.apiRequest('loadConfig/"' + this.get('name') + '"', null, options); +                return Backbone.Model.prototype.fetch.call(this, options); +            }, + +            save: function(options) { +                var config = this.toJSON(); +                var items = []; +                // Convert changed items to json +                _.each(config.items, function(item) { +                    if (item.isChanged()) { +                        items.push(item.prepareSave()); +                    } +                }); +                config.items = items; +                // TODO: only set new values on success + +                options = App.apiRequest('saveConfig', {config: config}, options); + +                return $.ajax(options); +            }, + +            parse: function(resp) { +                // Create item models +                resp.items = _.map(resp.items, function(item) { +                    return new ConfigItem(item); +                }); + +                return Backbone.Model.prototype.parse.call(this, resp); +            }, + +            isLoaded: function() { +                return this.has('items') || this.has('long_description'); +            }, + +            // check if any of the items has changes +            hasChanges: function() { +                var items = this.get('items'); +                if (!items) return false; +                return _.reduce(items, function(a, b) { +                    return a || b.isChanged(); +                }, false); +            } + +        }); +    });
\ No newline at end of file diff --git a/pyload/web/app/scripts/models/ConfigItem.js b/pyload/web/app/scripts/models/ConfigItem.js new file mode 100644 index 000000000..2d325c2a2 --- /dev/null +++ b/pyload/web/app/scripts/models/ConfigItem.js @@ -0,0 +1,40 @@ +define(['jquery', 'backbone', 'underscore', 'app', 'utils/apitypes'], +    function($, Backbone, _, App, Api) { +        'use strict'; + +        return Backbone.Model.extend({ + +            defaults: { +                name: '', +                label: '', +                description: '', +                input: null, +                default_value: null, +                value: null, +                // additional attributes +                inputView: null +            }, + +            // Model Constructor +            initialize: function() { + +            }, + +            isChanged: function() { +                return this.get('inputView') && this.get('inputView').getVal() !== this.get('value'); +            }, + +            // set new value and return json +            prepareSave: function() { +                // set the new value +                if (this.get('inputView')) +                    this.set('value', this.get('inputView').getVal()); + +                var data = this.toJSON(); +                delete data.inputView; +                delete data.description; + +                return data; +            } +        }); +    });
\ No newline at end of file diff --git a/pyload/web/app/scripts/models/File.js b/pyload/web/app/scripts/models/File.js new file mode 100644 index 000000000..562e6b0ae --- /dev/null +++ b/pyload/web/app/scripts/models/File.js @@ -0,0 +1,97 @@ +define(['jquery', 'backbone', 'underscore', 'app', 'utils/apitypes'], function($, Backbone, _, App, Api) { +    'use strict'; + +    var Finished = [Api.DownloadStatus.Finished, Api.DownloadStatus.Skipped]; +    var Failed = [Api.DownloadStatus.Failed, Api.DownloadStatus.Aborted, Api.DownloadStatus.TempOffline, Api.DownloadStatus.Offline]; +    // Unfinished - Other + +    return Backbone.Model.extend({ + +        idAttribute: 'fid', + +        defaults: { +            fid: -1, +            name: null, +            package: -1, +            owner: -1, +            size: -1, +            status: -1, +            media: -1, +            added: -1, +            fileorder: -1, +            download: null, + +            // UI attributes +            selected: false, +            visible: true, +            progress: 0, +            eta: 0 +        }, + +        // Model Constructor +        initialize: function() { + +        }, + +        fetch: function(options) { +            options = App.apiRequest( +                'getFileInfo', +                {fid: this.get('fid')}, +                options); + +            return Backbone.Model.prototype.fetch.call(this, options); +        }, + +        destroy: function(options) { +            // also not working when using data +            options = App.apiRequest( +                'deleteFiles/[' + this.get('fid') + ']', +                null, options); +            options.method = 'post'; + +            return Backbone.Model.prototype.destroy.call(this, options); +        }, + +        // Does not send a request to the server +        destroyLocal: function(options) { +            this.trigger('destroy', this, this.collection, options); +        }, + +        restart: function(options) { +            options = App.apiRequest( +                'restartFile', +                {fid: this.get('fid')}, +                options); + +            return $.ajax(options); +        }, + +        // Any time a model attribute is set, this method is called +        validate: function(attrs) { + +        }, + +        setDownloadStatus: function(status) { +            if (this.isDownload()) +                this.get('download').status = status; +        }, + +        isDownload: function() { +            return this.has('download'); +        }, + +        isFinished: function() { +            return _.indexOf(Finished, this.get('download').status) > -1; +        }, + +        isUnfinished: function() { +            return _.indexOf(Finished, this.get('download').status) === -1 && _.indexOf(Failed, this.get('download').status) === -1; +        }, + +        isFailed: function() { +            return _.indexOf(Failed, this.get('download').status) > -1; +        } + +    }); + +});
\ No newline at end of file diff --git a/pyload/web/app/scripts/models/InteractionTask.js b/pyload/web/app/scripts/models/InteractionTask.js new file mode 100644 index 000000000..54c739d4b --- /dev/null +++ b/pyload/web/app/scripts/models/InteractionTask.js @@ -0,0 +1,41 @@ +define(['jquery', 'backbone', 'underscore', 'app', 'utils/apitypes'], +    function($, Backbone, _, App, Api) { +        'use strict'; + +        return Backbone.Model.extend({ + +            idAttribute: 'iid', + +            defaults: { +                iid: -1, +                type: null, +                input: null, +                default_value: null, +                title: '', +                description: '', +                plugin: '', +                // additional attributes +                result: '' +            }, + +            // Model Constructor +            initialize: function() { + +            }, + +            save: function(options) { +                options = App.apiRequest('setInteractionResult/' + this.get('iid'), +                    {result: this.get('result')}, options); + +                return $.ajax(options); +            }, + +            isNotification: function() { +                return this.get('type') === Api.Interaction.Notification; +            }, + +            isCaptcha: function() { +                return this.get('type') === Api.Interaction.Captcha; +            } +        }); +    });
\ No newline at end of file diff --git a/pyload/web/app/scripts/models/Package.js b/pyload/web/app/scripts/models/Package.js new file mode 100644 index 000000000..a34ec1c69 --- /dev/null +++ b/pyload/web/app/scripts/models/Package.js @@ -0,0 +1,119 @@ +define(['jquery', 'backbone', 'underscore', 'app', 'collections/FileList', 'require'], +    function($, Backbone, _, App, FileList, require) { +        'use strict'; + +        return Backbone.Model.extend({ + +            idAttribute: 'pid', + +            defaults: { +                pid: -1, +                name: null, +                folder: '', +                root: -1, +                owner: -1, +                site: '', +                comment: '', +                password: '', +                added: -1, +                tags: null, +                status: -1, +                shared: false, +                packageorder: -1, +                stats: null, +                fids: null, +                pids: null, +                files: null, // Collection +                packs: null, // Collection + +                selected: false // For Checkbox +            }, + +            // Model Constructor +            initialize: function() { +            }, + +            toJSON: function(options) { +                var obj = Backbone.Model.prototype.toJSON.call(this, options); +                obj.percent = Math.round(obj.stats.linksdone * 100 / obj.stats.linkstotal); + +                return obj; +            }, + +            // Changes url + method and delegates call to super class +            fetch: function(options) { +                options = App.apiRequest( +                    'getFileTree/' + this.get('pid'), +                    {full: false}, +                    options); + +                return Backbone.Model.prototype.fetch.call(this, options); +            }, + +            // Create a pseudo package und use search to populate data +            search: function(qry, options) { +                options = App.apiRequest( +                    'findFiles', +                    {pattern: qry}, +                    options); + +                return Backbone.Model.prototype.fetch.call(this, options); +            }, + +            save: function(options) { +                // TODO +            }, + +            destroy: function(options) { +                // TODO: Not working when using data?, array seems to break it +                options = App.apiRequest( +                    'deletePackages/[' + this.get('pid') + ']', +                    null, options); +                options.method = 'post'; + +                console.log(options); + +                return Backbone.Model.prototype.destroy.call(this, options); +            }, + +            restart: function(options) { +                options = App.apiRequest( +                    'restartPackage', +                    {pid: this.get('pid')}, +                    options); + +                var self = this; +                options.success = function() { +                    self.fetch(); +                }; +                return $.ajax(options); +            }, + +            parse: function(resp) { +                // Package is loaded from tree collection +                if (_.has(resp, 'root')) { +                    if (!this.has('files')) +                        resp.root.files = new FileList(_.values(resp.files)); +                    else +                        this.get('files').set(_.values(resp.files)); + +                    // circular dependencies needs to be avoided +                    var PackageList = require('collections/PackageList'); + +                    if (!this.has('packs')) +                        resp.root.packs = new PackageList(_.values(resp.packages)); +                    else +                        this.get('packs').set(_.values(resp.packages)); + +                    return resp.root; +                } +                return Backbone.model.prototype.parse.call(this, resp); +            }, + +            // Any time a model attribute is set, this method is called +            validate: function(attrs) { + +            } + +        }); +    });
\ No newline at end of file diff --git a/pyload/web/app/scripts/models/Progress.js b/pyload/web/app/scripts/models/Progress.js new file mode 100644 index 000000000..b0bbb684d --- /dev/null +++ b/pyload/web/app/scripts/models/Progress.js @@ -0,0 +1,50 @@ +define(['jquery', 'backbone', 'underscore', 'utils/apitypes'], function($, Backbone, _, Api) { +    'use strict'; + +    return Backbone.Model.extend({ + +        // generated, not submitted +        idAttribute: 'pid', + +        defaults: { +            pid: -1, +            plugin: null, +            name: null, +            statusmsg: -1, +            eta: -1, +            done: -1, +            total: -1, +            download: null +        }, + +        getPercent: function() { +            if (this.get('total') > 0) +                return Math.round(this.get('done') * 100 / this.get('total')); +            return  0; +        }, + +        // Model Constructor +        initialize: function() { + +        }, + +        // Any time a model attribute is set, this method is called +        validate: function(attrs) { + +        }, + +        toJSON: function(options) { +            var obj = Backbone.Model.prototype.toJSON.call(this, options); +            obj.percent = this.getPercent(); +            obj.downloading = this.isDownload() && this.get('download').status === Api.DownloadStatus.Downloading; + +            return obj; +        }, + +        isDownload : function() { +            return this.has('download'); +        } + +    }); + +});
\ No newline at end of file diff --git a/pyload/web/app/scripts/models/ServerStatus.js b/pyload/web/app/scripts/models/ServerStatus.js new file mode 100644 index 000000000..59739b41e --- /dev/null +++ b/pyload/web/app/scripts/models/ServerStatus.js @@ -0,0 +1,47 @@ +define(['jquery', 'backbone', 'underscore'], +    function($, Backbone, _) { +        'use strict'; + +        return Backbone.Model.extend({ + +            defaults: { +                speed: 0, +                linkstotal: 0, +                linksqueue: 0, +                sizetotal: 0, +                sizequeue: 0, +                notifications: -1, +                paused: false, +                download: false, +                reconnect: false +            }, + +            // Model Constructor +            initialize: function() { + +            }, + +            fetch: function(options) { +                options || (options = {}); +                options.url = 'api/getServerStatus'; + +                return Backbone.Model.prototype.fetch.call(this, options); +            }, + +            toJSON: function(options) { +                var obj = Backbone.Model.prototype.toJSON.call(this, options); + +                obj.linksdone = obj.linkstotal - obj.linksqueue; +                obj.sizedone = obj.sizetotal - obj.sizequeue; +                if (obj.speed && obj.speed > 0) +                    obj.eta = Math.round(obj.sizequeue / obj.speed); +                else if (obj.sizequeue > 0) +                    obj.eta = Infinity; +                else +                    obj.eta = 0; + +                return obj; +            } + +        }); +    });
\ No newline at end of file diff --git a/pyload/web/app/scripts/models/TreeCollection.js b/pyload/web/app/scripts/models/TreeCollection.js new file mode 100644 index 000000000..2f761e6cc --- /dev/null +++ b/pyload/web/app/scripts/models/TreeCollection.js @@ -0,0 +1,50 @@ +define(['jquery', 'backbone', 'underscore', 'app', 'models/Package', 'collections/FileList', 'collections/PackageList'], +    function($, Backbone, _, App, Package, FileList, PackageList) { +        'use strict'; + +        // TreeCollection +        // A Model and not a collection, aggregates other collections +        return Backbone.Model.extend({ + +            defaults: { +                root: null, +                packages: null, +                files: null +            }, + +            initialize: function() { + +            }, + +            fetch: function(options) { +                options || (options = {}); +                var pid = options.pid || -1; + +                options = App.apiRequest( +                    'getFileTree/' + pid, +                    {full: false}, +                    options); + +                console.log('Fetching package tree ' + pid); +                return Backbone.Model.prototype.fetch.call(this, options); +            }, + +            // Parse the response and updates the collections +            parse: function(resp) { +                var ret = {}; +                if (!this.has('packages')) +                    ret.packages = new PackageList(_.values(resp.packages)); +                else +                    this.get('packages').set(_.values(resp.packages)); + +                if (!this.has('files')) +                    ret.files = new FileList(_.values(resp.files)); +                else +                    this.get('files').set(_.values(resp.files)); + +                ret.root = new Package(resp.root); +                return ret; +            } + +        }); +    });
\ No newline at end of file diff --git a/pyload/web/app/scripts/models/UserSession.js b/pyload/web/app/scripts/models/UserSession.js new file mode 100644 index 000000000..a7e9aa848 --- /dev/null +++ b/pyload/web/app/scripts/models/UserSession.js @@ -0,0 +1,20 @@ +define(['jquery', 'backbone', 'underscore',  'utils/apitypes', 'cookie'], +    function($, Backbone, _, Api) { +        'use strict'; + +        return Backbone.Model.extend({ + +            idAttribute: 'username', + +            defaults: { +                username: null, +                permissions: null, +                session: null +            }, + +            // Model Constructor +            initialize: function() { +                this.set('session', $.cookie('beaker.session.id')); +            } +        }); +    });
\ No newline at end of file diff --git a/pyload/web/app/scripts/router.js b/pyload/web/app/scripts/router.js new file mode 100644 index 000000000..68ea5575d --- /dev/null +++ b/pyload/web/app/scripts/router.js @@ -0,0 +1,29 @@ +/** + * Router defines routes that are handled by registered controller + */ +define([ +    // Libraries +    'backbone', +    'marionette', + +    // Modules +    'controller' +], +    function(Backbone, Marionette, Controller) { +        'use strict'; + +        return Backbone.Marionette.AppRouter.extend({ + +            appRoutes: { +                '': 'dashboard', +                'login': 'login', +                'logout': 'logout', +                'settings': 'settings', +                'accounts': 'accounts', +                'admin': 'admin' +            }, + +            // Our controller to handle the routes +            controller: Controller +        }); +    }); diff --git a/pyload/web/app/scripts/routers/defaultRouter.js b/pyload/web/app/scripts/routers/defaultRouter.js new file mode 100644 index 000000000..4b00d160c --- /dev/null +++ b/pyload/web/app/scripts/routers/defaultRouter.js @@ -0,0 +1,30 @@ +define(['jquery', 'backbone', 'views/headerView'], function($, Backbone, HeaderView) { +    'use strict'; + +    var Router = Backbone.Router.extend({ + +        initialize: function() { +            Backbone.history.start(); +        }, + +        // All of your Backbone Routes (add more) +        routes: { + +            // When there is no hash bang on the url, the home method is called +            '': 'home' + +        }, + +        'home': function() { +            // Instantiating mainView and anotherView instances +            var headerView = new HeaderView(); + +            // Renders the mainView template +            headerView.render(); + +        } +    }); + +    // Returns the Router class +    return Router; +});
\ No newline at end of file diff --git a/pyload/web/app/scripts/routers/mobileRouter.js b/pyload/web/app/scripts/routers/mobileRouter.js new file mode 100644 index 000000000..e24cb7a34 --- /dev/null +++ b/pyload/web/app/scripts/routers/mobileRouter.js @@ -0,0 +1,56 @@ +define(['jquery', 'backbone', 'underscore'], function($, Backbone, _) { +    'use strict'; + +    return Backbone.Router.extend({ + +        initialize: function() { +            _.bindAll(this, 'changePage'); + +            this.$el = $('#content'); + +            // Tells Backbone to start watching for hashchange events +            Backbone.history.start(); + +        }, + +        // All of your Backbone Routes (add more) +        routes: { + +            // When there is no hash bang on the url, the home method is called +            '': 'home' + +        }, + +        'home': function() { + +            var self = this; + +            $('#p1').fastClick(function() { +                self.changePage($('<div class=\'page\' style=\'background-color: #9acd32;\'><h1>Page 1</h1><br>some content<br>sdfdsf<br>sdffg<h3>oiuzz</h3></div>')); +            }); + +            $('#p2').bind('click', function() { +                self.changePage($('<div class=\'page\' style=\'background-color: blue;\'><h1>Page 2</h1><br>some content<br>sdfdsf<br><h2>sdfsdf</h2>sdffg</div>')); +            }); + +        }, + +        changePage: function(content) { + +            var oldpage = this.$el.find('.page'); +            content.css({x: '100%'}); +            this.$el.append(content); +            content.transition({x: 0}, function() { +                window.setTimeout(function() { +                    oldpage.remove(); +                }, 400); +            }); + +//            $("#viewport").transition({x: "100%"}, function(){ +//                $("#viewport").html(content); +//                $("#viewport").transition({x: 0}); +//            }); +        } + +    }); +});
\ No newline at end of file diff --git a/pyload/web/app/scripts/utils/animations.js b/pyload/web/app/scripts/utils/animations.js new file mode 100644 index 000000000..7f89afef1 --- /dev/null +++ b/pyload/web/app/scripts/utils/animations.js @@ -0,0 +1,129 @@ +define(['jquery', 'underscore', 'transit'], function(jQuery, _) { +    'use strict'; + +    // Adds an element and computes its height, which is saved as data attribute +    // Important function to have slide animations +    jQuery.fn.appendWithHeight = function(element, hide) { +        var o = jQuery(this[0]); +        element = jQuery(element); + +        // TODO: additionally it could be placed out of viewport first +        // The real height can only be retrieved when element is on DOM and display:true +        element.css('visibility', 'hidden'); +        o.append(element); + +        var height = element.height(); + +        // Hide the element +        if (hide === true) { +            element.hide(); +            element.height(0); +        } + +        element.css('visibility', ''); +        element.data('height', height); + +        return this; +    }; + +    // Shortcut to have a animation when element is added +    jQuery.fn.appendWithAnimation = function(element, animation) { +        var o = jQuery(this[0]); +        element = jQuery(element); + +        if (animation === true) +            element.hide(); + +        o.append(element); + +        if (animation === true) +            element.fadeIn(); + +//        element.calculateHeight(); + +        return this; +    }; + +    // calculate the height and write it to data, should be used on invisible elements +    jQuery.fn.calculateHeight = function(setHeight) { +        var o = jQuery(this[0]); +        var height = o.height(); +        if (!height) { +            var display = o.css('display'); +            o.css('visibility', 'hidden'); +            o.show(); +            height = o.height(); + +            o.css('display', display); +            o.css('visibility', ''); +        } + +        if (setHeight) +            o.css('height', height); + +        o.data('height', height); +        return this; +    }; + +    // TODO: carry arguments, optional height argument + +    // reset arguments, sets overflow hidden +    jQuery.fn.slideOut = function(reset) { +        var o = jQuery(this[0]); +        o.animate({height: o.data('height'), opacity: 'show'}, function() { +            // reset css attributes; +            if (reset) { +                this.css('overflow', ''); +                this.css('height', ''); +            } +        }); +        return this; +    }; + +    jQuery.fn.slideIn = function(reset) { +        var o = jQuery(this[0]); +        if (reset) { +            o.css('overflow', 'hidden'); +        } +        o.animate({height: 0, opacity: 'hide'}); +        return this; +    }; + +    jQuery.fn.initTooltips = function(placement) { +        placement || (placement = 'top'); + +        var o = jQuery(this[0]); +        o.find('[data-toggle="tooltip"]').tooltip( +            { +                delay: {show: 800, hide: 100}, +                placement: placement +            }); + +        return this; +    }; + +    jQuery.fn._transit = jQuery.fn.transit; + +    // Overriding transit plugin to support hide and show +    jQuery.fn.transit = jQuery.fn.transition = function(props, duration, easing, callback) { +        var self = this; +        var cb = callback; +        var newprops = _.extend({}, props); + +        if (newprops && (newprops.opacity === 'hide')) { +            newprops.opacity = 0; + +            callback = function() { +                self.css({display: 'none'}); +                if (typeof cb === 'function') { +                    cb.apply(self); +                } +            }; +        } else if (newprops && (newprops.opacity === 'show')) { +            newprops.opacity = 1; +            this.css({display: 'block'}); +        } + +        return this._transit(newprops, duration, easing, callback); +    }; +});
\ No newline at end of file diff --git a/pyload/web/app/scripts/utils/apitypes.js b/pyload/web/app/scripts/utils/apitypes.js new file mode 100644 index 000000000..342f61f68 --- /dev/null +++ b/pyload/web/app/scripts/utils/apitypes.js @@ -0,0 +1,16 @@ +// Autogenerated, do not edit! +/*jslint -W070: false*/ +define([], function() { +	'use strict'; +	return { +		DownloadState: {'Failed': 3, 'All': 0, 'Unmanaged': 4, 'Finished': 1, 'Unfinished': 2}, +		DownloadStatus: {'Downloading': 10, 'NA': 0, 'Processing': 14, 'Waiting': 9, 'Decrypting': 13, 'Paused': 4, 'Failed': 7, 'Finished': 5, 'Skipped': 6, 'Unknown': 16, 'Aborted': 12, 'Online': 2, 'TempOffline': 11, 'Offline': 1, 'Custom': 15, 'Starting': 8, 'Queued': 3}, +		FileStatus: {'Remote': 2, 'Ok': 0, 'Missing': 1}, +		InputType: {'PluginList': 13, 'Multiple': 11, 'Int': 2, 'NA': 0, 'Time': 7, 'List': 12, 'Bool': 8, 'File': 3, 'Text': 1, 'Table': 14, 'Folder': 4, 'Password': 6, 'Click': 9, 'Select': 10, 'Textbox': 5}, +		Interaction: {'Captcha': 2, 'All': 0, 'Query': 4, 'Notification': 1}, +		MediaType: {'All': 0, 'Audio': 2, 'Image': 4, 'Other': 1, 'Video': 8, 'Document': 16, 'Archive': 32}, +		PackageStatus: {'Paused': 1, 'Remote': 3, 'Folder': 2, 'Ok': 0}, +		Permission: {'All': 0, 'Interaction': 32, 'Modify': 4, 'Add': 1, 'Accounts': 16, 'Plugins': 64, 'Download': 8, 'Delete': 2}, +		Role: {'Admin': 0, 'User': 1}, +	}; +});
\ No newline at end of file diff --git a/pyload/web/app/scripts/utils/dialogs.js b/pyload/web/app/scripts/utils/dialogs.js new file mode 100644 index 000000000..3ceffc9c3 --- /dev/null +++ b/pyload/web/app/scripts/utils/dialogs.js @@ -0,0 +1,15 @@ +// Loads all helper and set own handlebars rules +define(['jquery', 'underscore', 'views/abstract/modalView'], function($, _, Modal) { +    'use strict'; + +    // Shows the confirm dialog for given context +    // on success executes func with context +    _.confirm = function(template, func, context) { +        template = 'hbs!tpl/' + template; +        _.requireOnce([template], function(html) { +            var dialog = new Modal(html, _.bind(func, context)); +            dialog.show(); +        }); + +    }; +});
\ No newline at end of file diff --git a/pyload/web/app/scripts/utils/i18n.js b/pyload/web/app/scripts/utils/i18n.js new file mode 100644 index 000000000..a8d948b4a --- /dev/null +++ b/pyload/web/app/scripts/utils/i18n.js @@ -0,0 +1,5 @@ +define(['jed'], function(Jed) { +    'use strict'; +    // TODO load i18n data +    return new Jed({}); +});
\ No newline at end of file diff --git a/pyload/web/app/scripts/utils/lazyRequire.js b/pyload/web/app/scripts/utils/lazyRequire.js new file mode 100644 index 000000000..96c07aa24 --- /dev/null +++ b/pyload/web/app/scripts/utils/lazyRequire.js @@ -0,0 +1,97 @@ +// Define the module. +define( +	[ +		'require', 'underscore' +	], +	function( require, _ ){ +        'use strict'; + + +		// Define the states of loading for a given set of modules +		// within a require() statement. +		var states = { +			unloaded: 'UNLOADED', +			loading: 'LOADING', +			loaded: 'LOADED' +		}; + + +		// Define the top-level module container. Mostly, we're making +		// the top-level container a non-Function so that users won't +		// try to invoke this without calling the once() method below. +		var lazyRequire = {}; + + +		// I will return a new, unique instance of the requrieOnce() +		// method. Each instance will only call the require() method +		// once internally. +		lazyRequire.once = function(){ + +			// The modules start in an unloaded state before +			// requireOnce() is invoked by the calling code. +			var state = states.unloaded; +            var args; + +			var requireOnce = function(dependencies, loadCallback ){ + +				// Use the module state to determine which method to +				// invoke (or just to ignore the invocation). +				if (state === states.loaded){ +					loadCallback.apply(null, args); + +				// The modules have not yet been requested - let's +				// lazy load them. +				} else if (state !== states.loading){ + +					// We're about to load the modules asynchronously; +					// flag the interim state. +					state = states.loading; + +					// Load the modules. +					require( +						dependencies, +						function(){ + +                            args = arguments; +							loadCallback.apply( null, args ); +                            state = states.loaded; + + +						} +					); + +				// RequireJS is currently loading the modules +				// asynchronously, but they have not finished +				// loading yet. +				} else { + +					// Simply ignore this call. +					return; + +				} + +			}; + +			// Return the new lazy loader. +			return( requireOnce ); + +		}; + + +		// -------------------------------------------------- // +		// -------------------------------------------------- // + +        // Set up holder for underscore +        var instances = {}; +        _.requireOnce = function(dependencies, loadCallback) { +            if (!_.has(instances, dependencies)) +                instances[dependencies] = lazyRequire.once(); + +            return instances[dependencies](dependencies, loadCallback); +        }; + + +		// Return the module definition. +		return( lazyRequire ); +	} +);
\ No newline at end of file diff --git a/pyload/web/app/scripts/vendor/jquery.omniwindow.js b/pyload/web/app/scripts/vendor/jquery.omniwindow.js new file mode 100644 index 000000000..e1f0b8f77 --- /dev/null +++ b/pyload/web/app/scripts/vendor/jquery.omniwindow.js @@ -0,0 +1,141 @@ +// jQuery OmniWindow plugin +// @version:  0.7.0 +// @author:   Rudenka Alexander (mur.mailbox@gmail.com) +// @license:  MIT + +;(function($) { +  "use strict"; +  $.fn.extend({ +    omniWindow: function(options) { + +      options = $.extend(true, { +        animationsPriority: { +          show: ['overlay', 'modal'], +          hide: ['modal', 'overlay'] +        }, +        overlay: { +          selector: '.ow-overlay', +          hideClass: 'ow-closed', +          animations: { +            show: function(subjects, internalCallback) { return internalCallback(subjects); }, +            hide: function(subjects, internalCallback) { return internalCallback(subjects); }, +            internal: { +              show: function(subjects){ subjects.overlay.removeClass(options.overlay.hideClass); }, +              hide: function(subjects){ subjects.overlay.addClass(options.overlay.hideClass); } +            } +          } +        }, +        modal:   { +          hideClass: 'ow-closed', +          animations: { +            show: function(subjects, internalCallback) { return internalCallback(subjects); }, +            hide: function(subjects, internalCallback) { return internalCallback(subjects); }, +            internal: { +              show: function(subjects){ subjects.modal.removeClass(options.modal.hideClass); }, +              hide: function(subjects){ subjects.modal.addClass(options.modal.hideClass); } +            } +          }, +          internal: { +            stateAttribute: 'ow-active' +          } +        }, +        eventsNames: { +          show: 'show.ow', +          hide: 'hide.ow', +          internal: { +            overlayClick:  'click.ow', +            keyboardKeyUp: 'keyup.ow' +          } +        }, +        callbacks: {                                                                                  // Callbacks execution chain +          beforeShow:  function(subjects, internalCallback) { return internalCallback(subjects); },   // 1 (stop if retruns false) +          positioning: function(subjects, internalCallback) { return internalCallback(subjects); },   // 2 +          afterShow:   function(subjects, internalCallback) { return internalCallback(subjects); },   // 3 +          beforeHide:  function(subjects, internalCallback) { return internalCallback(subjects); },   // 4 (stop if retruns false) +          afterHide:   function(subjects, internalCallback) { return internalCallback(subjects); },   // 5 +          internal: { +            beforeShow: function(subjects) { +              if (subjects.modal.data(options.modal.internal.stateAttribute)) { +                return false; +              } else { +                subjects.modal.data(options.modal.internal.stateAttribute, true); +                return true; +              } +            }, +            afterShow: function(subjects) { +              $(document).on(options.eventsNames.internal.keyboardKeyUp, function(e) { +                if (e.keyCode === 27) {                                              // if the key pressed is the ESC key +                  subjects.modal.trigger(options.eventsNames.hide); +                } +              }); + +              subjects.overlay.on(options.eventsNames.internal.overlayClick, function(){ +                subjects.modal.trigger(options.eventsNames.hide); +              }); +            }, +            positioning: function(subjects) { +              subjects.modal.css('margin-left', Math.round(subjects.modal.outerWidth() / -2)); +            }, +            beforeHide: function(subjects) { +              if (subjects.modal.data(options.modal.internal.stateAttribute)) { +                subjects.modal.data(options.modal.internal.stateAttribute, false); +                return true; +              } else { +                return false; +              } +            }, +            afterHide: function(subjects) { +              subjects.overlay.off(options.eventsNames.internal.overlayClick); +              $(document).off(options.eventsNames.internal.keyboardKeyUp); + +              subjects.overlay.css('display', ''); // clear inline styles after jQ animations +              subjects.modal.css('display', ''); +            } +          } +        } +      }, options); + +      var animate = function(process, subjects, callbackName) { +        var first  = options.animationsPriority[process][0], +            second = options.animationsPriority[process][1]; + +        options[first].animations[process](subjects, function(subjs) {        // call USER's    FIRST animation (depends on priority) +          options[first].animations.internal[process](subjs);                 // call internal  FIRST animation + +          options[second].animations[process](subjects, function(subjs) {     // call USER's    SECOND animation +            options[second].animations.internal[process](subjs);              // call internal  SECOND animation + +                                                                              // then we need to call USER's +                                                                              // afterShow of afterHide callback +            options.callbacks[callbackName](subjects, options.callbacks.internal[callbackName]); +          }); +        }); +      }; + +      var showModal = function(subjects) { +        if (!options.callbacks.beforeShow(subjects, options.callbacks.internal.beforeShow)) { return; } // cancel showing if beforeShow callback return false + +        options.callbacks.positioning(subjects, options.callbacks.internal.positioning); + +        animate('show', subjects, 'afterShow'); +      }; + +      var hideModal = function(subjects) { +        if (!options.callbacks.beforeHide(subjects, options.callbacks.internal.beforeHide)) { return; } // cancel hiding if beforeHide callback return false + +        animate('hide', subjects, 'afterHide'); +      }; + + +      var $overlay = $(options.overlay.selector); + +      return this.each(function() { +        var $modal  = $(this); +        var subjects = {modal: $modal, overlay: $overlay}; + +        $modal.bind(options.eventsNames.show, function(){ showModal(subjects); }) +              .bind(options.eventsNames.hide, function(){ hideModal(subjects); }); +      }); +    } +  }); +})(jQuery);
\ No newline at end of file diff --git a/pyload/web/app/scripts/vendor/remaining.js b/pyload/web/app/scripts/vendor/remaining.js new file mode 100644 index 000000000..d66a2931a --- /dev/null +++ b/pyload/web/app/scripts/vendor/remaining.js @@ -0,0 +1,149 @@ +/** + * Javascript Countdown + * Copyright (c) 2009 Markus Hedlund + * Version 1.1 + * Licensed under MIT license + * http://www.opensource.org/licenses/mit-license.php + * http://labs.mimmin.com/countdown + */ +define([], function() { +    var remaining = { +        /** +         * Get the difference of the passed date, and now. The different formats of the taget parameter are: +         * January 12, 2009 15:14:00     (Month dd, yyyy hh:mm:ss) +         * January 12, 2009              (Month dd, yyyy) +         * 09,00,12,15,14,00             (yy,mm,dd,hh,mm,ss) Months range from 0-11, not 1-12. +         * 09,00,12                      (yy,mm,dd)          Months range from 0-11, not 1-12. +         * 500                           (milliseconds) +         * 2009-01-12 15:14:00           (yyyy-mm-dd hh-mm-ss) +         * 2009-01-12 15:14              (yyyy-mm-dd hh-mm) +         * @param target Target date. Can be either a date object or a string (formated like '24 December, 2010 15:00:00') +         * @return Difference in seconds +         */ +        getSeconds: function(target) { +            var today = new Date(); + +            if (typeof(target) == 'object') { +                var targetDate = target; +            } else { +                var matches = target.match(/(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2})(:(\d{2}))?/);   // YYYY-MM-DD HH-MM-SS +                if (matches != null) { +                    matches[7] = typeof(matches[7]) == 'undefined' ? '00' : matches[7]; +                    var targetDate = new Date(matches[1], matches[2] - 1, matches[3], matches[4], matches[5], matches[7]); +                } else { +                    var targetDate = new Date(target); +                } +            } + +            return Math.floor((targetDate.getTime() - today.getTime()) / 1000); +        }, + +        /** +         * @param seconds Difference in seconds +         * @param i18n A language object (see code) +         * @param onlyLargestUnit Return only the largest unit (see documentation) +         * @param hideEmpty Hide empty units (see documentation) +         * @return String formated something like '1 week, 1 hours, 1 second' +         */ +        getString: function(seconds, i18n, onlyLargestUnit, hideEmpty) { +            if (seconds < 1) { +                return ''; +            } + +            if (typeof(hideEmpty) == 'undefined' || hideEmpty == null) { +                hideEmpty = true; +            } +            if (typeof(onlyLargestUnit) == 'undefined' || onlyLargestUnit == null) { +                onlyLargestUnit = false; +            } +            if (typeof(i18n) == 'undefined' || i18n == null) { +                i18n = { +                    weeks: ['week', 'weeks'], +                    days: ['day', 'days'], +                    hours: ['hour', 'hours'], +                    minutes: ['minute', 'minutes'], +                    seconds: ['second', 'seconds'] +                }; +            } + +            var units = { +                weeks: 7 * 24 * 60 * 60, +                days: 24 * 60 * 60, +                hours: 60 * 60, +                minutes: 60, +                seconds: 1 +            }; + +            var returnArray = []; +            var value; +            for (unit in units) { +                value = units[unit]; +                if (seconds / value >= 1 || unit == 'seconds' || !hideEmpty) { +                    secondsConverted = Math.floor(seconds / value); +                    var i18nUnit = i18n[unit][secondsConverted == 1 ? 0 : 1]; +                    returnArray.push(secondsConverted + ' ' + i18nUnit); +                    seconds -= secondsConverted * value; + +                    if (onlyLargestUnit) { +                        break; +                    } +                } +            } +            ; + +            return returnArray.join(', '); +        }, + +        /** +         * @param seconds Difference in seconds +         * @return String formated something like '169:00:01' +         */ +        getStringDigital: function(seconds) { +            if (seconds < 1) { +                return ''; +            } + +            remainingTime = remaining.getArray(seconds); + +            for (index in remainingTime) { +                remainingTime[index] = remaining.padNumber(remainingTime[index]); +            } +            ; + +            return remainingTime.join(':'); +        }, + +        /** +         * @param seconds Difference in seconds +         * @return Array with hours, minutes and seconds +         */ +        getArray: function(seconds) { +            if (seconds < 1) { +                return []; +            } + +            var units = [60 * 60, 60, 1]; + +            var returnArray = []; +            var value; +            for (index in units) { +                value = units[index]; +                secondsConverted = Math.floor(seconds / value); +                returnArray.push(secondsConverted); +                seconds -= secondsConverted * value; +            } +            ; + +            return returnArray; +        }, + +        /** +         * @param number An integer +         * @return Integer padded with a 0 if necessary +         */ +        padNumber: function(number) { +            return (number >= 0 && number < 10) ? '0' + number : number; +        } +    }; +    return remaining; +});
\ No newline at end of file diff --git a/pyload/web/app/scripts/views/abstract/itemView.js b/pyload/web/app/scripts/views/abstract/itemView.js new file mode 100644 index 000000000..c37118a4c --- /dev/null +++ b/pyload/web/app/scripts/views/abstract/itemView.js @@ -0,0 +1,47 @@ +define(['jquery', 'backbone', 'underscore'], function($, Backbone, _) { +    'use strict'; + +    // A view that is meant for temporary displaying +    // All events must be unbound in onDestroy +    return Backbone.View.extend({ + +        tagName: 'li', +        destroy: function() { +            this.undelegateEvents(); +            this.unbind(); +            if (this.onDestroy) { +                this.onDestroy(); +            } +            this.$el.removeData().unbind(); +            this.remove(); +        }, + +        hide: function() { +            this.$el.slideUp(); +        }, + +        show: function() { +            this.$el.slideDown(); +        }, + +        unrender: function() { +            var self = this; +            this.$el.slideUp(function() { +                self.destroy(); +            }); +        }, + +        deleteItem: function(e) { +            if (e) +                e.stopPropagation(); +            this.model.destroy(); +        }, + +        restart: function(e) { +            if(e) +                e.stopPropagation(); +            this.model.restart(); +        } + +    }); +});
\ No newline at end of file diff --git a/pyload/web/app/scripts/views/abstract/modalView.js b/pyload/web/app/scripts/views/abstract/modalView.js new file mode 100644 index 000000000..65bc0a3c8 --- /dev/null +++ b/pyload/web/app/scripts/views/abstract/modalView.js @@ -0,0 +1,124 @@ +define(['jquery', 'backbone', 'underscore', 'omniwindow'], function($, Backbone, _) { +    'use strict'; + +    return Backbone.View.extend({ + +        events: { +            'click .btn-confirm': 'confirm', +            'click .btn-close': 'hide', +            'click .close': 'hide' +        }, + +        template: null, +        dialog: null, + +        onHideDestroy: false, +        confirmCallback: null, + +        initialize: function(template, confirm) { +            this.confirmCallback = confirm; +            var self = this; +            if (this.template === null) { +                if (template) { +                    this.template = template; +                    // When template was provided this is a temporary dialog +                    this.onHideDestroy = true; +                } +                else +                    require(['hbs!tpl/dialogs/modal'], function(template) { +                        self.template = template; +                    }); +            } +        }, + +        // TODO: whole modal stuff is not very elegant +        render: function() { +            this.$el.html(this.template(this.renderContent())); +            this.onRender(); + +            if (this.dialog === null) { +                this.$el.addClass('modal hide'); +                this.$el.css({opacity: 0, scale: 0.7}); + +                var self = this; +                $('body').append(this.el); +                this.dialog = this.$el.omniWindow({ +                    overlay: { +                        selector: '#modal-overlay', +                        hideClass: 'hide', +                        animations: { +                            hide: function(subjects, internalCallback) { +                                subjects.overlay.transition({opacity: 'hide', delay: 100}, 300, function() { +                                    internalCallback(subjects); +                                    self.onHide(); +                                    if (self.onHideDestroy) +                                        self.destroy(); +                                }); +                            }, +                            show: function(subjects, internalCallback) { +                                subjects.overlay.fadeIn(300); +                                internalCallback(subjects); +                            }}}, +                    modal: { +                        hideClass: 'hide', +                        animations: { +                            hide: function(subjects, internalCallback) { +                                subjects.modal.transition({opacity: 'hide', scale: 0.7}, 300); +                                internalCallback(subjects); +                            }, + +                            show: function(subjects, internalCallback) { +                                subjects.modal.transition({opacity: 'show', scale: 1, delay: 100}, 300, function() { +                                    internalCallback(subjects); +                                }); +                            }} +                    }}); +            } + +            return this; +        }, + +        onRender: function() { + +        }, + +        renderContent: function() { +            return {}; +        }, + +        show: function() { +            if (this.dialog === null) +                this.render(); + +            this.dialog.trigger('show'); + +            this.onShow(); +        }, + +        onShow: function() { + +        }, + +        hide: function() { +            this.dialog.trigger('hide'); +        }, + +        onHide: function() { + +        }, + +        confirm: function() { +            if (this.confirmCallback) +                this.confirmCallback.apply(); + +            this.hide(); +        }, + +        destroy: function() { +            this.$el.remove(); +            this.dialog = null; +            this.remove(); +        } + +    }); +});
\ No newline at end of file diff --git a/pyload/web/app/scripts/views/accounts/accountListView.js b/pyload/web/app/scripts/views/accounts/accountListView.js new file mode 100644 index 000000000..4eb5bfe7d --- /dev/null +++ b/pyload/web/app/scripts/views/accounts/accountListView.js @@ -0,0 +1,52 @@ +define(['jquery', 'underscore', 'backbone', 'app', 'collections/AccountList', './accountView', +    'hbs!tpl/accounts/layout', 'hbs!tpl/accounts/actionbar'], +    function($, _, Backbone, App, AccountList, accountView, template, templateBar) { +        'use strict'; + +        // Renders settings over view page +        return Backbone.Marionette.CollectionView.extend({ + +            itemView: accountView, +            template: template, + +            collection: null, +            modal: null, + +            initialize: function() { +                this.actionbar = Backbone.Marionette.ItemView.extend({ +                    template: templateBar, +                    events: { +                        'click .btn': 'addAccount' +                    }, +                    addAccount: _.bind(this.addAccount, this) +                }); + +                this.collection = new AccountList(); +                this.update(); + +                this.listenTo(App.vent, 'accounts:updated', this.update); +            }, + +            update: function() { +                this.collection.fetch(); +            }, + +            onBeforeRender: function() { +                this.$el.html(template()); +            }, + +            appendHtml: function(collectionView, itemView, index) { +                this.$('.account-list').append(itemView.el); +            }, + +            addAccount: function() { +                var self = this; +                _.requireOnce(['views/accounts/accountModal'], function(Modal) { +                    if (self.modal === null) +                        self.modal = new Modal(); + +                    self.modal.show(); +                }); +            } +        }); +    });
\ No newline at end of file diff --git a/pyload/web/app/scripts/views/accounts/accountModal.js b/pyload/web/app/scripts/views/accounts/accountModal.js new file mode 100644 index 000000000..6c2b226df --- /dev/null +++ b/pyload/web/app/scripts/views/accounts/accountModal.js @@ -0,0 +1,72 @@ +define(['jquery', 'underscore', 'app', 'views/abstract/modalView', 'hbs!tpl/dialogs/addAccount', 'helpers/pluginIcon', 'select2'], +    function($, _, App, modalView, template, pluginIcon) { +        'use strict'; +        return modalView.extend({ + +            events: { +                'submit form': 'add', +                'click .btn-add': 'add' +            }, +            template: template, +            plugins: null, +            select: null, + +            initialize: function() { +                // Inherit parent events +                this.events = _.extend({}, modalView.prototype.events, this.events); +                var self = this; +                $.ajax(App.apiRequest('getAccountTypes', null, {success: function(data) { +                    self.plugins = _.sortBy(data, function(item) { +                        return item; +                    }); +                    self.render(); +                }})); +            }, + +            onRender: function() { +                // TODO: could be a separate input type if needed on multiple pages +                if (this.plugins) +                    this.select = this.$('#pluginSelect').select2({ +                        escapeMarkup: function(m) { +                            return m; +                        }, +                        formatResult: this.format, +                        formatSelection: this.format, +                        data: {results: this.plugins, text: function(item) { +                            return item; +                        }}, +                        id: function(item) { +                            return item; +                        } +                    }); +            }, + +            onShow: function() { +            }, + +            onHide: function() { +            }, + +            format: function(data) { +                return '<img class="logo-select" src="' + pluginIcon(data) + '"> ' + data; +            }, + +            add: function(e) { +                e.stopPropagation(); +                if (this.select) { +                    var plugin = this.select.val(), +                        login = this.$('#login').val(), +                        password = this.$('#password').val(), +                        self = this; + +                    $.ajax(App.apiRequest('updateAccount', { +                        plugin: plugin, login: login, password: password +                    }, { success: function() { +                        App.vent.trigger('accounts:updated'); +                        self.hide(); +                    }})); +                } +                return false; +            } +        }); +    });
\ No newline at end of file diff --git a/pyload/web/app/scripts/views/accounts/accountView.js b/pyload/web/app/scripts/views/accounts/accountView.js new file mode 100644 index 000000000..89f69d7e7 --- /dev/null +++ b/pyload/web/app/scripts/views/accounts/accountView.js @@ -0,0 +1,18 @@ +define(['jquery', 'underscore', 'backbone', 'app', 'hbs!tpl/accounts/account'], +    function($, _, Backbone, App, template) { +        'use strict'; + +        return Backbone.Marionette.ItemView.extend({ + +            tagName: 'tr', +            template: template, + +            events: { +                'click .btn-danger': 'deleteAccount' +            }, + +            deleteAccount: function() { +                this.model.destroy(); +            } +        }); +    });
\ No newline at end of file diff --git a/pyload/web/app/scripts/views/dashboard/dashboardView.js b/pyload/web/app/scripts/views/dashboard/dashboardView.js new file mode 100644 index 000000000..8a0446203 --- /dev/null +++ b/pyload/web/app/scripts/views/dashboard/dashboardView.js @@ -0,0 +1,172 @@ +define(['jquery', 'backbone', 'underscore', 'app', 'models/TreeCollection', 'collections/FileList', +    './packageView', './fileView', 'hbs!tpl/dashboard/layout', 'select2'], +    function($, Backbone, _, App, TreeCollection, FileList, PackageView, FileView, template) { +        'use strict'; +        // Renders whole dashboard +        return Backbone.Marionette.ItemView.extend({ + +            template: template, + +            events: { +            }, + +            ui: { +                'packages': '.package-list', +                'files': '.file-list' +            }, + +            // Package tree +            tree: null, +            // Current open files +            files: null, +            // True when loading animation is running +            isLoading: false, + +            initialize: function() { +                App.dashboard = this; +                this.tree = new TreeCollection(); + +                var self = this; +                // When package is added we reload the data +                App.vent.on('package:added', function() { +                    console.log('Package tree caught, package:added event'); +                    self.tree.fetch(); +                }); + +                App.vent.on('file:updated', _.bind(this.fileUpdated, this)); + +                // TODO: merge? +                this.init(); +                // TODO: file:added +                // TODO: package:deleted +                // TODO: package:updated +            }, + +            init: function() { +                var self = this; +                // TODO: put in separated function +                // TODO: order of elements? +                // Init the tree and callback for package added +                this.tree.fetch({success: function() { +                    self.update(); +                    self.tree.get('packages').on('add', function(pack) { +                        console.log('Package ' + pack.get('pid') + ' added to tree'); +                        self.appendPackage(pack, 0, true); +                        self.openPackage(pack); +                    }); +                }}); + +                this.$('.input').select2({tags: ['a', 'b', 'sdf']}); +            }, + +            update: function() { +                console.log('Update package list'); + +                var packs = this.tree.get('packages'); +                this.files = this.tree.get('files'); + +                if (packs) +                    packs.each(_.bind(this.appendPackage, this)); + +                if (!this.files || this.files.length === 0) { +                    // no files are displayed +                    this.files = null; +                    // Open the first package +                    if (packs && packs.length >= 1) +                        this.openPackage(packs.at(0)); +                } +                else +                    this.files.each(_.bind(this.appendFile, this)); + +                return this; +            }, + +            // TODO sorting ?! +            // Append a package to the list, index, animate it +            appendPackage: function(pack, i, animation) { +                var el = new PackageView({model: pack}).render().el; +                $(this.ui.packages).appendWithAnimation(el, animation); +            }, + +            appendFile: function(file, i, animation) { +                var el = new FileView({model: file}).render().el; +                $(this.ui.files).appendWithAnimation(el, animation); +            }, + +            // Show content of the packages on main view +            openPackage: function(pack) { +                var self = this; + +                // load animation only when something is shown and its different from current package +                if (this.files && this.files !== pack.get('files')) +                    self.loading(); + +                pack.fetch({silent: true, success: function() { +                    console.log('Package ' + pack.get('pid') + ' loaded'); +                    self.contentReady(pack.get('files')); +                }, failure: function() { +                    self.failure(); +                }}); + +            }, + +            contentReady: function(files) { +                var old_files = this.files; +                this.files = files; +                App.vent.trigger('dashboard:contentReady'); + +                // show the files when no loading animation is running and not already open +                if (!this.isLoading && old_files !== files) +                    this.show(); +            }, + +            // Do load animation, remove the old stuff +            loading: function() { +                this.isLoading = true; +                this.files = null; +                var self = this; +                $(this.ui.files).fadeOut({complete: function() { +                    // All file views should vanish +                    App.vent.trigger('dashboard:destroyContent'); + +                    // Loading was faster than animation +                    if (self.files) +                        self.show(); + +                    self.isLoading = false; +                }}); +            }, + +            failure: function() { +                // TODO +            }, + +            show: function() { +                // fileUL has to be resetted before +                this.files.each(_.bind(this.appendFile, this)); +                //TODO: show placeholder when nothing is displayed (filtered content empty) +                $(this.ui.files).fadeIn(); +                App.vent.trigger('dashboard:updated'); +            }, + +            // Refresh the file if it is currently shown +            fileUpdated: function(data) { +                var fid; +                if (_.isObject(data)) +                    fid = data.fid; +                else +                    fid = data; +                // this works with ids and object TODO: not anymore +                var file = this.files.get(fid); +                if (file) +                    if (_.isObject(data)) { // update directly +                        file.set(data); +                        App.vent.trigger('dashboard:updated'); +                    } else { // fetch from server +                        file.fetch({success: function() { +                            App.vent.trigger('dashboard:updated'); +                        }}); +                    } +            } +        }); +    });
\ No newline at end of file diff --git a/pyload/web/app/scripts/views/dashboard/fileView.js b/pyload/web/app/scripts/views/dashboard/fileView.js new file mode 100644 index 000000000..ce91a5f38 --- /dev/null +++ b/pyload/web/app/scripts/views/dashboard/fileView.js @@ -0,0 +1,103 @@ +define(['jquery', 'backbone', 'underscore', 'app', 'utils/apitypes', 'views/abstract/itemView', 'helpers/formatTime', 'hbs!tpl/dashboard/file'], +    function($, Backbone, _, App, Api, ItemView, formatTime, template) { +        'use strict'; + +        // Renders single file item +        return ItemView.extend({ + +            tagName: 'li', +            className: 'file-view row-fluid', +            template: template, +            events: { +                'click .checkbox': 'select', +                'click .btn-delete': 'deleteItem', +                'click .btn-restart': 'restart' +            }, + +            initialize: function() { +                this.listenTo(this.model, 'change', this.render); +                // This will be triggered manually and changed before with silent=true +                this.listenTo(this.model, 'change:visible', this.visibility_changed); +                this.listenTo(this.model, 'change:progress', this.progress_changed); +                this.listenTo(this.model, 'remove', this.unrender); +                this.listenTo(App.vent, 'dashboard:destroyContent', this.destroy); +            }, + +            onDestroy: function() { +            }, + +            render: function() { +                var data = this.model.toJSON(); +                if (data.download) { +                    var status = data.download.status; +                    if (status === Api.DownloadStatus.Offline || status === Api.DownloadStatus.TempOffline) +                        data.offline = true; +                    else if (status === Api.DownloadStatus.Online) +                        data.online = true; +                    else if (status === Api.DownloadStatus.Waiting) +                        data.waiting = true; +                    else if (status === Api.DownloadStatus.Downloading) +                        data.downloading = true; +                    else if (this.model.isFailed()) +                        data.failed = true; +                    else if (this.model.isFinished()) +                        data.finished = true; +                } + +                this.$el.html(this.template(data)); +                if (this.model.get('selected')) +                    this.$el.addClass('ui-selected'); +                else +                    this.$el.removeClass('ui-selected'); + +                if (this.model.get('visible')) +                    this.$el.show(); +                else +                    this.$el.hide(); + +                return this; +            }, + +            select: function(e) { +                e.preventDefault(); +                var checked = this.$el.hasClass('ui-selected'); +                // toggle class immediately, so no re-render needed +                this.model.set('selected', !checked, {silent: true}); +                this.$el.toggleClass('ui-selected'); +                App.vent.trigger('file:selection'); +            }, + +            visibility_changed: function(visible) { +                // TODO: improve animation, height is not available when element was not visible +                if (visible) +                    this.$el.slideOut(true); +                else { +                    this.$el.calculateHeight(true); +                    this.$el.slideIn(true); +                } +            }, + +            progress_changed: function() { +                // TODO: progress for non download statuses +                if (!this.model.isDownload()) +                    return; + +                if (this.model.get('download').status === Api.DownloadStatus.Downloading) { +                    var bar = this.$('.progress .bar'); +                    if (!bar) { // ensure that the dl bar is rendered +                        this.render(); +                        bar = this.$('.progress .bar'); +                    } + +                    bar.width(this.model.get('progress') + '%'); +                    bar.html('  ' + formatTime(this.model.get('eta'))); +                } else if (this.model.get('download').status === Api.DownloadStatus.Waiting) { +                    this.$('.second').html( +                        '<i class="icon-time"></i> ' + formatTime(this.model.get('eta'))); + +                } else // Every else state can be rendered normally +                    this.render(); + +            } +        }); +    });
\ No newline at end of file diff --git a/pyload/web/app/scripts/views/dashboard/filterView.js b/pyload/web/app/scripts/views/dashboard/filterView.js new file mode 100644 index 000000000..ad72cf926 --- /dev/null +++ b/pyload/web/app/scripts/views/dashboard/filterView.js @@ -0,0 +1,147 @@ +define(['jquery', 'backbone', 'underscore', 'app', 'utils/apitypes', 'models/Package', 'hbs!tpl/dashboard/actionbar'], +    /*jslint -W040: false*/ +    function($, Backbone, _, App, Api, Package, template) { +        'use strict'; + +        // Modified version of type ahead show, nearly the same without absolute positioning +        function show() { +            this.$menu +                .insertAfter(this.$element) +                .show(); + +            this.shown = true; +            return this; +        } + +        // Renders the actionbar for the dashboard, handles everything related to filtering displayed files +        return Backbone.Marionette.ItemView.extend({ + +            events: { +                'click .li-check': 'toggle_selection', +                'click .filter-type': 'filter_type', +                'click .filter-state': 'switch_filter', +                'submit .form-search': 'search' +            }, + +            ui: { +                'search': '.search-query', +                'stateMenu': '.dropdown-toggle .state', +                'select': '.btn-check', +                'name': '.breadcrumb .active' +            }, + +            template: template, +            state: null, + +            initialize: function() { +                this.state = Api.DownloadState.All; + +                // Apply the filter before the content is shown +                this.listenTo(App.vent, 'dashboard:contentReady', this.apply_filter); +                this.listenTo(App.vent, 'dashboard:updated', this.apply_filter); +                this.listenTo(App.vent, 'dashboard:updated', this.updateName); +            }, + +            onRender: function() { +                // use our modified method +                $.fn.typeahead.Constructor.prototype.show = show; +                this.ui.search.typeahead({ +                    minLength: 2, +                    source: this.getSuggestions +                }); + +            }, + +            // TODO: app level api request +            search: function(e) { +                e.stopPropagation(); +                var query = this.ui.search.val(); +                this.ui.search.val(''); + +                var pack = new Package(); +                // Overwrite fetch method to use a search +                // TODO: quite hackish, could be improved to filter packages +                //       or show performed search +                pack.fetch = function(options) { +                    pack.search(query, options); +                }; + +                App.dashboard.openPackage(pack); +            }, + +            getSuggestions: function(query, callback) { +                $.ajax(App.apiRequest('searchSuggestions', {pattern: query}, { +                    method: 'POST', +                    success: function(data) { +                        callback(data); +                    } +                })); +            }, + +            switch_filter: function(e) { +                e.stopPropagation(); +                var element = $(e.target); +                var state = parseInt(element.data('state'), 10); +                var menu = this.ui.stateMenu.parent().parent(); +                menu.removeClass('open'); + +                if (state === Api.DownloadState.Finished) { +                    menu.removeClass().addClass('dropdown finished'); +                } else if (state === Api.DownloadState.Unfinished) { +                    menu.removeClass().addClass('dropdown active'); +                } else if (state === Api.DownloadState.Failed) { +                    menu.removeClass().addClass('dropdown failed'); +                } else { +                    menu.removeClass().addClass('dropdown'); +                } + +                this.state = state; +                this.ui.stateMenu.text(element.text()); +                this.apply_filter(); +            }, + +            // Applies the filtering to current open files +            apply_filter: function() { +                if (!App.dashboard.files) +                    return; + +                var self = this; +                App.dashboard.files.map(function(file) { +                    var visible = file.get('visible'); +                    if (visible !== self.is_visible(file)) { +                        file.set('visible', !visible, {silent: true}); +                        file.trigger('change:visible', !visible); +                    } +                }); + +                App.vent.trigger('dashboard:filtered'); +            }, + +            // determine if a file should be visible +            // TODO: non download files +            is_visible: function(file) { +                if (this.state === Api.DownloadState.Finished) +                    return file.isFinished(); +                else if (this.state === Api.DownloadState.Unfinished) +                    return file.isUnfinished(); +                else if (this.state === Api.DownloadState.Failed) +                    return file.isFailed(); + +                return true; +            }, + +            updateName: function() { +                // TODO +//                this.ui.name.text(App.dashboard.package.get('name')); +            }, + +            toggle_selection: function() { +                App.vent.trigger('selection:toggle'); +            }, + +            filter_type: function(e) { + +            } + +        }); +    });
\ No newline at end of file diff --git a/pyload/web/app/scripts/views/dashboard/packageView.js b/pyload/web/app/scripts/views/dashboard/packageView.js new file mode 100644 index 000000000..2738fcbea --- /dev/null +++ b/pyload/web/app/scripts/views/dashboard/packageView.js @@ -0,0 +1,75 @@ +define(['jquery', 'app', 'views/abstract/itemView', 'underscore', 'hbs!tpl/dashboard/package'], +    function($, App, itemView, _, template) { +        'use strict'; + +        // Renders a single package item +        return itemView.extend({ + +            tagName: 'li', +            className: 'package-view', +            template: template, +            events: { +                'click .package-name, .btn-open': 'open', +                'click .icon-refresh': 'restart', +                'click .select': 'select', +                'click .btn-delete': 'deleteItem' +            }, + +            // Ul for child packages (unused) +            ul: null, +            // Currently unused +            expanded: false, + +            initialize: function() { +                this.listenTo(this.model, 'filter:added', this.hide); +                this.listenTo(this.model, 'filter:removed', this.show); +                this.listenTo(this.model, 'change', this.render); +                this.listenTo(this.model, 'remove', this.unrender); + +                // Clear drop down menu +                var self = this; +                this.$el.on('mouseleave', function() { +                    self.$('.dropdown-menu').parent().removeClass('open'); +                }); +            }, + +            onDestroy: function() { +            }, + +            // Render everything, optional only the fileViews +            render: function() { +                this.$el.html(this.template(this.model.toJSON())); +                this.$el.initTooltips(); + +                return this; +            }, + +            unrender: function() { +                itemView.prototype.unrender.apply(this); + +                // TODO: display other package +                App.vent.trigger('dashboard:loading', null); +            }, + + +            // TODO +            // Toggle expanding of packages +            expand: function(e) { +                e.preventDefault(); +            }, + +            open: function(e) { +                e.preventDefault(); +                App.dashboard.openPackage(this.model); +            }, + +            select: function(e) { +                e.preventDefault(); +                var checked = this.$('.select').hasClass('icon-check'); +                // toggle class immediately, so no re-render needed +                this.model.set('selected', !checked, {silent: true}); +                this.$('.select').toggleClass('icon-check').toggleClass('icon-check-empty'); +                App.vent.trigger('package:selection'); +            } +        }); +    });
\ No newline at end of file diff --git a/pyload/web/app/scripts/views/dashboard/selectionView.js b/pyload/web/app/scripts/views/dashboard/selectionView.js new file mode 100644 index 000000000..25b7998df --- /dev/null +++ b/pyload/web/app/scripts/views/dashboard/selectionView.js @@ -0,0 +1,154 @@ +define(['jquery', 'backbone', 'underscore', 'app', 'hbs!tpl/dashboard/select'], +    function($, Backbone, _, App, template) { +        'use strict'; + +        // Renders context actions for selection packages and files +        return Backbone.Marionette.ItemView.extend({ + +            el: '#selection-area', +            template: template, + +            events: { +                'click .icon-check': 'deselect', +                'click .icon-pause': 'pause', +                'click .icon-trash': 'trash', +                'click .icon-refresh': 'restart' +            }, + +            // Element of the action bar +            actionBar: null, +            // number of currently selected elements +            current: 0, + +            initialize: function() { +                this.$el.calculateHeight().height(0); +                var render = _.bind(this.render, this); + +                App.vent.on('dashboard:updated', render); +                App.vent.on('dashboard:filtered', render); +                App.vent.on('package:selection', render); +                App.vent.on('file:selection', render); +                App.vent.on('selection:toggle', _.bind(this.select_toggle, this)); + + +                // API events, maybe better to rely on internal ones? +                App.vent.on('package:deleted', render); +                App.vent.on('file:deleted', render); +            }, + +            get_files: function(all) { +                var files = []; +                if (App.dashboard.files) +                    if (all) +                        files = App.dashboard.files.where({visible: true}); +                    else +                        files = App.dashboard.files.where({selected: true, visible: true}); + +                return files; +            }, + +            get_packs: function() { +                if (!App.dashboard.tree.get('packages')) +                    return []; // TODO + +                return App.dashboard.tree.get('packages').where({selected: true}); +            }, + +            render: function() { +                var files = this.get_files().length; +                var packs = this.get_packs().length; + +                if (files + packs > 0) { +                    this.$el.html(this.template({files: files, packs: packs})); +                    this.$el.initTooltips('bottom'); +                } + +                if (files + packs > 0 && this.current === 0) +                    this.$el.slideOut(); +                else if (files + packs === 0 && this.current > 0) +                    this.$el.slideIn(); + +                // TODO: accessing ui directly, should be events +                if (files > 0) { +                    App.actionbar.currentView.ui.select.addClass('icon-check').removeClass('icon-check-empty'); +                    App.dashboard.ui.packages.addClass('ui-files-selected'); +                } +                else { +                    App.actionbar.currentView.ui.select.addClass('icon-check-empty').removeClass('icon-check'); +                    App.dashboard.ui.packages.removeClass('ui-files-selected'); +                } + +                this.current = files + packs; +            }, + +            // Deselects all items +            deselect: function() { +                this.get_files().map(function(file) { +                    file.set('selected', false); +                }); + +                this.get_packs().map(function(pack) { +                    pack.set('selected', false); +                }); + +                this.render(); +            }, + +            pause: function() { +                alert('Not implemented yet'); +                this.deselect(); +            }, + +            trash: function() { +                _.confirm('dialogs/confirmDelete', function() { + +                    var pids = []; +                    // TODO: delete many at once +                    this.get_packs().map(function(pack) { +                        pids.push(pack.get('pid')); +                        pack.destroy(); +                    }); + +                    // get only the fids of non deleted packages +                    var fids = _.filter(this.get_files(),function(file) { +                        return !_.contains(pids, file.get('package')); +                    }).map(function(file) { +                            file.destroyLocal(); +                            return file.get('fid'); +                        }); + +                    if (fids.length > 0) +                        $.ajax(App.apiRequest('deleteFiles', {fids: fids})); + +                    this.deselect(); +                }, this); +            }, + +            restart: function() { +                this.get_files().map(function(file) { +                    file.restart(); +                }); +                this.get_packs().map(function(pack) { +                    pack.restart(); +                }); + +                this.deselect(); +            }, + +            // Select or deselect all visible files +            select_toggle: function() { +                var files = this.get_files(); +                if (files.length === 0) { +                    this.get_files(true).map(function(file) { +                        file.set('selected', true); +                    }); + +                } else +                    files.map(function(file) { +                        file.set('selected', false); +                    }); + +                this.render(); +            } +        }); +    });
\ No newline at end of file diff --git a/pyload/web/app/scripts/views/headerView.js b/pyload/web/app/scripts/views/headerView.js new file mode 100644 index 000000000..2c83fb381 --- /dev/null +++ b/pyload/web/app/scripts/views/headerView.js @@ -0,0 +1,252 @@ +define(['jquery', 'underscore', 'backbone', 'app', 'models/ServerStatus', 'collections/ProgressList', +    'views/progressView', 'views/notificationView', 'helpers/formatSize', 'hbs!tpl/header/layout', +    'hbs!tpl/header/status', 'hbs!tpl/header/progressbar', 'hbs!tpl/header/progressSup', 'hbs!tpl/header/progressSub' , 'flot'], +    function( +        $, _, Backbone, App, ServerStatus, ProgressList, ProgressView, NotificationView, formatSize, template, templateStatus, templateProgress, templateSup, templateSub) { +        'use strict'; +        // Renders the header with all information +        return Backbone.Marionette.ItemView.extend({ + +            events: { +                'click .icon-list': 'toggle_taskList', +                'click .popover .close': 'toggle_taskList', +                'click .btn-grabber': 'open_grabber', +                'click .logo': 'gotoDashboard' +            }, + +            ui: { +                progress: '.progress-list', +                speedgraph: '#speedgraph' +            }, + +            template: template, + +            // view +            grabber: null, +            speedgraph: null, + +            // models and data +            ws: null, +            status: null, +            progressList: null, +            speeds: null, + +            // sub view +            notificationView: null, + +            // save if last progress was empty +            wasEmpty: false, +            lastStatus: null, + +            initialize: function() { +                var self = this; +                this.notificationView = new NotificationView(); + +                this.status = new ServerStatus(); +                this.listenTo(this.status, 'change', this.update); + +                this.progressList = new ProgressList(); +                this.listenTo(this.progressList, 'add', function(model) { +                    self.ui.progress.appendWithAnimation(new ProgressView({model: model}).render().el); +                }); + +                // TODO: button to start stop refresh +                var ws = App.openWebSocket('/async'); +                ws.onopen = function() { +                    ws.send(JSON.stringify('start')); +                }; +                // TODO compare with polling +                ws.onmessage = _.bind(this.onData, this); +                ws.onerror = function(error) { +                    console.log(error); +                    alert('WebSocket error' + error); +                }; + +                this.ws = ws; +            }, + +            gotoDashboard: function() { +                App.navigate(''); +            }, + +            initGraph: function() { +                var totalPoints = 120; +                var data = []; + +                // init with empty data +                while (data.length < totalPoints) +                    data.push([data.length, 0]); + +                this.speeds = data; +                this.speedgraph = $.plot(this.ui.speedgraph, [this.speeds], { +                    series: { +                        lines: { show: true, lineWidth: 2 }, +                        shadowSize: 0, +                        color: '#fee247' +                    }, +                    xaxis: { ticks: [] }, +                    yaxis: { ticks: [], min: 1, autoscaleMargin: 0.1, tickFormatter: function(data) { +                        return formatSize(data * 1024); +                    }, position: 'right' }, +                    grid: { +                        show: true, +//            borderColor: "#757575", +                        borderColor: 'white', +                        borderWidth: 1, +                        labelMargin: 0, +                        axisMargin: 0, +                        minBorderMargin: 0 +                    } +                }); + +            }, + +            // Must be called after view was attached +            init: function() { +                this.initGraph(); +                this.update(); +            }, + +            update: function() { +                // TODO: what should be displayed in the header +                // queue/processing size? + +                var status = this.status.toJSON(); +                status.maxspeed = _.max(this.speeds, function(speed) { +                    return speed[1]; +                })[1] * 1024; +                this.$('.status-block').html( +                    templateStatus(status) +                ); + +                var data = {tasks: 0, downloads: 0, speed: 0, single: false}; +                this.progressList.each(function(progress) { +                    if (progress.isDownload()) { +                        data.downloads++; +                        data.speed += progress.get('download').speed; +                    } else +                        data.tasks++; +                }); + +                // Show progress of one task +                if (data.tasks + data.downloads === 1) { +                    var progress = this.progressList.at(0); +                    data.single = true; +                    data.eta = progress.get('eta'); +                    data.percent = progress.getPercent(); +                    data.name = progress.get('name'); +                    data.statusmsg = progress.get('statusmsg'); +                } + +                data.etaqueue = status.eta; +                data.linksqueue = status.linksqueue; +                data.sizequeue = status.sizequeue; + +                // Render progressbar only when needed +                if (!_.isEqual([data.tasks, data.downloads], this.lastStatus)) { +                    console.log('render bar'); +                    this.lastStatus = [data.tasks, data.downloads]; +                    this.$('#progress-info').html(templateProgress(data)); +                } else { +                    this.$('#progress-info .bar').width(data.percent + '%'); +                } + +                // render upper and lower part +                this.$('.sup').html(templateSup(data)); +                this.$('.sub').html(templateSub(data)); + +                return this; +            }, + +            toggle_taskList: function() { +                this.$('.popover').animate({opacity: 'toggle'}); +            }, + +            open_grabber: function() { +                var self = this; +                _.requireOnce(['views/linkGrabberModal'], function(ModalView) { +                    if (self.grabber === null) +                        self.grabber = new ModalView(); + +                    self.grabber.show(); +                }); +            }, + +            onData: function(evt) { +                var data = JSON.parse(evt.data); +                if (data === null) return; + +                if (data['@class'] === 'ServerStatus') { +                    this.status.set(data); + +                    // There tasks at the server, but not in queue: so fetch them +                    // or there are tasks in our queue but not on the server +                    if (this.status.get('notifications') && !this.notificationView.tasks.hasTaskWaiting() || +                        !this.status.get('notifications') && this.notificationView.tasks.hasTaskWaiting()) +                        this.notificationView.tasks.fetch(); + +                    this.speeds = this.speeds.slice(1); +                    this.speeds.push([this.speeds[this.speeds.length - 1][0] + 1, Math.floor(data.speed / 1024)]); + +                    // TODO: if everything is 0 re-render is not needed +                    this.speedgraph.setData([this.speeds]); +                    // adjust the axis +                    this.speedgraph.setupGrid(); +                    this.speedgraph.draw(); + +                } +                else if (_.isArray(data)) +                    this.onProgressUpdate(data); +                else if (data['@class'] === 'EventInfo') +                    this.onEvent(data.eventname, data.event_args); +                else +                    console.log('Unknown Async input', data); + +            }, + +            onProgressUpdate: function(progress) { +                // generate a unique id +                _.each(progress, function(prog) { +                    if (prog.download) +                        prog.pid = prog.download.fid; +                    else +                        prog.pid = prog.plugin + prog.name; +                }); + +                this.progressList.set(progress); +                // update currently open files with progress +                this.progressList.each(function(prog) { +                    if (prog.isDownload() && App.dashboard.files) { +                        var file = App.dashboard.files.get(prog.get('download').fid); +                        if (file) { +                            file.set({ +                                progress: prog.getPercent(), +                                eta: prog.get('eta'), +                                size: prog.get('total') +                            }, {silent: true}); +                            file.setDownloadStatus(prog.get('download').status); +                            file.trigger('change:progress'); +                        } +                    } +                }); + +                if (progress.length === 0) { +                    // only render one time when last was not empty already +                    if (!this.wasEmpty) { +                        this.update(); +                        this.wasEmpty = true; +                    } +                } else { +                    this.wasEmpty = false; +                    this.update(); +                } +            }, + +            onEvent: function(event, args) { +                args.unshift(event); +                console.log('Core send event', args); +                App.vent.trigger.apply(App.vent, args); +            } + +        }); +    });
\ No newline at end of file diff --git a/pyload/web/app/scripts/views/input/inputLoader.js b/pyload/web/app/scripts/views/input/inputLoader.js new file mode 100644 index 000000000..04d591d30 --- /dev/null +++ b/pyload/web/app/scripts/views/input/inputLoader.js @@ -0,0 +1,8 @@ +define(['./textInput'], function(textInput) { +    'use strict'; + +    // selects appropriate input element +    return function(input) { +        return textInput; +    }; +});
\ No newline at end of file diff --git a/pyload/web/app/scripts/views/input/inputView.js b/pyload/web/app/scripts/views/input/inputView.js new file mode 100644 index 000000000..1860fcaf1 --- /dev/null +++ b/pyload/web/app/scripts/views/input/inputView.js @@ -0,0 +1,86 @@ +define(['jquery', 'backbone', 'underscore'], function($, Backbone, _) { +    'use strict'; + +    // Renders input elements +    return Backbone.View.extend({ + +        tagName: 'input', + +        input: null, +        value: null, +        description: null, +        default_value: null, + +        // enables tooltips +        tooltip: true, + +        initialize: function(options) { +            this.input = options.input; +            this.default_value = this.input.default_value; +            this.value = options.value; +            this.description = options.description; +        }, + +        render: function() { +            this.renderInput(); +            // data for tooltips +            if (this.description && this.tooltip) { +                this.$el.data('content', this.description); +                // TODO: render default value in popup? +//                this.$el.data('title', "TODO: title"); +                this.$el.popover({ +                    placement: 'right', +                    trigger: 'hover' +//                    delay: { show: 500, hide: 100 } +                }); +            } + +            return this; +        }, + +        renderInput: function() { +            // Overwrite this +        }, + +        showTooltip: function() { +            if (this.description && this.tooltip) +                this.$el.popover('show'); +        }, + +        hideTooltip: function() { +            if (this.description && this.tooltip) +                this.$el.popover('hide'); +        }, + +        destroy: function() { +            this.undelegateEvents(); +            this.unbind(); +            if (this.onDestroy) { +                this.onDestroy(); +            } +            this.$el.removeData().unbind(); +            this.remove(); +        }, + +        // focus the input element +        focus: function() { +            this.$el.focus(); +        }, + +        // Clear the input +        clear: function() { + +        }, + +        // retrieve value of the input +        getVal: function() { +            return this.value; +        }, + +        // the child class must call this when the value changed +        setVal: function(value) { +            this.value = value; +            this.trigger('change', value); +        } +    }); +});
\ No newline at end of file diff --git a/pyload/web/app/scripts/views/input/textInput.js b/pyload/web/app/scripts/views/input/textInput.js new file mode 100644 index 000000000..0eebbf91e --- /dev/null +++ b/pyload/web/app/scripts/views/input/textInput.js @@ -0,0 +1,36 @@ +define(['jquery', 'backbone', 'underscore', './inputView'], function($, Backbone, _, inputView) { +    'use strict'; + +    return inputView.extend({ + +        // TODO +        tagName: 'input', +        events: { +            'keyup': 'onChange', +            'focus': 'showTooltip', +            'focusout': 'hideTooltip' +        }, + +        renderInput: function() { +            this.$el.attr('type', 'text'); +            this.$el.attr('name', 'textInput'); + +            if (this.default_value) +                this.$el.attr('placeholder', this.default_value); + +            if (this.value) +                this.$el.val(this.value); + +            return this; +        }, + +        clear: function() { +            this.$el.val(''); +        }, + +        onChange: function(e) { +            this.setVal(this.$el.val()); +        } + +    }); +});
\ No newline at end of file diff --git a/pyload/web/app/scripts/views/linkGrabberModal.js b/pyload/web/app/scripts/views/linkGrabberModal.js new file mode 100644 index 000000000..e6f59c134 --- /dev/null +++ b/pyload/web/app/scripts/views/linkGrabberModal.js @@ -0,0 +1,49 @@ +define(['jquery', 'underscore', 'app', 'views/abstract/modalView', 'hbs!tpl/dialogs/linkgrabber'], +    function($, _, App, modalView, template) { +        'use strict'; +        // Modal dialog for package adding - triggers package:added when package was added +        return modalView.extend({ + +            events: { +                'click .btn-success': 'addPackage', +                'keypress #inputPackageName': 'addOnEnter' +            }, + +            template: template, + +            initialize: function() { +                // Inherit parent events +                this.events = _.extend({}, modalView.prototype.events, this.events); +            }, + +            addOnEnter: function(e) { +                if (e.keyCode !== 13) return; +                this.addPackage(e); +            }, + +            addPackage: function(e) { +                var self = this; +                var options = App.apiRequest('addPackage', +                    { +                        name: $('#inputPackageName').val(), +                        // TODO: better parsing / tokenization +                        links: $('#inputLinks').val().split('\n') +                    }, +                    { +                        success: function() { +                            App.vent.trigger('package:added'); +                            self.hide(); +                        } +                    }); + +                $.ajax(options); +                $('#inputPackageName').val(''); +                $('#inputLinks').val(''); +            }, + +            onShow: function() { +                this.$('#inputPackageName').focus(); +            } + +        }); +    });
\ No newline at end of file diff --git a/pyload/web/app/scripts/views/loginView.js b/pyload/web/app/scripts/views/loginView.js new file mode 100644 index 000000000..891b3ec99 --- /dev/null +++ b/pyload/web/app/scripts/views/loginView.js @@ -0,0 +1,37 @@ +define(['jquery', 'backbone', 'underscore', 'app', 'hbs!tpl/login'], +    function($, Backbone, _, App, template) { +        'use strict'; + +        // Renders context actions for selection packages and files +        return Backbone.Marionette.ItemView.extend({ +            template: template, + +            events: { +                'submit form': 'login' +            }, + +            ui: { +                'form': 'form' +            }, + +            login: function(e) { +                e.stopPropagation(); + +                var options = App.apiRequest('login', null, { +                    data: this.ui.form.serialize(), +                    type : 'post', +                    success: function(data) { +                        // TODO: go to last page, better error +                        if (data) +                            App.navigate(''); +                        else +                            alert('Wrong login'); +                    } +                }); + +                $.ajax(options); +                return false; +            } + +        }); +    });
\ No newline at end of file diff --git a/pyload/web/app/scripts/views/notificationView.js b/pyload/web/app/scripts/views/notificationView.js new file mode 100644 index 000000000..93d07a0f3 --- /dev/null +++ b/pyload/web/app/scripts/views/notificationView.js @@ -0,0 +1,85 @@ +define(['jquery', 'backbone', 'underscore', 'app', 'collections/InteractionList', 'hbs!tpl/notification'], +    function($, Backbone, _, App, InteractionList, template) { +        'use strict'; + +        // Renders context actions for selection packages and files +        return Backbone.Marionette.ItemView.extend({ + +            // Only view for this area so it's hardcoded +            el: '#notification-area', +            template: template, + +            events: { +                'click .btn-query': 'openQuery', +                'click .btn-notification': 'openNotifications' +            }, + +            tasks: null, +            // area is slided out +            visible: false, +            // the dialog +            modal: null, + +            initialize: function() { +                this.tasks = new InteractionList(); + +                App.vent.on('interaction:added', _.bind(this.onAdd, this)); +                App.vent.on('interaction:deleted', _.bind(this.onDelete, this)); + +                var render = _.bind(this.render, this); +                this.listenTo(this.tasks, 'add', render); +                this.listenTo(this.tasks, 'remove', render); + +            }, + +            onAdd: function(task) { +                this.tasks.add(task); +            }, + +            onDelete: function(task) { +                this.tasks.remove(task); +            }, + +            onRender: function() { +                this.$el.calculateHeight().height(0); +            }, + +            render: function() { + +                // only render when it will be visible +                if (this.tasks.length > 0) +                    this.$el.html(this.template(this.tasks.toJSON())); + +                if (this.tasks.length > 0 && !this.visible) { +                    this.$el.slideOut(); +                    this.visible = true; +                } +                else if (this.tasks.length === 0 && this.visible) { +                    this.$el.slideIn(); +                    this.visible = false; +                } + +                return this; +            }, + +            openQuery: function() { +                var self = this; + +                _.requireOnce(['views/queryModal'], function(ModalView) { +                    if (self.modal === null) { +                        self.modal = new ModalView(); +                        self.modal.parent = self; +                    } + +                    self.modal.model = self.tasks.at(0); +                    self.modal.render(); +                    self.modal.show(); +                }); + +            }, + +            openNotifications: function() { + +            } +        }); +    });
\ No newline at end of file diff --git a/pyload/web/app/scripts/views/progressView.js b/pyload/web/app/scripts/views/progressView.js new file mode 100644 index 000000000..7b9dbb74b --- /dev/null +++ b/pyload/web/app/scripts/views/progressView.js @@ -0,0 +1,46 @@ +define(['jquery', 'backbone', 'underscore', 'app', 'utils/apitypes', 'views/abstract/itemView', +    'hbs!tpl/header/progress', 'hbs!tpl/header/progressStatus', 'helpers/pluginIcon'], +    function($, Backbone, _, App, Api, ItemView, template, templateStatus, pluginIcon) { +        'use strict'; + +        // Renders single file item +        return ItemView.extend({ + +            idAttribute: 'pid', +            tagName: 'li', +            template: template, +            events: { +            }, + +            // Last name +            name: null, + +            initialize: function() { +                this.listenTo(this.model, 'change', this.update); +                this.listenTo(this.model, 'remove', this.unrender); +            }, + +            onDestroy: function() { +            }, + +            // Update html without re-rendering +            update: function() { +                if (this.name !== this.model.get('name')) { +                    this.name = this.model.get('name'); +                    this.render(); +                } + +                this.$('.bar').width(this.model.getPercent() + '%'); +                this.$('.progress-status').html(templateStatus(this.model.toJSON())); +            }, + +            render: function() { +                // TODO: icon +                // TODO: other states +                // TODO: non download progress +                this.$el.css('background-image', 'url(' + pluginIcon('todo') + ')'); +                this.$el.html(this.template(this.model.toJSON())); +                return this; +            } +        }); +    });
\ No newline at end of file diff --git a/pyload/web/app/scripts/views/queryModal.js b/pyload/web/app/scripts/views/queryModal.js new file mode 100644 index 000000000..ce624814a --- /dev/null +++ b/pyload/web/app/scripts/views/queryModal.js @@ -0,0 +1,69 @@ +define(['jquery', 'underscore', 'app', 'views/abstract/modalView', './input/inputLoader', 'hbs!tpl/dialogs/interactionTask'], +    function($, _, App, modalView, load_input, template) { +        'use strict'; +        return modalView.extend({ + +            events: { +                'click .btn-success': 'submit', +                'submit form': 'submit' +            }, +            template: template, + +            // the notificationView +            parent: null, + +            model: null, +            input: null, + +            initialize: function() { +                // Inherit parent events +                this.events = _.extend({}, modalView.prototype.events, this.events); +            }, + +            renderContent: function() { +                var data = { +                    title: this.model.get('title'), +                    plugin: this.model.get('plugin'), +                    description: this.model.get('description') +                }; + +                var input = this.model.get('input').data; +                if (this.model.isCaptcha()) { +                    data.captcha = input[0]; +                    data.type = input[1]; +                } +                return data; +            }, + +            onRender: function() { +                // instantiate the input +                var input = this.model.get('input'); +                var InputView = load_input(input); +                this.input = new InputView({input: input}); +                // only renders after wards +                this.$('#inputField').append(this.input.render().el); +            }, + +            submit: function(e) { +                e.stopPropagation(); +                // TODO: load next task + +                this.model.set('result', this.input.getVal()); +                var self = this; +                this.model.save({success: function() { +                    self.hide(); +                }}); + +                this.input.clear(); +                return false; +            }, + +            onShow: function() { +                this.input.focus(); +            }, + +            onHide: function() { +                this.input.destroy(); +            } +        }); +    });
\ No newline at end of file diff --git a/pyload/web/app/scripts/views/settings/configSectionView.js b/pyload/web/app/scripts/views/settings/configSectionView.js new file mode 100644 index 000000000..0d9b0762f --- /dev/null +++ b/pyload/web/app/scripts/views/settings/configSectionView.js @@ -0,0 +1,99 @@ +define(['jquery', 'underscore', 'backbone', 'app', '../abstract/itemView', '../input/inputLoader', +    'hbs!tpl/settings/config', 'hbs!tpl/settings/configItem'], +    function($, _, Backbone, App, itemView, load_input, template, templateItem) { +        'use strict'; + +        // Renders settings over view page +        return itemView.extend({ + +            tagName: 'div', + +            template: template, +            templateItem: templateItem, + +            // Will only render one time with further attribute updates +            rendered: false, + +            events: { +                'click .btn-primary': 'submit', +                'click .btn-reset': 'reset' +            }, + +            initialize: function() { +                this.listenTo(this.model, 'destroy', this.destroy); +            }, + +            render: function() { +                if (!this.rendered) { +                    this.$el.html(this.template(this.model.toJSON())); + +                    // initialize the popover +                    this.$('.page-header a').popover({ +                        placement: 'left' +//                        trigger: 'hover' +                    }); + +                    var container = this.$('.control-content'); +                    var self = this; +                    _.each(this.model.get('items'), function(item) { +                        var json = item.toJSON(); +                        var el = $('<div>').html(self.templateItem(json)); +                        var InputView = load_input(item.get('input')); +                        var input = new InputView(json).render(); +                        item.set('inputView', input); + +                        self.listenTo(input, 'change', _.bind(self.render, self)); +                        el.find('.controls').append(input.el); +                        container.append(el); +                    }); +                    this.rendered = true; +                } +                // Enable button if something is changed +                if (this.model.hasChanges()) +                    this.$('.btn-primary').removeClass('disabled'); +                else +                    this.$('.btn-primary').addClass('disabled'); + +                // Mark all inputs that are modified +                _.each(this.model.get('items'), function(item) { +                    var input = item.get('inputView'); +                    var el = input.$el.parent().parent(); +                    if (item.isChanged()) +                        el.addClass('info'); +                    else +                        el.removeClass('info'); +                }); + +                return this; +            }, + +            onDestroy: function() { +                // TODO: correct cleanup after building up so many views and models +            }, + +            submit: function(e) { +                e.stopPropagation(); +                // TODO: success / failure popups +                var self = this; +                this.model.save({success: function() { +                    self.render(); +                    App.vent.trigger('config:change'); +                }}); + +            }, + +            reset: function(e) { +                e.stopPropagation(); +                // restore the original value +                _.each(this.model.get('items'), function(item) { +                    if (item.has('inputView')) { +                        var input = item.get('inputView'); +                        input.setVal(item.get('value')); +                        input.render(); +                    } +                }); +                this.render(); +            } + +        }); +    });
\ No newline at end of file diff --git a/pyload/web/app/scripts/views/settings/pluginChooserModal.js b/pyload/web/app/scripts/views/settings/pluginChooserModal.js new file mode 100644 index 000000000..242d11a5a --- /dev/null +++ b/pyload/web/app/scripts/views/settings/pluginChooserModal.js @@ -0,0 +1,72 @@ +define(['jquery', 'underscore', 'app', 'views/abstract/modalView', 'hbs!tpl/dialogs/addPluginConfig', +    'helpers/pluginIcon', 'select2'], +    function($, _, App, modalView, template, pluginIcon) { +        'use strict'; +        return modalView.extend({ + +            events: { +                'click .btn-add': 'add' +            }, +            template: template, +            plugins: null, +            select: null, + +            initialize: function() { +                // Inherit parent events +                this.events = _.extend({}, modalView.prototype.events, this.events); +                var self = this; +                $.ajax(App.apiRequest('getAvailablePlugins', null, {success: function(data) { +                    self.plugins = _.sortBy(data, function(item) { +                        return item.name; +                    }); +                    self.render(); +                }})); +            }, + +            onRender: function() { +                // TODO: could be a seperate input type if needed on multiple pages +                if (this.plugins) +                    this.select = this.$('#pluginSelect').select2({ +                        escapeMarkup: function(m) { +                            return m; +                        }, +                        formatResult: this.format, +                        formatSelection: this.formatSelection, +                        data: {results: this.plugins, text: function(item) { +                            return item.label; +                        }}, +                        id: function(item) { +                            return item.name; +                        } +                    }); +            }, + +            onShow: function() { +            }, + +            onHide: function() { +            }, + +            format: function(data) { +                var s = '<div class="plugin-select" style="background-image: url(' + pluginIcon(data.name) + ')">' + data.label; +                s += '<br><span>' + data.description + '<span></div>'; +                return s; +            }, + +            formatSelection: function(data) { +                if (!data || _.isEmpty(data)) +                    return ''; + +                return '<img class="logo-select" src="' + pluginIcon(data.name) + '"> ' + data.label; +            }, + +            add: function(e) { +                e.stopPropagation(); +                if (this.select) { +                    var plugin = this.select.val(); +                    App.vent.trigger('config:open', plugin); +                    this.hide(); +                } +            } +        }); +    });
\ No newline at end of file diff --git a/pyload/web/app/scripts/views/settings/settingsView.js b/pyload/web/app/scripts/views/settings/settingsView.js new file mode 100644 index 000000000..ff86efdf9 --- /dev/null +++ b/pyload/web/app/scripts/views/settings/settingsView.js @@ -0,0 +1,184 @@ +define(['jquery', 'underscore', 'backbone', 'app', 'models/ConfigHolder', './configSectionView', +    'hbs!tpl/settings/layout', 'hbs!tpl/settings/menu', 'hbs!tpl/settings/actionbar'], +    function($, _, Backbone, App, ConfigHolder, ConfigSectionView, template, templateMenu, templateBar) { +        'use strict'; + +        // Renders settings over view page +        return Backbone.Marionette.ItemView.extend({ + +            template: template, +            templateMenu: templateMenu, + +            events: { +                'click .settings-menu li > a': 'change_section', +                'click .icon-remove': 'deleteConfig' +            }, + +            ui: { +                'menu': '.settings-menu', +                'content': '.setting-box > form' +            }, + +            selected: null, +            modal: null, + +            coreConfig: null, // It seems collections are not needed +            pluginConfig: null, + +            // currently open configHolder +            config: null, +            lastConfig: null, +            isLoading: false, + +            initialize: function() { +                this.actionbar = Backbone.Marionette.ItemView.extend({ +                    template: templateBar, +                    events: { +                        'click .btn': 'choosePlugin' +                    }, +                    choosePlugin: _.bind(this.choosePlugin, this) + +                }); +                this.listenTo(App.vent, 'config:open', this.openConfig); +                this.listenTo(App.vent, 'config:change', this.refresh); + +                this.refresh(); +            }, + +            refresh: function() { +                var self = this; +                $.ajax(App.apiRequest('getCoreConfig', null, {success: function(data) { +                    self.coreConfig = data; +                    self.renderMenu(); +                }})); +                $.ajax(App.apiRequest('getPluginConfig', null, {success: function(data) { +                    self.pluginConfig = data; +                    self.renderMenu(); +                }})); +            }, + +            onRender: function() { +                // set a height with css so animations will work +                this.ui.content.height(this.ui.content.height()); +            }, + +            renderMenu: function() { +                var plugins = [], +                    addons = []; + +                // separate addons and default plugins +                // addons have an activated state +                _.each(this.pluginConfig, function(item) { +                    if (item.activated === null) +                        plugins.push(item); +                    else +                        addons.push(item); +                }); + +                this.$(this.ui.menu).html(this.templateMenu({ +                    core: this.coreConfig, +                    plugin: plugins, +                    addon: addons +                })); + +                // mark the selected element +                this.$('li[data-name="' + this.selected + '"]').addClass('active'); +            }, + +            openConfig: function(name) { +                // Do nothing when this config is already open +                if (this.config && this.config.get('name') === name) +                    return; + +                this.lastConfig = this.config; +                this.config = new ConfigHolder({name: name}); +                this.loading(); + +                var self = this; +                this.config.fetch({success: function() { +                    if (!self.isLoading) +                        self.show(); + +                }, failure: _.bind(this.failure, this)}); + +            }, + +            loading: function() { +                this.isLoading = true; +                var self = this; +                this.ui.content.fadeOut({complete: function() { +                    if (self.config.isLoaded()) +                        self.show(); + +                    self.isLoading = false; +                }}); + +            }, + +            show: function() { +                // TODO animations are bit sloppy +                this.ui.content.css('display', 'block'); +                var oldHeight = this.ui.content.height(); + +                // this will destroy the old view +                if (this.lastConfig) +                    this.lastConfig.trigger('destroy'); +                else +                    this.ui.content.empty(); + +                // reset the height +                this.ui.content.css('height', ''); +                // append the new element +                this.ui.content.append(new ConfigSectionView({model: this.config}).render().el); +                // get the new height +                var height = this.ui.content.height(); +                // set the old height again +                this.ui.content.height(oldHeight); +                this.ui.content.animate({ +                    opacity: 'show', +                    height: height +                }); +            }, + +            failure: function() { +                // TODO +                this.config = null; +            }, + +            change_section: function(e) { +                // TODO check for changes +                // TODO move this into render? + +                var el = $(e.target).closest('li'); + +                this.selected = el.data('name'); +                this.openConfig(this.selected); + +                this.ui.menu.find('li.active').removeClass('active'); +                el.addClass('active'); +                e.preventDefault(); +            }, + +            choosePlugin: function(e) { +                var self = this; +                _.requireOnce(['views/settings/pluginChooserModal'], function(Modal) { +                    if (self.modal === null) +                        self.modal = new Modal(); + +                    self.modal.show(); +                }); +            }, + +            deleteConfig: function(e) { +                e.stopPropagation(); +                var el = $(e.target).parent().parent(); +                var name = el.data('name'); +                var self = this; +                $.ajax(App.apiRequest('deleteConfig', {plugin: name}, { success: function() { +                    self.refresh(); +                }})); +                return false; +            } + +        }); +    });
\ No newline at end of file diff --git a/pyload/web/app/styles/default/accounts.less b/pyload/web/app/styles/default/accounts.less new file mode 100644 index 000000000..9b45b64b3 --- /dev/null +++ b/pyload/web/app/styles/default/accounts.less @@ -0,0 +1,6 @@ +@import "common"; + +.logo-select { +  width: 20px; +  height: 20px; +}
\ No newline at end of file diff --git a/pyload/web/app/styles/default/admin.less b/pyload/web/app/styles/default/admin.less new file mode 100644 index 000000000..92524c153 --- /dev/null +++ b/pyload/web/app/styles/default/admin.less @@ -0,0 +1,17 @@ +@import "common"; + +/*  +    Admin  +*/ + +#btn_newuser { +  float: right; +} + +#user_permissions { +  float: right; +} + +.userperm { +  width: 115px; +}
\ No newline at end of file diff --git a/pyload/web/app/styles/default/dashboard.less b/pyload/web/app/styles/default/dashboard.less new file mode 100644 index 000000000..ed87e19a1 --- /dev/null +++ b/pyload/web/app/styles/default/dashboard.less @@ -0,0 +1,330 @@ +@import "bootstrap/less/mixins"; +@import "common"; + +/* +    Dashboard +*/ + +#dashboard ul { +  margin: 0; +  list-style: none; +} + +.sidebar-header { +  font-size: 25px; +  line-height: 25px; +  margin: 4px 0; +  border-bottom: 1px dashed @grey; +} + +/* +  Packages +*/ +.package-list { +  list-style: none; +  margin-left: 0; +} + +@frame-top: 20px; +@frame-bottom: 18px; + +.package-frame { +  position: absolute; +  top: -@frame-top; +  left: -@frame-top / 2; +  right: -@frame-top / 2; +  bottom: -@frame-bottom + 2px; // + size of visible bar +  z-index: -1; // lies under package +  border: 1px solid @grey; +  border-radius: 5px; +  box-shadow: 3px 3px 6px rgba(0, 0, 0, 0.75); +} + +.package-view { +  padding-bottom: 4px; +  margin: 8px 0; +  position: relative; +  overflow: hidden; +  .hyphens; + + +  i { +    cursor: pointer; +  } + +  & > i { +    vertical-align: middle; +  } + +  .progress { +    position: absolute; +    height: @frame-bottom; +    line-height: @frame-bottom; +    font-size: 12px; +    text-align: center; +    border-radius: 0; +    border-bottom-left-radius: 5px; +    border-bottom-right-radius: 5px; +    bottom: 0; +    left: 0; +    right: 0; +    margin-bottom: 0; +    background-image: none; +    color: @light; +    background-color: @yellow; +  } + +  .bar-info { +    background-image: none; +    background-color: @blue; +  } + +  &:hover { +    overflow: visible; +    z-index: 10; + +    .package-frame { +      background-color: @light; +    } +  } + +  &.ui-selected:hover { +    color: @light; + +    .package-frame { +      background-color: @dark; +    } + +  } +} + +.package-name { +  cursor: pointer; +} + +.package-indicator { +  position: absolute; +  top: 0; +  right: 0; +  float: right; +  color: @blue; +  text-shadow: @yellowDark 1px 1px; +  height: @frame-top; +  line-height: @frame-top; + +  & > i:hover { +    color: @green; +  } + +  .dropdown-menu { +    text-shadow: none; +  } + +  .tooltip { +    text-shadow: none; +    width: 100%; +  } + +  .btn-move { +    color: @green; +    display: none; +  } + +} + +.ui-files-selected .btn-move { +  display: inline; +} + +// Tag area with different effect on hover +.tag-area { +  position: absolute; +  top: -2px; +  left: 0; + +  .badge { +    font-size: 11px; +    line-height: 11px; +  } + +  .badge i { +    cursor: pointer; +    &:hover:before { +      content: "\f024"; // show Remove icon +    } +  } + +  .badge-ghost { +    visibility: hidden; +    cursor: pointer; +    opacity: 0.5; +  } + +  &:hover .badge-ghost { +    visibility: visible; +  } + +} + +/* +  File View +*/ + +.file-list { +  list-style: none; +  margin: 0; +} + +@file-height: 22px; + +.file-view { +  position: relative; +  padding: 0 4px; +  border-top: 1px solid #dddddd; +  line-height: @file-height; + +  &:first-child { +    border-top: none; +  } + +  &:hover, &.ui-selected:hover { +    border-radius: 5px; +    .gradient(top, @blue, @blueLight); +    color: @light; +  } + +  &.ui-selected { +    .gradient(top, @yellow, @yellowDark); +    color: @dark; +    border-color: @greenDark; + +    .file-row.downloading .bar { +        .gradient(top, @green, @greenLight); +    } + +  } + +  img { // plugin logo +    margin-top: -2px; +    padding: 0 2px; +    height: @file-height; +    width: @file-height; +  } + +  .icon-chevron-down:hover { +    cursor: pointer; +    color: @yellow; +  } + +} + +.file-row { +  min-height: 0 !important; +//  padding-left: 5px; +  padding-top: 4px; +  padding-bottom: 4px; + +  // TODO: better styling for filestatus +  &.second { +//    border-radius: 4px; +//    background: @light; +    font-size: small; +    font-weight: bold; +//    box-shadow: 3px 3px 6px rgba(0, 0, 0, 0.75); +//    .default-shadow; +  } + +  &.third { +    margin-left: 0; +    position: relative; +    font-size: small; +  } + +  .dropdown-menu { +    font-size: medium; +  } +} + +/* + TODO: more colorful states + better fileView design +*/ + +.file-row.finished { +//  .gradient(top, @green, @greenLight); +//  color: @light; +  color: @green; +} + +.file-row.failed { +//  .gradient(top, @red, @redLight); +//  color: @light; +  color: @red; +} + +.file-row.downloading  { + +  .progress { +    height: @file-height; +    background: @light; +    margin: 0; +  } + +  .bar { +    text-align: left; +    .gradient(top, @yellow, @yellowDark); +    .transition-duration(2s); +    color: @dark; +  } + +} + +/* +FANCY CHECKBOXES +*/ +.file-view .checkbox { +  width: 20px; +  height: 21px; +  background: url(../../images/default/checks_sheet.png) left top no-repeat; +  cursor: pointer; +} + +.file-view.ui-selected .checkbox { +  background: url(../../images/default/checks_sheet.png) -21px top no-repeat; +} + +/* +  Actionbar +*/ + +.form-search { +  position: relative; + +  .dropdown-menu { +    min-width: 100%; +    position: absolute; +    right: 0; +    left: auto; +  } + +} + +li.finished > a, li.finished:hover > a { +  background-color: @green; +  color: @light; + +  .caret, .caret:hover { +    border-bottom-color: @light !important; +    border-top-color: @light !important; +  } +} + +li.failed > a, li.failed:hover > a { +  background-color: @red; +  color: @light; + +  .caret, .caret:hover { +    border-bottom-color: @light !important; +    border-top-color: @light !important; +  } +}
\ No newline at end of file diff --git a/pyload/web/app/styles/default/main.less b/pyload/web/app/styles/default/main.less new file mode 100644 index 000000000..0bfa4fe2f --- /dev/null +++ b/pyload/web/app/styles/default/main.less @@ -0,0 +1,21 @@ +@import "bootstrap/less/bootstrap"; +@import "bootstrap/less/responsive"; +@import "font-awesome/less/font-awesome"; + +@FontAwesomePath:   "../../fonts"; + +@import "pyload-common/styles/base"; +@import "pyload-common/styles/basic-layout"; + +@import "style"; +@import "dashboard"; +@import "settings"; +@import "accounts"; +@import "admin"; + +@ResourcePath: "../.."; +@DefaultFont: 'Abel', sans-serif; + +// Changed dimensions +@header-height: 70px; +@footer-height: 66px;
\ No newline at end of file diff --git a/pyload/web/app/styles/default/settings.less b/pyload/web/app/styles/default/settings.less new file mode 100644 index 000000000..34bfcb92a --- /dev/null +++ b/pyload/web/app/styles/default/settings.less @@ -0,0 +1,121 @@ +@import "common"; + +/*  +    Settings  +*/ +.settings-menu { +  background-color: #FFF; +  box-shadow: 0 0 5px #000; //  border: 10px solid #EEE; + +  .nav-header { +    background: @blueDark; +    color: @light; +  } + +  li > a, .nav-header { +    margin-left: -16px; +    margin-right: -16px; +    text-shadow: none; +  } + +  i { +    margin-top: 0; +  } + +  .plugin, .addon { +    a { +      padding-left: 28px; +      background-position: 4px 2px; +      background-repeat: no-repeat; +      background-size: 20px 20px; +    } + +    .icon-remove { +      display: none; +    } + +    &:hover { +      i { +        display: block; +      } +    } + +  } + +  .addon { +    div { +      font-size: small; +    } +    .addon-on { +      color: @green; +    } + +    .addon-off { +      color: @red; +    } + +  } + +  border-top-left-radius: 0; +  border-top-right-radius: 0; + +  .nav > li > a:hover { +    color: @blueDark; +  } +} + +.setting-box { +  border: 10px solid @blueDark; +  box-shadow: 0 0 5px @dark; //  .gradient(bottom, @yellowLightest, @light); +  overflow: hidden; + +  .page-header { +    margin: 0; + +    .btn { +      float: right; +      margin-top: 5px; +    } + +    .popover { +      font-size: medium; +    } + +  } + +  // Bit wider control labels +  .control-label { +    width: 180px; +  } +  .controls { +    margin-left: 200px; +  } +  .form-actions { +    padding-left: 200px; +  } + +} + +/* +  Plugin select +*/ + +.plugin-select { +  background-position: left 2px; +  background-repeat: no-repeat; +  background-size: 20px 20px; +  padding-left: 24px; + +  font-weight: bold; +  span { +    line-height: 14px; +    font-size: small; +    font-weight: normal; +  } + +} + +.logo-select { +  width: 20px; +  height: 20px; +}
\ No newline at end of file diff --git a/pyload/web/app/styles/default/style.less b/pyload/web/app/styles/default/style.less new file mode 100644 index 000000000..b75f45a65 --- /dev/null +++ b/pyload/web/app/styles/default/style.less @@ -0,0 +1,297 @@ +@import "bootstrap/less/mixins"; +@import "common"; + +/* +    Header +*/ +header { //  background-color: @greyDark; +  .gradient(to bottom, #222222, #111111); +  height: @header-height; +  position: fixed; +  top: 0; +  vertical-align: top; +  width: 100%; +  z-index: 10; +  color: #ffffff; + +  a { +    color: #ffffff; +  } +  .container-fluid, .row-fluid { +    height: @header-height; +  } + +  span.title { +    color: white; +    float: left; +    font-family: SansationRegular, sans-serif; +    font-size: 40px; +    line-height: @header-height; +    cursor: default; +  } + +  .logo { +    margin-right: 10px; +    margin-top: 10px; +    width: 105px; +    height: 107px; +    background-size: auto; +    cursor: pointer; +  } + +} + +@header-inner-height: @header-height - 16px; + +// centered header element +.centered { +  height: @header-inner-height; +  margin: 8px 0; +} + +.header-block { +  .centered; +  float: left; +  line-height: @header-inner-height / 3; // 3 rows +  font-size: small; +} + +.status-block { +  min-width: 15%; +} + +.header-btn { +  float: right; +  position: relative; +  .centered; + +  .lower { +    position: absolute; +    bottom: 0; +    left: 0; +    right: 0; +    margin-left: 0; + +    button { +      width: 100% / 3; // 3 buttons +    } + +  } +} + +#progress-area { +  .centered; +  position: relative; +  margin-top: 8px; +  line-height: 16px; + +  .sub { +    font-size: small; +  } + +  .popover { //    display: block; +    max-width: none; +    width: 120%; +    left: -60%; // Half of width +    margin-left: 50%; +    top: 100%; +  } + +  .popover-title, .popover-content { +    color: @greyDark; +  } + +  .icon-list { +    cursor: pointer; +    margin-right: 2px; // same as globalprogress margin + +    &:hover { +      color: @yellow; +    } +  } +  .close { +    line-height: 14px; +  } +} + +.progress-list { +  list-style: none; +  margin: 0; +  font-size: small; + +  li { +    background-repeat: no-repeat; +    background-size: 32px 32px; +    background-position: 0px 8px; +    padding-left: 40px; + +    &:not(:last-child) { +      margin-bottom: 5px; +      padding-bottom: 5px; +      border-bottom: 1px dashed @greyLight; +    } + +    .progress { +      height: 8px; +      margin-bottom: 0; + +      .bar { +        .transition-duration(2s); +        .gradient(bottom, @blue, @blueLight); +      } +    } +  } +} + +#globalprogress { +  background-color: @greyDark; +  background-image: none; +  height: 8px; +  margin: 4px 0; +  border-radius: 8px; +  border: 2px solid @grey; + +  .bar { +    color: @dark; +    background-image: none; +    background-color: @yellow; +    .transition-duration(2s); + +    &.running { +      width: 100%; +      .stripes(@yellowLighter, @yellowDark); +    } +  } +} + +.speedgraph-container { +  // Allows speedgraph to take up remaining space +  display: block; +  overflow: hidden; +  padding: 0 8px; + +  #speedgraph { +    float: right; +    width: 100%; +    .centered; +//    height: @header-height - 16px; +//    margin: 8px 0; +    font-family: sans-serif; +  } +} + +.header-area { +  display: none; // hidden by default +  position: absolute; +  bottom: -28px; +  line-height: 18px; +  top: @header-height; +  padding: 4px 10px 6px 10px; +  text-align: center; +  border-radius: 0 0 6px 6px; +  color: @light; +  background-color: @greyDark; +  .default-shadow; +} + +#notification-area { +  .header-area; +  left: 140px; + +  .badge { +    vertical-align: top; +  } + +  .btn-query, .btn-notification { +    cursor: pointer; +  } +} + +#selection-area { +  .header-area; +  left: 50%; +  min-width: 15%; + +  i { +    cursor: pointer; + +    &:hover { +      color: @yellow; +    } +  } + +} + +/* +   Actionbar +*/ + +.nav > li > a:hover { +  color: @blue; +} + +.actionbar { +  padding-bottom: 3px; +  margin-bottom: 0; +  border-bottom: 1px dashed @grey; + +  height: @actionbar-height; + +  padding-top: 2px; +  margin-bottom: 5px; + +} + +.actionbar > li > a { +  margin-top: 4px; +} + +.actionbar .breadcrumb { +  margin: 0; +  padding-top: 10px; +  padding-bottom: 0; + +  .active { +    color: @grey; +  } + +} + +.actionbar form { +  margin-top: 6px; +  margin-bottom: 0; +} + +.actionbar input, .actionbar button { +  padding-top: 2px; +  padding-bottom: 2px; +} + +.actionbar .dropdown-menu i { +  margin-top: 4px; +  padding-right: 5px; +} + +/* +    Login +*/ +.login { +  vertical-align: middle; +  border: 2px solid @dark; +  padding: 15px 50px; +  font-size: 17px; +  border-radius: 15px; +  -moz-border-radius: 15px; +  -webkit-border-radius: 15px; +} + +/* +    Footer +*/ +footer .copyright { +  background-size: 40px 40px; +  background-position: 12px center; +  height: 40px; +  padding-left: 40px; +  padding-top: 10px; +} diff --git a/pyload/web/app/styles/font.css b/pyload/web/app/styles/font.css new file mode 100644 index 000000000..088b6f14c --- /dev/null +++ b/pyload/web/app/styles/font.css @@ -0,0 +1,13 @@ +/** + * @file + * Font styling + */ + +@font-face { +  font-family: 'Abel'; +  font-style: normal; +  font-weight: 400; +  src: local('Abel'), local('Abel-Regular'); +  src: url(../fonts/Abel-Regular.woff) format('woff'); +       url(../fonts/Abel-Regular.ttf) format('truetype'); +} diff --git a/pyload/web/app/templates/default/accounts/account.html b/pyload/web/app/templates/default/accounts/account.html new file mode 100644 index 000000000..90bd632c8 --- /dev/null +++ b/pyload/web/app/templates/default/accounts/account.html @@ -0,0 +1,10 @@ +<td>{{ plugin }}</td> +<td>{{ loginname }}</td> +<td>{{ valid }}</td> +<td>{{ premium }}</td> +<td>{{ trafficleft }}</td> +<td>{{ shared }}</td> +<td>{{ activated }}</td> +<td> +    <button type="button" class="btn btn-danger">Delete</button> +</td>
\ No newline at end of file diff --git a/pyload/web/app/templates/default/accounts/actionbar.html b/pyload/web/app/templates/default/accounts/actionbar.html new file mode 100644 index 000000000..f4652ec42 --- /dev/null +++ b/pyload/web/app/templates/default/accounts/actionbar.html @@ -0,0 +1,5 @@ +<div class="span2 offset1"> +</div> +<span class="span9"> +    <button class="btn btn-small btn-blue btn-add">Add Account</button> +</span>
\ No newline at end of file diff --git a/pyload/web/app/templates/default/accounts/layout.html b/pyload/web/app/templates/default/accounts/layout.html new file mode 100644 index 000000000..e6627500d --- /dev/null +++ b/pyload/web/app/templates/default/accounts/layout.html @@ -0,0 +1,19 @@ +<!--{#  TODO: responsive layout instead of table  #}--> +<div class="span10 offset2"> +    <table class="table table-striped"> +        <thead> +        <tr> +            <th>Plugin</th> +            <th>Name</th> +            <th>Valid</th> +            <th>Premium</th> +            <th>Traffic</th> +            <th>Shared</th> +            <th>Activated</th> +            <th>Delete</th> +        </tr> +        </thead> +        <tbody class="account-list"> +        </tbody> +    </table> +</div>
\ No newline at end of file diff --git a/pyload/web/app/templates/default/admin.html b/pyload/web/app/templates/default/admin.html new file mode 100644 index 000000000..2eb90d7e0 --- /dev/null +++ b/pyload/web/app/templates/default/admin.html @@ -0,0 +1,223 @@ +{% extends 'default/base.html' %} + +{% block title %}{{ _("Admin") }} - {{ super() }} {% endblock %} +{% block subtitle %}{{ _("Admin") }} +{% endblock %} + +{% block css %} +    <link href="static/css/default/admin.less" rel="stylesheet/less" type="text/css" media="screen"/> +    <link rel="stylesheet" type="text/css" href="static/css/fontawesome.css" /> +{% endblock %} + +{% block require %} +{% endblock %} + +{% block content %} +    <div class="container-fluid"> +      <div class="row-fluid"> +        <div id="userlist" class="span10"> +            <div class="page-header"> +                <h1>Admin Bereich +                <small>Userverwaltung, Systeminfos</small> +                    <a id="btn_newuser" class="btn btn-warning btn-large" type="button"><i class="iconf-plus-sign iconf-large "></i></a> +                </h1> +                 + +                 +            </div> + +            <div class="dropdown">         +                <span class="label name">User</span> +                <a class="dropdown-toggle" data-toggle="dropdown" href="#"><i class="iconf-user iconf-8x"></i></a> +                <ul class="dropdown-menu" role="menu" aria-labelledby="dropdownMenu"> +                    <li><a tabindex="-1" id="useredit" href="#" role="button" data-backdrop="true" data-controls-modal="event-modal" data-keyboard="true"><i class="icon-pencil"></i>Edit</a></li> +                    <li><a tabindex="-1" href="#"><i class="icon-tasks"></i>Statistik</a></li> +                    <li class="divider"></li> +                    <li><a tabindex="-1" href="#"><i class="icon-remove-sign"></i>Delete</a></li> +                </ul> +            </div> + +            <div id="event-modal" class="modal hide fade"> +                <div class="modal-header"> +                    <a class="close" id="useredit_close" href="#">x</a> +                    <h3>User Settings</h3> +                </div> +                <div class="modal-body"> +                    <p>Set password and permissions</p> +                    <table style="width:100%;" class="table "> +                        <td> +                            <div class="input-prepend"> +                                <span class="add-on"><i class="iconf-key"></i></span> +                                <input class="span2" style="min-width:120px;" id="prependedInput" type="text" placeholder="New Password"> +                            </div> +                            <div class="input-prepend"> +                                <span class="add-on"><i class="icon-repeat"></i></span> +                                <input class="span2" style="min-width:120px;" id="prependedInput" type="text" placeholder="Repeat"> +                            </div> +                            <br> +                            <br> +                            <br> +                            <form class="form-horizontal"> +                                <div class="control-group"> +                                    <label class="control-label" for="onoff">Administrator</label> + +                                    <div class="controls"> +                                        <div class="btn-group" id="onoff" data-toggle="buttons-radio"> +                                            <button type="button" class="btn btn-primary" >On</button> +                                            <button type="button" class="btn btn-primary active">Off</button> +                                        </div> +                                    </div> +                                </div> +                            </form> +                        </td> +                        <td> +                            <div id="user_permissions"> +                                <h3>Permissions</h3> +                                <div class="btn-group btn-group-vertical" data-toggle="buttons-checkbox"> +                                    <button type="button" class="btn btn-inverse userperm">Accounts</button> +                                    <button type="button" class="btn btn-inverse userperm active">Add</button> +                                    <button type="button" class="btn btn-inverse userperm">Delete</button> +                                    <button type="button" class="btn btn-inverse userperm active">Download</button> +                                    <button type="button" class="btn btn-inverse userperm active">List</button> +                                    <button type="button" class="btn btn-inverse userperm">Logs</button> +                                    <button type="button" class="btn btn-inverse userperm">Modify</button> +                                    <button type="button" class="btn btn-inverse userperm">Settings</button> +                                    <button type="button" class="btn btn-inverse userperm active">Status</button> +                                </div> +                            </div> +                        </td> +                    </table> +                </div> +                <div class="modal-footer"> +                    <a class="btn btn-primary" id="useredit_save"href="#">Save</a> +                     +                </div> +            </div> + + +     +        </div> + +        <div class="span2"> +            <br> +            <h2>Support</h2> +            <table> +                <tr> +                    <td> +                        <i class="icon-globe"></i> +                    </td> +                    <td> +                        <a href="#">Wiki |</a> +                        <a href="#">Forum |</a> +                        <a href="#">Chat</a> +                    </td> +                </tr> +                <tr> +                    <td> +                        <i class="icon-book"></i> +                    </td> +                    <td> +                        <a href="#">Documentation</a> +                    </td> +                </tr> +                <tr> +                    <td> +                        <i class="icon-fire"></i> +                    </td> +                    <td> +                        <a href="#">Development</a> +                    </td> +                </tr> +                <tr> +                    <td> +                        <i class="icon-bullhorn"></i> +                    </td> +                    <td> +                        <a href="#">Issue Tracker</a> +                    </td> +                </tr> +            </table> +            <br> +            <a href="#" class="btn btn-inverse" id="info" rel="popover"  data-content="<table class='table table-striped'> +                <tbody> +                    <tr> +                        <td>Python:</td> +                        <td>2.6.4 </td> +                    </tr> +                    <tr> +                        <td>Betriebssystem:</td> +                        <td>nt win32</td> +                    </tr> +                    <tr> +                        <td>pyLoad Version:</td> +                        <td>0.4.9</td> +                    </tr> +                    <tr> +                        <td>Installationsordner:</td> +                        <td>C:\pyLoad</td> +                    </tr> +                    <tr> +                        <td>Konfigurationsordner:</td> +                        <td>C:\Users\Marvin\pyload</td> +                    </tr> +                    <tr> +                        <td>Downloadordner:</td> +                        <td>C:\Users\Marvin\new</td> +                    </tr> +                    <tr> +                        <td>HDD:</td> +                        <td>1.67 TiB <div class='progress progress-striped active'> +  <div class='bar' style='width: 40%;'></div> +</div></td> +                    </tr> +                    <tr> +                        <td>Sprache:</td> +                        <td>de</td> +                    </tr> +                    <tr> +                        <td>Webinterface Port:</td> +                        <td>8000</td> +                    </tr> +                    <tr> +                        <td>Remote Interface Port:</td> +                        <td>7227</td> +                    </tr> +                </tbody> +            </table>" title="Systeminformationen">System</a> +             +        </div> +      </div> +    </div> +    +    <script src="static/js/libs/jquery-1.9.0.js"></script> +    {##} +    <script src="static/js/libs/bootstrap-2.2.2.js"></script> +    <script type="text/javascript"> +        $('#info').popover({ +            placement: 'left', +            trigger: 'click', +            html:'true', +        }); +         +            $('.dropdown-toggle').dropdown(); + +            $("#btn_newuser").click(function() { +                  +                 str = "<div class='dropdown1'><span class='label name'>User</span><a class='dropdown-toggle' data-toggle='dropdown1' href='#'><i class='iconf-user iconf-8x'></i></a><ul class='dropdown-menu' role='menu' aria-labelledby='dropdownMenu'><li><a tabindex='-1' href='#'>Action</a></li><li><a tabindex='-1' href='#'>Another action</a></li><li><a tabindex='-1' href='#'>Something else here</a></li><li class='divider'></li><li><a tabindex='-1' href='#'>Separated link</a></li></ul></div>"; + +            $("#userlist").append(str); + +            }); + +            $("#useredit").click(function() { +                $('#event-modal').modal(); +            }); +            $("#useredit_close").click(function() { +                $('#event-modal').modal('hide'); +            }); +            $("#useredit_save").click(function() { +                $('#event-modal').modal('hide'); +            }); + +    </script> +{% endblock %} 
\ No newline at end of file diff --git a/pyload/web/app/templates/default/dashboard/actionbar.html b/pyload/web/app/templates/default/dashboard/actionbar.html new file mode 100644 index 000000000..a8b2ebecd --- /dev/null +++ b/pyload/web/app/templates/default/dashboard/actionbar.html @@ -0,0 +1,54 @@ +<div class="span2 offset1"> +</div> +<ul class="actionbar nav nav-pills span9"> +    <li> +        <ul class="breadcrumb"> +            <li><a href="#">Local</a> <span class="divider">/</span></li> +            <li class="active"></li> +        </ul> +    </li> + +    <li style="float: right;"> +        <form class="form-search" action="#"> +            <div class="input-append"> +                <input type="text" class="search-query" style="width: 120px"> +                <button type="submit" class="btn">Search</button> +            </div> +        </form> +    </li> +    <li style="float: right" class="li-check"> +        <a href="#"><i class="icon-check-empty btn-check"></i></a> +    </li> +    <li class="dropdown" style="float: right;"> +        <a class="dropdown-toggle type" +           data-toggle="dropdown" +           href="#"> +            Type +            <b class="caret"></b> +        </a> +        <ul class="dropdown-menu"> +            <li><a class="filter-type" data-type="2" href="#"><i class="icon-ok"></i> Audio</a></li> +            <li><a class="filter-type" data-type="4" href="#"><i class="icon-ok"></i> Image</a></li> +            <li><a class="filter-type" data-type="8" href="#"><i class="icon-ok"></i> Video</a></li> +            <li><a class="filter-type" data-type="16" href="#"><i class="icon-ok"></i> Document</a></li> +            <li><a class="filter-type" data-type="32" href="#"><i class="icon-remove"></i> Archive</a></li> +            <li><a class="filter-type" data-type="1" href="#"><i class="icon-remove"></i> Other</a></li> +        </ul> +    </li> +    <li class="dropdown" style="float: right;"> +        <a class="dropdown-toggle" +           data-toggle="dropdown" +           href="#"> +            <span class="state"> +                All +            </span> +            <b class="caret"></b> +        </a> +        <ul class="dropdown-menu"> +            <li><a class="filter-state" data-state="0" href="#">All</a></li> +            <li><a class="filter-state" data-state="1" href="#">Finished</a></li> +            <li><a class="filter-state" data-state="2" href="#">Unfinished</a></li> +            <li><a class="filter-state" data-state="3" href="#">Failed</a></li> +        </ul> +    </li> +</ul>
\ No newline at end of file diff --git a/pyload/web/app/templates/default/dashboard/file.html b/pyload/web/app/templates/default/dashboard/file.html new file mode 100644 index 000000000..4bf3c7a97 --- /dev/null +++ b/pyload/web/app/templates/default/dashboard/file.html @@ -0,0 +1,34 @@ +<div class="file-row first span6"> +    <i class="checkbox"></i>  +        <span class="name"> +        {{ name }} +        </span> +</div> +<div class="file-row second span3 {{ fileClass this }}"> +    {{ fileStatus this }} +</div> + +<div class="file-row third span3 pull-right"> +    <i class="{{ fileIcon media }}"></i>  +    {{ formatSize size }} +        <span class="pull-right"> +            <img src="{{ pluginIcon download.plugin }}"/> +            {{ download.plugin }}  +            <i class="icon-chevron-down" data-toggle="dropdown"></i> +            <ul class="dropdown-menu" role="menu"> +                <li><a href="#" class="btn-delete"><i class="icon-trash"></i> Delete</a></li> +                <li><a href="#" class="btn-restart"><i class="icon-refresh"></i> Restart</a></li> +                <!--{# TODO: only show when finished #}--> +                <li><a href="download/{{ fid }}" target="_blank" class="btn-dowload"><i class="icon-download"></i> +                    Download</a></li> +                <li><a href="#" class="btn-share"><i class="icon-share"></i> Share</a></li> +                <li class="divider"></li> +                <li class="dropdown-submenu pull-left"> +                    <a>Addons</a> +                    <ul class="dropdown-menu"> +                        <li><a>Test</a></li> +                    </ul> +                </li> +            </ul> +        </span> +</div>
\ No newline at end of file diff --git a/pyload/web/app/templates/default/dashboard/layout.html b/pyload/web/app/templates/default/dashboard/layout.html new file mode 100644 index 000000000..cd84d3a26 --- /dev/null +++ b/pyload/web/app/templates/default/dashboard/layout.html @@ -0,0 +1,32 @@ +<div class="span3"> +    <div class="sidebar-header"> +        <i class="icon-hdd"></i> Local +        <div class="pull-right" style="font-size: medium; line-height: normal"> +            <i class="icon-chevron-down" style="font-size: 20px"></i> +        </div> +        <div class="clearfix"></div> +    </div> +    <ul class="package-list"> + +    </ul> +    <div class="sidebar-header"> +        <i class="icon-group"></i> Shared +    </div> +    <ul class="package-list"> +        <li>Shared content</li> +        <li>from other user</li> +    </ul> +    <div class="sidebar-header"> +        <i class="icon-sitemap"></i> Remote +    </div> +    <ul> +        <li>Content from</li> +        <li>remote sites or</li> +        <li>other pyload instances</li> +    </ul> +</div> +<div class="span9"> +    <ul class="file-list"> + +    </ul> +</div>
\ No newline at end of file diff --git a/pyload/web/app/templates/default/dashboard/package.html b/pyload/web/app/templates/default/dashboard/package.html new file mode 100644 index 000000000..0f2496046 --- /dev/null +++ b/pyload/web/app/templates/default/dashboard/package.html @@ -0,0 +1,50 @@ +{{#if selected }} +    <i class="icon-check select"></i> +    {{ else }} +    <i class="icon-check-empty select"></i> +    {{/if}} +    <span class="package-name"> +    {{ name }} +    </span> + +    <div class="package-frame"> +        <div class="tag-area"> +            <span class="badge badge-success"><i class="icon-tag"></i>video</span> +            <span class="badge badge-success badge-ghost"><i class="icon-tag"></i> Add Tag</span> +        </div> +        <div class="package-indicator"> +            <i class="icon-plus-sign btn-move" data-toggle="tooltip" title="Move files here"></i> +            <i class="icon-pause" data-toggle="tooltip" title="Pause Package"></i> +            <i class="icon-refresh" data-toggle="tooltip" title="Restart Package"></i> +            {{#if shared }} +            <i class="icon-eye-open" data-toggle="tooltip" title="Package is public"></i> +            {{ else }} +            <i class="icon-eye-close" data-toggle="tooltip" title="Package is private"></i> +            {{/if}} +            <i class="icon-chevron-down" data-toggle="dropdown"> +            </i> +            <ul class="dropdown-menu" role="menu"> +                <li><a href="#" class="btn-open"><i class="icon-folder-open-alt"></i> Open</a></li> +                <li><a href="#"><i class="icon-plus-sign"></i> Add links</a></li> +                <li><a href="#"><i class="icon-edit"></i> Details</a></li> +                <li><a href="#" class="btn-delete"><i class="icon-trash"></i> Delete</a></li> +                <li><a href="#" class="btn-recheck"><i class="icon-refresh"></i> Recheck</a></li> +                <li class="divider"></li> +                <li class="dropdown-submenu"> +                    <a>Addons</a> +                    <ul class="dropdown-menu"> +                        <li><a>Test</a></li> +                    </ul> +                </li> +            </ul> +        </div> +        <div class="progress"> +            <span style="position: absolute; left: 5px"> +                {{ stats.linksdone }} / {{ stats.linkstotal }} +            </span> +            <div class="bar bar-info" style="width: {{ percent }}%"></div> +            <span style="position: absolute; right: 5px"> +                    {{ formatSize stats.sizedone }} / {{ formatSize stats.sizetotal }} +            </span> +        </div> +    </div>
\ No newline at end of file diff --git a/pyload/web/app/templates/default/dashboard/select.html b/pyload/web/app/templates/default/dashboard/select.html new file mode 100644 index 000000000..f4c696d11 --- /dev/null +++ b/pyload/web/app/templates/default/dashboard/select.html @@ -0,0 +1,11 @@ +<i class="icon-check" data-toggle="tooltip" title="Deselect"></i>  +{{#if packs }}{{ ngettext "1 package" "%d packages" packs }}{{/if}} +{{#if files}} +{{#if packs}}, {{/if}} +{{ ngettext "1 file" "%d files" files }} +{{/if }} +selected + |  +<i class="icon-pause" data-toggle="tooltip" title="Pause"></i>  +<i class="icon-trash" data-toggle="tooltip" title="Delete"></i>  +<i class="icon-refresh" data-toggle="tooltip" title="Restart"></i>
\ No newline at end of file diff --git a/pyload/web/app/templates/default/dialogs/addAccount.html b/pyload/web/app/templates/default/dialogs/addAccount.html new file mode 100755 index 000000000..bdc8a609a --- /dev/null +++ b/pyload/web/app/templates/default/dialogs/addAccount.html @@ -0,0 +1,42 @@ +<div class="modal-header"> +    <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button> +    <h3>Add an account</h3> +</div> +<div class="modal-body"> +    <form class="form-horizontal" autocomplete="off"> +        <legend> +            Please enter your account data +        </legend> +        <div class="control-group"> +            <label class="control-label" for="pluginSelect"> +                Plugin +            </label> + +            <div class="controls"> +                <input type="hidden" id="pluginSelect"> +            </div> +        </div> +        <div class="control-group"> +            <label class="control-label" for="login"> +                Loginname +            </label> + +            <div class="controls"> +                <input type="text" id="login"> +            </div> +        </div> +        <div class="control-group"> +            <label class="control-label" for="password"> +                Password +            </label> + +            <div class="controls"> +                <input type="password" id="password"> +            </div> +        </div> +    </form> +</div> +<div class="modal-footer"> +    <a class="btn btn-success btn-add">Add</a> +    <a class="btn btn-close">Close</a> +</div>
\ No newline at end of file diff --git a/pyload/web/app/templates/default/dialogs/addPluginConfig.html b/pyload/web/app/templates/default/dialogs/addPluginConfig.html new file mode 100755 index 000000000..e7a42a208 --- /dev/null +++ b/pyload/web/app/templates/default/dialogs/addPluginConfig.html @@ -0,0 +1,26 @@ +<div class="modal-header"> +    <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button> +    <h3> +        Choose a plugin +    </h3> +</div> +<div class="modal-body"> +    <form class="form-horizontal"> +        <legend> +            Please choose a plugin, which you want to configure +        </legend> +        <div class="control-group"> +            <label class="control-label" for="pluginSelect"> +                Plugin +            </label> + +            <div class="controls"> +                <input type="hidden" id="pluginSelect"> +            </div> +        </div> +    </form> +</div> +<div class="modal-footer"> +    <a class="btn btn-success btn-add">Add</a> +    <a class="btn btn-close">Close</a> +</div>
\ No newline at end of file diff --git a/pyload/web/app/templates/default/dialogs/confirmDelete.html b/pyload/web/app/templates/default/dialogs/confirmDelete.html new file mode 100644 index 000000000..65ae1cb21 --- /dev/null +++ b/pyload/web/app/templates/default/dialogs/confirmDelete.html @@ -0,0 +1,11 @@ +<div class="modal-header"> +    <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button> +    <h3>Please confirm</h3> +</div> +<div class="modal-body"> +    Do you want to delete the selected items? +</div> +<div class="modal-footer"> +    <a class="btn btn-danger btn-confirm"><i class="icon-trash icon-white"></i> Delete</a> +    <a class="btn btn-close">Cancel</a> +</div>
\ No newline at end of file diff --git a/pyload/web/app/templates/default/dialogs/interactionTask.html b/pyload/web/app/templates/default/dialogs/interactionTask.html new file mode 100755 index 000000000..a152a5046 --- /dev/null +++ b/pyload/web/app/templates/default/dialogs/interactionTask.html @@ -0,0 +1,37 @@ +<div class="modal-header"> +    <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button> +    <h3> +        {{ title }} +        <small style="background: url('{{ pluginIcon plugin }}') no-repeat right 0; background-size: 20px; padding-right: 22px"> +            {{ plugin }} +        </small> +    </h3> +</div> +<div class="modal-body"> +    <form class="form-horizontal" action="#"> +        <legend>{{ description }}</legend> +        {{#if captcha }} +        <div class="control-group"> +            <label class="control-label" for="captchaImage"> +                Captcha Image +            </label> + +            <div class="controls"> +                <img id="captchaImage" src="data:image/{{ type }};base64,{{ captcha }}"> +            </div> +        </div> +        <div class="control-group"> +            <label class="control-label" for="inputField">Captcha Text</label> + +            <div class="controls" id="inputField"> +            </div> +        </div> +        {{ else }} +        {{ content }} +        {{/if}} +    </form> +</div> +<div class="modal-footer"> +    <a class="btn btn-success">Submit</a> +    <a class="btn btn-close">Close</a> +</div>
\ No newline at end of file diff --git a/pyload/web/app/templates/default/dialogs/linkgrabber.html b/pyload/web/app/templates/default/dialogs/linkgrabber.html new file mode 100755 index 000000000..08418cf03 --- /dev/null +++ b/pyload/web/app/templates/default/dialogs/linkgrabber.html @@ -0,0 +1,49 @@ +<div class="modal-header"> +    <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button> +    <h3> +        AddPackage +        <small>paste&add links to pyLoad</small> +    </h3> +</div> + +<div class="modal-body"> +    <div class="alert alert-error hidden"> +        Upload files container failed. Please try again. +    </div> +    <form class="form-horizontal"> +        <div class="control-group"> +            <label class="control-label" for="inputPackageName">Package name</label> + +            <div class="controls"> +                <input type="text" class="span4" id="inputPackageName" placeholder="Name of your package"> +            </div> +        </div> +        <div class="control-group"> +            <label class="control-label" for="inputLinks">Links</label> + +            <div class="controls"> +                <textarea id="inputLinks" class="span4" rows="10" placeholder="Paste your links here..."></textarea> +            </div> +        </div> +        <div class="control-group"> +            <label class="control-label" for="inputPassword">Password</label> + +            <div class="controls"> +                <input type="text" id="inputPassword" class="span4" placeholder="Password for .rar files"> +            </div> +        </div> +        <div class="control-group"> +            <label class="control-label" for="inputContainer">Upload links container</label> + +            <div class="controls controls-row"> +                <input type="text" id="inputContainer" class="span3" placeholder="Path to your container"> +                <button id="inputContainer-btn" class="btn span1" type="button">Browse…</button> +            </div> +        </div> +    </form> +</div> + +<div class="modal-footer"> +    <a class="btn btn-success"><i class="icon-plus icon-white"></i> Add</a> +    <a class="btn btn-close">Close</a> +</div>
\ No newline at end of file diff --git a/pyload/web/app/templates/default/dialogs/modal.html b/pyload/web/app/templates/default/dialogs/modal.html new file mode 100755 index 000000000..1e44cc99c --- /dev/null +++ b/pyload/web/app/templates/default/dialogs/modal.html @@ -0,0 +1,10 @@ +<div class="modal-header"> +    <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button> +    <h3>Dialog</h3> +</div> +<div class="modal-body"> +</div> +<div class="modal-footer"> +    <a class="btn btn-close">Close</a> +    <a class="btn btn-primary">Save</a> +</div>
\ No newline at end of file diff --git a/pyload/web/app/templates/default/header/layout.html b/pyload/web/app/templates/default/header/layout.html new file mode 100644 index 000000000..0fe61c4e3 --- /dev/null +++ b/pyload/web/app/templates/default/header/layout.html @@ -0,0 +1,61 @@ +<div class="span3"> +    <div class="logo"></div> +    <span class="title visible-large-screen">pyLoad</span> +</div> +<div class="span4 offset1"> +    <div id="progress-area"> +    <span id="progress-info"> +    </span> +        <div class="popover bottom"> +            <div class="arrow"></div> +            <div class="popover-inner"> +                <h3 class="popover-title"> +                    Running... +                    <button type="button" class="close" aria-hidden="true">×</button> +                </h3> +                <div class="popover-content"> +                    <ul class="progress-list"></ul> +                </div> +            </div> +        </div> +    </div> +</div> +<div class="span4"> +    <div class="header-block"> +        <i class="icon-download-alt icon-white"></i> Max. Speed:<br> +        <i class="icon-off icon-white"></i> Running:<br> +        <i class="icon-refresh icon-white"></i> Reconnect:<br> +    </div> + +    <div class="header-block status-block"></div> + +    <div class="header-btn"> +        <div class="btn-group"> +            <a class="btn btn-blue btn-small" href="#"><i class="icon-user icon-white"></i> User</a> +            <a class="btn btn-blue btn-small dropdown-toggle" data-toggle="dropdown" href="#"><span +                    class="caret"></span></a> +            <ul class="dropdown-menu" style="right: 0; left: -100%"> +                <li><a data-nav href="/"><i class="icon-list-alt"></i> Dashboard</a></li> +                <li><a data-nav href="/settings"><i class="icon-wrench"></i> Settings</a></li> +                <li><a data-nav href="/accounts"><i class="icon-key"></i> Accounts</a></li> +                <li><a data-nav href="/admin"><i class="icon-cogs"></i> Admin</a></li> +                <li class="divider"></li> +                <li><a data-nav href="/logout"><i class="icon-signout"></i> Logout</a></li> +            </ul> +        </div> +        <div class="btn-group lower"> +            <button class="btn btn-success btn-grabber btn-mini" href="#"> +                <i class="icon-plus icon-white"></i> +            </button> +            <button class="btn btn-blue btn-play btn-mini" href="#"> +                <i class="icon-play icon-white"></i> +            </button> +            <button class="btn btn-danger btn-delete btn-mini" href="#"> +                <i class="icon-remove icon-white"></i> +            </button> +        </div> +    </div> +<span class="visible-desktop speedgraph-container"> +    <div id="speedgraph"></div> +</span> +</div>
\ No newline at end of file diff --git a/pyload/web/app/templates/default/header/progress.html b/pyload/web/app/templates/default/header/progress.html new file mode 100644 index 000000000..740e18a4c --- /dev/null +++ b/pyload/web/app/templates/default/header/progress.html @@ -0,0 +1,10 @@ +{{ name }} +<span class="pull-right">{{ plugin }}</span> + +<div class="progress"> +    <div class="bar" style="width: {{ percent }}%"></div> +</div> + +<div class="progress-status"> +    <!-- rendered by progressInfo template --> +</div> diff --git a/pyload/web/app/templates/default/header/progressStatus.html b/pyload/web/app/templates/default/header/progressStatus.html new file mode 100644 index 000000000..020ed2e96 --- /dev/null +++ b/pyload/web/app/templates/default/header/progressStatus.html @@ -0,0 +1,8 @@ +{{#if downloading }} +    {{ formatSize done }} of {{ formatSize total }} ({{ formatSize download.speed }}/s) +{{ else }} +    {{ statusmsg }} +{{/if}} +<span class="pull-right"> +    {{ formatTime eta }} +</span>
\ No newline at end of file diff --git a/pyload/web/app/templates/default/header/progressSub.html b/pyload/web/app/templates/default/header/progressSub.html new file mode 100644 index 000000000..3400ee011 --- /dev/null +++ b/pyload/web/app/templates/default/header/progressSub.html @@ -0,0 +1,6 @@ +{{#if linksqueue }} +    {{ linksqueue }} downloads left ({{ formatSize sizequeue }}) +{{/if}} +<span class="pull-right"> +    {{ formatTime etaqueue }} +</span>
\ No newline at end of file diff --git a/pyload/web/app/templates/default/header/progressSup.html b/pyload/web/app/templates/default/header/progressSup.html new file mode 100644 index 000000000..f2c0ac734 --- /dev/null +++ b/pyload/web/app/templates/default/header/progressSup.html @@ -0,0 +1,10 @@ +{{#if single }} +    {{ truncate name 32}} ({{ statusmsg }}) +{{ else }} +    {{#if downloads }} +        {{ downloads }} downloads running {{#if speed }}({{ formatSize speed }}/s){{/if}} +    {{ else }} +        No running tasks +    {{/if}} +{{/if}} +<i class="icon-list pull-right"></i>
\ No newline at end of file diff --git a/pyload/web/app/templates/default/header/progressbar.html b/pyload/web/app/templates/default/header/progressbar.html new file mode 100644 index 000000000..2775e664b --- /dev/null +++ b/pyload/web/app/templates/default/header/progressbar.html @@ -0,0 +1,16 @@ + +<div class="sup"> +</div> + +<div class="progress" id="globalprogress"> +    {{#if single }} +    <div class="bar" style="width: {{ percent }}%"> +        {{ else }} +        <div class="bar {{#if downloads }}running{{/if}}"> +            {{/if}} +        </div> +    </div> +</div> + +<div class="sub"> +</div>
\ No newline at end of file diff --git a/pyload/web/app/templates/default/header/status.html b/pyload/web/app/templates/default/header/status.html new file mode 100644 index 000000000..f840b6e33 --- /dev/null +++ b/pyload/web/app/templates/default/header/status.html @@ -0,0 +1,3 @@ +<span class="pull-right maxspeed">{{ formatSize maxspeed }}/s</span><br> +<span class="pull-right running">{{ paused }}</span><br> +<span class="pull-right reconnect">{{#if reconnect }}true{{ else }}false{{/if}}</span>
\ No newline at end of file diff --git a/pyload/web/app/templates/default/login.html b/pyload/web/app/templates/default/login.html new file mode 100644 index 000000000..9e8d9eeb6 --- /dev/null +++ b/pyload/web/app/templates/default/login.html @@ -0,0 +1,28 @@ +<br> +<div class="login"> +    <form method="post" class="form-horizontal"> +        <legend>Login</legend> +        <div class="control-group"> +            <label class="control-label" for="inputUser">Username</label> +            <div class="controls"> +                <input type="text" id="inputUser" placeholder="Username" name="username"> +            </div> +        </div> +        <div class="control-group"> +            <label class="control-label" for="inputPassword">Password</label> +            <div class="controls"> +                <input type="password" id="inputPassword" placeholder="Password" name="password"> +            </div> +        </div> +        <div class="control-group"> +            <div class="controls"> +                <label class="checkbox"> +                    <input type="checkbox"> Remember me +                </label> +                <button type="submit" class="btn">Login</button> +            </div> +        </div> +    </form> +</div> +<br> +<!-- TODO: Errors --> diff --git a/pyload/web/app/templates/default/notification.html b/pyload/web/app/templates/default/notification.html new file mode 100644 index 000000000..1b6d21e27 --- /dev/null +++ b/pyload/web/app/templates/default/notification.html @@ -0,0 +1,10 @@ +{{#if queries }} +    <span class="btn-query"> +    Queries <span class="badge badge-info">{{ queries }}</span> +    </span> +{{/if}} +{{#if notifications }} +    <span class="btn-notification"> +    Notifications <span class="badge badge-success">{{ notifications }}</span> +    </span> +{{/if}}
\ No newline at end of file diff --git a/pyload/web/app/templates/default/settings/actionbar.html b/pyload/web/app/templates/default/settings/actionbar.html new file mode 100644 index 000000000..25b10d463 --- /dev/null +++ b/pyload/web/app/templates/default/settings/actionbar.html @@ -0,0 +1,5 @@ +<div class="span2 offset1"> +</div> +<span class="span9"> +    <button class="btn btn-small btn-blue btn-add">Add Plugin</button> +</span>
\ No newline at end of file diff --git a/pyload/web/app/templates/default/settings/config.html b/pyload/web/app/templates/default/settings/config.html new file mode 100644 index 000000000..47ff45f0b --- /dev/null +++ b/pyload/web/app/templates/default/settings/config.html @@ -0,0 +1,17 @@ +<legend> +    <div class="page-header"> +        <h1>{{ label }} +            <small>{{ description }}</small> +            {{#if long_description }} +            <a class="btn btn-small" data-title="Help" data-content="{{ long_description }}"><i +                    class="icon-question-sign"></i></a> +            {{/if}} +        </h1> +    </div> +</legend> +<div class="control-content"> +</div> +<div class="form-actions"> +    <button type="button" class="btn btn-primary">Save changes</button> +    <button type="button" class="btn btn-reset">Reset</button> +</div>
\ No newline at end of file diff --git a/pyload/web/app/templates/default/settings/configItem.html b/pyload/web/app/templates/default/settings/configItem.html new file mode 100644 index 000000000..5b583b8df --- /dev/null +++ b/pyload/web/app/templates/default/settings/configItem.html @@ -0,0 +1,7 @@ +        <div class="control-group"> +            <label class="control-label">{{ label }}</label> + +            <div class="controls"> +                <!--{#                <span class="help-inline">{{ description }}</span>#}--> +            </div> +        </div>
\ No newline at end of file diff --git a/pyload/web/app/templates/default/settings/layout.html b/pyload/web/app/templates/default/settings/layout.html new file mode 100644 index 000000000..39f1a2ec9 --- /dev/null +++ b/pyload/web/app/templates/default/settings/layout.html @@ -0,0 +1,11 @@ +<div class="span2"> +    <ul class="nav nav-list well settings-menu"> +    </ul> +</div> +<div class="span10"> +    <div class="well setting-box"> +        <form class="form-horizontal" action="#"> +            <h1>Please choose a config section</h1> +        </form> +    </div> +</div>
\ No newline at end of file diff --git a/pyload/web/app/templates/default/settings/menu.html b/pyload/web/app/templates/default/settings/menu.html new file mode 100644 index 000000000..893fd7b5b --- /dev/null +++ b/pyload/web/app/templates/default/settings/menu.html @@ -0,0 +1,40 @@ +{{#if core}} +<li class="nav-header"><i class="icon-globe icon-white"></i> General</li> +{{#each core}} +<li data-name="{{ name }}"><a href="#">{{ label }}</a></li> +{{/each}} +{{/if}} +<li class="divider"></li> +<li class="nav-header"><i class="icon-th-large icon-white"></i> Addons</li> +{{#each addon }} +<li class="addon" data-name="{{ name }}"> +    <a href="#" style="background-image: url({{ pluginIcon name }});"> +        {{ label }} +        <i class="icon-remove pull-right"></i> +        {{#if activated }} +        <div class="addon-on"> +            active +            {{else}} +            <div class="addon-off"> +                inactive +                {{/if}} +                {{#if user_context }} +                <!--{# TODO: tooltip #}--> +                <i class="icon-user pull-right"></i> +                {{else}} +                <i class="icon-globe pull-right"></i> +                {{/if}} +            </div> +    </a> +</li> +{{/each}} +<li class="divider"></li> +<li class="nav-header"><i class="icon-th-list icon-white"></i> Plugin Configs</li> +{{#each plugin }} +<li class="plugin" data-name="{{ name }}"> +    <a style="background-image: url({{ pluginIcon name }});"> +        {{ label }} +        <i class="icon-remove pull-right"></i> +    </a> +</li> +{{/each}}
\ No newline at end of file diff --git a/pyload/web/app/templates/default/setup.html b/pyload/web/app/templates/default/setup.html new file mode 100644 index 000000000..e5c9f4b8c --- /dev/null +++ b/pyload/web/app/templates/default/setup.html @@ -0,0 +1,16 @@ +{% extends 'default/base.html' %} +{% block title %} +    {{_("Setup")}} - {{ super()}} +{% endblock %} + +{% block content %} +    <div class="hero-unit"> +        <h1>You did it!</h1> +        <p>pyLoad is running and ready for configuration.</p> +        <p> +            <a class="btn btn-primary btn-large"> +                Go on +            </a> +        </p> +    </div> +{% endblock %}
\ No newline at end of file diff --git a/pyload/web/app/unavailable.html b/pyload/web/app/unavailable.html new file mode 100644 index 000000000..6706a693c --- /dev/null +++ b/pyload/web/app/unavailable.html @@ -0,0 +1,18 @@ +<!DOCTYPE html> +<html> +<head> +    <title>WebUI not available</title> +</head> +<body> + +<h1>WebUI not available</h1> +You are running a pyLoad version without prebuilt webUI. You can download a build from our website or deactivate the dev mode. +If desired you can build it yourself by running: +<ul> +    <li>npm install</li> +    <li>bower install</li> +    <li>grunt build</li> +</ul> + +</body> +</html>
\ No newline at end of file | 
