2015-10-17 23:49:24 +02:00
|
|
|
'use strict';
|
|
|
|
|
|
|
|
var Backbone = require('backbone'),
|
|
|
|
DragView = require('../views/drag-view'),
|
|
|
|
MenuView = require('../views/menu/menu-view'),
|
|
|
|
FooterView = require('../views/footer-view'),
|
|
|
|
ListView = require('../views/list-view'),
|
|
|
|
DetailsView = require('../views/details/details-view'),
|
2015-10-31 20:09:32 +01:00
|
|
|
GrpView = require('../views/grp-view'),
|
2015-10-17 23:49:24 +02:00
|
|
|
OpenView = require('../views/open-view'),
|
|
|
|
SettingsView = require('../views/settings/settings-view'),
|
2015-10-25 10:44:19 +01:00
|
|
|
Alerts = require('../comp/alerts'),
|
2015-10-17 23:49:24 +02:00
|
|
|
Keys = require('../const/keys'),
|
2015-10-25 10:44:19 +01:00
|
|
|
KeyHandler = require('../comp/key-handler'),
|
2015-10-31 07:18:24 +01:00
|
|
|
Launcher = require('../comp/launcher'),
|
2015-11-16 20:04:33 +01:00
|
|
|
ThemeChanger = require('../util/theme-changer'),
|
|
|
|
UpdateModel = require('../models/update-model');
|
2015-10-17 23:49:24 +02:00
|
|
|
|
|
|
|
var AppView = Backbone.View.extend({
|
|
|
|
el: 'body',
|
|
|
|
|
|
|
|
template: require('templates/app.html'),
|
|
|
|
|
|
|
|
events: {
|
|
|
|
'contextmenu': 'contextmenu',
|
|
|
|
'drop': 'drop',
|
2015-10-22 20:03:44 +02:00
|
|
|
'dragover': 'dragover',
|
|
|
|
'click a[target=_blank]': 'extLinkClick'
|
2015-10-17 23:49:24 +02:00
|
|
|
},
|
|
|
|
|
|
|
|
views: null,
|
|
|
|
|
|
|
|
initialize: function () {
|
|
|
|
this.views = {};
|
|
|
|
this.views.menu = new MenuView({ model: this.model.menu });
|
|
|
|
this.views.menuDrag = new DragView('x');
|
|
|
|
this.views.footer = new FooterView({ model: this.model });
|
|
|
|
this.views.list = new ListView({ model: this.model });
|
|
|
|
this.views.listDrag = new DragView('x');
|
|
|
|
this.views.details = new DetailsView();
|
|
|
|
this.views.details.appModel = this.model;
|
2015-10-31 20:09:32 +01:00
|
|
|
this.views.grp = new GrpView();
|
2015-10-17 23:49:24 +02:00
|
|
|
|
|
|
|
this.views.menu.listenDrag(this.views.menuDrag);
|
|
|
|
this.views.list.listenDrag(this.views.listDrag);
|
|
|
|
|
|
|
|
this.listenTo(this.model.settings, 'change:theme', this.setTheme);
|
|
|
|
this.listenTo(this.model.files, 'update reset', this.fileListUpdated);
|
|
|
|
|
2015-10-31 20:09:32 +01:00
|
|
|
this.listenTo(Backbone, 'select-all', this.selectAll);
|
2015-10-17 23:49:24 +02:00
|
|
|
this.listenTo(Backbone, 'menu-select', this.menuSelect);
|
|
|
|
this.listenTo(Backbone, 'lock-workspace', this.lockWorkspace);
|
|
|
|
this.listenTo(Backbone, 'show-file', this.showFileSettings);
|
|
|
|
this.listenTo(Backbone, 'open-file', this.toggleOpenFile);
|
|
|
|
this.listenTo(Backbone, 'save-all', this.saveAll);
|
|
|
|
this.listenTo(Backbone, 'switch-view', this.switchView);
|
|
|
|
this.listenTo(Backbone, 'toggle-settings', this.toggleSettings);
|
2015-10-24 11:15:54 +02:00
|
|
|
this.listenTo(Backbone, 'toggle-menu', this.toggleMenu);
|
2015-10-26 22:07:19 +01:00
|
|
|
this.listenTo(Backbone, 'toggle-details', this.toggleDetails);
|
2015-10-31 20:09:32 +01:00
|
|
|
this.listenTo(Backbone, 'edit-group', this.editGroup);
|
2015-10-24 21:06:44 +02:00
|
|
|
this.listenTo(Backbone, 'launcher-open-file', this.launcherOpenFile);
|
2015-11-16 20:04:33 +01:00
|
|
|
|
|
|
|
this.listenTo(UpdateModel.instance, 'change:updateReady', this.updateApp);
|
2015-10-17 23:49:24 +02:00
|
|
|
|
|
|
|
window.onbeforeunload = this.beforeUnload.bind(this);
|
|
|
|
window.onresize = this.windowResize.bind(this);
|
|
|
|
|
|
|
|
KeyHandler.onKey(Keys.DOM_VK_ESCAPE, this.escPressed, this);
|
|
|
|
KeyHandler.onKey(Keys.DOM_VK_BACK_SPACE, this.backspacePressed, this);
|
|
|
|
},
|
|
|
|
|
|
|
|
render: function () {
|
|
|
|
this.setTheme();
|
|
|
|
this.$el.html(this.template());
|
|
|
|
this.views.menu.setElement(this.$el.find('.app__menu')).render();
|
|
|
|
this.views.menuDrag.setElement(this.$el.find('.app__menu-drag')).render();
|
|
|
|
this.views.footer.setElement(this.$el.find('.app__footer')).render();
|
|
|
|
this.views.list.setElement(this.$el.find('.app__list')).render();
|
|
|
|
this.views.listDrag.setElement(this.$el.find('.app__list-drag')).render();
|
|
|
|
this.views.details.setElement(this.$el.find('.app__details')).render();
|
2015-10-31 20:09:32 +01:00
|
|
|
this.views.grp.setElement(this.$el.find('.app__grp')).render().hide();
|
2015-10-17 23:49:24 +02:00
|
|
|
return this;
|
|
|
|
},
|
|
|
|
|
2015-10-24 21:06:44 +02:00
|
|
|
showOpenFile: function(filePath) {
|
2015-10-17 23:49:24 +02:00
|
|
|
this.views.menu.hide();
|
|
|
|
this.views.menuDrag.hide();
|
|
|
|
this.views.list.hide();
|
|
|
|
this.views.listDrag.hide();
|
|
|
|
this.views.details.hide();
|
2015-10-31 20:09:32 +01:00
|
|
|
this.views.grp.hide();
|
2015-10-17 23:49:24 +02:00
|
|
|
this.views.footer.toggle(this.model.files.hasOpenFiles());
|
|
|
|
this.hideSettings();
|
|
|
|
this.hideOpenFile();
|
|
|
|
this.views.open = new OpenView({ model: this.model });
|
|
|
|
this.views.open.setElement(this.$el.find('.app__body')).render();
|
|
|
|
this.views.open.on('cancel', this.showEntries, this);
|
2015-10-24 21:06:44 +02:00
|
|
|
if (Launcher && filePath) {
|
|
|
|
this.views.open.showOpenLocalFile(filePath);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
launcherOpenFile: function(path) {
|
|
|
|
if (path && /\.kdbx$/i.test(path)) {
|
|
|
|
this.showOpenFile(path);
|
|
|
|
}
|
2015-10-17 23:49:24 +02:00
|
|
|
},
|
|
|
|
|
2015-10-29 22:20:01 +01:00
|
|
|
updateApp: function() {
|
2015-11-16 20:04:33 +01:00
|
|
|
if (UpdateModel.instance.get('updateStatus') === 'ready' &&
|
|
|
|
!Launcher && !this.model.files.hasOpenFiles()) {
|
2015-11-11 20:26:04 +01:00
|
|
|
window.location.reload();
|
2015-10-29 22:20:01 +01:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2015-10-17 23:49:24 +02:00
|
|
|
showEntries: function() {
|
|
|
|
this.views.menu.show();
|
|
|
|
this.views.menuDrag.show();
|
|
|
|
this.views.list.show();
|
|
|
|
this.views.listDrag.show();
|
|
|
|
this.views.details.show();
|
2015-10-31 20:09:32 +01:00
|
|
|
this.views.grp.hide();
|
2015-10-17 23:49:24 +02:00
|
|
|
this.views.footer.show();
|
|
|
|
this.hideOpenFile();
|
|
|
|
this.hideSettings();
|
|
|
|
},
|
|
|
|
|
|
|
|
hideOpenFile: function() {
|
|
|
|
if (this.views.open) {
|
|
|
|
this.views.open.remove();
|
|
|
|
this.views.open = null;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
hideSettings: function() {
|
|
|
|
if (this.views.settings) {
|
|
|
|
this.model.menu.setMenu('app');
|
|
|
|
this.views.settings.remove();
|
|
|
|
this.views.settings = null;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
showSettings: function(selectedMenuItem) {
|
|
|
|
this.model.menu.setMenu('settings');
|
|
|
|
this.views.menu.show();
|
|
|
|
this.views.menuDrag.show();
|
|
|
|
this.views.list.hide();
|
|
|
|
this.views.listDrag.hide();
|
|
|
|
this.views.details.hide();
|
2015-10-31 20:09:32 +01:00
|
|
|
this.views.grp.hide();
|
2015-10-17 23:49:24 +02:00
|
|
|
this.hideOpenFile();
|
|
|
|
this.views.settings = new SettingsView();
|
|
|
|
this.views.settings.setElement(this.$el.find('.app__body')).render();
|
|
|
|
if (!selectedMenuItem) {
|
|
|
|
selectedMenuItem = this.model.menu.generalSection.get('items').first();
|
|
|
|
}
|
|
|
|
this.model.menu.select({ item: selectedMenuItem });
|
2015-10-26 22:07:19 +01:00
|
|
|
this.views.menu.switchVisibility(false);
|
2015-10-17 23:49:24 +02:00
|
|
|
},
|
|
|
|
|
2015-10-31 20:09:32 +01:00
|
|
|
showEditGroup: function() {
|
|
|
|
this.views.list.hide();
|
|
|
|
this.views.listDrag.hide();
|
|
|
|
this.views.details.hide();
|
|
|
|
this.views.grp.show();
|
|
|
|
},
|
|
|
|
|
2015-10-17 23:49:24 +02:00
|
|
|
fileListUpdated: function() {
|
|
|
|
if (this.model.files.hasOpenFiles()) {
|
|
|
|
this.showEntries();
|
|
|
|
} else {
|
|
|
|
this.showOpenFile();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
showFileSettings: function(e) {
|
|
|
|
var menuItem = this.model.menu.filesSection.get('items').find(function(item) {
|
|
|
|
return item.get('file').cid === e.fileId;
|
|
|
|
});
|
|
|
|
if (this.views.settings) {
|
2015-10-18 16:02:00 +02:00
|
|
|
if (this.views.settings.file === menuItem.get('file')) {
|
2015-10-17 23:49:24 +02:00
|
|
|
this.showEntries();
|
|
|
|
} else {
|
|
|
|
this.model.menu.select({ item: menuItem });
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
this.showSettings(menuItem);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
toggleOpenFile: function() {
|
|
|
|
if (this.views.open) {
|
|
|
|
this.showEntries();
|
|
|
|
} else {
|
|
|
|
this.showOpenFile();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2015-11-04 07:16:39 +01:00
|
|
|
beforeUnload: function(e) {
|
2015-10-17 23:49:24 +02:00
|
|
|
if (this.model.files.hasUnsavedFiles()) {
|
2015-11-04 07:16:39 +01:00
|
|
|
if (Launcher && !Launcher.exitRequested) {
|
2015-11-14 12:09:36 +01:00
|
|
|
if (!this.exitAlertShown) {
|
|
|
|
var that = this;
|
|
|
|
that.exitAlertShown = true;
|
|
|
|
Alerts.yesno({
|
|
|
|
header: 'Unsaved changes!',
|
|
|
|
body: 'You have unsaved files, all changes will be lost.',
|
|
|
|
buttons: [{result: 'yes', title: 'Exit and discard unsaved changes'}, {result: '', title: 'Don\'t exit'}],
|
|
|
|
success: function () {
|
|
|
|
Launcher.exit();
|
|
|
|
},
|
|
|
|
cancel: function() {
|
|
|
|
Launcher.cancelRestart(false);
|
|
|
|
},
|
|
|
|
complete: function () {
|
|
|
|
that.exitAlertShown = false;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
return Launcher.preventExit(e);
|
2015-11-04 07:16:39 +01:00
|
|
|
}
|
2015-10-17 23:49:24 +02:00
|
|
|
return 'You have unsaved files, all changes will be lost.';
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
windowResize: function() {
|
|
|
|
Backbone.trigger('page-geometry', { source: 'window' });
|
|
|
|
},
|
|
|
|
|
|
|
|
escPressed: function() {
|
|
|
|
if (this.views.open && this.model.files.hasOpenFiles()) {
|
|
|
|
this.showEntries();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
backspacePressed: function(e) {
|
|
|
|
if (e.target === document.body) {
|
|
|
|
e.preventDefault();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2015-10-31 20:09:32 +01:00
|
|
|
selectAll: function() {
|
|
|
|
this.menuSelect({ item: this.model.menu.allItemsSection.get('items').first() });
|
|
|
|
},
|
|
|
|
|
|
|
|
menuSelect: function(opt) {
|
|
|
|
this.model.menu.select(opt);
|
|
|
|
if (!this.views.grp.isHidden()) {
|
|
|
|
this.showEntries();
|
|
|
|
}
|
2015-10-17 23:49:24 +02:00
|
|
|
},
|
|
|
|
|
|
|
|
lockWorkspace: function() {
|
2015-11-17 21:57:32 +01:00
|
|
|
var that = this;
|
2015-10-25 10:19:00 +01:00
|
|
|
if (this.model.files.hasUnsavedFiles()) {
|
2015-11-17 21:57:32 +01:00
|
|
|
if (this.model.settings.get('autoSave')) {
|
|
|
|
this.saveAndLock();
|
|
|
|
} else {
|
|
|
|
Alerts.alert({
|
|
|
|
icon: 'lock',
|
|
|
|
header: 'Lock',
|
|
|
|
body: 'You have unsaved changes that will be lost. Continue?',
|
|
|
|
buttons: [
|
|
|
|
{ result: 'save', title: 'Save changes' },
|
|
|
|
{ result: 'discard', title: 'Discard changes', error: true },
|
|
|
|
{ result: '', title: 'Cancel' }
|
|
|
|
],
|
|
|
|
checkbox: 'Auto save changes each time I lock the app',
|
|
|
|
success: function(result, autoSaveChecked) {
|
|
|
|
if (result === 'save') {
|
|
|
|
if (autoSaveChecked) {
|
|
|
|
that.model.settings.set('autoSave', autoSaveChecked);
|
|
|
|
}
|
|
|
|
that.saveAndLock();
|
|
|
|
} else if (result === 'discard') {
|
|
|
|
that.model.closeAllFiles();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
2015-10-25 10:19:00 +01:00
|
|
|
} else {
|
2015-11-17 21:57:32 +01:00
|
|
|
this.closeAllFilesAndShowFirst();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
saveAndLock: function() {
|
|
|
|
var pendingCallbacks = 0,
|
|
|
|
errorFiles = [],
|
|
|
|
that = this;
|
|
|
|
if (this.model.files.some(function(file) { return file.get('modified') && !file.get('path'); })) {
|
|
|
|
Alerts.error({
|
|
|
|
header: 'Cannot auto-save',
|
|
|
|
body: 'Some opened files cannot be saved automatically. To enable auto-save, you can sync them to Dropbox.'
|
|
|
|
});
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
this.model.files.forEach(function(file) {
|
|
|
|
if (!file.get('modified')) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (file.get('path')) {
|
|
|
|
try {
|
|
|
|
file.autoSave(fileSaved.bind(this, file));
|
|
|
|
pendingCallbacks++;
|
|
|
|
} catch (e) {
|
|
|
|
console.error('Failed to auto-save file', file.get('path'), e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}, this);
|
|
|
|
if (!pendingCallbacks) {
|
|
|
|
this.closeAllFilesAndShowFirst();
|
|
|
|
}
|
|
|
|
function fileSaved(file, err) {
|
|
|
|
if (err) {
|
|
|
|
errorFiles.push(file.get('name'));
|
|
|
|
}
|
|
|
|
if (--pendingCallbacks === 0) {
|
|
|
|
if (errorFiles.length) {
|
|
|
|
Alerts.error({
|
|
|
|
header: 'Save Error',
|
|
|
|
body: 'Failed to auto-save file' + (errorFiles.length > 1 ? 's: ' : '') + ' ' + errorFiles.join(', ')
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
that.closeAllFilesAndShowFirst();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
closeAllFilesAndShowFirst: function() {
|
|
|
|
var firstFile = this.model.files.find(function(file) { return !file.get('demo'); });
|
|
|
|
this.model.closeAllFiles();
|
|
|
|
if (firstFile) {
|
|
|
|
this.views.open.showClosedFile(firstFile);
|
2015-10-25 10:19:00 +01:00
|
|
|
}
|
2015-10-17 23:49:24 +02:00
|
|
|
},
|
|
|
|
|
|
|
|
saveAll: function() {
|
2015-10-24 21:06:44 +02:00
|
|
|
var fileId;
|
|
|
|
this.model.files.forEach(function(file) {
|
|
|
|
if (file.get('path')) {
|
|
|
|
try {
|
|
|
|
file.autoSave();
|
|
|
|
} catch (e) {
|
2015-11-04 21:23:55 +01:00
|
|
|
console.error('Failed to auto-save file', file.get('path'), e);
|
2015-10-24 21:06:44 +02:00
|
|
|
fileId = file.cid;
|
|
|
|
}
|
|
|
|
} else if (!fileId) {
|
|
|
|
fileId = file.cid;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
if (fileId) {
|
|
|
|
this.showFileSettings({fileId: fileId});
|
|
|
|
}
|
2015-10-17 23:49:24 +02:00
|
|
|
},
|
|
|
|
|
2015-10-22 20:03:44 +02:00
|
|
|
toggleSettings: function(page) {
|
|
|
|
var menuItem = page ? this.model.menu[page + 'Section'] : null;
|
|
|
|
if (menuItem) {
|
|
|
|
menuItem = menuItem.get('items').first();
|
|
|
|
}
|
2015-10-17 23:49:24 +02:00
|
|
|
if (this.views.settings) {
|
2015-10-22 20:08:11 +02:00
|
|
|
if (this.views.settings.page === page || !menuItem) {
|
2015-10-22 20:03:44 +02:00
|
|
|
this.showEntries();
|
|
|
|
} else {
|
|
|
|
if (menuItem) {
|
|
|
|
this.model.menu.select({item: menuItem});
|
|
|
|
}
|
|
|
|
}
|
2015-10-17 23:49:24 +02:00
|
|
|
} else {
|
|
|
|
this.showSettings();
|
2015-10-22 20:03:44 +02:00
|
|
|
if (menuItem) {
|
|
|
|
this.model.menu.select({item: menuItem});
|
|
|
|
}
|
2015-10-17 23:49:24 +02:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2015-10-24 11:15:54 +02:00
|
|
|
toggleMenu: function() {
|
|
|
|
this.views.menu.switchVisibility();
|
|
|
|
},
|
|
|
|
|
2015-10-17 23:49:24 +02:00
|
|
|
switchView: function() {
|
|
|
|
Alerts.notImplemented();
|
|
|
|
},
|
|
|
|
|
2015-10-26 22:07:19 +01:00
|
|
|
toggleDetails: function(visible) {
|
|
|
|
this.$el.find('.app__list').toggleClass('app__list--details-visible', visible);
|
|
|
|
this.views.menu.switchVisibility(false);
|
|
|
|
},
|
|
|
|
|
2015-10-31 20:09:32 +01:00
|
|
|
editGroup: function(group) {
|
|
|
|
if (group && this.views.grp.isHidden()) {
|
|
|
|
this.showEditGroup();
|
|
|
|
this.views.grp.showGroup(group);
|
|
|
|
} else {
|
|
|
|
this.showEntries();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2015-10-17 23:49:24 +02:00
|
|
|
contextmenu: function(e) {
|
|
|
|
if (['input', 'textarea'].indexOf(e.target.tagName.toLowerCase()) < 0) {
|
|
|
|
e.preventDefault();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
dragover: function(e) {
|
|
|
|
e.preventDefault();
|
|
|
|
},
|
|
|
|
|
|
|
|
drop: function(e) {
|
|
|
|
e.preventDefault();
|
|
|
|
},
|
|
|
|
|
|
|
|
setTheme: function() {
|
2015-10-31 07:18:24 +01:00
|
|
|
ThemeChanger.setTheme(this.model.settings.get('theme'));
|
2015-10-22 20:03:44 +02:00
|
|
|
},
|
|
|
|
|
|
|
|
extLinkClick: function(e) {
|
|
|
|
if (Launcher) {
|
|
|
|
e.preventDefault();
|
|
|
|
Launcher.openLink(e.target.href);
|
|
|
|
}
|
2015-10-17 23:49:24 +02:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
module.exports = AppView;
|