webdav settings ui

This commit is contained in:
Antelle 2016-03-12 14:22:35 +03:00
parent 1c17bcc8c5
commit 4a13d4f130
10 changed files with 277 additions and 12 deletions

View File

@ -5,6 +5,7 @@ var Launcher = require('../comp/launcher');
var Storage = {
file: require('./storage-file'),
dropbox: require('./storage-dropbox'),
webdav: require('./storage-webdav'),
cache: Launcher ? require('./storage-file-cache') : require('./storage-cache')
};

View File

@ -7,6 +7,7 @@ var logger = new Logger('storage-dropbox');
var StorageDropbox = {
name: 'dropbox',
icon: 'dropbox',
enabled: true,
_convertError: function(err) {

View File

@ -9,6 +9,7 @@ var fileWatchers = {};
var StorageFile = {
name: 'file',
icon: 'hdd-o',
enabled: !!Launcher,
load: function(path, callback) {

View File

@ -0,0 +1,42 @@
'use strict';
var Logger = require('../util/logger');
var logger = new Logger('storage-webdav');
var StorageDropbox = {
name: 'webdav',
icon: 'server',
enabled: true,
openFields: [
{ id: 'url', title: 'openUrl', desc: 'openUrlDesc', type: 'text', required: true },
{ id: 'user', title: 'openUser', placeholder: 'openUserPlaceholder', type: 'text' },
{ id: 'password', title: 'openPass', placeholder: 'openPassPlaceholder', type: 'password' }
],
load: function(path, callback) {
logger.debug('Load', path);
var ts = logger.ts();
var stat = {};
logger.debug('Loaded', path, stat ? stat.versionTag : null, logger.ts(ts));
callback('not implemented');
},
stat: function(path, callback) {
logger.debug('Stat', path);
var ts = logger.ts();
var stat = {};
logger.debug('Stated', path, stat ? stat.versionTag : null, logger.ts(ts));
callback('not implemented');
},
save: function(path, data, callback, rev) {
logger.debug('Save', path, rev);
var ts = logger.ts();
logger.debug('Saved', path, logger.ts(ts));
callback('not implemented');
}
};
module.exports = StorageDropbox;

View File

@ -19,6 +19,11 @@ var Locale = {
or: 'or',
notImplemented: 'Not Implemented',
cache: 'cache',
file: 'file',
webdav: 'WebDAV',
dropbox: 'Dropbox',
menuAllItems: 'All Items',
menuColors: 'Colors',
menuTags: 'Tags',
@ -126,6 +131,14 @@ var Locale = {
openWrongFileBody: 'This file format is not supported. This app works with KeePass database format files (KDBX).',
openKdbFileBody: 'You are opening an old version format file (KDB). This app supports only new format (KDBX), ' +
'please use KeePass v2 to convert between them.',
openConfigHeader: '{} Settings',
openUrl: 'URL',
openUrlDesc: 'https://server/path/file.kdbx, or just file.kdbx',
openUser: 'Username',
openUserPlaceholder: 'no username',
openPass: 'Password',
openPassPlaceholder: 'no password',
openConfigError: 'Error: {}',
detAttDownload: 'Shift-click attachment button to download or ',
detAttDelToRemove: 'Delete to remove',

View File

@ -0,0 +1,79 @@
'use strict';
var Backbone = require('backbone'),
Locale = require('../util/locale'),
Keys = require('../const/keys');
var OpenConfigView = Backbone.View.extend({
template: require('templates/open-config.hbs'),
events: {
'click .open__config-btn-cancel': 'cancel',
'click .open__config-btn-ok': 'apply',
'input input': 'changeInput',
'keyup input': 'keyup'
},
render: function() {
this.renderTemplate(this.model);
this.$el.find(':input:first').focus();
this.checkValidity();
return this;
},
cancel: function() {
this.trigger('cancel');
},
apply: function() {
var data = this.getData();
if (data) {
this.trigger('apply', data);
}
},
changeInput: function() {
this.checkValidity();
},
keyup: function(e) {
if (e.which === Keys.DOM_VK_RETURN) {
this.apply();
}
},
checkValidity: function() {
var isValid = this.getData();
this.$el.find('.open__config-btn-ok').prop('disabled', !isValid);
},
getData: function() {
var data = { storage: this.model.id };
this.model.fields.every(function(field) {
var input = this.$el.find('#open__config-field-' + field.id)[0];
if (data && input.checkValidity()) {
data[field.id] = input.value;
} else {
data = null;
return false;
}
return true;
}, this);
return data;
},
setDisabled: function(disabled) {
disabled = !!disabled;
this.$el.find(':input:not(.open__config-btn-cancel)').prop('disabled', disabled);
this.$el.toggleClass('open__config--disabled', disabled);
if (disabled) {
this.$el.find('.open__config-error').text('');
}
},
setError: function(err) {
this.$el.find('.open__config-error').text(Locale.openConfigError.replace('{}', err));
}
});
module.exports = OpenConfigView;

View File

@ -2,12 +2,14 @@
var Backbone = require('backbone'),
kdbxweb = require('kdbxweb'),
OpenConfigView = require('./open-config-view'),
Keys = require('../const/keys'),
Alerts = require('../comp/alerts'),
SecureInput = require('../comp/secure-input'),
DropboxLink = require('../comp/dropbox-link'),
Logger = require('../util/logger'),
Locale = require('../util/locale');
Locale = require('../util/locale'),
Storage = require('../storage');
var logger = new Logger('open-view');
@ -22,6 +24,7 @@ var OpenView = Backbone.View.extend({
'click .open__icon-import-xml': 'importFromXml',
'click .open__icon-demo': 'createDemo',
'click .open__icon-more': 'toggleMore',
'click .open__icon-webdav': 'toggleWebDav',
'click .open__pass-input[readonly]': 'openFile',
'input .open__pass-input': 'inputInput',
'keydown .open__pass-input': 'inputKeydown',
@ -35,11 +38,13 @@ var OpenView = Backbone.View.extend({
'drop': 'drop'
},
views: null,
params: null,
passwordInput: null,
busy: false,
initialize: function () {
this.views = {};
this.params = {
id: null,
name: '',
@ -65,17 +70,10 @@ var OpenView = Backbone.View.extend({
getLastOpenFiles: function() {
return this.model.fileInfos.map(function(f) {
var icon;
switch (f.get('storage')) {
case 'dropbox':
icon = 'dropbox';
break;
case 'file':
icon = 'hdd-o';
break;
default:
icon = 'file-text';
break;
var icon = 'file-text';
var storage = f.get('storage');
if (Storage[storage] && Storage[storage].icon) {
icon = Storage[storage].icon;
}
return {
id: f.get('id'),
@ -223,12 +221,14 @@ var OpenView = Backbone.View.extend({
openFile: function() {
if (!this.busy) {
this.closeConfig();
this.openAny('fileData');
}
},
importFromXml: function() {
if (!this.busy) {
this.closeConfig();
this.openAny('fileXml', 'xml');
}
},
@ -355,9 +355,13 @@ var OpenView = Backbone.View.extend({
drop: function(e) {
e.preventDefault();
if (this.busy) {
return;
}
if (this.dragTimeout) {
clearTimeout(this.dragTimeout);
}
this.closeConfig();
this.$el.removeClass('open--drag');
var files = e.target.files || e.originalEvent.dataTransfer.files;
var dataFile = _.find(files, function(file) { return file.name.split('.').pop().toLowerCase() === 'kdbx'; });
@ -376,6 +380,7 @@ var OpenView = Backbone.View.extend({
if (this.busy) {
return;
}
this.closeConfig();
var that = this;
DropboxLink.authenticate(function(err) {
if (err) {
@ -467,6 +472,7 @@ var OpenView = Backbone.View.extend({
createDemo: function() {
if (!this.busy) {
this.closeConfig();
if (!this.model.createDemoFile()) {
this.trigger('close');
}
@ -515,7 +521,75 @@ var OpenView = Backbone.View.extend({
},
toggleMore: function() {
if (this.busy) {
return;
}
this.closeConfig();
this.$el.find('.open__icons--lower').toggleClass('hide');
},
toggleWebDav: function() {
if (this.busy) {
return;
}
this.$el.find('.open__icons--lower').addClass('hide');
this.$el.find('.open__pass-area').addClass('hide');
this.showConfig(Storage.webdav);
},
showConfig: function(storage) {
if (this.busy) {
return;
}
if (this.views.openConfig) {
this.views.openConfig.remove();
}
var config = {
id: storage.name,
name: Locale[storage.name] || storage.name,
icon: storage.icon,
fields: storage.openFields
};
this.views.openConfig = new OpenConfigView({ el: this.$el.find('.open__config-wrap'), model: config }).render();
this.views.openConfig.on('cancel', this.closeConfig.bind(this));
this.views.openConfig.on('apply', this.applyConfig.bind(this));
},
closeConfig: function() {
if (this.busy) {
this.storageWaitId = null;
this.busy = false;
}
if (this.views.openConfig) {
this.views.openConfig.remove();
delete this.views.openConfig;
}
this.$el.find('.open__pass-area').removeClass('hide');
this.$el.find('.open__config').addClass('hide');
this.inputEl.focus();
},
applyConfig: function(config) {
if (this.busy || !config) {
return;
}
this.busy = true;
this.views.openConfig.setDisabled(true);
var storage = Storage[config.storage];
this.storageWaitId = Math.random();
storage.stat(config, this.storageStatComplete.bind(this, this.storageWaitId));
},
storageStatComplete: function(waitId, err) {
if (this.storageWaitId !== waitId) {
return;
}
this.storageWaitId = null;
this.busy = false;
this.views.openConfig.setDisabled(false);
if (err) {
this.views.openConfig.setError(err);
}
}
});

View File

@ -73,6 +73,35 @@
}
}
&__config {
@include display(flex);
@include align-items(stretch);
@include flex-direction(column);
@include justify-content(flex-start);
position: relative;
width: 30em;
.open--drag & {
display: none;
}
&-buttons {
@include align-self(flex-end);
}
&-header {
>i { margin-right: .3em; }
}
&-field {
width: 100%;
}
&:not(.open__config--disabled) {
.open__config-btn-ok-text { display: inline; }
.open__config-btn-ok-spinner { display: none; }
}
&.open__config--disabled {
.open__config-btn-ok-text { display: none; }
.open__config-btn-ok-spinner { display: block; }
}
}
input[type=password].open__pass-input {
font-size: $large-pass-font-size;
margin-bottom: 0;

View File

@ -0,0 +1,23 @@
<div class="open__config">
<h2 class="open__config-header"><i class="fa fa-{{icon}}"></i> {{#res 'openConfigHeader'}}{{name}}{{/res}}</h2>
<div class="open__config-fields">
{{#each fields as |field ix|}}
<label for="open__config-field-{{id}}">{{res title}}:</label>
{{#if desc}}<div class="open__config-field-desc muted-color">{{res desc}}</div>{{/if}}
<input type="{{type}}" class="open__config-field input-base" id="open__config-field-{{id}}"
autocomplete="off"
{{#if placeholder}}placeholder="{{res placeholder}}"{{/if}}
{{#if required}}required{{/if}}
{{#if pattern}}pattern="{{pattern}}"{{/if}}
/>
{{/each}}
</div>
<div class="open__config-error error-color"></div>
<div class="open__config-buttons">
<button class="btn-silent open__config-btn-cancel">{{res 'alertCancel'}}</button>
<button class="open__config-btn-ok">
<span class="open__config-btn-ok-text">{{res 'alertOk'}}</span>
<i class="open__config-btn-ok-spinner fa fa-spinner fa-spin"></i>
</button>
</div>
</div>

View File

@ -67,6 +67,8 @@
{{/each}}
</div>
</div>
<div class="open__config-wrap">
</div>
<div class="open__dropzone">
<i class="fa fa-lock muted-color open__dropzone-icon"></i>
<h1 class="muted-color open__dropzone-header">{{res 'openDropHere'}}</h1>