diff --git a/config.json b/config.json index e1c8d5e..c61c0be 100644 --- a/config.json +++ b/config.json @@ -284,6 +284,8 @@ ]}, {"id": "users", "title": "Users"}, {"id": "devices", "title": "Devices"}, + {"id": "import", "title": "Import"}, + {"id": "export", "title": "Export"}, {"id": "notifications", "title": "Notifications"}, {"id": "transfers", "title": "Transfers"}, {"id": "gettingstarted", "title": "Getting Started"}, diff --git a/oml/user/models.py b/oml/user/models.py index 10ec456..5fb5019 100644 --- a/oml/user/models.py +++ b/oml/user/models.py @@ -174,6 +174,7 @@ class List(db.Model): for item_id in items: i = Item.get(item_id) i.update_lists() + db.session.add(i) db.session.commit() if self.user_id == settings.USER_ID: Changelog.record(self.user, 'addlistitems', self.name, items) @@ -189,6 +190,8 @@ class List(db.Model): for item_id in items: i = Item.get(item_id) i.update_lists() + db.session.add(i) + db.session.commit() db.session.commit() if self.user_id == settings.USER_ID: Changelog.record(self.user, 'removelistitems', self.name, items) diff --git a/static/js/Preferences.js b/static/js/Preferences.js index 9eb770b..277e6ec 100644 --- a/static/js/Preferences.js +++ b/static/js/Preferences.js @@ -19,7 +19,6 @@ oml.Preferences = (function() { set[key] = value; } }); - if (Ox.len(set)) { oml.api.setPreferences(set); Ox.forEach(set, function(value, key) { @@ -38,4 +37,4 @@ oml.Preferences = (function() { return that; -}()); \ No newline at end of file +}()); diff --git a/static/js/appPanel.js b/static/js/appPanel.js index acf4cfd..6ec1a9f 100644 --- a/static/js/appPanel.js +++ b/static/js/appPanel.js @@ -18,23 +18,38 @@ oml.ui.appPanel = function() { }) .bindEvent({ oml_page: function(data) { - setPage(data.value); + setPage(data.value, data.previousValue); } }); setPage(ui.page); - function setPage(page) { + function setPage(page, previousPage) { // close dialogs - $('.OxDialog:visible').each(function() { - Ox.UI.elements[$(this).data('oxid')].close(); - }); + if ( + !Ox.contains(['import', 'export'], page) + || !Ox.contains(['import', 'export'], previousPage) + ) { + $('.OxDialog:visible').each(function() { + Ox.UI.elements[$(this).data('oxid')].close(); + }); + } // open dialog if (Ox.contains([ 'welcome', 'app', 'preferences', 'users', 'notifications', 'transfers', 'help' ], page)) { oml.$ui[page + 'Dialog'] = oml.ui[page + 'Dialog']().open(); + } else if (Ox.contains(['import', 'export'], page)) { + if ( + oml.$ui.importExportDialog + && oml.$ui.importExportDialog.is(':visible') + ) { + Ox.print('AAAAAAAA') + oml.$ui.importExportDialog.select(page); + } else { + oml.$ui.importExportDialog = oml.ui.importExportDialog(page).open(); + } } } diff --git a/static/js/folders.js b/static/js/folders.js index f225e5e..3f4526a 100644 --- a/static/js/folders.js +++ b/static/js/folders.js @@ -10,7 +10,7 @@ oml.ui.folders = function() { that = Ox.Element() .css({ - //overflowX: 'hidden', + overflowX: 'hidden', //overflowY: 'auto', }) .bindEvent({ diff --git a/static/js/importDialog.js b/static/js/importDialog.js deleted file mode 100644 index 91da9e4..0000000 --- a/static/js/importDialog.js +++ /dev/null @@ -1,117 +0,0 @@ -'use strict'; - -oml.ui.importDialog = function() { - - var ui = oml.user.ui, - username = oml.user.preferences.username, - - lists = ui._lists.filter(function(list) { - return list.user == username && list.type == 'static'; - }), - items = [ - {id: ':', title: Ox._('Library')} - ].concat( - lists.length ? [{}] : [] - ).concat( - lists.map(function(list) { - return {id: list.id, title: list.name}; - }) - ).concat([ - {}, - {id: '', title: Ox._('New List...')} - ]), - - listNames = ui._lists.filter(function(list) { - return list.user == username; - }).map(function(list) { - return list.name; - }), - - $form = Ox.Form({ - items: [ - Ox.Input({ - changeOnKeypress: true, - id: 'path', - label: 'Source Path', - labelWidth: 128, - width: 480 - }), - Ox.SelectInput({ - id: 'list', - inputValue: oml.validateName(Ox._('Untitled'), listNames), - inputWidth: 224, - items: items, - label: 'Destination', - labelWidth: 128, - max: 1, - min: 1, - value: ':', - width: 480 - }), - Ox.Select({ - id: 'action', - items: [ - {id: 'copy', title: Ox._('Keep files in source path')}, - {id: 'move', title: Ox._('Remove files from source path')} - ], - label: Ox._('Import Mode'), - labelWidth: 128, - width: 480 - }) - ] - }) - .css({margin: '16px'}) - .bindEvent({ - change: function(data) { - var values = $form.values(); - Ox.print('FORM CHANGE', data); - if (data.id == 'list') { - // FIXME: WRONG - if (data.data.value[0] != ':') { - $form.values('list', oml.validateName(data.data.value, listNames)) - } - } - that[ - values.path && values.list ? 'enableButton' : 'disableButton' - ]('import'); - } - }), - - that = Ox.Dialog({ - buttons: [ - Ox.Button({ - id: 'dontimport', - title: Ox._('No, Don\'t Import') - }) - .bindEvent({ - click: function() { - that.close(); - } - }), - Ox.Button({ - disabled: true, - id: 'import', - title: Ox._('Yes, Import') - }) - .bindEvent({ - click: function() { - var data = $form.values(); - oml.api.import({ - path: data.path, - list: data.list == ':' ? null : data.list - }, function() { - // ... - }) - that.close(); - } - }) - ], - content: $form, - height: 128, - title: Ox._('Import Books'), - width: 512 - }); - - return that; - -}; \ No newline at end of file diff --git a/static/js/importExportDialog.js b/static/js/importExportDialog.js new file mode 100644 index 0000000..3a2cd0b --- /dev/null +++ b/static/js/importExportDialog.js @@ -0,0 +1,391 @@ +'use strict'; + +oml.ui.importExportDialog = function(selected) { + + var ui = oml.user.ui, + username = oml.user.preferences.username, + + $bar = Ox.Bar({size: 24}), + + $buttons = Ox.ButtonGroup({ + buttons: [ + {id: 'import', title: Ox._('Import Books')}, + {id: 'export', title: Ox._('Export Books')} + ], + min: 1, + max: 1, + selectable: true, + selected: selected + }) + .css({ + width: '768px', + padding: '4px 0', + textAlign: 'center' + }) + .bindEvent({ + change: function(data) { + oml.UI.set({page: data.value}); + } + }) + .appendTo($bar), + + $innerPanel = Ox.SlidePanel({ + elements: [ + {id: 'import', element: Ox.Element()}, + {id: 'export', element: Ox.Element()} + ], + orientation: 'horizontal', + selected: selected, + size: 512 + }), + + $outerPanel = Ox.SplitPanel({ + elements: [ + {element: $bar, size: 24}, + {element: $innerPanel} + ], + orientation: 'vertical' + }), + + that = Ox.Dialog({ + buttons: [ + Ox.Button({ + id: 'close', + title: Ox._('Close') + }) + .bindEvent({ + click: function() { + that.close(); + } + }) + ], + closeButton: true, + content: Ox.LoadingScreen().start(), + height: 144, + removeOnClose: true, + title: Ox._('Import & Export Books'), + width: 512 + }) + .bindEvent({ + close: function() { + if (Ox.contains(['import', 'export'], ui.page)) { + oml.UI.set({page: ''}); + } + }, + oml_page: function(data) { + if (Ox.contains(['import', 'export'], data.value)) { + selected = data.value; + $buttons.options({selected: selected}); + $innerPanel.options({selected: selected}); + } + } + }), + + $label = {}, + $activityButton = {}, + $progress = {}, + $status = {}, + $progressButton = {}; + + oml.getUsersAndLists(function() { + oml.api.getActivity(function(result) { + var isActive = !Ox.isEmpty(result.data), + activity = result.data.activity; + /* + result.data = { + path: '/Users/rolux/Desktop/Books', + status: {}, + progress: [0,42] + }; + */ + Ox.print(result.data, '!!!!!!!!') + $innerPanel + .replaceElement(0, + activity == 'import' ? renderActivity(result.data) + : renderForm('import', isActive) + ) + .replaceElement(1, + activity == 'export' ? renderActivity(result.data) + : renderForm('export', isActive) + ); + that.options({content: $outerPanel}); + }); + }); + + function getListItems(selected) { + var lists = ui._lists.filter(function(list) { + return list.user == username && list.type != 'library' && ( + selected == 'export' || list.type == 'static' + ); + }); + return [ + {id: ':', title: Ox._('Library')} + ].concat( + lists.length ? [{}] : [] + ).concat( + lists.map(function(list) { + return {id: list.id, title: list.name}; + }) + ).concat(selected == 'import' ? [ + {}, + {id: '', title: Ox._('New List...')} + ] : []); + } + + function getListNames() { + return ui._lists.filter(function(list) { + return list.user == username; + }).map(function(list) { + return list.name; + }); + } + + function renderActivity(data) { + var $element = Ox.Element(), + $title = $('
') + .addClass('OxSelectable') + .css({ + margin: '16px 16px 4px 16px', + width: '480px', + height: '16px', + whiteSpace: 'nowrap', + overflow: 'hidden', + textOverflow: 'ellipsis' + }) + .html( + Ox._(data.activity == 'import' ? 'Import from' : 'Export to') + + ' ' + data.path + '' + ) + .appendTo($element); + $progress[data.activity] = Ox.Progressbar({ + progress: -1, + showTooltips: true, + width: 480 + }) + .css({margin: '4px 16px'}) + .appendTo($element); + $status[data.activity] = $('
') + .addClass('OxSelectable') + .css({ + margin: '6px 16px 4px 16px', + height: '16px' + }) + .appendTo($element); + + $progressButton[data.activity] = Ox.Button({ + title: '', + width: 128 + }) + .css({ + position: 'absolute', + right: '16px', + bottom: '16px' + }) + .bindEvent({ + click: function() { + if (this.options('title') == Ox._('Done')) { + $innerPanel.replaceElement(0, renderForm(data.activity)); + } else { + oml.api[ + data.activity == 'import' ? 'cancelImport' : 'cancelExport' + ](function() { + // ... + }); + } + } + }) + .appendTo($element); + if (data.progress) { + setProgress(data); + setStatus(data); + setButton(data); + } + return $element; + } + + function renderForm(selected, isActive) { + var $element = Ox.Element(), + $form = Ox.Form({ + items: selected == 'import' ? [ + Ox.Input({ + changeOnKeypress: true, + id: 'path', + label: 'Source Path', + labelWidth: 128, + width: 480 + }), + Ox.SelectInput({ + id: 'list', + inputValue: oml.validateName(Ox._('Untitled'), getListNames()), + inputWidth: 224, + items: getListItems('import'), + label: 'Destination', + labelWidth: 128, + max: 1, + min: 1, + value: ':', + width: 480 + }), + Ox.Select({ + id: 'mode', + items: [ + {id: 'copy', title: Ox._('Copy files')}, + {id: 'move', title: Ox._('Move files')} + ], + label: Ox._('Import Mode'), + labelWidth: 128, + width: 480 + }) + ] : [ + Ox.Input({ + changeOnKeypress: true, + id: 'path', + label: 'Destination Path', + labelWidth: 128, + width: 480 + }), + Ox.Select({ + id: 'list', + items: getListItems('export'), + label: 'Source', + labelWidth: 128, + value: ':', + width: 480 + }), + Ox.Select({ + id: 'mode', + items: [ + {id: 'keep', title: Ox._('Keep existing files')}, + {id: 'remove', title: Ox._('Remove existing files')} + ], + label: Ox._('Import Mode'), + labelWidth: 128, + width: 480 + }) + ] + }) + .css({margin: '16px'}) + .bindEvent({ + change: function(data) { + var values = $form.values(); + Ox.print('FORM CHANGE', data); + if (data.id == 'list') { + // FIXME: WRONG + if (data.data.value[0] != ':') { + $form.values('list', oml.validateName(data.data.value, getListNames())) + } + } + $activityButton[selected].options({ + disabled: !values.path || !values.list + }); + } + }) + .appendTo($element); + $label[selected] = Ox.Label({ + //textAlign: 'center', + title: Ox._( + 'Waiting for ' + + (selected == 'import' ? 'export' : 'import') + + ' to finish...' + ), + width: 344 + }) + .css({ + position: 'absolute', + bottom: '16px', + left: '16px' + }) + [isActive ? 'show' : 'hide']() + .appendTo($element); + $activityButton[selected] = Ox.Button({ + disabled: true, + id: 'import', + title: Ox._(selected == 'import' ? 'Import' : 'Export'), + width: 128 + }) + .css({ + position: 'absolute', + right: '16px', + bottom: '16px' + }) + .bindEvent({ + click: function() { + var data = $form.values(); + $innerPanel.replaceElement(0, + renderActivity({ + activity: 'import', + path: data.path, + progress: [0, 0] + }) + ); + $label['export'].show(); + oml.api.import({ + list: data.list, // FIXME: WRONG for Library + mode: data.mode, + path: data.path, + }, function() { + // ... + }) + } + }) + .appendTo($element); + return $element; + } + + function setButton(data) { + $progressButton[data.activity].options({ + title: !data.status ? Ox._( + data.activity == 'import' ? 'Cancel Import' : 'Cancel Export' + ) : Ox._('Done') + }); + } + + function setProgress(data) { + Ox.print('SET PROGRESS', data, $progress) + var progress = data.status ? 1 + : !data.progress[0] || !data.progress[1] ? -1 + : data.progress[0] / data.progress[1]; + $progress[data.activity].options({progress: progress}) + } + + function setStatus(data) { + // FIXME: LOCALIZATION + var total = Ox.formatCount(data.progress[1], 'book').replace(/$no /, 'No'), + status = data.status && data.status.code + ? ( + data.status.code == 200 ? ( + data.progress[1] + ? 'Done. ' + total + ' ' + (selected == 'import' ? 'imported' : 'exported.') + : Ox._('No books found.') + ) + : data.status.code == 404 ? Ox._(( + selected == 'import' ? 'Source' : 'Destination' + ) + ' path not found.') + : Ox._((selected == 'import' ? 'Import' : 'Export') + 'failed.') + ) + : !data.progress[0] && data.progress[1] ? Ox._('Scanning: {0} found.', [total]) + : data.progress[0] ? Ox._(selected == 'import' ? 'Importing:' : 'Exporting') + + ' ' + Ox._('{0} of {1}', [data.progress[0], total]) + : ''; + $status[data.activity].html(status); + } + + that.select = function(selected_) { + selected = selected_; + $buttons.options({selected: selected}); + $innerPanel.options({selected: selected}); + return that; + }; + + oml.bindEvent({ + activity: function(data) { + Ox.print('activity', arguments); + setProgress(data); + setStatus(data); + setButton(data); + } + }); + + return that; + +}; \ No newline at end of file diff --git a/static/js/infoView.js b/static/js/infoView.js index 7fe7710..869f7cb 100644 --- a/static/js/infoView.js +++ b/static/js/infoView.js @@ -247,6 +247,10 @@ oml.ui.infoView = function(identifyData) { keys: [] }, function(result) { + if (!identifyData && ui.item != id) { + return; + } + if (result) { data = result.data; } diff --git a/static/js/mainMenu.js b/static/js/mainMenu.js index 46295f6..f5b04c1 100644 --- a/static/js/mainMenu.js +++ b/static/js/mainMenu.js @@ -371,7 +371,9 @@ oml.ui.mainMenu = function() { } else if (id == 'deletelist') { oml.ui.deleteListDialog().open(); } else if (id == 'import') { - oml.ui.importDialog().open(); + oml.UI.set({page: 'import'}); + } else if (id == 'export') { + oml.UI.set({page: 'export'}); } else if (id == 'selectall') { oml.$ui.list.selectAll(); } else if (id == 'selectnone') { @@ -548,11 +550,11 @@ oml.ui.mainMenu = function() { items: [ { id: 'import', - title: Ox._('Import Books...') + title: Ox._(oml.user.importing ? 'Importing Books...' : 'Import Books...') // FIXME }, { id: 'export', - title: Ox._('Export Books...') + title: Ox._(oml.user.exporting ? 'Exporting Books...' : 'Export Books...') // FIXME }, {}, { diff --git a/static/js/oml.js b/static/js/oml.js index 8ffa751..0e924a6 100644 --- a/static/js/oml.js +++ b/static/js/oml.js @@ -37,16 +37,18 @@ config: data.config, user: data.user }); - // make sure all valid ui settings are present - oml.user.ui = Ox.extend( - Ox.clone(data.config.user.ui, true), - oml.user.ui - ); - // make sure no invalid ui settings are present - Object.keys(oml.user.ui).forEach(function(key) { - if (Ox.isUndefined(oml.config.user.ui[key])) { - delete oml.user.ui[key]; - } + ['preferences', 'ui'].forEach(function(key) { + // make sure all valid settings are present + oml.user[key] = Ox.extend( + Ox.clone(data.config.user[key], true), + oml.user[key] + ); + // make sure no invalid settings are present + Object.keys(oml.user[key]).forEach(function(key_) { + if (Ox.isUndefined(oml.config.user[key][key_])) { + delete oml.user[key][key_]; + } + }); }); // TODO: make sure listView, listSort and itemView are valid Ox.extend(oml.config, { diff --git a/static/js/preferencesDialog.js b/static/js/preferencesDialog.js index a4227dd..14e0ca5 100644 --- a/static/js/preferencesDialog.js +++ b/static/js/preferencesDialog.js @@ -25,7 +25,7 @@ oml.ui.preferencesDialog = function() { { id: 'libraryPath', title: 'Library Path', - value: preferences.libaryPath, + value: preferences.libraryPath, help: 'The directory in which your "Books" folder is located. This is where your media files are stored. It can be on your local machine, but just as well on an external drive or networked volume.' }, { @@ -366,7 +366,7 @@ oml.ui.preferencesDialog = function() { ? Ox.Input({ height: 328, type: 'textarea', - value: item.value, + value: oml.user.preferences[item.id] || item.value, width: 400 }) : Ox.isBoolean(item.value) @@ -379,7 +379,7 @@ oml.ui.preferencesDialog = function() { label: Ox._(item.title), labelWidth: 128, placeholder: item.placeholder || '', - value: item.value || '', + value: oml.user.preferences[item.id] || item.value || '', width: 384 - !!item.unit * 48 }) @@ -441,4 +441,4 @@ oml.ui.preferencesDialog = function() { return that.update(); -}; \ No newline at end of file +}; diff --git a/static/js/usersDialog.js b/static/js/usersDialog.js index 2ac1c50..2a78d69 100644 --- a/static/js/usersDialog.js +++ b/static/js/usersDialog.js @@ -174,8 +174,6 @@ oml.ui.usersDialog = function() { function renderUser(user) { - Ox.print('renderUSER', user) - var $user = Ox.Element(), $form = Ox.Element() @@ -521,6 +519,7 @@ oml.ui.usersDialog = function() { } function updateUsers(callback) { + oml.api.getUsers(function(result) { users = result.data.users; @@ -580,6 +579,7 @@ oml.ui.usersDialog = function() { callback && callback(); }); + } that.update = function() { diff --git a/static/js/utils.js b/static/js/utils.js index 9b6083a..0c48390 100644 --- a/static/js/utils.js +++ b/static/js/utils.js @@ -727,6 +727,7 @@ oml.getListData = function(list) { oml.getListFoldersHeight = function() { var ui = oml.user.ui; return Object.keys(ui.showFolder).reduce(function(value, id, index) { + Ox.print('WTF WTF', index) var items = oml.$ui.folderList[index].options('items').length; return value + 16 + ui.showFolder[id] * (1 + items) * 16; }, 16); @@ -828,15 +829,15 @@ oml.resizeListFolders = function() { var width = oml.getListFoldersWidth(), columnWidth = width - 58; oml.$ui.librariesList - .resizeColumn('title', columnWidth) + .resizeColumn('name', columnWidth) .css({width: width + 'px'}); Ox.forEach(oml.$ui.folder, function($folder, index) { $folder.css({width: width + 'px'}); oml.$ui.libraryList[index] - .resizeColumn('title', columnWidth) + .resizeColumn('name', columnWidth) .css({width: width + 'px'}); oml.$ui.folderList[index] - .resizeColumn('title', columnWidth) + .resizeColumn('name', columnWidth) .css({width: width + 'px'}); }); oml.$ui.librariesList diff --git a/static/json/js.json b/static/json/js.json index 665c0bb..1672bd8 100644 --- a/static/json/js.json +++ b/static/json/js.json @@ -25,7 +25,7 @@ "gridView.js", "iconDialog.js", "identifyDialog.js", - "importDialog.js", + "importExportDialog.js", "info.js", "infoView.js", "itemInnerPanel.js",