view refactoring

This commit is contained in:
antelle 2019-09-15 18:33:45 +02:00
parent 9bbea458c6
commit 7589a112d0
14 changed files with 369 additions and 183 deletions

View File

@ -1,5 +1,5 @@
{ {
"presets": ["@babel/preset-env"], "presets": [],
"plugins": [ "plugins": [
["@babel/plugin-proposal-class-properties", { "loose": true }], ["@babel/plugin-proposal-class-properties", { "loose": true }],
"@babel/plugin-external-helpers" "@babel/plugin-external-helpers"

View File

@ -11,6 +11,9 @@ const Tip = function(el, config) {
this.hideTimeout = null; this.hideTimeout = null;
this.force = (config && config.force) || false; this.force = (config && config.force) || false;
this.hide = this.hide.bind(this); this.hide = this.hide.bind(this);
this.destroy = this.destroy.bind(this);
this.mouseenter = this.mouseenter.bind(this);
this.mouseleave = this.mouseleave.bind(this);
}; };
Tip.enabled = !Features.isMobile; Tip.enabled = !Features.isMobile;
@ -21,8 +24,8 @@ Tip.prototype.init = function() {
} }
this.el.removeAttr('title'); this.el.removeAttr('title');
this.el.attr('data-title', this.title); this.el.attr('data-title', this.title);
this.el.mouseenter(this.mouseenter.bind(this)).mouseleave(this.mouseleave.bind(this)); this.el.mouseenter(this.mouseenter).mouseleave(this.mouseleave);
this.el.click(this.mouseleave.bind(this)); this.el.click(this.mouseleave);
}; };
Tip.prototype.show = function() { Tip.prototype.show = function() {
@ -80,8 +83,15 @@ Tip.prototype.hide = function() {
if (this.tipEl) { if (this.tipEl) {
this.tipEl.remove(); this.tipEl.remove();
this.tipEl = null; this.tipEl = null;
Backbone.off('page-geometry', this.hide);
} }
Backbone.off('page-geometry', this.hide); };
Tip.prototype.destroy = function() {
this.hide();
this.el.off('mouseenter', this.mouseenter);
this.el.off('mouseleave', this.mouseleave);
this.el.off('click', this.mouseleave);
}; };
Tip.prototype.mouseenter = function() { Tip.prototype.mouseenter = function() {
@ -138,7 +148,7 @@ Tip.createTips = function(container) {
if (!Tip.enabled) { if (!Tip.enabled) {
return; return;
} }
container.find('[title]').each((ix, el) => { $('[title]', container).each((ix, el) => {
Tip.createTip(el); Tip.createTip(el);
}); });
}; };
@ -156,10 +166,10 @@ Tip.createTip = function(el, options) {
}; };
Tip.hideTips = function(container) { Tip.hideTips = function(container) {
if (!Tip.enabled) { if (!Tip.enabled || !container) {
return; return;
} }
container.find('[data-title]').each((ix, el) => { $('[data-title]', container).each((ix, el) => {
Tip.hideTip(el); Tip.hideTip(el);
}); });
}; };
@ -180,4 +190,13 @@ Tip.updateTip = function(el, props) {
} }
}; };
Tip.destroyTips = function(container) {
$('[data-title]', container).each((ix, el) => {
if (el._tip) {
el._tip.destroy();
el._tip = undefined;
}
});
};
export { Tip }; export { Tip };

View File

