diff options
author | Marius David Wieschollek <passwords.public@mdns.eu> | 2021-05-09 14:32:08 +0300 |
---|---|---|
committer | Marius David Wieschollek <passwords.public@mdns.eu> | 2021-05-09 14:32:08 +0300 |
commit | 39dd656aa1907100696ec9b34b93014b33766be2 (patch) | |
tree | 6a351927bf609653a8e2fdf10833795ed2736516 | |
parent | 49c54548251215eddd3c8ad2903f68ce7d58d1ab (diff) |
Change popup state handling
Signed-off-by: Marius David Wieschollek <passwords.public@mdns.eu>
-rw-r--r-- | src/js/App/Popup.js | 7 | ||||
-rw-r--r-- | src/js/Controller/Popup/Status/Get.js | 63 | ||||
-rw-r--r-- | src/js/Controller/Popup/Status/Set.js | 17 | ||||
-rw-r--r-- | src/js/Services/PopupStateService.js | 156 | ||||
-rw-r--r-- | src/platform/generic/_locales/de/messages.json | 12 | ||||
-rw-r--r-- | src/platform/generic/_locales/en/messages.json | 12 | ||||
-rw-r--r-- | src/vue/App/Popup.vue | 191 | ||||
-rw-r--r-- | src/vue/Components/Foldout.vue | 12 | ||||
-rw-r--r-- | src/vue/Components/Password/CustomProperty.vue | 2 | ||||
-rw-r--r-- | src/vue/Components/Password/Property.vue | 6 | ||||
-rw-r--r-- | src/vue/Components/Password/View.vue | 14 | ||||
-rw-r--r-- | src/vue/Components/Popup/Browse.vue | 53 | ||||
-rw-r--r-- | src/vue/Components/Popup/Related.vue | 2 | ||||
-rw-r--r-- | src/vue/Components/Popup/Search.vue | 20 | ||||
-rw-r--r-- | src/vue/Components/Popup/Tools.vue | 12 |
15 files changed, 343 insertions, 236 deletions
diff --git a/src/js/App/Popup.js b/src/js/App/Popup.js index 2b8e38e..0940e05 100644 --- a/src/js/App/Popup.js +++ b/src/js/App/Popup.js @@ -11,6 +11,7 @@ import SettingsService from '@js/Services/SettingsService'; import ClientSettingsProvider from '@js/Settings/ClientSettingsProvider'; import LocalisationService from "@js/Services/LocalisationService"; import PasswordSettingsManager from '@js/Manager/PasswordSettingsManager'; +import PopupStateService from '@js/Services/PopupStateService'; class Popup { @@ -50,6 +51,7 @@ class Popup { await ThemeService.apply(); await LocalisationService.init(); + await PopupStateService.init(); await this._initVue(); await ToastService.init(); } catch(e) { @@ -60,9 +62,8 @@ class Popup { async _initVue() { document.body.lang = LocalisationService.getLocale(); - let reply = await MessageService.send('popup.status.get'), - status = reply.getPayload(); - document.body.classList.add(status.device); + let status = PopupStateService.getPayload(); + document.body.classList.add(PopupStateService.getStatus('device')); this._app = new Vue({propsData: status, ...App}); } diff --git a/src/js/Controller/Popup/Status/Get.js b/src/js/Controller/Popup/Status/Get.js index 6c99511..9eac680 100644 --- a/src/js/Controller/Popup/Status/Get.js +++ b/src/js/Controller/Popup/Status/Get.js @@ -9,13 +9,13 @@ export default class Get extends AbstractController { async execute(message, reply) { let status = { - tab : this._getCurrentTab(), - search : this._getSearchStatus(), - browse : this._getBrowseStatus(), - collected : this._getCollectedStatus(), - device : await this._getDevice(), - authorized: this._isAuthorized(), - firstRun: await this._isFirstRun() + tab : this._getCurrentTab(), + data : this._getTabData(), + status: { + device : await this._getDevice(), + authorized: this._isAuthorized(), + firstRun : await this._isFirstRun() + } }; reply @@ -23,51 +23,12 @@ export default class Get extends AbstractController { .setPayload(status); } - /** - * - * @returns {{query: string}} - * @private - */ - _getSearchStatus() { - if(RegistryService.has('popup.search.status')) { - return RegistryService.get('popup.search.status'); + _getTabData() { + if(RegistryService.has('popup.data')) { + return RegistryService.get('popup.data'); } - return { - query: '' - }; - } - - /** - * - * @returns {{server: (null|String), info: (null|Boolean), folder: (null|String)}} - * @private - */ - _getBrowseStatus() { - if(RegistryService.has('popup.browse.status')) { - return RegistryService.get('popup.browse.status'); - } - - return { - server: null, - info : false, - folder: null - }; - } - - /** - * - * @returns {{current: (null|String)}} - * @private - */ - _getCollectedStatus() { - if(RegistryService.has('popup.collected.status')) { - return RegistryService.get('popup.collected.status'); - } - - return { - current: null - }; + return {}; } /** @@ -115,6 +76,6 @@ export default class Get extends AbstractController { async _isFirstRun() { let servers = await ServerRepository.findAll(); - return servers.length === 0 + return servers.length === 0; } }
\ No newline at end of file diff --git a/src/js/Controller/Popup/Status/Set.js b/src/js/Controller/Popup/Status/Set.js index 38d533b..8f85149 100644 --- a/src/js/Controller/Popup/Status/Set.js +++ b/src/js/Controller/Popup/Status/Set.js @@ -6,11 +6,20 @@ export default class Set extends AbstractController { async execute(message, reply) { let payload = message.getPayload(); - if(!payload.hasOwnProperty('tab')) return; - RegistryService.set('popup.tab', payload.tab); + if(payload.hasOwnProperty('service')) { + if(!payload.hasOwnProperty('tab')) return; + RegistryService.set('popup.tab', payload.tab); - if(payload.hasOwnProperty('status')) { - RegistryService.set(`popup.${payload.tab}.status`, payload.status); + if(payload.hasOwnProperty('data')) { + RegistryService.set('popup.data', payload.data); + } + } else { + if(!payload.hasOwnProperty('tab')) return; + RegistryService.set('popup.tab', payload.tab); + + if(payload.hasOwnProperty('status')) { + RegistryService.set(`popup.${payload.tab}.status`, payload.status); + } } } diff --git a/src/js/Services/PopupStateService.js b/src/js/Services/PopupStateService.js new file mode 100644 index 0000000..2254deb --- /dev/null +++ b/src/js/Services/PopupStateService.js @@ -0,0 +1,156 @@ +import MessageService from "@js/Services/MessageService"; +import ErrorManager from "@js/Manager/ErrorManager"; + +export default new class PopupStateService { + + constructor() { + this._tab = 'related'; + this._data = {}; + this._status = {}; + + this._defaults = { + 'search.query': '' + }; + this._payload = null; + } + + async init() { + let reply = await MessageService.send('popup.status.get'), + status = reply.getPayload(); + + this._tab = status.tab; + this._status = status.status; + this._data = status.data; + this._payload = status; + } + + /** + * @deprecated + * @returns {*} + */ + getPayload() { + return this._payload; + } + + /** + * @returns {String} + */ + getTab() { + return this._tab; + } + + /** + * @param {String} tab + */ + setTab(tab) { + this._tab = tab; + this._saveState(); + } + + /** + * @param {String} item + * @returns {*} + */ + getStatus(item) { + return this._status.hasOwnProperty(item) ? this._status[item]:null; + } + + /** + * @param {(String|String[])} key + * @param {(String|null)} [tab=null] + * @returns {*} + */ + get(key, tab = null) { + if(typeof key !== 'string') { + let data = {}; + for(let value of key) { + data[value] = this._getValue(value, tab); + } + + return data; + } + return this._getValue(key, tab); + } + + /** + * @param {(String|Object)} key The key of the value to set or an object with the keys and values to set + * @param {*} [value] The value or the tab of the key is an object + * @param {(String|null)} [tab=null] + * @returns void + */ + set(key, value, tab = null) { + if(typeof key !== 'string') { + this._setValues(key, value === undefined ? null:value); + } else { + this._setValue(key, value, tab); + } + + this._saveState(); + } + + /** + * + * @param key + * @param tab + * @returns {null|*} + * @private + */ + _getValue(key, tab = null) { + if(tab === null) tab = this._tab; + if(!this._data.hasOwnProperty(tab) || !this._data[tab].hasOwnProperty(key)) { + let defaultKey = `${tab}.${key}`; + if(this._defaults.hasOwnProperty(defaultKey)) { + return this._defaults[defaultKey]; + } + + return null; + } + + return this._data[tab][key]; + } + + /** + * + * @param data + * @param tab + * @private + */ + _setValues(data, tab = null) { + for(let key in data) { + if(data.hasOwnProperty(key)) this._setValue(key, data[key], tab); + } + } + + /** + * + * @param key + * @param value + * @param tab + * @returns {{}} + * @private + */ + _setValue(key, value, tab = null) { + if(tab === null) tab = this._tab; + if(!this._data.hasOwnProperty(tab)) this._data[tab] = {}; + this._data[tab][key] = value; + } + + /** + * + * @private + */ + _saveState() { + let message = { + type : 'popup.status.set', + payload: { + service: true, + tab : this._tab, + data : this._data + } + }; + + MessageService + .send(message) + .catch(ErrorManager.catch); + } +};
\ No newline at end of file diff --git a/src/platform/generic/_locales/de/messages.json b/src/platform/generic/_locales/de/messages.json index 56ff4c2..b1f2a74 100644 --- a/src/platform/generic/_locales/de/messages.json +++ b/src/platform/generic/_locales/de/messages.json @@ -1384,5 +1384,17 @@ "PasswordEditValidationRequired" : { "message" : "This field is required", "description": "The validation error message in the password edit form if a required field is empty" + }, + "MiningPasswordDiscarded" : { + "message" : "Passwort verworfen", + "description": "The toast notification after the user has successfully discarded a mined password" + }, + "MiningPasswordCreated" : { + "message" : "Passwort gespeichert", + "description": "The toast notification after the password was successfully saved" + }, + "MiningPasswordUpdated" : { + "message" : "Passwort aktualisiert", + "description": "The toast notification after the password was successfully saved" } } diff --git a/src/platform/generic/_locales/en/messages.json b/src/platform/generic/_locales/en/messages.json index 063f3ae..8a4a54f 100644 --- a/src/platform/generic/_locales/en/messages.json +++ b/src/platform/generic/_locales/en/messages.json @@ -1398,5 +1398,17 @@ "PasswordEditValidationRequired" : { "message" : "This field is required", "description": "The validation error message in the password edit form if a required field is empty" + }, + "MiningPasswordDiscarded" : { + "message" : "Password discarded", + "description": "The toast notification after the user has successfully discarded a mined password" + }, + "MiningPasswordCreated" : { + "message" : "Password saved", + "description": "The toast notification after the password was successfully saved" + }, + "MiningPasswordUpdated" : { + "message" : "Password updated", + "description": "The toast notification after the password was successfully saved" } } diff --git a/src/vue/App/Popup.vue b/src/vue/App/Popup.vue index 14bcee6..81979a7 100644 --- a/src/vue/App/Popup.vue +++ b/src/vue/App/Popup.vue @@ -2,10 +2,10 @@ <div id="manager"> <tabs :tabs="tabs" :initial-tab="tab" ref="tabs" v-on:switch="saveTab($event)" v-if="authorized"> <related slot="related" v-on:search="searchEvent"/> - <search slot="search" :initial-status="search"/> - <browse slot="browse" :initial-status="browse"/> + <search slot="search"/> + <browse slot="browse"/> <collected slot="collected" :initial-status="collected"/> - <tools slot="tools" /> + <tools slot="tools"/> </tabs> <authorisation v-if="!authorized"></authorisation> <first-run-wizard v-if="firstRun"/> @@ -21,7 +21,7 @@ import Related from '@vue/Components/Popup/Related'; import Authorisation from '@vue/Components/Popup/Authorisation'; import Collected from '@vue/Components/Popup/Collected'; - import MessageService from '@js/Services/MessageService'; + import PopupStateService from "@js/Services/PopupStateService"; export default { el : '#app', @@ -37,47 +37,26 @@ }, props: { - authorized: { - type : Boolean, - default: true - }, - tab : { - type : String, - default: 'related' - }, - search : { - type : Object, - default: () => { - return { - query: '' - }; - } - }, - browse : { - type : Object, - default: () => { - return { - server: null, - info : false, - folder: null - }; - } - }, - collected : { + collected: { type : Object, default: () => { return { current: null }; } - }, - firstRun : { - type : Boolean, - default: true } }, computed: { + tab() { + return PopupStateService.getTab(); + }, + firstRun() { + return PopupStateService.getStatus('firstRun'); + }, + authorized() { + return PopupStateService.getStatus('authorized'); + }, tabs() { return { related : { @@ -96,9 +75,9 @@ icon : 'history', label: 'TabCollected' }, - tools : { - icon : 'tools', - label : 'TabTools' + tools : { + icon : 'tools', + label: 'TabTools' } }; } @@ -106,105 +85,99 @@ methods: { saveTab($event) { - let tab = $event.tab; - - MessageService - .send({type: 'popup.status.set', payload: {tab}}); + PopupStateService.setTab($event.tab); }, searchEvent($event) { - var pwdViews = document.getElementsByClassName("password-view") - if(pwdViews.length === 0) { - this.search.query = $event; - this.$refs.tabs.setActive('search'); - } + this.search.query = $event; + this.$refs.tabs.setActive('search'); } } }; </script> <style lang="scss"> - @import "@scss/includes"; - @import "@scssP/browser.scss"; +@import "@scss/includes"; +@import "@scssP/browser.scss"; - body { - overflow : hidden; +body { + overflow : hidden; - &.mobile { - width : 100vw; - height : 100vh; - } - - &.desktop { - width : 360px; - height : 360px; - } + &.mobile { + width : 100vw; + height : 100vh; } - #manager { - width : 100vw; - height : 100vh; - display : block; - overflow : hidden; + &.desktop { + width : 360px; + height : 360px; + } +} + +#manager { + width : 100vw; + height : 100vh; + display : block; + overflow : hidden; + + > .tab-container > .tabs .tab { + overflow : hidden; + width : calc(100vw - 12rem); + transition : var(--popup-tab-transition); + box-sizing : border-box; + box-shadow : var(--main-tab-border); + flex-shrink : 0; + + .label { + opacity : 1; + transition : var(--fade-transition); + } - > .tab-container > .tabs .tab { - overflow : hidden; - width : calc(100vw - 12rem); - transition : var(--popup-tab-transition); - box-sizing : border-box; - box-shadow : var(--main-tab-border); - flex-shrink : 0; + &:not(.active) { + width : 3rem; + flex-grow : 0; .label { - opacity : 1; - transition : var(--fade-transition); - } - - &:not(.active) { - width : 3rem; - flex-grow : 0; - - .label { - opacity : 0; - } - } - - &.active { - box-shadow : var(--main-tab-active-border); + opacity : 0; } } - > .tab-container > .tab-content { - max-height : calc(100vh - 3rem - 2px); - overflow : auto; - scrollbar-width : thin; - scrollbar-color : var(--element-active-fg-color) var(--element-active-bg-color); + &.active { + box-shadow : var(--main-tab-active-border); } + } - @media screen and (min-aspect-ratio : 13/9) { - > .tab-container { - display : grid; - grid-template-columns : 3rem 1fr; - height : 100vh; + > .tab-container > .tab-content { + max-height : calc(100vh - 3rem - 2px); + overflow : auto; + scrollbar-width : thin; + scrollbar-color : var(--element-active-fg-color) var(--element-active-bg-color); + } + + @media screen and (min-aspect-ratio : 13/9) { + > .tab-container { + display : grid; + grid-template-columns : 3rem 1fr; + height : 100vh; - > .tabs { - display : block; - border-right : 1px solid var(--element-hover-bg-color); + > .tabs { + display : block; + border-right : 1px solid var(--element-hover-bg-color); - > .tab { - &.active { - box-shadow : var(--main-tab-mobile-active-border); + > .tab { + &.active { + box-shadow : var(--main-tab-mobile-active-border); - .label { - opacity : 0; - } + .label { + opacity : 0; } } } + } - > .tab-content { - max-height : 100vh; - } + > .tab-content { + max-height : 100vh; } } } +} </style> diff --git a/src/vue/Components/Foldout.vue b/src/vue/Components/Foldout.vue index da9f102..30a7a56 100644 --- a/src/vue/Components/Foldout.vue +++ b/src/vue/Components/Foldout.vue @@ -40,12 +40,22 @@ initialOpen: { type : Boolean, default: false + }, + initialTab: { + type : String, + default: null } }, data() { let tab = null; - if(this.initialOpen) tab = Object.keys(this.tabs)[0]; + if(this.initialOpen) { + if(this.initialTab && this.tabs.hasOwnProperty(this.initialTab)) { + tab = this.initialTab; + } else { + tab = Object.keys(this.tabs)[0]; + } + } return {tab}; }, diff --git a/src/vue/Components/Password/CustomProperty.vue b/src/vue/Components/Password/CustomProperty.vue index 780868c..c84f690 100644 --- a/src/vue/Components/Password/CustomProperty.vue +++ b/src/vue/Components/Password/CustomProperty.vue @@ -33,7 +33,7 @@ type: Boolean }, maxLength: { - type: Boolean + type: Number } }, diff --git a/src/vue/Components/Password/Property.vue b/src/vue/Components/Password/Property.vue index 7a4f6c4..77772af 100644 --- a/src/vue/Components/Password/Property.vue +++ b/src/vue/Components/Password/Property.vue @@ -32,9 +32,6 @@ export default { components: {Icon, InputField, SliderField}, props : { - password: { - type: Password - }, field : { type: Object }, @@ -235,7 +232,8 @@ background-color : var(--element-hover-bg-color); span.icon { - width : 1.75rem; + width: 1.5rem; + display: inline-block; } } diff --git a/src/vue/Components/Password/View.vue b/src/vue/Components/Password/View.vue index 9ce2525..97a265b 100644 --- a/src/vue/Components/Password/View.vue +++ b/src/vue/Components/Password/View.vue @@ -112,13 +112,13 @@ }, getFieldObject(property, type, editable, required, allowCopy, maxLength) { return { - name : property, - type : type, - value : this.password.getProperty(property), - editable : editable, - allowCopy: allowCopy, - required : required, - maxLength: maxLength + name : property, + type, + value: this.password.getProperty(property), + editable, + allowCopy, + required, + maxLength }; }, getCustomFields() { diff --git a/src/vue/Components/Popup/Browse.vue b/src/vue/Components/Popup/Browse.vue index a3f25eb..4b2de66 100644 --- a/src/vue/Components/Popup/Browse.vue +++ b/src/vue/Components/Popup/Browse.vue @@ -1,6 +1,6 @@ <template> <div class="browse-container"> - <foldout :tabs="serverNames" :translate="false" ref="foldout" v-on:switch="switchTab($event)"> + <foldout :tabs="serverNames" :translate="false" ref="foldout" v-on:switch="switchTab($event)" v-if="servers.length !== 0"> <div v-for="server in servers" :key="`${server.getId()}-tab`" :slot="`${server.getId()}-tab`" class="options"> <div class="option" @click="reloadServer(server)"> <icon icon="sync" font="solid" :spin="reloading[server.getId()]"/> @@ -14,10 +14,10 @@ </div> <div v-for="server in servers" :key="server.getId()" :slot="server.getId()"> <server-info :server="server" v-if="info"/> - <server-browser :server="server" :folder="getInitialFolder(server)" v-on:open="setFolder($event)" v-else/> + <server-browser :server="server" :folder="folder" v-on:open="setFolder($event)" v-else/> </div> </foldout> - <translate tag="div" class="browse-no-servers" say="BrowseNoServers" v-if="servers.length === 0"/> + <translate tag="div" class="browse-no-servers" say="BrowseNoServers" v-else/> </div> </template> @@ -27,39 +27,29 @@ import MessageService from '@js/Services/MessageService'; import ServerInfo from '@vue/Components/Browse/ServerInfo'; import ServerBrowser from '@vue/Components/Browse/ServerBrowser'; - import Translate from "@vue/Components/Translate"; + import Translate from '@vue/Components/Translate'; + import PopupStateService from '@js/Services/PopupStateService'; export default { components: {Translate, ServerInfo, ServerBrowser, Icon, Foldout}, - props: { - initialStatus: { - type : Object, - default: () => { - return { - server: null, - info : false, - folder: null - }; - } - } - }, - data() { return { servers : [], reloading: {}, info : false, - current : null, + current : PopupStateService.get('current'), folder : null }; }, async mounted() { await this.loadServers(); - if(this.initialStatus.server !== null && this.serverNames.hasOwnProperty(this.initialStatus.server)) { - this.$refs.foldout.setActive(this.initialStatus.server); - if(this.initialStatus.info) this.info = true; + if(this.current !== null && this.serverNames.hasOwnProperty(this.current)) { + let {folder, info} = PopupStateService.get(['folder', 'info']); + this.$refs.foldout.setActive(this.current); + this.folder = folder; + this.info = info; } }, @@ -85,16 +75,8 @@ let message = await MessageService.send('server.list'); this.servers = message.getPayload(); }, - getInitialFolder(server) { - if(server.getId() === this.initialStatus.server) { - return this.initialStatus.folder; - } - - return null; - }, showInfo() { this.info = !this.info; - this.folder = null; }, async reloadServer(server) { if(this.reloading[server.getId()]) return; @@ -114,13 +96,12 @@ this.folder = $event.folder; }, sendStatus() { - let status = { - server: this.current, - info : this.info, - folder: this.folder - }; - MessageService - .send({type: 'popup.status.set', payload: {tab: 'browse', status}}); + PopupStateService + .set({ + current: this.current, + info : this.info, + folder : this.folder + }); } }, diff --git a/src/vue/Components/Popup/Related.vue b/src/vue/Components/Popup/Related.vue index 895382f..c23da81 100644 --- a/src/vue/Components/Popup/Related.vue +++ b/src/vue/Components/Popup/Related.vue @@ -52,7 +52,7 @@ }); }, search(event) { - if(event.key.length === 1) { + if(event.target.tagName !== 'INPUT' && event.target.tagName !== 'TEXTAREA' && event.key.length === 1) { this.$emit('search', event.key); } } diff --git a/src/vue/Components/Popup/Search.vue b/src/vue/Components/Popup/Search.vue index 0ebe8e4..b5a0379 100644 --- a/src/vue/Components/Popup/Search.vue +++ b/src/vue/Components/Popup/Search.vue @@ -15,24 +15,14 @@ import PasswordList from '@vue/Components/List/PasswordList'; import Translate from '@vue/Components/Translate'; import LocalisationService from '@js/Services/LocalisationService'; + import PopupStateService from "@js/Services/PopupStateService"; export default { components: {Translate, PasswordList}, - props: { - initialStatus: { - type : Object, - default: () => { - return { - query: '' - }; - } - } - }, - data() { return { - query : this.initialStatus.query, + query : PopupStateService.get('query'), passwords : [], placeholder: LocalisationService.translate('SearchPlaceholder') }; @@ -68,17 +58,13 @@ if(this.query === query) this.passwords = r.getPayload(); }); - MessageService - .send({type: 'popup.status.set', payload: {tab: 'search', status: {query}}}); + PopupStateService.set('query', query); } }, watch: { query(query) { this.search(query); - }, - "initialStatus.query"(query) { - this.query = query; } } }; diff --git a/src/vue/Components/Popup/Tools.vue b/src/vue/Components/Popup/Tools.vue index 74bf62c..5d4965c 100644 --- a/src/vue/Components/Popup/Tools.vue +++ b/src/vue/Components/Popup/Tools.vue @@ -1,6 +1,6 @@ <template> <div class="tools-container"> - <foldout :tabs="tabs" :initial-open="true" ref="foldout"> + <foldout :tabs="tabs" :initial-open="true" :initial-tab="initialTab" v-on:switch="switchTab" ref="foldout"> <generate slot="generate"/> <debug slot="debug"/> </foldout> @@ -17,11 +17,15 @@ import Foldout from "@vue/Components/Foldout"; import Debug from "@vue/Components/Tools/Debug"; import Generate from "@vue/Components/Tools/Generate"; + import PopupStateService from "@js/Services/PopupStateService"; export default { components: {Generate, Debug, Foldout, Icon, Translate}, computed: { + initialTab() { + return PopupStateService.get('current'); + }, tabs() { return { generate: { @@ -37,10 +41,14 @@ }; } }, - methods : { + + methods: { openSettings() { MessageService.send('popup.settings.open'); window.close(); + }, + switchTab($event) { + PopupStateService.set('current', $event.tab); } } }; |