From 2cf160d497e501bf254bd8be054c0f5880ab90ca Mon Sep 17 00:00:00 2001 From: RaNaN Date: Sat, 8 Jun 2013 17:37:43 +0200 Subject: restructured webui to single-page-app, removed jinja --- module/web/app/favicon.ico | Bin 0 -> 6006 bytes module/web/app/fonts/Sansation_Bold-webfont.eot | Bin 0 -> 35336 bytes module/web/app/fonts/Sansation_Bold-webfont.ttf | Bin 0 -> 35160 bytes module/web/app/fonts/Sansation_Bold-webfont.woff | Bin 0 -> 18496 bytes module/web/app/fonts/Sansation_Light-webfont.eot | Bin 0 -> 36700 bytes module/web/app/fonts/Sansation_Light-webfont.ttf | Bin 0 -> 36520 bytes module/web/app/fonts/Sansation_Light-webfont.woff | Bin 0 -> 18408 bytes module/web/app/fonts/Sansation_Regular-webfont.eot | Bin 0 -> 36368 bytes module/web/app/fonts/Sansation_Regular-webfont.ttf | Bin 0 -> 36180 bytes .../web/app/fonts/Sansation_Regular-webfont.woff | Bin 0 -> 18316 bytes module/web/app/images/default/bgpattern.png | Bin 0 -> 2487 bytes module/web/app/images/default/checks_sheet.png | Bin 0 -> 1145 bytes module/web/app/images/default/fancy_deboss.png | Bin 0 -> 265 bytes module/web/app/images/default/logo.png | Bin 0 -> 5329 bytes module/web/app/images/default/logo_grey.png | Bin 0 -> 1141 bytes module/web/app/images/icon.png | Bin 0 -> 1912 bytes module/web/app/index.html | 107 + module/web/app/scripts/app.js | 105 + module/web/app/scripts/collections/AccountList.js | 23 + module/web/app/scripts/collections/FileList.js | 17 + .../web/app/scripts/collections/InteractionList.js | 48 + module/web/app/scripts/collections/PackageList.js | 15 + module/web/app/scripts/collections/ProgressList.js | 17 + module/web/app/scripts/config.js | 73 + module/web/app/scripts/controller.js | 67 + module/web/app/scripts/default.js | 30 + module/web/app/scripts/helpers/fileHelper.js | 55 + module/web/app/scripts/helpers/formatSize.js | 13 + module/web/app/scripts/helpers/formatTime.js | 17 + module/web/app/scripts/helpers/pluginIcon.js | 14 + module/web/app/scripts/models/Account.js | 50 + module/web/app/scripts/models/ConfigHolder.js | 67 + module/web/app/scripts/models/ConfigItem.js | 39 + module/web/app/scripts/models/File.js | 91 + module/web/app/scripts/models/InteractionTask.js | 40 + module/web/app/scripts/models/Package.js | 118 + module/web/app/scripts/models/Progress.js | 49 + module/web/app/scripts/models/ServerStatus.js | 46 + module/web/app/scripts/models/TreeCollection.js | 49 + module/web/app/scripts/models/UserSession.js | 20 + module/web/app/scripts/router.js | 29 + module/web/app/scripts/routers/defaultRouter.js | 29 + module/web/app/scripts/routers/mobileRouter.js | 55 + module/web/app/scripts/utils/animations.js | 128 ++ module/web/app/scripts/utils/apitypes.js | 14 + module/web/app/scripts/utils/dialogs.js | 15 + module/web/app/scripts/utils/initHB.js | 10 + module/web/app/scripts/utils/lazyRequire.js | 96 + module/web/app/scripts/utils/remaining.js | 149 ++ module/web/app/scripts/vendor/Handlebars-1.0rc1.js | 1927 ++++++++++++++++ module/web/app/scripts/vendor/bootstrap-2.3.2.js | 2291 ++++++++++++++++++++ module/web/app/scripts/vendor/jquery.omniwindow.js | 141 ++ module/web/app/scripts/views/abstract/itemView.js | 46 + module/web/app/scripts/views/abstract/modalView.js | 124 ++ .../app/scripts/views/accounts/accountListView.js | 52 + .../web/app/scripts/views/accounts/accountModal.js | 71 + .../web/app/scripts/views/accounts/accountView.js | 18 + .../app/scripts/views/dashboard/dashboardView.js | 167 ++ module/web/app/scripts/views/dashboard/fileView.js | 101 + .../web/app/scripts/views/dashboard/filterView.js | 132 ++ .../web/app/scripts/views/dashboard/packageView.js | 74 + .../app/scripts/views/dashboard/selectionView.js | 155 ++ module/web/app/scripts/views/headerView.js | 240 ++ module/web/app/scripts/views/input/inputLoader.js | 7 + module/web/app/scripts/views/input/inputView.js | 85 + module/web/app/scripts/views/input/textInput.js | 35 + module/web/app/scripts/views/linkGrabberModal.js | 48 + module/web/app/scripts/views/loginView.js | 37 + module/web/app/scripts/views/notificationView.js | 83 + module/web/app/scripts/views/progressView.js | 32 + module/web/app/scripts/views/queryModal.js | 68 + .../scripts/views/settings/configSectionView.js | 98 + .../scripts/views/settings/pluginChooserModal.js | 68 + .../web/app/scripts/views/settings/settingsView.js | 184 ++ module/web/app/styles/default/accounts.less | 6 + module/web/app/styles/default/admin.less | 17 + module/web/app/styles/default/base.less | 163 ++ module/web/app/styles/default/common.less | 90 + module/web/app/styles/default/dashboard.less | 331 +++ module/web/app/styles/default/main.less | 11 + module/web/app/styles/default/settings.less | 121 ++ module/web/app/styles/default/style.less | 366 ++++ module/web/app/styles/font.css | 37 + .../app/templates/default/accounts/account.html | 10 + .../app/templates/default/accounts/actionbar.html | 5 + .../web/app/templates/default/accounts/layout.html | 19 + module/web/app/templates/default/admin.html | 223 ++ .../app/templates/default/dashboard/actionbar.html | 54 + .../web/app/templates/default/dashboard/file.html | 34 + .../app/templates/default/dashboard/layout.html | 35 + .../app/templates/default/dashboard/package.html | 50 + .../app/templates/default/dashboard/select.html | 11 + .../app/templates/default/dialogs/addAccount.html | 42 + .../templates/default/dialogs/addPluginConfig.html | 26 + .../templates/default/dialogs/confirmDelete.html | 11 + .../templates/default/dialogs/interactionTask.html | 37 + .../app/templates/default/dialogs/linkgrabber.html | 49 + .../web/app/templates/default/dialogs/modal.html | 10 + .../web/app/templates/default/header/layout.html | 62 + .../web/app/templates/default/header/progress.html | 14 + .../app/templates/default/header/progressbar.html | 27 + .../web/app/templates/default/header/status.html | 3 + module/web/app/templates/default/login.html | 28 + module/web/app/templates/default/notification.html | 11 + .../app/templates/default/settings/actionbar.html | 5 + .../web/app/templates/default/settings/config.html | 17 + .../app/templates/default/settings/configItem.html | 7 + .../web/app/templates/default/settings/layout.html | 11 + .../web/app/templates/default/settings/menu.html | 40 + module/web/app/templates/default/setup.html | 16 + module/web/app/unavailable.html | 18 + 111 files changed, 9996 insertions(+) create mode 100644 module/web/app/favicon.ico create mode 100644 module/web/app/fonts/Sansation_Bold-webfont.eot create mode 100644 module/web/app/fonts/Sansation_Bold-webfont.ttf create mode 100644 module/web/app/fonts/Sansation_Bold-webfont.woff create mode 100644 module/web/app/fonts/Sansation_Light-webfont.eot create mode 100644 module/web/app/fonts/Sansation_Light-webfont.ttf create mode 100644 module/web/app/fonts/Sansation_Light-webfont.woff create mode 100644 module/web/app/fonts/Sansation_Regular-webfont.eot create mode 100644 module/web/app/fonts/Sansation_Regular-webfont.ttf create mode 100644 module/web/app/fonts/Sansation_Regular-webfont.woff create mode 100644 module/web/app/images/default/bgpattern.png create mode 100644 module/web/app/images/default/checks_sheet.png create mode 100644 module/web/app/images/default/fancy_deboss.png create mode 100644 module/web/app/images/default/logo.png create mode 100644 module/web/app/images/default/logo_grey.png create mode 100644 module/web/app/images/icon.png create mode 100644 module/web/app/index.html create mode 100644 module/web/app/scripts/app.js create mode 100644 module/web/app/scripts/collections/AccountList.js create mode 100644 module/web/app/scripts/collections/FileList.js create mode 100644 module/web/app/scripts/collections/InteractionList.js create mode 100644 module/web/app/scripts/collections/PackageList.js create mode 100644 module/web/app/scripts/collections/ProgressList.js create mode 100644 module/web/app/scripts/config.js create mode 100644 module/web/app/scripts/controller.js create mode 100644 module/web/app/scripts/default.js create mode 100644 module/web/app/scripts/helpers/fileHelper.js create mode 100644 module/web/app/scripts/helpers/formatSize.js create mode 100644 module/web/app/scripts/helpers/formatTime.js create mode 100644 module/web/app/scripts/helpers/pluginIcon.js create mode 100644 module/web/app/scripts/models/Account.js create mode 100644 module/web/app/scripts/models/ConfigHolder.js create mode 100644 module/web/app/scripts/models/ConfigItem.js create mode 100644 module/web/app/scripts/models/File.js create mode 100644 module/web/app/scripts/models/InteractionTask.js create mode 100644 module/web/app/scripts/models/Package.js create mode 100644 module/web/app/scripts/models/Progress.js create mode 100644 module/web/app/scripts/models/ServerStatus.js create mode 100644 module/web/app/scripts/models/TreeCollection.js create mode 100644 module/web/app/scripts/models/UserSession.js create mode 100644 module/web/app/scripts/router.js create mode 100644 module/web/app/scripts/routers/defaultRouter.js create mode 100644 module/web/app/scripts/routers/mobileRouter.js create mode 100644 module/web/app/scripts/utils/animations.js create mode 100644 module/web/app/scripts/utils/apitypes.js create mode 100644 module/web/app/scripts/utils/dialogs.js create mode 100644 module/web/app/scripts/utils/initHB.js create mode 100644 module/web/app/scripts/utils/lazyRequire.js create mode 100644 module/web/app/scripts/utils/remaining.js create mode 100644 module/web/app/scripts/vendor/Handlebars-1.0rc1.js create mode 100755 module/web/app/scripts/vendor/bootstrap-2.3.2.js create mode 100644 module/web/app/scripts/vendor/jquery.omniwindow.js create mode 100644 module/web/app/scripts/views/abstract/itemView.js create mode 100644 module/web/app/scripts/views/abstract/modalView.js create mode 100644 module/web/app/scripts/views/accounts/accountListView.js create mode 100644 module/web/app/scripts/views/accounts/accountModal.js create mode 100644 module/web/app/scripts/views/accounts/accountView.js create mode 100644 module/web/app/scripts/views/dashboard/dashboardView.js create mode 100644 module/web/app/scripts/views/dashboard/fileView.js create mode 100644 module/web/app/scripts/views/dashboard/filterView.js create mode 100644 module/web/app/scripts/views/dashboard/packageView.js create mode 100644 module/web/app/scripts/views/dashboard/selectionView.js create mode 100644 module/web/app/scripts/views/headerView.js create mode 100644 module/web/app/scripts/views/input/inputLoader.js create mode 100644 module/web/app/scripts/views/input/inputView.js create mode 100644 module/web/app/scripts/views/input/textInput.js create mode 100644 module/web/app/scripts/views/linkGrabberModal.js create mode 100644 module/web/app/scripts/views/loginView.js create mode 100644 module/web/app/scripts/views/notificationView.js create mode 100644 module/web/app/scripts/views/progressView.js create mode 100644 module/web/app/scripts/views/queryModal.js create mode 100644 module/web/app/scripts/views/settings/configSectionView.js create mode 100644 module/web/app/scripts/views/settings/pluginChooserModal.js create mode 100644 module/web/app/scripts/views/settings/settingsView.js create mode 100644 module/web/app/styles/default/accounts.less create mode 100644 module/web/app/styles/default/admin.less create mode 100644 module/web/app/styles/default/base.less create mode 100644 module/web/app/styles/default/common.less create mode 100644 module/web/app/styles/default/dashboard.less create mode 100644 module/web/app/styles/default/main.less create mode 100644 module/web/app/styles/default/settings.less create mode 100644 module/web/app/styles/default/style.less create mode 100644 module/web/app/styles/font.css create mode 100644 module/web/app/templates/default/accounts/account.html create mode 100644 module/web/app/templates/default/accounts/actionbar.html create mode 100644 module/web/app/templates/default/accounts/layout.html create mode 100644 module/web/app/templates/default/admin.html create mode 100644 module/web/app/templates/default/dashboard/actionbar.html create mode 100644 module/web/app/templates/default/dashboard/file.html create mode 100644 module/web/app/templates/default/dashboard/layout.html create mode 100644 module/web/app/templates/default/dashboard/package.html create mode 100644 module/web/app/templates/default/dashboard/select.html create mode 100755 module/web/app/templates/default/dialogs/addAccount.html create mode 100755 module/web/app/templates/default/dialogs/addPluginConfig.html create mode 100644 module/web/app/templates/default/dialogs/confirmDelete.html create mode 100755 module/web/app/templates/default/dialogs/interactionTask.html create mode 100755 module/web/app/templates/default/dialogs/linkgrabber.html create mode 100755 module/web/app/templates/default/dialogs/modal.html create mode 100644 module/web/app/templates/default/header/layout.html create mode 100644 module/web/app/templates/default/header/progress.html create mode 100644 module/web/app/templates/default/header/progressbar.html create mode 100644 module/web/app/templates/default/header/status.html create mode 100644 module/web/app/templates/default/login.html create mode 100644 module/web/app/templates/default/notification.html create mode 100644 module/web/app/templates/default/settings/actionbar.html create mode 100644 module/web/app/templates/default/settings/config.html create mode 100644 module/web/app/templates/default/settings/configItem.html create mode 100644 module/web/app/templates/default/settings/layout.html create mode 100644 module/web/app/templates/default/settings/menu.html create mode 100644 module/web/app/templates/default/setup.html create mode 100644 module/web/app/unavailable.html (limited to 'module/web/app') diff --git a/module/web/app/favicon.ico b/module/web/app/favicon.ico new file mode 100644 index 000000000..d7f9f1857 Binary files /dev/null and b/module/web/app/favicon.ico differ diff --git a/module/web/app/fonts/Sansation_Bold-webfont.eot b/module/web/app/fonts/Sansation_Bold-webfont.eot new file mode 100644 index 000000000..43ed2ee31 Binary files /dev/null and b/module/web/app/fonts/Sansation_Bold-webfont.eot differ diff --git a/module/web/app/fonts/Sansation_Bold-webfont.ttf b/module/web/app/fonts/Sansation_Bold-webfont.ttf new file mode 100644 index 000000000..d2e7c4c2a Binary files /dev/null and b/module/web/app/fonts/Sansation_Bold-webfont.ttf differ diff --git a/module/web/app/fonts/Sansation_Bold-webfont.woff b/module/web/app/fonts/Sansation_Bold-webfont.woff new file mode 100644 index 000000000..9ee938d55 Binary files /dev/null and b/module/web/app/fonts/Sansation_Bold-webfont.woff differ diff --git a/module/web/app/fonts/Sansation_Light-webfont.eot b/module/web/app/fonts/Sansation_Light-webfont.eot new file mode 100644 index 000000000..d83fa9cf6 Binary files /dev/null and b/module/web/app/fonts/Sansation_Light-webfont.eot differ diff --git a/module/web/app/fonts/Sansation_Light-webfont.ttf b/module/web/app/fonts/Sansation_Light-webfont.ttf new file mode 100644 index 000000000..64d734bec Binary files /dev/null and b/module/web/app/fonts/Sansation_Light-webfont.ttf differ diff --git a/module/web/app/fonts/Sansation_Light-webfont.woff b/module/web/app/fonts/Sansation_Light-webfont.woff new file mode 100644 index 000000000..5f3dce493 Binary files /dev/null and b/module/web/app/fonts/Sansation_Light-webfont.woff differ diff --git a/module/web/app/fonts/Sansation_Regular-webfont.eot b/module/web/app/fonts/Sansation_Regular-webfont.eot new file mode 100644 index 000000000..46219c9ff Binary files /dev/null and b/module/web/app/fonts/Sansation_Regular-webfont.eot differ diff --git a/module/web/app/fonts/Sansation_Regular-webfont.ttf b/module/web/app/fonts/Sansation_Regular-webfont.ttf new file mode 100644 index 000000000..92f686359 Binary files /dev/null and b/module/web/app/fonts/Sansation_Regular-webfont.ttf differ diff --git a/module/web/app/fonts/Sansation_Regular-webfont.woff b/module/web/app/fonts/Sansation_Regular-webfont.woff new file mode 100644 index 000000000..524b67992 Binary files /dev/null and b/module/web/app/fonts/Sansation_Regular-webfont.woff differ diff --git a/module/web/app/images/default/bgpattern.png b/module/web/app/images/default/bgpattern.png new file mode 100644 index 000000000..5111e6bdf Binary files /dev/null and b/module/web/app/images/default/bgpattern.png differ diff --git a/module/web/app/images/default/checks_sheet.png b/module/web/app/images/default/checks_sheet.png new file mode 100644 index 000000000..9662b8070 Binary files /dev/null and b/module/web/app/images/default/checks_sheet.png differ diff --git a/module/web/app/images/default/fancy_deboss.png b/module/web/app/images/default/fancy_deboss.png new file mode 100644 index 000000000..926a762db Binary files /dev/null and b/module/web/app/images/default/fancy_deboss.png differ diff --git a/module/web/app/images/default/logo.png b/module/web/app/images/default/logo.png new file mode 100644 index 000000000..bb9c13679 Binary files /dev/null and b/module/web/app/images/default/logo.png differ diff --git a/module/web/app/images/default/logo_grey.png b/module/web/app/images/default/logo_grey.png new file mode 100644 index 000000000..a4114d832 Binary files /dev/null and b/module/web/app/images/default/logo_grey.png differ diff --git a/module/web/app/images/icon.png b/module/web/app/images/icon.png new file mode 100644 index 000000000..1ab4ca081 Binary files /dev/null and b/module/web/app/images/icon.png differ diff --git a/module/web/app/index.html b/module/web/app/index.html new file mode 100644 index 000000000..87fd6c612 --- /dev/null +++ b/module/web/app/index.html @@ -0,0 +1,107 @@ + + + + + + + pyLoad WebUI + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+ + + + diff --git a/module/web/app/scripts/app.js b/module/web/app/scripts/app.js new file mode 100644 index 000000000..f841c9393 --- /dev/null +++ b/module/web/app/scripts/app.js @@ -0,0 +1,105 @@ +/* + * Global Application Object + * Contains all necessary logic shared across views + */ +define([ + + // Libraries. + 'jquery', + 'underscore', + 'backbone', + 'utils/initHB', + '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) { + // TODO + 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/module/web/app/scripts/collections/AccountList.js b/module/web/app/scripts/collections/AccountList.js new file mode 100644 index 000000000..1bcf87c1e --- /dev/null +++ b/module/web/app/scripts/collections/AccountList.js @@ -0,0 +1,23 @@ +define(['jquery', 'backbone', 'underscore', 'app', 'models/Account'], function($, Backbone, _, App, Account) { + + 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/module/web/app/scripts/collections/FileList.js b/module/web/app/scripts/collections/FileList.js new file mode 100644 index 000000000..e91088867 --- /dev/null +++ b/module/web/app/scripts/collections/FileList.js @@ -0,0 +1,17 @@ +define(['jquery', 'backbone', 'underscore', 'models/File'], function($, Backbone, _, File) { + + return Backbone.Collection.extend({ + + model: File, + + comparator: function(file) { + return file.get('fileorder'); + }, + + initialize: function() { + + } + + }); + +}); \ No newline at end of file diff --git a/module/web/app/scripts/collections/InteractionList.js b/module/web/app/scripts/collections/InteractionList.js new file mode 100644 index 000000000..57622a075 --- /dev/null +++ b/module/web/app/scripts/collections/InteractionList.js @@ -0,0 +1,48 @@ +define(['jquery', 'backbone', 'underscore', 'app', 'models/InteractionTask'], + function($, Backbone, _, App, InteractionTask) { + + 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/module/web/app/scripts/collections/PackageList.js b/module/web/app/scripts/collections/PackageList.js new file mode 100644 index 000000000..a36f8bcdc --- /dev/null +++ b/module/web/app/scripts/collections/PackageList.js @@ -0,0 +1,15 @@ +define(['jquery', 'backbone', 'underscore', 'models/Package'], function($, Backbone, _, Package) { + + return Backbone.Collection.extend({ + + model: Package, + + comparator: function(pack) { + return pack.get('packageorder'); + }, + + initialize: function() { + } + + }); +}); \ No newline at end of file diff --git a/module/web/app/scripts/collections/ProgressList.js b/module/web/app/scripts/collections/ProgressList.js new file mode 100644 index 000000000..1706d5f16 --- /dev/null +++ b/module/web/app/scripts/collections/ProgressList.js @@ -0,0 +1,17 @@ +define(['jquery', 'backbone', 'underscore', 'models/Progress'], function($, Backbone, _, Progress) { + + return Backbone.Collection.extend({ + + model: Progress, + + comparator: function(progress) { + return progress.get('eta'); + }, + + initialize: function() { + + } + + }); + +}); \ No newline at end of file diff --git a/module/web/app/scripts/config.js b/module/web/app/scripts/config.js new file mode 100644 index 000000000..9aeaf65a2 --- /dev/null +++ b/module/web/app/scripts/config.js @@ -0,0 +1,73 @@ +// 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: 'vendor/bootstrap-2.3.2', + underscore: '../components/underscore/underscore', + backbone: '../components/backbone/backbone', + marionette: '../components/backbone.marionette/lib/backbone.marionette', +// handlebars: '../components/handlebars.js/dist/handlebars', + handlebars: 'vendor/Handlebars-1.0rc1', + 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) { + // 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/module/web/app/scripts/controller.js b/module/web/app/scripts/controller.js new file mode 100644 index 000000000..05237914d --- /dev/null +++ b/module/web/app/scripts/controller.js @@ -0,0 +1,67 @@ +define([ + 'app', + 'backbone', + + // 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 completly visible after reattaching + 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/module/web/app/scripts/default.js b/module/web/app/scripts/default.js new file mode 100644 index 000000000..a337cee21 --- /dev/null +++ b/module/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/module/web/app/scripts/helpers/fileHelper.js b/module/web/app/scripts/helpers/fileHelper.js new file mode 100644 index 000000000..d48d7d863 --- /dev/null +++ b/module/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 = " "; + if (file.download.error) + s += file.download.error; + else s += msg; + } else if (file.finished) + s = " " + msg; + else if (file.downloading) + s = "
  " + + formatTime(file.eta) + "
"; + else if (file.waiting) + s = " " + 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/module/web/app/scripts/helpers/formatSize.js b/module/web/app/scripts/helpers/formatSize.js new file mode 100644 index 000000000..a50588bc6 --- /dev/null +++ b/module/web/app/scripts/helpers/formatSize.js @@ -0,0 +1,13 @@ +// Format bytes in human readable format +define('helpers/formatSize', ['handlebars'], function(Handlebars) { + 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))); + // 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/module/web/app/scripts/helpers/formatTime.js b/module/web/app/scripts/helpers/formatTime.js new file mode 100644 index 000000000..77d67a39c --- /dev/null +++ b/module/web/app/scripts/helpers/formatTime.js @@ -0,0 +1,17 @@ +// Format bytes in human readable format +define('helpers/formatTime', ['handlebars', 'utils/remaining'], function(Handlebars, Remaining) { + + + 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/module/web/app/scripts/helpers/pluginIcon.js b/module/web/app/scripts/helpers/pluginIcon.js new file mode 100644 index 000000000..6b2fdc67f --- /dev/null +++ b/module/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/module/web/app/scripts/models/Account.js b/module/web/app/scripts/models/Account.js new file mode 100644 index 000000000..c6e023578 --- /dev/null +++ b/module/web/app/scripts/models/Account.js @@ -0,0 +1,50 @@ +define(['jquery', 'backbone', 'underscore', 'app', 'utils/apitypes'], function($, Backbone, _, App, Api) { + + 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/module/web/app/scripts/models/ConfigHolder.js b/module/web/app/scripts/models/ConfigHolder.js new file mode 100644 index 000000000..b05b1e14b --- /dev/null +++ b/module/web/app/scripts/models/ConfigHolder.js @@ -0,0 +1,67 @@ +define(['jquery', 'backbone', 'underscore', 'app', './ConfigItem'], + function($, Backbone, _, App, ConfigItem) { + + 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/module/web/app/scripts/models/ConfigItem.js b/module/web/app/scripts/models/ConfigItem.js new file mode 100644 index 000000000..01a85c6cc --- /dev/null +++ b/module/web/app/scripts/models/ConfigItem.js @@ -0,0 +1,39 @@ +define(['jquery', 'backbone', 'underscore', 'app', 'utils/apitypes'], + function($, Backbone, _, App, Api) { + + 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/module/web/app/scripts/models/File.js b/module/web/app/scripts/models/File.js new file mode 100644 index 000000000..524637cb4 --- /dev/null +++ b/module/web/app/scripts/models/File.js @@ -0,0 +1,91 @@ +define(['jquery', 'backbone', 'underscore', 'app', 'utils/apitypes'], function($, Backbone, _, App, Api) { + + 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) { + + }, + + 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/module/web/app/scripts/models/InteractionTask.js b/module/web/app/scripts/models/InteractionTask.js new file mode 100644 index 000000000..56fdbf8bf --- /dev/null +++ b/module/web/app/scripts/models/InteractionTask.js @@ -0,0 +1,40 @@ +define(['jquery', 'backbone', 'underscore', 'app', 'utils/apitypes'], + function($, Backbone, _, App, Api) { + + 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/module/web/app/scripts/models/Package.js b/module/web/app/scripts/models/Package.js new file mode 100644 index 000000000..3435265fe --- /dev/null +++ b/module/web/app/scripts/models/Package.js @@ -0,0 +1,118 @@ +define(['jquery', 'backbone', 'underscore', 'app', 'collections/FileList', 'require'], + function($, Backbone, _, App, FileList, require) { + + 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/module/web/app/scripts/models/Progress.js b/module/web/app/scripts/models/Progress.js new file mode 100644 index 000000000..96beb0198 --- /dev/null +++ b/module/web/app/scripts/models/Progress.js @@ -0,0 +1,49 @@ +define(['jquery', 'backbone', 'underscore', 'utils/apitypes'], function($, Backbone, _, Api) { + + 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/module/web/app/scripts/models/ServerStatus.js b/module/web/app/scripts/models/ServerStatus.js new file mode 100644 index 000000000..9242bdf95 --- /dev/null +++ b/module/web/app/scripts/models/ServerStatus.js @@ -0,0 +1,46 @@ +define(['jquery', 'backbone', 'underscore'], + function($, Backbone, _) { + + 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.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/module/web/app/scripts/models/TreeCollection.js b/module/web/app/scripts/models/TreeCollection.js new file mode 100644 index 000000000..a528854b5 --- /dev/null +++ b/module/web/app/scripts/models/TreeCollection.js @@ -0,0 +1,49 @@ +define(['jquery', 'backbone', 'underscore', 'app', 'models/Package', 'collections/FileList', 'collections/PackageList'], + function($, Backbone, _, App, Package, FileList, PackageList) { + + // 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/module/web/app/scripts/models/UserSession.js b/module/web/app/scripts/models/UserSession.js new file mode 100644 index 000000000..a7e9aa848 --- /dev/null +++ b/module/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/module/web/app/scripts/router.js b/module/web/app/scripts/router.js new file mode 100644 index 000000000..68ea5575d --- /dev/null +++ b/module/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/module/web/app/scripts/routers/defaultRouter.js b/module/web/app/scripts/routers/defaultRouter.js new file mode 100644 index 000000000..95b8de967 --- /dev/null +++ b/module/web/app/scripts/routers/defaultRouter.js @@ -0,0 +1,29 @@ +define(['jquery','backbone','views/headerView'], function($, Backbone, HeaderView){ + + 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/module/web/app/scripts/routers/mobileRouter.js b/module/web/app/scripts/routers/mobileRouter.js new file mode 100644 index 000000000..7f1f7805e --- /dev/null +++ b/module/web/app/scripts/routers/mobileRouter.js @@ -0,0 +1,55 @@ +define(['jquery','backbone', 'underscore'], function($, Backbone, _){ + + 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($("

Page 1


some content
sdfdsf
sdffg

oiuzz

")); + }); + + $("#p2").bind("click", function(){ + self.changePage($("

Page 2


some content
sdfdsf

sdfsdf

sdffg
")); + }); + + }, + + 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/module/web/app/scripts/utils/animations.js b/module/web/app/scripts/utils/animations.js new file mode 100644 index 000000000..5131d3b8a --- /dev/null +++ b/module/web/app/scripts/utils/animations.js @@ -0,0 +1,128 @@ +define(['jquery', 'underscore', 'transit'], function(jQuery, _) { + + // 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/module/web/app/scripts/utils/apitypes.js b/module/web/app/scripts/utils/apitypes.js new file mode 100644 index 000000000..28620250e --- /dev/null +++ b/module/web/app/scripts/utils/apitypes.js @@ -0,0 +1,14 @@ +// Autogenerated, do not edit! +define([], function() { + 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: {'Multiple': 10, 'Int': 2, 'NA': 0, 'List': 11, 'Bool': 7, 'File': 3, 'Text': 1, 'Table': 12, 'Folder': 4, 'Password': 6, 'Click': 8, 'Select': 9, '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/module/web/app/scripts/utils/dialogs.js b/module/web/app/scripts/utils/dialogs.js new file mode 100644 index 000000000..13478ff88 --- /dev/null +++ b/module/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) { + + // Shows the confirm dialog for given context + // on success executes func with context + _.confirm = function(template, func, context) { + template = "text!tpl/" + template; + _.requireOnce([template], function(html) { + var template = _.compile(html); + var dialog = new modal(template, _.bind(func, context)); + dialog.show(); + }); + + }; +}); \ No newline at end of file diff --git a/module/web/app/scripts/utils/initHB.js b/module/web/app/scripts/utils/initHB.js new file mode 100644 index 000000000..c977f063d --- /dev/null +++ b/module/web/app/scripts/utils/initHB.js @@ -0,0 +1,10 @@ +// Loads all helper and set own handlebars rules +define(['underscore', 'handlebars', + 'helpers/formatSize', 'helpers/fileHelper', 'helpers/formatTime'], + function(_, Handlebars) { + // Replace with own lexer rules compiled from handlebars.l + Handlebars.Parser.lexer.rules = [/^(?:[^\x00]*?(?=(<%)))/, /^(?:[^\x00]+)/, /^(?:[^\x00]{2,}?(?=(\{\{|$)))/, /^(?:\{\{>)/, /^(?:<%=)/, /^(?:<%\/)/, /^(?:\{\{\^)/, /^(?:<%\s*else\b)/, /^(?:\{<%%)/, /^(?:\{\{&)/, /^(?:<%![\s\S]*?%>)/, /^(?:<%)/, /^(?:=)/, /^(?:\.(?=[%} ]))/, /^(?:\.\.)/, /^(?:[\/.])/, /^(?:\s+)/, /^(?:%%>)/, /^(?:%>)/, /^(?:"(\\["]|[^"])*")/, /^(?:'(\\[']|[^'])*')/, /^(?:@[a-zA-Z]+)/, /^(?:true(?=[%}\s]))/, /^(?:false(?=[%}\s]))/, /^(?:[0-9]+(?=[%}\s]))/, /^(?:[a-zA-Z0-9_$-]+(?=[=%}\s\/.]))/, /^(?:\[[^\]]*\])/, /^(?:.)/, /^(?:$)/]; + _.compile = Handlebars.compile; + + return Handlebars; + }); \ No newline at end of file diff --git a/module/web/app/scripts/utils/lazyRequire.js b/module/web/app/scripts/utils/lazyRequire.js new file mode 100644 index 000000000..b381e0ce6 --- /dev/null +++ b/module/web/app/scripts/utils/lazyRequire.js @@ -0,0 +1,96 @@ +// Define the module. +define( + [ + "require", "underscore" + ], + function( require, _ ){ + + + // 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/module/web/app/scripts/utils/remaining.js b/module/web/app/scripts/utils/remaining.js new file mode 100644 index 000000000..d66a2931a --- /dev/null +++ b/module/web/app/scripts/utils/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/module/web/app/scripts/vendor/Handlebars-1.0rc1.js b/module/web/app/scripts/vendor/Handlebars-1.0rc1.js new file mode 100644 index 000000000..991242461 --- /dev/null +++ b/module/web/app/scripts/vendor/Handlebars-1.0rc1.js @@ -0,0 +1,1927 @@ +// lib/handlebars/base.js +(function () { +/*jshint eqnull:true*/ +this.Handlebars = {}; + +(function(Handlebars) { + +Handlebars.VERSION = "1.0.rc.1"; + +Handlebars.helpers = {}; +Handlebars.partials = {}; + +Handlebars.registerHelper = function(name, fn, inverse) { + if(inverse) { fn.not = inverse; } + this.helpers[name] = fn; +}; + +Handlebars.registerPartial = function(name, str) { + this.partials[name] = str; +}; + +Handlebars.registerHelper('helperMissing', function(arg) { + if(arguments.length === 2) { + return undefined; + } else { + throw new Error("Could not find property '" + arg + "'"); + } +}); + +var toString = Object.prototype.toString, functionType = "[object Function]"; + +Handlebars.registerHelper('blockHelperMissing', function(context, options) { + var inverse = options.inverse || function() {}, fn = options.fn; + + + var ret = ""; + var type = toString.call(context); + + if(type === functionType) { context = context.call(this); } + + if(context === true) { + return fn(this); + } else if(context === false || context == null) { + return inverse(this); + } else if(type === "[object Array]") { + if(context.length > 0) { + return Handlebars.helpers.each(context, options); + } else { + return inverse(this); + } + } else { + return fn(context); + } +}); + +Handlebars.K = function() {}; + +Handlebars.createFrame = Object.create || function(object) { + Handlebars.K.prototype = object; + var obj = new Handlebars.K(); + Handlebars.K.prototype = null; + return obj; +}; + +Handlebars.registerHelper('each', function(context, options) { + var fn = options.fn, inverse = options.inverse; + var ret = "", data; + + if (options.data) { + data = Handlebars.createFrame(options.data); + } + + if(context && context.length > 0) { + for(var i=0, j=context.length; i 2) { + expected.push("'" + this.terminals_[p] + "'"); + } + if (this.lexer.showPosition) { + errStr = "Parse error on line " + (yylineno + 1) + ":\n" + this.lexer.showPosition() + "\nExpecting " + expected.join(", ") + ", got '" + (this.terminals_[symbol] || symbol) + "'"; + } else { + errStr = "Parse error on line " + (yylineno + 1) + ": Unexpected " + (symbol == 1?"end of input":"'" + (this.terminals_[symbol] || symbol) + "'"); + } + this.parseError(errStr, {text: this.lexer.match, token: this.terminals_[symbol] || symbol, line: this.lexer.yylineno, loc: yyloc, expected: expected}); + } + } + if (action[0] instanceof Array && action.length > 1) { + throw new Error("Parse Error: multiple actions possible at state: " + state + ", token: " + symbol); + } + switch (action[0]) { + case 1: + stack.push(symbol); + vstack.push(this.lexer.yytext); + lstack.push(this.lexer.yylloc); + stack.push(action[1]); + symbol = null; + if (!preErrorSymbol) { + yyleng = this.lexer.yyleng; + yytext = this.lexer.yytext; + yylineno = this.lexer.yylineno; + yyloc = this.lexer.yylloc; + if (recovering > 0) + recovering--; + } else { + symbol = preErrorSymbol; + preErrorSymbol = null; + } + break; + case 2: + len = this.productions_[action[1]][1]; + yyval.$ = vstack[vstack.length - len]; + yyval._$ = {first_line: lstack[lstack.length - (len || 1)].first_line, last_line: lstack[lstack.length - 1].last_line, first_column: lstack[lstack.length - (len || 1)].first_column, last_column: lstack[lstack.length - 1].last_column}; + if (ranges) { + yyval._$.range = [lstack[lstack.length - (len || 1)].range[0], lstack[lstack.length - 1].range[1]]; + } + r = this.performAction.call(yyval, yytext, yyleng, yylineno, this.yy, action[1], vstack, lstack); + if (typeof r !== "undefined") { + return r; + } + if (len) { + stack = stack.slice(0, -1 * len * 2); + vstack = vstack.slice(0, -1 * len); + lstack = lstack.slice(0, -1 * len); + } + stack.push(this.productions_[action[1]][0]); + vstack.push(yyval.$); + lstack.push(yyval._$); + newState = table[stack[stack.length - 2]][stack[stack.length - 1]]; + stack.push(newState); + break; + case 3: + return true; + } + } + return true; +} +}; +/* Jison generated lexer */ +var lexer = (function(){ +var lexer = ({EOF:1, +parseError:function parseError(str, hash) { + if (this.yy.parser) { + this.yy.parser.parseError(str, hash); + } else { + throw new Error(str); + } + }, +setInput:function (input) { + this._input = input; + this._more = this._less = this.done = false; + this.yylineno = this.yyleng = 0; + this.yytext = this.matched = this.match = ''; + this.conditionStack = ['INITIAL']; + this.yylloc = {first_line:1,first_column:0,last_line:1,last_column:0}; + if (this.options.ranges) this.yylloc.range = [0,0]; + this.offset = 0; + return this; + }, +input:function () { + var ch = this._input[0]; + this.yytext += ch; + this.yyleng++; + this.offset++; + this.match += ch; + this.matched += ch; + var lines = ch.match(/(?:\r\n?|\n).*/g); + if (lines) { + this.yylineno++; + this.yylloc.last_line++; + } else { + this.yylloc.last_column++; + } + if (this.options.ranges) this.yylloc.range[1]++; + + this._input = this._input.slice(1); + return ch; + }, +unput:function (ch) { + var len = ch.length; + var lines = ch.split(/(?:\r\n?|\n)/g); + + this._input = ch + this._input; + this.yytext = this.yytext.substr(0, this.yytext.length-len-1); + //this.yyleng -= len; + this.offset -= len; + var oldLines = this.match.split(/(?:\r\n?|\n)/g); + this.match = this.match.substr(0, this.match.length-1); + this.matched = this.matched.substr(0, this.matched.length-1); + + if (lines.length-1) this.yylineno -= lines.length-1; + var r = this.yylloc.range; + + this.yylloc = {first_line: this.yylloc.first_line, + last_line: this.yylineno+1, + first_column: this.yylloc.first_column, + last_column: lines ? + (lines.length === oldLines.length ? this.yylloc.first_column : 0) + oldLines[oldLines.length - lines.length].length - lines[0].length: + this.yylloc.first_column - len + }; + + if (this.options.ranges) { + this.yylloc.range = [r[0], r[0] + this.yyleng - len]; + } + return this; + }, +more:function () { + this._more = true; + return this; + }, +less:function (n) { + this.unput(this.match.slice(n)); + }, +pastInput:function () { + var past = this.matched.substr(0, this.matched.length - this.match.length); + return (past.length > 20 ? '...':'') + past.substr(-20).replace(/\n/g, ""); + }, +upcomingInput:function () { + var next = this.match; + if (next.length < 20) { + next += this._input.substr(0, 20-next.length); + } + return (next.substr(0,20)+(next.length > 20 ? '...':'')).replace(/\n/g, ""); + }, +showPosition:function () { + var pre = this.pastInput(); + var c = new Array(pre.length + 1).join("-"); + return pre + this.upcomingInput() + "\n" + c+"^"; + }, +next:function () { + if (this.done) { + return this.EOF; + } + if (!this._input) this.done = true; + + var token, + match, + tempMatch, + index, + col, + lines; + if (!this._more) { + this.yytext = ''; + this.match = ''; + } + var rules = this._currentRules(); + for (var i=0;i < rules.length; i++) { + tempMatch = this._input.match(this.rules[rules[i]]); + if (tempMatch && (!match || tempMatch[0].length > match[0].length)) { + match = tempMatch; + index = i; + if (!this.options.flex) break; + } + } + if (match) { + lines = match[0].match(/(?:\r\n?|\n).*/g); + if (lines) this.yylineno += lines.length; + this.yylloc = {first_line: this.yylloc.last_line, + last_line: this.yylineno+1, + first_column: this.yylloc.last_column, + last_column: lines ? lines[lines.length-1].length-lines[lines.length-1].match(/\r?\n?/)[0].length : this.yylloc.last_column + match[0].length}; + this.yytext += match[0]; + this.match += match[0]; + this.matches = match; + this.yyleng = this.yytext.length; + if (this.options.ranges) { + this.yylloc.range = [this.offset, this.offset += this.yyleng]; + } + this._more = false; + this._input = this._input.slice(match[0].length); + this.matched += match[0]; + token = this.performAction.call(this, this.yy, this, rules[index],this.conditionStack[this.conditionStack.length-1]); + if (this.done && this._input) this.done = false; + if (token) return token; + else return; + } + if (this._input === "") { + return this.EOF; + } else { + return this.parseError('Lexical error on line '+(this.yylineno+1)+'. Unrecognized text.\n'+this.showPosition(), + {text: "", token: null, line: this.yylineno}); + } + }, +lex:function lex() { + var r = this.next(); + if (typeof r !== 'undefined') { + return r; + } else { + return this.lex(); + } + }, +begin:function begin(condition) { + this.conditionStack.push(condition); + }, +popState:function popState() { + return this.conditionStack.pop(); + }, +_currentRules:function _currentRules() { + return this.conditions[this.conditionStack[this.conditionStack.length-1]].rules; + }, +topState:function () { + return this.conditionStack[this.conditionStack.length-2]; + }, +pushState:function begin(condition) { + this.begin(condition); + }}); +lexer.options = {}; +lexer.performAction = function anonymous(yy,yy_,$avoiding_name_collisions,YY_START) { + +var YYSTATE=YY_START +switch($avoiding_name_collisions) { +case 0: + if(yy_.yytext.slice(-1) !== "\\") this.begin("mu"); + if(yy_.yytext.slice(-1) === "\\") yy_.yytext = yy_.yytext.substr(0,yy_.yyleng-1), this.begin("emu"); + if(yy_.yytext) return 14; + +break; +case 1: return 14; +break; +case 2: + if(yy_.yytext.slice(-1) !== "\\") this.popState(); + if(yy_.yytext.slice(-1) === "\\") yy_.yytext = yy_.yytext.substr(0,yy_.yyleng-1); + return 14; + +break; +case 3: return 24; +break; +case 4: return 16; +break; +case 5: return 20; +break; +case 6: return 19; +break; +case 7: return 19; +break; +case 8: return 23; +break; +case 9: return 23; +break; +case 10: yy_.yytext = yy_.yytext.substr(3,yy_.yyleng-5); this.popState(); return 15; +break; +case 11: return 22; +break; +case 12: return 35; +break; +case 13: return 34; +break; +case 14: return 34; +break; +case 15: return 37; +break; +case 16: /*ignore whitespace*/ +break; +case 17: this.popState(); return 18; +break; +case 18: this.popState(); return 18; +break; +case 19: yy_.yytext = yy_.yytext.substr(1,yy_.yyleng-2).replace(/\\"/g,'"'); return 29; +break; +case 20: yy_.yytext = yy_.yytext.substr(1,yy_.yyleng-2).replace(/\\"/g,'"'); return 29; +break; +case 21: yy_.yytext = yy_.yytext.substr(1); return 27; +break; +case 22: return 31; +break; +case 23: return 31; +break; +case 24: return 30; +break; +case 25: return 34; +break; +case 26: yy_.yytext = yy_.yytext.substr(1, yy_.yyleng-2); return 34; +break; +case 27: return 'INVALID'; +break; +case 28: return 5; +break; +} +}; +lexer.rules = [/^(?:[^\x00]*?(?=(\{\{)))/,/^(?:[^\x00]+)/,/^(?:[^\x00]{2,}?(?=(\{\{|$)))/,/^(?:\{\{>)/,/^(?:\{\{#)/,/^(?:\{\{\/)/,/^(?:\{\{\^)/,/^(?:\{\{\s*else\b)/,/^(?:\{\{\{)/,/^(?:\{\{&)/,/^(?:\{\{![\s\S]*?\}\})/,/^(?:\{\{)/,/^(?:=)/,/^(?:\.(?=[} ]))/,/^(?:\.\.)/,/^(?:[\/.])/,/^(?:\s+)/,/^(?:\}\}\})/,/^(?:\}\})/,/^(?:"(\\["]|[^"])*")/,/^(?:'(\\[']|[^'])*')/,/^(?:@[a-zA-Z]+)/,/^(?:true(?=[}\s]))/,/^(?:false(?=[}\s]))/,/^(?:[0-9]+(?=[}\s]))/,/^(?:[a-zA-Z0-9_$-]+(?=[=}\s\/.]))/,/^(?:\[[^\]]*\])/,/^(?:.)/,/^(?:$)/]; +lexer.conditions = {"mu":{"rules":[3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28],"inclusive":false},"emu":{"rules":[2],"inclusive":false},"INITIAL":{"rules":[0,1,28],"inclusive":true}}; +return lexer;})() +parser.lexer = lexer; +function Parser () { this.yy = {}; }Parser.prototype = parser;parser.Parser = Parser; +return new Parser; +})(); +if (typeof require !== 'undefined' && typeof exports !== 'undefined') { +exports.parser = handlebars; +exports.Parser = handlebars.Parser; +exports.parse = function () { return handlebars.parse.apply(handlebars, arguments); } +exports.main = function commonjsMain(args) { + if (!args[1]) + throw new Error('Usage: '+args[0]+' FILE'); + var source, cwd; + if (typeof process !== 'undefined') { + source = require('fs').readFileSync(require('path').resolve(args[1]), "utf8"); + } else { + source = require("file").path(require("file").cwd()).join(args[1]).read({charset: "utf-8"}); + } + return exports.parser.parse(source); +} +if (typeof module !== 'undefined' && require.main === module) { + exports.main(typeof process !== 'undefined' ? process.argv.slice(1) : require("system").args); +} +}; +; +// lib/handlebars/compiler/base.js +Handlebars.Parser = handlebars; + +Handlebars.parse = function(string) { + Handlebars.Parser.yy = Handlebars.AST; + return Handlebars.Parser.parse(string); +}; + +Handlebars.print = function(ast) { + return new Handlebars.PrintVisitor().accept(ast); +}; + +Handlebars.logger = { + DEBUG: 0, INFO: 1, WARN: 2, ERROR: 3, level: 3, + + // override in the host environment + log: function(level, str) {} +}; + +Handlebars.log = function(level, str) { Handlebars.logger.log(level, str); }; +; +// lib/handlebars/compiler/ast.js +(function() { + + Handlebars.AST = {}; + + Handlebars.AST.ProgramNode = function(statements, inverse) { + this.type = "program"; + this.statements = statements; + if(inverse) { this.inverse = new Handlebars.AST.ProgramNode(inverse); } + }; + + Handlebars.AST.MustacheNode = function(rawParams, hash, unescaped) { + this.type = "mustache"; + this.escaped = !unescaped; + this.hash = hash; + + var id = this.id = rawParams[0]; + var params = this.params = rawParams.slice(1); + + // a mustache is an eligible helper if: + // * its id is simple (a single part, not `this` or `..`) + var eligibleHelper = this.eligibleHelper = id.isSimple; + + // a mustache is definitely a helper if: + // * it is an eligible helper, and + // * it has at least one parameter or hash segment + this.isHelper = eligibleHelper && (params.length || hash); + + // if a mustache is an eligible helper but not a definite + // helper, it is ambiguous, and will be resolved in a later + // pass or at runtime. + }; + + Handlebars.AST.PartialNode = function(id, context) { + this.type = "partial"; + + // TODO: disallow complex IDs + + this.id = id; + this.context = context; + }; + + var verifyMatch = function(open, close) { + if(open.original !== close.original) { + throw new Handlebars.Exception(open.original + " doesn't match " + close.original); + } + }; + + Handlebars.AST.BlockNode = function(mustache, program, inverse, close) { + verifyMatch(mustache.id, close); + this.type = "block"; + this.mustache = mustache; + this.program = program; + this.inverse = inverse; + + if (this.inverse && !this.program) { + this.isInverse = true; + } + }; + + Handlebars.AST.ContentNode = function(string) { + this.type = "content"; + this.string = string; + }; + + Handlebars.AST.HashNode = function(pairs) { + this.type = "hash"; + this.pairs = pairs; + }; + + Handlebars.AST.IdNode = function(parts) { + this.type = "ID"; + this.original = parts.join("."); + + var dig = [], depth = 0; + + for(var i=0,l=parts.length; i": ">", + '"': """, + "'": "'", + "`": "`" + }; + + var badChars = /[&<>"'`]/g; + var possible = /[&<>"'`]/; + + var escapeChar = function(chr) { + return escape[chr] || "&"; + }; + + Handlebars.Utils = { + escapeExpression: function(string) { + // don't escape SafeStrings, since they're already safe + if (string instanceof Handlebars.SafeString) { + return string.toString(); + } else if (string == null || string === false) { + return ""; + } + + if(!possible.test(string)) { return string; } + return string.replace(badChars, escapeChar); + }, + + isEmpty: function(value) { + if (typeof value === "undefined") { + return true; + } else if (value === null) { + return true; + } else if (value === false) { + return true; + } else if(Object.prototype.toString.call(value) === "[object Array]" && value.length === 0) { + return true; + } else { + return false; + } + } + }; +})();; +// lib/handlebars/compiler/compiler.js + +/*jshint eqnull:true*/ +Handlebars.Compiler = function() {}; +Handlebars.JavaScriptCompiler = function() {}; + +(function(Compiler, JavaScriptCompiler) { + // the foundHelper register will disambiguate helper lookup from finding a + // function in a context. This is necessary for mustache compatibility, which + // requires that context functions in blocks are evaluated by blockHelperMissing, + // and then proceed as if the resulting value was provided to blockHelperMissing. + + Compiler.prototype = { + compiler: Compiler, + + disassemble: function() { + var opcodes = this.opcodes, opcode, out = [], params, param; + + for (var i=0, l=opcodes.length; i 0) { + this.source[1] = this.source[1] + ", " + locals.join(", "); + } + + // Generate minimizer alias mappings + if (!this.isChild) { + var aliases = []; + for (var alias in this.context.aliases) { + this.source[1] = this.source[1] + ', ' + alias + '=' + this.context.aliases[alias]; + } + } + + if (this.source[1]) { + this.source[1] = "var " + this.source[1].substring(2) + ";"; + } + + // Merge children + if (!this.isChild) { + this.source[1] += '\n' + this.context.programs.join('\n') + '\n'; + } + + if (!this.environment.isSimple) { + this.source.push("return buffer;"); + } + + var params = this.isChild ? ["depth0", "data"] : ["Handlebars", "depth0", "helpers", "partials", "data"]; + + for(var i=0, l=this.environment.depths.list.length; i this.stackVars.length) { this.stackVars.push("stack" + this.stackSlot); } + return "stack" + this.stackSlot; + }, + + popStack: function() { + var item = this.compileStack.pop(); + + if (item instanceof Literal) { + return item.value; + } else { + this.stackSlot--; + return item; + } + }, + + topStack: function() { + var item = this.compileStack[this.compileStack.length - 1]; + + if (item instanceof Literal) { + return item.value; + } else { + return item; + } + }, + + quotedString: function(str) { + return '"' + str + .replace(/\\/g, '\\\\') + .replace(/"/g, '\\"') + .replace(/\n/g, '\\n') + .replace(/\r/g, '\\r') + '"'; + }, + + setupHelper: function(paramSize, name) { + var params = []; + this.setupParams(paramSize, params); + var foundHelper = this.nameLookup('helpers', name, 'helper'); + + return { + params: params, + name: foundHelper, + callParams: ["depth0"].concat(params).join(", "), + helperMissingParams: ["depth0", this.quotedString(name)].concat(params).join(", ") + }; + }, + + // the params and contexts arguments are passed in arrays + // to fill in + setupParams: function(paramSize, params) { + var options = [], contexts = [], param, inverse, program; + + options.push("hash:" + this.popStack()); + + inverse = this.popStack(); + program = this.popStack(); + + // Avoid setting fn and inverse if neither are set. This allows + // helpers to do a check for `if (options.fn)` + if (program || inverse) { + if (!program) { + this.context.aliases.self = "this"; + program = "self.noop"; + } + + if (!inverse) { + this.context.aliases.self = "this"; + inverse = "self.noop"; + } + + options.push("inverse:" + inverse); + options.push("fn:" + program); + } + + for(var i=0; i') + .appendTo(document.body) + + this.$backdrop.click( + this.options.backdrop == 'static' ? + $.proxy(this.$element[0].focus, this.$element[0]) + : $.proxy(this.hide, this) + ) + + if (doAnimate) this.$backdrop[0].offsetWidth // force reflow + + this.$backdrop.addClass('in') + + if (!callback) return + + doAnimate ? + this.$backdrop.one($.support.transition.end, callback) : + callback() + + } else if (!this.isShown && this.$backdrop) { + this.$backdrop.removeClass('in') + + $.support.transition && this.$element.hasClass('fade')? + this.$backdrop.one($.support.transition.end, callback) : + callback() + + } else if (callback) { + callback() + } + } + } + + + /* MODAL PLUGIN DEFINITION + * ======================= */ + + var old = $.fn.modal + + $.fn.modal = function (option) { + return this.each(function () { + var $this = $(this) + , data = $this.data('modal') + , options = $.extend({}, $.fn.modal.defaults, $this.data(), typeof option == 'object' && option) + if (!data) $this.data('modal', (data = new Modal(this, options))) + if (typeof option == 'string') data[option]() + else if (options.show) data.show() + }) + } + + $.fn.modal.defaults = { + backdrop: true + , keyboard: true + , show: true + } + + $.fn.modal.Constructor = Modal + + + /* MODAL NO CONFLICT + * ================= */ + + $.fn.modal.noConflict = function () { + $.fn.modal = old + return this + } + + + /* MODAL DATA-API + * ============== */ + + $(document).on('click.modal.data-api', '[data-toggle="modal"]', function (e) { + var $this = $(this) + , href = $this.attr('href') + , $target = $($this.attr('data-target') || (href && href.replace(/.*(?=#[^\s]+$)/, ''))) //strip for ie7 + , option = $target.data('modal') ? 'toggle' : $.extend({ remote:!/#/.test(href) && href }, $target.data(), $this.data()) + + e.preventDefault() + + $target + .modal(option) + .one('hide', function () { + $this.focus() + }) + }) + +}(window.jQuery); + +/* ============================================================ + * bootstrap-dropdown.js v2.3.2 + * http://twitter.github.com/bootstrap/javascript.html#dropdowns + * ============================================================ + * Copyright 2012 Twitter, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============================================================ */ + + +!function ($) { + + "use strict"; // jshint ;_; + + + /* DROPDOWN CLASS DEFINITION + * ========================= */ + + var toggle = '[data-toggle=dropdown]' + , Dropdown = function (element) { + var $el = $(element).on('click.dropdown.data-api', this.toggle) + $('html').on('click.dropdown.data-api', function () { + $el.parent().removeClass('open') + }) + } + + Dropdown.prototype = { + + constructor: Dropdown + + , toggle: function (e) { + var $this = $(this) + , $parent + , isActive + + if ($this.is('.disabled, :disabled')) return + + $parent = getParent($this) + + isActive = $parent.hasClass('open') + + clearMenus() + + if (!isActive) { + if ('ontouchstart' in document.documentElement) { + // if mobile we we use a backdrop because click events don't delegate + $('