@ -13,6 +13,7 @@ const Scrollable = {
this.removeScroll(); this.removeScroll();
} }
this.scroll = baron(opts); this.scroll = baron(opts);
this.once('remove', () => this.removeScroll);
} }
this.scroller = this.$el.find('.scroller'); this.scroller = this.$el.find('.scroller');
this.scrollerBar = this.$el.find('.scroller__bar'); this.scrollerBar = this.$el.find('.scroller__bar');
@ -21,7 +22,9 @@ const Scrollable = {
removeScroll() { removeScroll() {
if (this.scroll) { if (this.scroll) {
this.scroll.dispose(); try {
this.scroll.dispose();
} catch {}
this.scroll = null; this.scroll = null;
} }
}, },

View File

@ -0,0 +1,177 @@
import morphdom from 'morphdom';
import EventEmitter from 'events';
import { Tip } from 'util/ui/tip';
import { KeyHandler } from 'comp/browser/key-handler';
import { Logger } from 'util/logger';
class View extends EventEmitter {
parent = undefined;
template = undefined;
events = {};
views = {};
hidden = false;
removed = false;
boundEvents = [];
debugLogger = localStorage.debugViews ? new Logger('view', this.constructor.name) : undefined;
constructor(model) {
super();
this.model = model;
}
render(templateData) {
if (this.removed) {
return;
}
this.debugLogger && this.debugLogger.debug('Render start');
if (this.el) {
Tip.destroyTips(this.el);
}
this.unbindEvents();
this.renderElement(templateData);
this.bindEvents();
Tip.createTips(this.el);
this.debugLogger && this.debugLogger.debug('Render finished');
return this;
}
renderElement(templateData) {
const html = this.template(templateData);
if (this.el) {
morphdom(this.el, html);
} else {
const el = document.createElement('div');
el.innerHTML = html;
this.el = el.firstChild;
if (this.parent) {
const parent = document.querySelector(this.parent);
if (!parent) {
throw new Error(
`Error rendering ${this.constructor.name}: parent not found: ${parent}`
);
}
parent.appendChild(this.el);
} else {
throw new Error(
`Error rendering ${this.constructor.name}: I don't know how to insert the view`
);
}
this.$el = $(this.el); // legacy
}
}
bindEvents() {
for (const [eventDef, method] of Object.entries(this.events)) {
const spaceIx = eventDef.indexOf(' ');
let event, targets;
if (spaceIx > 0) {
event = eventDef.substr(0, spaceIx);
const selector = eventDef.substr(spaceIx + 1);
targets = this.el.querySelectorAll(selector);
} else {
event = eventDef;
targets = [this.el];
}
for (const target of targets) {
const listener = e => {
this.debugLogger && this.debugLogger.debug('Listener', method);
this[method](e);
};
target.addEventListener(event, listener);
this.boundEvents.push({ target, event, listener });
}
}
}
unbindEvents() {
for (const boundEvent of this.boundEvents) {
const { target, event, listener } = boundEvent;
target.removeEventListener(event, listener);
}
}
remove() {
this.emit('remove');
this.removeInnerViews();
Tip.hideTips(this.el);
this.el.remove();
this.removed = true;
this.debugLogger && this.debugLogger.debug('Remove');
}
removeInnerViews() {
if (this.views) {
for (const view of Object.values(this.views)) {
if (view) {
if (view instanceof Array) {
view.forEach(v => v.remove());
} else {
view.remove();
}
}
}
this.views = {};
}
}
listenTo(model, event, callback) {
const boundCallback = callback.bind(this);
model.on(event, boundCallback);
this.once('remove', () => model.off(event, boundCallback));
}
stopListening(model, event, callback) {
model.off(event, callback);
}
hide() {
Tip.hideTips(this.el);
return this.toggle(false);
}
show() {
return this.toggle(true);
}
toggle(visible) {
if (visible === undefined) {
visible = this.hidden;
}
this.el.classList.toggle('show', !!visible);
this.el.classList.toggle('hide', !visible);
this.hidden = !visible;
this.emit(visible ? 'show' : 'hide');
if (!visible) {
Tip.hideTips(this.el);
}
return this;
}
isHidden() {
return this.hidden;
}
isVisible() {
return !this.hidden;
}
afterPaint(callback) {
requestAnimationFrame(() => requestAnimationFrame(callback));
}
onKey(key, handler, shortcut, modal, noPrevent) {
KeyHandler.onKey(key, handler, this, shortcut, modal, noPrevent);
this.once('remove', () => KeyHandler.offKey(key, handler, this));
}
}
export { View };

View File

@ -45,7 +45,7 @@ const AppView = Backbone.View.extend({
this.views = {}; this.views = {};
this.views.menu = new MenuView({ model: this.model.menu }); this.views.menu = new MenuView({ model: this.model.menu });
this.views.menuDrag = new DragView('x'); this.views.menuDrag = new DragView('x');
this.views.footer = new FooterView({ model: this.model }); this.views.footer = new FooterView(this.model);
this.views.listWrap = new ListWrapView({ model: this.model }); this.views.listWrap = new ListWrapView({ model: this.model });
this.views.list = new ListView({ model: this.model }); this.views.list = new ListView({ model: this.model });
this.views.listDrag = new DragView('x'); this.views.listDrag = new DragView('x');
@ -146,7 +146,7 @@ const AppView = Backbone.View.extend({
this.views.listWrap.setElement(this.$el.find('.app__list-wrap')).render(); this.views.listWrap.setElement(this.$el.find('.app__list-wrap')).render();
this.views.menu.setElement(this.$el.find('.app__menu')).render(); this.views.menu.setElement(this.$el.find('.app__menu')).render();
this.views.menuDrag.setElement(this.$el.find('.app__menu-drag')).render(); this.views.menuDrag.setElement(this.$el.find('.app__menu-drag')).render();
this.views.footer.setElement(this.$el.find('.app__footer')).render(); this.views.footer.render();
this.views.list.setElement(this.$el.find('.app__list')).render(); this.views.list.setElement(this.$el.find('.app__list')).render();
this.views.listDrag.setElement(this.$el.find('.app__list-drag')).render(); this.views.listDrag.setElement(this.$el.find('.app__list-drag')).render();
this.views.details.setElement(this.$el.find('.app__details')).render(); this.views.details.setElement(this.$el.find('.app__details')).render();
@ -240,7 +240,8 @@ const AppView = Backbone.View.extend({
this.views.listDrag.hide(); this.views.listDrag.hide();
this.views.details.hide(); this.views.details.hide();
this.hidePanelView(); this.hidePanelView();
this.views.panel = view.setElement(this.panelEl).render(); view.render();
this.views.panel = view;
this.panelEl.removeClass('hide'); this.panelEl.removeClass('hide');
}, },
@ -280,11 +281,11 @@ const AppView = Backbone.View.extend({
}, },
showEditGroup(group) { showEditGroup(group) {
this.showPanelView(new GrpView({ model: group })); this.showPanelView(new GrpView(group));
}, },
showEditTag() { showEditTag() {
this.showPanelView(new TagView({ model: this.model })); this.showPanelView(new TagView(this.model));
}, },
showKeyChange(file, viewConfig) { showKeyChange(file, viewConfig) {
@ -688,7 +689,7 @@ const AppView = Backbone.View.extend({
if (this.views.settings) { if (this.views.settings) {
this.showEntries(); this.showEntries();
} }
this.showPanelView(new GeneratorPresetsView({ model: this.model })); this.showPanelView(new GeneratorPresetsView(this.model));
} else { } else {
this.showEntries(); this.showEntries();
} }

View File

@ -93,10 +93,8 @@ const FieldViewText = FieldView.extend({
const fieldRect = this.input[0].getBoundingClientRect(); const fieldRect = this.input[0].getBoundingClientRect();
const shadowSpread = parseInt(this.input.css('--focus-shadow-spread')); const shadowSpread = parseInt(this.input.css('--focus-shadow-spread'));
this.gen = new GeneratorView({ this.gen = new GeneratorView({
model: { pos: { left: fieldRect.left, top: fieldRect.bottom + shadowSpread },
pos: { left: fieldRect.left, top: fieldRect.bottom + shadowSpread }, password: this.value
password: this.value
}
}).render(); }).render();
this.gen.once('remove', this.generatorClosed.bind(this)); this.gen.once('remove', this.generatorClosed.bind(this));
this.gen.once('result', this.generatorResult.bind(this)); this.gen.once('result', this.generatorResult.bind(this));

View File

@ -1,68 +1,61 @@
import Backbone from 'backbone'; import Backbone from 'backbone';
import { View } from 'view-engine/view';
import { KeyHandler } from 'comp/browser/key-handler'; import { KeyHandler } from 'comp/browser/key-handler';
import { Keys } from 'const/keys'; import { Keys } from 'const/keys';
import { UpdateModel } from 'models/update-model'; import { UpdateModel } from 'models/update-model';
import { GeneratorView } from 'views/generator-view'; import { GeneratorView } from 'views/generator-view';
import template from 'templates/footer.hbs';
const FooterView = Backbone.View.extend({ class FooterView extends View {
template: require('templates/footer.hbs'), parent = '.app__footer';
events: { template = template;
events = {
'click .footer__db-item': 'showFile', 'click .footer__db-item': 'showFile',
'click .footer__db-open': 'openFile', 'click .footer__db-open': 'openFile',
'click .footer__btn-help': 'toggleHelp', 'click .footer__btn-help': 'toggleHelp',
'click .footer__btn-settings': 'toggleSettings', 'click .footer__btn-settings': 'toggleSettings',
'click .footer__btn-generate': 'genPass', 'click .footer__btn-generate': 'genPass',
'click .footer__btn-lock': 'lockWorkspace' 'click .footer__btn-lock': 'lockWorkspace'
}, };
initialize() { constructor(model) {
this.views = {}; super(model);
KeyHandler.onKey( this.onKey(Keys.DOM_VK_L, this.lockWorkspace, KeyHandler.SHORTCUT_ACTION, false, true);
Keys.DOM_VK_L, this.onKey(Keys.DOM_VK_G, this.genPass, KeyHandler.SHORTCUT_ACTION);
this.lockWorkspace, this.onKey(Keys.DOM_VK_O, this.openFile, KeyHandler.SHORTCUT_ACTION);
this, this.onKey(Keys.DOM_VK_S, this.saveAll, KeyHandler.SHORTCUT_ACTION);
KeyHandler.SHORTCUT_ACTION, this.onKey(Keys.DOM_VK_COMMA, this.toggleSettings, KeyHandler.SHORTCUT_ACTION);
false,
true
);
KeyHandler.onKey(Keys.DOM_VK_G, this.genPass, this, KeyHandler.SHORTCUT_ACTION);
KeyHandler.onKey(Keys.DOM_VK_O, this.openFile, this, KeyHandler.SHORTCUT_ACTION);
KeyHandler.onKey(Keys.DOM_VK_S, this.saveAll, this, KeyHandler.SHORTCUT_ACTION);
KeyHandler.onKey(Keys.DOM_VK_COMMA, this.toggleSettings, this, KeyHandler.SHORTCUT_ACTION);
this.listenTo(this, 'hide', this.viewHidden); this.listenTo(this, 'hide', this.viewHidden);
this.listenTo(this.model.files, 'update reset change', this.render); this.listenTo(this.model.files, 'update reset change', this.render);
this.listenTo(Backbone, 'set-locale', this.render); this.listenTo(Backbone, 'set-locale', this.render);
this.listenTo(UpdateModel.instance, 'change:updateStatus', this.render); this.listenTo(UpdateModel.instance, 'change:updateStatus', this.render);
}, }
render() { render() {
this.renderTemplate( super.render({
{ files: this.model.files,
files: this.model.files, updateAvailable:
updateAvailable: ['ready', 'found'].indexOf(UpdateModel.instance.get('updateStatus')) >= 0
['ready', 'found'].indexOf(UpdateModel.instance.get('updateStatus')) >= 0 });
}, }
{ plain: true }
);
return this;
},
viewHidden() { viewHidden() {
if (this.views.gen) { if (this.views.gen) {
this.views.gen.remove(); this.views.gen.remove();
delete this.views.gen; delete this.views.gen;
} }
}, }
lockWorkspace(e) { lockWorkspace(e) {
if (this.model.files.hasOpenFiles()) { if (this.model.files.hasOpenFiles()) {
e.preventDefault(); e.preventDefault();
Backbone.trigger('lock-workspace'); Backbone.trigger('lock-workspace');
} }
}, }
genPass(e) { genPass(e) {
e.stopPropagation(); e.stopPropagation();
@ -75,14 +68,12 @@ const FooterView = Backbone.View.extend({
const bodyRect = document.body.getBoundingClientRect(); const bodyRect = document.body.getBoundingClientRect();
const right = bodyRect.right - rect.right; const right = bodyRect.right - rect.right;
const bottom = bodyRect.bottom - rect.top; const bottom = bodyRect.bottom - rect.top;
const generator = new GeneratorView({ const generator = new GeneratorView({ copy: true, pos: { right, bottom } }).render();
model: { copy: true, pos: { right, bottom } }
}).render();
generator.once('remove', () => { generator.once('remove', () => {
delete this.views.gen; delete this.views.gen;
}); });
this.views.gen = generator; this.views.gen = generator;
}, }
showFile(e) { showFile(e) {
const fileId = $(e.target) const fileId = $(e.target)
@ -91,23 +82,23 @@ const FooterView = Backbone.View.extend({
if (fileId) { if (fileId) {
Backbone.trigger('show-file', { fileId }); Backbone.trigger('show-file', { fileId });
} }
}, }
openFile() { openFile() {
Backbone.trigger('open-file'); Backbone.trigger('open-file');
}, }
saveAll() { saveAll() {
Backbone.trigger('save-all'); Backbone.trigger('save-all');
}, }
toggleHelp() { toggleHelp() {
Backbone.trigger('toggle-settings', 'help'); Backbone.trigger('toggle-settings', 'help');
}, }
toggleSettings() { toggleSettings() {
Backbone.trigger('toggle-settings', 'general'); Backbone.trigger('toggle-settings', 'general');
} }
}); }
export { FooterView }; export { FooterView };

View File

@ -1,13 +1,17 @@
import Backbone from 'backbone'; import Backbone from 'backbone';
import { View } from 'view-engine/view';
import { GeneratorPresets } from 'comp/app/generator-presets'; import { GeneratorPresets } from 'comp/app/generator-presets';
import { PasswordGenerator } from 'util/generators/password-generator'; import { PasswordGenerator } from 'util/generators/password-generator';
import { Locale } from 'util/locale'; import { Locale } from 'util/locale';
import { Scrollable } from 'view-engine/scrollable'; import { Scrollable } from 'view-engine/scrollable';
import template from 'templates/generator-presets.hbs';
const GeneratorPresetsView = Backbone.View.extend({ class GeneratorPresetsView extends View {
template: require('templates/generator-presets.hbs'), parent = '.app__panel';
events: { template = template;
events = {
'click .back-button': 'returnToApp', 'click .back-button': 'returnToApp',
'change .gen-ps__list': 'changePreset', 'change .gen-ps__list': 'changePreset',
'click .gen-ps__btn-create': 'createPreset', 'click .gen-ps__btn-create': 'createPreset',
@ -18,44 +22,36 @@ const GeneratorPresetsView = Backbone.View.extend({
'input #gen-ps__field-length': 'changeLength', 'input #gen-ps__field-length': 'changeLength',
'change .gen-ps__check-range': 'changeRange', 'change .gen-ps__check-range': 'changeRange',
'input #gen-ps__field-include': 'changeInclude' 'input #gen-ps__field-include': 'changeInclude'
}, };
selected: null, selected = null;
reservedTitles: [Locale.genPresetDerived], reservedTitles = [Locale.genPresetDerived];
initialize() {
this.appModel = this.model;
},
render() { render() {
this.presets = GeneratorPresets.all; this.presets = GeneratorPresets.all;
if (!this.selected || !this.presets.some(p => p.name === this.selected)) { if (!this.selected || !this.presets.some(p => p.name === this.selected)) {
this.selected = (this.presets.filter(p => p.default)[0] || this.presets[0]).name; this.selected = (this.presets.filter(p => p.default)[0] || this.presets[0]).name;
} }
this.renderTemplate( super.render({
{ presets: this.presets,
presets: this.presets, selected: this.getPreset(this.selected),
selected: this.getPreset(this.selected), ranges: this.getSelectedRanges()
ranges: this.getSelectedRanges() });
},
true
);
this.createScroll({ this.createScroll({
root: this.$el.find('.gen-ps')[0], root: this.$el.find('.gen-ps')[0],
scroller: this.$el.find('.scroller')[0], scroller: this.$el.find('.scroller')[0],
bar: this.$el.find('.scroller__bar')[0] bar: this.$el.find('.scroller__bar')[0]
}); });
this.renderExample(); this.renderExample();
return this; }
},
renderExample() { renderExample() {
const selectedPreset = this.getPreset(this.selected); const selectedPreset = this.getPreset(this.selected);
const example = PasswordGenerator.generate(selectedPreset); const example = PasswordGenerator.generate(selectedPreset);
this.$el.find('.gen-ps__example').text(example); this.$el.find('.gen-ps__example').text(example);
this.pageResized(); this.pageResized();
}, }
getSelectedRanges() { getSelectedRanges() {
const sel = this.getPreset(this.selected); const sel = this.getPreset(this.selected);
@ -73,20 +69,20 @@ const GeneratorPresetsView = Backbone.View.extend({
}; };
} }
); );
}, }
getPreset(name) { getPreset(name) {
return this.presets.filter(p => p.name === name)[0]; return this.presets.filter(p => p.name === name)[0];
}, }
returnToApp() { returnToApp() {
Backbone.trigger('edit-generator-presets'); Backbone.trigger('edit-generator-presets');
}, }
changePreset(e) { changePreset(e) {
this.selected = e.target.value; this.selected = e.target.value;
this.render(); this.render();
}, }
createPreset() { createPreset() {
let name; let name;
@ -116,12 +112,12 @@ const GeneratorPresetsView = Backbone.View.extend({
GeneratorPresets.add(preset); GeneratorPresets.add(preset);
this.selected = name; this.selected = name;
this.render(); this.render();
}, }
deletePreset() { deletePreset() {
GeneratorPresets.remove(this.selected); GeneratorPresets.remove(this.selected);
this.render(); this.render();
}, }
changeTitle(e) { changeTitle(e) {
const title = $.trim(e.target.value); const title = $.trim(e.target.value);
@ -139,17 +135,17 @@ const GeneratorPresetsView = Backbone.View.extend({
GeneratorPresets.setPreset(this.selected, { title }); GeneratorPresets.setPreset(this.selected, { title });
this.$el.find('.gen-ps__list option[selected]').text(title); this.$el.find('.gen-ps__list option[selected]').text(title);
} }
}, }
changeEnabled(e) { changeEnabled(e) {
const enabled = e.target.checked; const enabled = e.target.checked;
GeneratorPresets.setDisabled(this.selected, !enabled); GeneratorPresets.setDisabled(this.selected, !enabled);
}, }
changeDefault(e) { changeDefault(e) {
const isDefault = e.target.checked; const isDefault = e.target.checked;
GeneratorPresets.setDefault(isDefault ? this.selected : null); GeneratorPresets.setDefault(isDefault ? this.selected : null);
}, }
changeLength(e) { changeLength(e) {
const length = +e.target.value; const length = +e.target.value;
@ -161,7 +157,7 @@ const GeneratorPresetsView = Backbone.View.extend({
} }
this.presets = GeneratorPresets.all; this.presets = GeneratorPresets.all;
this.renderExample(); this.renderExample();
}, }
changeRange(e) { changeRange(e) {
const enabled = e.target.checked; const enabled = e.target.checked;
@ -169,7 +165,7 @@ const GeneratorPresetsView = Backbone.View.extend({
GeneratorPresets.setPreset(this.selected, { [range]: enabled }); GeneratorPresets.setPreset(this.selected, { [range]: enabled });
this.presets = GeneratorPresets.all; this.presets = GeneratorPresets.all;
this.renderExample(); this.renderExample();
}, }
changeInclude(e) { changeInclude(e) {
const include = e.target.value; const include = e.target.value;
@ -179,8 +175,8 @@ const GeneratorPresetsView = Backbone.View.extend({
this.presets = GeneratorPresets.all; this.presets = GeneratorPresets.all;
this.renderExample(); this.renderExample();
} }
}); }
_.extend(GeneratorPresetsView.prototype, Scrollable); Object.assign(GeneratorPresetsView.prototype, Scrollable);
export { GeneratorPresetsView }; export { GeneratorPresetsView };

View File

@ -1,20 +1,21 @@
import Backbone from 'backbone'; import Backbone from 'backbone';
import { View } from 'view-engine/view';
import { GeneratorPresets } from 'comp/app/generator-presets'; import { GeneratorPresets } from 'comp/app/generator-presets';
import { CopyPaste } from 'comp/browser/copy-paste'; import { CopyPaste } from 'comp/browser/copy-paste';
import { AppSettingsModel } from 'models/app-settings-model'; import { AppSettingsModel } from 'models/app-settings-model';
import { PasswordGenerator } from 'util/generators/password-generator'; import { PasswordGenerator } from 'util/generators/password-generator';
import { Locale } from 'util/locale'; import { Locale } from 'util/locale';
import { Tip } from 'util/ui/tip'; import { Tip } from 'util/ui/tip';
import template from 'templates/generator.hbs';
const GeneratorView = Backbone.View.extend({ class GeneratorView extends View {
el: 'body', parent = 'body';
template: require('templates/generator.hbs'), template = template;
events: { events = {
'click': 'click', 'click': 'click',
'mousedown .gen__length-range': 'generate', 'mousedown .gen__length-range': 'generate',
'mousemove .gen__length-range': 'lengthMouseMove',
'input .gen__length-range': 'lengthChange', 'input .gen__length-range': 'lengthChange',
'change .gen__length-range': 'lengthChange', 'change .gen__length-range': 'lengthChange',
'change .gen__check input[type=checkbox]': 'checkChange', 'change .gen__check input[type=checkbox]': 'checkChange',
@ -22,9 +23,9 @@ const GeneratorView = Backbone.View.extend({
'click .gen__btn-ok': 'btnOkClick', 'click .gen__btn-ok': 'btnOkClick',
'change .gen__sel-tpl': 'presetChange', 'change .gen__sel-tpl': 'presetChange',
'click .gen__btn-refresh': 'newPass' 'click .gen__btn-refresh': 'newPass'
}, };
valuesMap: [ valuesMap = [
3, 3,
4, 4,
5, 5,
@ -51,19 +52,20 @@ const GeneratorView = Backbone.View.extend({
32, 32,
48, 48,
64 64
], ];
presets: null, presets = null;
preset: null, preset = null;
initialize() { constructor(model) {
super(model);
this.createPresets(); this.createPresets();
const preset = this.preset; const preset = this.preset;
this.gen = _.clone(_.find(this.presets, pr => pr.name === preset)); this.gen = _.clone(_.find(this.presets, pr => pr.name === preset));
this.hide = AppSettingsModel.instance.get('generatorHidePassword'); this.hide = AppSettingsModel.instance.get('generatorHidePassword');
$('body').one('click', this.remove.bind(this)); $('body').one('click', this.remove.bind(this));
this.listenTo(Backbone, 'lock-workspace', this.remove.bind(this)); this.listenTo(Backbone, 'lock-workspace', this.remove.bind(this));
}, }
render() { render() {
const canCopy = document.queryCommandSupported('copy'); const canCopy = document.queryCommandSupported('copy');
@ -72,7 +74,7 @@ const GeneratorView = Backbone.View.extend({
? Locale.alertCopy ? Locale.alertCopy
: Locale.alertClose : Locale.alertClose
: Locale.alertOk; : Locale.alertOk;
this.renderTemplate({ super.render({
btnTitle, btnTitle,
showToggleButton: this.model.copy, showToggleButton: this.model.copy,
opt: this.gen, opt: this.gen,
@ -84,7 +86,7 @@ const GeneratorView = Backbone.View.extend({
this.$el.css(this.model.pos); this.$el.css(this.model.pos);
this.generate(); this.generate();
return this; return this;
}, }
createPresets() { createPresets() {
this.presets = GeneratorPresets.enabled; this.presets = GeneratorPresets.enabled;
@ -100,10 +102,10 @@ const GeneratorView = Backbone.View.extend({
const defaultPreset = this.presets.filter(p => p.default)[0] || this.presets[0]; const defaultPreset = this.presets.filter(p => p.default)[0] || this.presets[0];
this.preset = defaultPreset.name; this.preset = defaultPreset.name;
} }
this.presets.forEach(function(pr) { this.presets.forEach(pr => {
pr.pseudoLength = this.lengthToPseudoValue(pr.length); pr.pseudoLength = this.lengthToPseudoValue(pr.length);
}, this); });
}, }
lengthToPseudoValue(length) { lengthToPseudoValue(length) {
for (let ix = 0; ix < this.valuesMap.length; ix++) { for (let ix = 0; ix < this.valuesMap.length; ix++) {
@ -112,7 +114,7 @@ const GeneratorView = Backbone.View.extend({
} }
} }
return this.valuesMap.length - 1; return this.valuesMap.length - 1;
}, }
showPassword() { showPassword() {
if (this.hide && !this.model.copy) { if (this.hide && !this.model.copy) {
@ -120,11 +122,11 @@ const GeneratorView = Backbone.View.extend({
} else { } else {
this.resultEl.text(this.password); this.resultEl.text(this.password);
} }
}, }
click(e) { click(e) {
e.stopPropagation(); e.stopPropagation();
}, }
lengthChange(e) { lengthChange(e) {
const val = this.valuesMap[e.target.value]; const val = this.valuesMap[e.target.value];
@ -134,7 +136,7 @@ const GeneratorView = Backbone.View.extend({
this.optionChanged('length'); this.optionChanged('length');
this.generate(); this.generate();
} }
}, }
checkChange(e) { checkChange(e) {
const id = $(e.target).data('id'); const id = $(e.target).data('id');
@ -143,7 +145,7 @@ const GeneratorView = Backbone.View.extend({
} }
this.optionChanged(id); this.optionChanged(id);
this.generate(); this.generate();
}, }
optionChanged(option) { optionChanged(option) {
if ( if (
@ -154,32 +156,31 @@ const GeneratorView = Backbone.View.extend({
} }
this.preset = this.gen.name = 'Custom'; this.preset = this.gen.name = 'Custom';
this.$el.find('.gen__sel-tpl').val(''); this.$el.find('.gen__sel-tpl').val('');
}, }
generate() { generate() {
this.password = PasswordGenerator.generate(this.gen); this.password = PasswordGenerator.generate(this.gen);
this.showPassword(); this.showPassword();
const isLong = this.password.length > 32; const isLong = this.password.length > 32;
this.resultEl.toggleClass('gen__result--long-pass', isLong); this.resultEl.toggleClass('gen__result--long-pass', isLong);
}, }
hideChange(e) { hideChange(e) {
this.hide = e.target.checked; this.hide = e.target.checked;
// AppSettingsModel.instance.unset('generatorHidePassword', { silent: true });
AppSettingsModel.instance.set('generatorHidePassword', this.hide); AppSettingsModel.instance.set('generatorHidePassword', this.hide);
const label = this.$el.find('.gen__check-hide-label'); const label = this.$el.find('.gen__check-hide-label');
Tip.updateTip(label[0], { title: this.hide ? Locale.genShowPass : Locale.genHidePass }); Tip.updateTip(label[0], { title: this.hide ? Locale.genShowPass : Locale.genHidePass });
this.showPassword(); this.showPassword();
}, }
btnOkClick() { btnOkClick() {
if (!CopyPaste.simpleCopy) { if (!CopyPaste.simpleCopy) {
CopyPaste.createHiddenInput(this.password); CopyPaste.createHiddenInput(this.password);
} }
CopyPaste.copy(this.password); CopyPaste.copy(this.password);
this.trigger('result', this.password); this.emit('result', this.password);
this.remove(); this.remove();
}, }
presetChange(e) { presetChange(e) {
const name = e.target.value; const name = e.target.value;
@ -192,11 +193,11 @@ const GeneratorView = Backbone.View.extend({
const preset = _.find(this.presets, t => t.name === name); const preset = _.find(this.presets, t => t.name === name);
this.gen = _.clone(preset); this.gen = _.clone(preset);
this.render(); this.render();
}, }
newPass() { newPass() {
this.generate(); this.generate();
} }
}); }
export { GeneratorView }; export { GeneratorView };

View File

@ -1,13 +1,17 @@
import { View } from 'view-engine/view';
import { AutoType } from 'auto-type'; import { AutoType } from 'auto-type';
import Backbone from 'backbone'; import Backbone from 'backbone';
import { Scrollable } from 'view-engine/scrollable'; import { Scrollable } from 'view-engine/scrollable';
import { AutoTypeHintView } from 'views/auto-type-hint-view'; import { AutoTypeHintView } from 'views/auto-type-hint-view';
import { IconSelectView } from 'views/icon-select-view'; import { IconSelectView } from 'views/icon-select-view';
import template from 'templates/grp.hbs';
const GrpView = Backbone.View.extend({ class GrpView extends View {
template: require('templates/grp.hbs'), parent = '.app__panel';
events: { template = template;
events = {
'click .grp__icon': 'showIconsSelect', 'click .grp__icon': 'showIconsSelect',
'click .grp__buttons-trash': 'moveToTrash', 'click .grp__buttons-trash': 'moveToTrash',
'click .back-button': 'returnToApp', 'click .back-button': 'returnToApp',
@ -16,28 +20,21 @@ const GrpView = Backbone.View.extend({
'input #grp__field-auto-type-seq': 'changeAutoTypeSeq', 'input #grp__field-auto-type-seq': 'changeAutoTypeSeq',
'change #grp__check-search': 'setEnableSearching', 'change #grp__check-search': 'setEnableSearching',
'change #grp__check-auto-type': 'setEnableAutoType' 'change #grp__check-auto-type': 'setEnableAutoType'
}, };
initialize() {
this.views = {};
},
render() { render() {
this.removeSubView(); this.removeSubView();
this.renderTemplate( super.render({
{ title: this.model.get('title'),
title: this.model.get('title'), icon: this.model.get('icon') || 'folder',
icon: this.model.get('icon') || 'folder', customIcon: this.model.get('customIcon'),
customIcon: this.model.get('customIcon'), enableSearching: this.model.getEffectiveEnableSearching(),
enableSearching: this.model.getEffectiveEnableSearching(), readonly: this.model.get('top'),
readonly: this.model.get('top'), canAutoType: AutoType.enabled,
canAutoType: AutoType.enabled, autoTypeSeq: this.model.get('autoTypeSeq'),
autoTypeSeq: this.model.get('autoTypeSeq'), autoTypeEnabled: this.model.getEffectiveEnableAutoType(),
autoTypeEnabled: this.model.getEffectiveEnableAutoType(), defaultAutoTypeSeq: this.model.getParentEffectiveAutoTypeSeq()
defaultAutoTypeSeq: this.model.getParentEffectiveAutoTypeSeq() });
},
true
);
if (!this.model.get('title')) { if (!this.model.get('title')) {
this.$el.find('#grp__field-title').focus(); this.$el.find('#grp__field-title').focus();
} }
@ -47,15 +44,14 @@ const GrpView = Backbone.View.extend({
bar: this.$el.find('.scroller__bar')[0] bar: this.$el.find('.scroller__bar')[0]
}); });
this.pageResized(); this.pageResized();
return this; }
},
removeSubView() { removeSubView() {
if (this.views.sub) { if (this.views.sub) {
this.views.sub.remove(); this.views.sub.remove();
delete this.views.sub; delete this.views.sub;
} }
}, }
changeTitle(e) { changeTitle(e) {
const title = $.trim(e.target.value); const title = $.trim(e.target.value);
@ -69,7 +65,7 @@ const GrpView = Backbone.View.extend({
Backbone.trigger('edit-group'); Backbone.trigger('edit-group');
} }
} }
}, }
changeAutoTypeSeq(e) { changeAutoTypeSeq(e) {
const el = e.target; const el = e.target;
@ -80,7 +76,7 @@ const GrpView = Backbone.View.extend({
this.model.setAutoTypeSeq(seq); this.model.setAutoTypeSeq(seq);
} }
}); });
}, }
focusAutoTypeSeq(e) { focusAutoTypeSeq(e) {
if (!this.views.hint) { if (!this.views.hint) {
@ -89,7 +85,7 @@ const GrpView = Backbone.View.extend({
delete this.views.hint; delete this.views.hint;
}); });
} }
}, }
showIconsSelect() { showIconsSelect() {
if (this.views.sub) { if (this.views.sub) {
@ -107,7 +103,7 @@ const GrpView = Backbone.View.extend({
this.views.sub = subView; this.views.sub = subView;
} }
this.pageResized(); this.pageResized();
}, }
iconSelected(sel) { iconSelected(sel) {
if (sel.custom) { if (sel.custom) {
@ -118,28 +114,28 @@ const GrpView = Backbone.View.extend({
this.model.setIcon(+sel.id); this.model.setIcon(+sel.id);
} }
this.render(); this.render();
}, }
moveToTrash() { moveToTrash() {
this.model.moveToTrash(); this.model.moveToTrash();
Backbone.trigger('select-all'); Backbone.trigger('select-all');
}, }
setEnableSearching(e) { setEnableSearching(e) {
const enabled = e.target.checked; const enabled = e.target.checked;
this.model.setEnableSearching(enabled); this.model.setEnableSearching(enabled);
}, }
setEnableAutoType(e) { setEnableAutoType(e) {
const enabled = e.target.checked; const enabled = e.target.checked;
this.model.setEnableAutoType(enabled); this.model.setEnableAutoType(enabled);
}, }
returnToApp() { returnToApp() {
Backbone.trigger('edit-group'); Backbone.trigger('edit-group');
} }
}); }
_.extend(GrpView.prototype, Scrollable); Object.assign(GrpView.prototype, Scrollable);
export { GrpView }; export { GrpView };

View File

@ -1,40 +1,36 @@
import Backbone from 'backbone'; import Backbone from 'backbone';
import { View } from 'view-engine/view';
import { Alerts } from 'comp/ui/alerts'; import { Alerts } from 'comp/ui/alerts';
import { Locale } from 'util/locale'; import { Locale } from 'util/locale';
import template from 'templates/tag.hbs';
const TagView = Backbone.View.extend({ class TagView extends View {
template: require('templates/tag.hbs'), parent = '.app__panel';
events: { template = template;
events = {
'click .tag__buttons-trash': 'moveToTrash', 'click .tag__buttons-trash': 'moveToTrash',
'click .back-button': 'returnToApp', 'click .back-button': 'returnToApp',
'click .tag__btn-rename': 'renameTag' 'click .tag__btn-rename': 'renameTag'
}, };
initialize() {
this.appModel = this.model;
},
render() { render() {
if (this.model) { if (this.tag) {
this.renderTemplate( super.render({
{ title: this.tag.get('title')
title: this.model.get('title') });
},
true
);
} }
return this; }
},
showTag(tag) { showTag(tag) {
this.model = tag; this.tag = tag;
this.render(); this.render();
}, }
renameTag() { renameTag() {
const title = $.trim(this.$el.find('#tag__field-title').val()); const title = $.trim(this.$el.find('#tag__field-title').val());
if (!title || title === this.model.get('title')) { if (!title || title === this.tag.get('title')) {
return; return;
} }
if (/[;,:]/.test(title)) { if (/[;,:]/.test(title)) {
@ -44,13 +40,13 @@ const TagView = Backbone.View.extend({
}); });
return; return;
} }
if (this.appModel.tags.some(t => t.toLowerCase() === title.toLowerCase())) { if (this.model.tags.some(t => t.toLowerCase() === title.toLowerCase())) {
Alerts.error({ header: Locale.tagExists, body: Locale.tagExistsBody }); Alerts.error({ header: Locale.tagExists, body: Locale.tagExistsBody });
return; return;
} }
this.appModel.renameTag(this.model.get('title'), title); this.model.renameTag(this.tag.get('title'), title);
Backbone.trigger('select-all'); Backbone.trigger('select-all');
}, }
moveToTrash() { moveToTrash() {
this.title = null; this.title = null;
@ -58,15 +54,15 @@ const TagView = Backbone.View.extend({
header: Locale.tagTrashQuestion, header: Locale.tagTrashQuestion,
body: Locale.tagTrashQuestionBody, body: Locale.tagTrashQuestionBody,
success: () => { success: () => {
this.appModel.renameTag(this.model.get('title'), undefined); this.model.renameTag(this.tag.get('title'), undefined);
Backbone.trigger('select-all'); Backbone.trigger('select-all');
} }
}); });
}, }
returnToApp() { returnToApp() {
Backbone.trigger('edit-tag'); Backbone.trigger('edit-tag');
} }
}); }
export { TagView }; export { TagView };

5
package-lock.json generated
View File

@ -8189,6 +8189,11 @@
} }
} }
}, },
"morphdom": {
"version": "2.5.6",
"resolved": "https://registry.npmjs.org/morphdom/-/morphdom-2.5.6.tgz",
"integrity": "sha512-uw+fgVRCV7DK9EWJ87NeiFXTDdLklajJQNLHCAJStqTY/uwFpK5ormeU2PYSX5DDk+cI9dtFli/MHKd2wP/KGg=="
},
"move-concurrently": { "move-concurrently": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz",

View File

@ -60,6 +60,7 @@
"load-grunt-tasks": "5.1.0", "load-grunt-tasks": "5.1.0",
"marked": "^0.7.0", "marked": "^0.7.0",
"mini-css-extract-plugin": "^0.8.0", "mini-css-extract-plugin": "^0.8.0",
"morphdom": "^2.5.6",
"node-sass": "^4.12.0", "node-sass": "^4.12.0",
"node-stream-zip": "1.8.2", "node-stream-zip": "1.8.2",
"normalize.css": "8.0.1", "normalize.css": "8.0.1",

View File

@ -25,6 +25,7 @@ function config(grunt, mode = 'production') {
'jquery', 'jquery',
'underscore', 'underscore',
'backbone', 'backbone',
'morphdom',
'kdbxweb', 'kdbxweb',
'baron', 'baron',
'pikaday', 'pikaday',
@ -61,6 +62,7 @@ function config(grunt, mode = 'production') {
underscore: `underscore/underscore${devMode ? '-min' : ''}.js`, underscore: `underscore/underscore${devMode ? '-min' : ''}.js`,
_: `underscore/underscore${devMode ? '-min' : ''}.js`, _: `underscore/underscore${devMode ? '-min' : ''}.js`,
jquery: `jquery/dist/jquery${devMode ? '.min' : ''}.js`, jquery: `jquery/dist/jquery${devMode ? '.min' : ''}.js`,
morphdom: `morphdom/dist/morphdom-umd${devMode ? '.min' : ''}.js`,
kdbxweb: 'kdbxweb/dist/kdbxweb.js', kdbxweb: 'kdbxweb/dist/kdbxweb.js',
baron: `baron/baron${devMode ? '.min' : ''}.js`, baron: `baron/baron${devMode ? '.min' : ''}.js`,
qrcode: `jsqrcode/dist/qrcode${devMode ? '.min' : ''}.js`, qrcode: `jsqrcode/dist/qrcode${devMode ? '.min' : ''}.js`,