diff options
author | Marius David Wieschollek <passwords.public@mdns.eu> | 2020-03-08 01:29:41 +0300 |
---|---|---|
committer | Marius David Wieschollek <passwords.public@mdns.eu> | 2020-03-08 01:29:41 +0300 |
commit | 710e9cdc69d9464f284c9c83bd68a7a9847c9493 (patch) | |
tree | f738d91da4999f0d6c0e6fc90b3296af41138da8 | |
parent | e8ffd3b93ae5842ff405fa39c05e0416b844356a (diff) |
Added custom theme section
Signed-off-by: Marius David Wieschollek <passwords.public@mdns.eu>
48 files changed, 1860 insertions, 505 deletions
diff --git a/package-lock.json b/package-lock.json index 9df7c0b..dcee739 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6799,9 +6799,9 @@ } }, "terser": { - "version": "4.6.3", - "resolved": "https://registry.npmjs.org/terser/-/terser-4.6.3.tgz", - "integrity": "sha512-Lw+ieAXmY69d09IIc/yqeBqXpEQIpDGZqT34ui1QWXIUpR2RjbqEkT8X7Lgex19hslSqcWM5iMN2kM11eMsESQ==", + "version": "4.6.6", + "resolved": "https://registry.npmjs.org/terser/-/terser-4.6.6.tgz", + "integrity": "sha512-4lYPyeNmstjIIESr/ysHg2vUPRGf2tzF9z2yYwnowXVuVzLEamPN1Gfrz7f8I9uEPuHcbFlW4PLIAsJoxXyJ1g==", "dev": true, "requires": { "commander": "^2.20.0", @@ -6959,9 +6959,9 @@ } }, "tslib": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz", - "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.11.1.tgz", + "integrity": "sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA==", "dev": true }, "tty-browserify": { @@ -7355,9 +7355,9 @@ "dev": true }, "webpack": { - "version": "4.41.6", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.41.6.tgz", - "integrity": "sha512-yxXfV0Zv9WMGRD+QexkZzmGIh54bsvEs+9aRWxnN8erLWEOehAKUTeNBoUbA6HPEZPlRo7KDi2ZcNveoZgK9MA==", + "version": "4.42.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.42.0.tgz", + "integrity": "sha512-EzJRHvwQyBiYrYqhyjW9AqM90dE4+s1/XtCfn7uWg6cS72zH+2VPFAlsnW0+W0cDi0XRjNKUMoJtpSi50+Ph6w==", "dev": true, "requires": { "@webassemblyjs/ast": "1.8.5", @@ -7397,6 +7397,12 @@ "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", "dev": true }, + "emojis-list": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", + "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", + "dev": true + }, "json5": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", @@ -7407,13 +7413,13 @@ } }, "loader-utils": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.2.3.tgz", - "integrity": "sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", + "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", "dev": true, "requires": { "big.js": "^5.2.2", - "emojis-list": "^2.0.0", + "emojis-list": "^3.0.0", "json5": "^1.0.1" } }, diff --git a/package.json b/package.json index 28bde59..a86ab81 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "vue-style-loader": "^4.1.2", "vue-template-compiler": "^2.6.11", "webextension-polyfill": "^0.6.0", - "webpack": "^4.41.6", + "webpack": "^4.42.0", "webpack-cli": "^3.3.11" }, "repository": { diff --git a/src/js/App/Background.js b/src/js/App/Background.js index 9a4c7ee..ae6c1d9 100644 --- a/src/js/App/Background.js +++ b/src/js/App/Background.js @@ -12,6 +12,8 @@ import BadgeManager from '@js/Manager/BadgeManager'; import ContextMenuManager from '@js/Manager/ContextMenuManager'; import MiningManager from '@js/Manager/MiningManager'; import NotificationService from '@js/Services/NotificationService'; +import ThemeService from '@js/Services/ThemeService'; +import ThemeRepository from '@js/Repositories/ThemeRepository'; class Background { async init() { @@ -27,6 +29,7 @@ class Background { TabManager.init(); NotificationService.init(); RecommendationManager.init(); + ThemeService.init(ThemeRepository); BadgeManager.init(); ContextMenuManager.init(); MiningManager.init(); diff --git a/src/js/Controller/Theme/List.js b/src/js/Controller/Theme/List.js index 4273d00..1a42419 100644 --- a/src/js/Controller/Theme/List.js +++ b/src/js/Controller/Theme/List.js @@ -4,11 +4,8 @@ import ThemeRepository from '@js/Repositories/ThemeRepository'; export default class List extends AbstractController { async execute(message, reply) { - let themes = await ThemeRepository.findAll(), - list = {}; + let themes = await ThemeRepository.findAll(); - for(let theme of themes) list[theme.getId()] = theme.getLabel(); - - reply.setType('theme.info').setPayload(list); + reply.setType('theme.items').setPayload(themes); } }
\ No newline at end of file diff --git a/src/js/Controller/Theme/Save.js b/src/js/Controller/Theme/Save.js new file mode 100644 index 0000000..2e809a4 --- /dev/null +++ b/src/js/Controller/Theme/Save.js @@ -0,0 +1,16 @@ +import AbstractController from '@js/Controller/AbstractController'; +import ThemeRepository from '@js/Repositories/ThemeRepository'; +import ErrorManager from '@js/Manager/ErrorManager'; + +export default class Save extends AbstractController { + + async execute(message, reply) { + try { + await ThemeRepository.update(message.getPayload()); + reply.setPayload({success: true}); + } catch(e) { + ErrorManager.logError(e); + reply.setType('error').setPayload({success: false, message: e.message}); + } + } +}
\ No newline at end of file diff --git a/src/js/Controller/Theme/Show.js b/src/js/Controller/Theme/Show.js new file mode 100644 index 0000000..9cb7a62 --- /dev/null +++ b/src/js/Controller/Theme/Show.js @@ -0,0 +1,13 @@ +import AbstractController from '@js/Controller/AbstractController'; +import ThemeRepository from '@js/Repositories/ThemeRepository'; + +export default class Show extends AbstractController { + async execute(message, reply) { + try { + let theme = await ThemeRepository.findById(message.getPayload()); + reply.setType('theme.item').setPayload(theme); + } catch(e) { + reply.setType('error'); + } + } +}
\ No newline at end of file diff --git a/src/js/Converter/ThemeConverter.js b/src/js/Converter/ThemeConverter.js new file mode 100644 index 0000000..77133a6 --- /dev/null +++ b/src/js/Converter/ThemeConverter.js @@ -0,0 +1,45 @@ +import Theme from '@js/Models/Theme/Theme'; + +export default class ThemeConverter { + /** + * + * @param {Message} message + * @returns {Message} + */ + convert(message) { + if(message.getType() === 'theme.items') { + return this._covertItems(message); + } else { + return this._covertItem(message); + } + } + + /** + * + * @param {Message} message + * @returns {Message} + */ + _covertItems(message) { + let payload = message.getPayload(), + servers = []; + + if(payload !== null) { + for(let data of payload) { + servers.push(new Theme(data)); + } + } + + return message.setPayload(servers); + } + + /** + * + * @param {Message} message + * @returns {Message} + */ + _covertItem(message) { + let payload = message.getPayload(); + + return message.setPayload(new Theme(payload)); + } +}
\ No newline at end of file diff --git a/src/js/Definition/Theme.json b/src/js/Definition/Theme.json index c160dc8..2b166fc 100644 --- a/src/js/Definition/Theme.json +++ b/src/js/Definition/Theme.json @@ -5,7 +5,19 @@ "label": { "type": "string" }, + "font": { + "type": "object" + }, + "variables": { + "type": "object" + }, + "badge": { + "type": "object" + }, "colors": { "type": "object" + }, + "style": { + "type": "boolean" } }
\ No newline at end of file diff --git a/src/js/Manager/BadgeManager.js b/src/js/Manager/BadgeManager.js index 793f636..0bf8c6d 100644 --- a/src/js/Manager/BadgeManager.js +++ b/src/js/Manager/BadgeManager.js @@ -4,6 +4,7 @@ import TabManager from '@js/Manager/TabManager'; import ServerManager from '@js/Manager/ServerManager'; import ErrorManager from '@js/Manager/ErrorManager'; import LocalisationService from '@js/Services/LocalisationService'; +import ThemeService from '@js/Services/ThemeService'; class BadgeManager { @@ -64,11 +65,7 @@ class BadgeManager { await this._api.browserAction.setBadgeText({text: '', tabId}); } - if(SystemService.getBrowserPlatform() === 'firefox') { - await this._api.browserAction.setBadgeTextColor({color: '#fff'}); - } - - await this._api.browserAction.setBadgeBackgroundColor({color: '#0082c9'}); + await this._setBadgeTheme(); } catch(e) { ErrorManager.logError(e); } @@ -96,6 +93,19 @@ class BadgeManager { ErrorManager.logError(e); } } + + async _setBadgeTheme() { + if(SystemService.getBrowserPlatform() === 'firefox') { + let color = await ThemeService.getBadgeTextColor(); + await this._api.browserAction.setBadgeTextColor({color}); + } + + let color = await ThemeService.getBadgeBackgroundColor(); + await this._api.browserAction.setBadgeBackgroundColor({color}); + + let icon = await ThemeService.getBadgeIcon(); + await this._api.browserAction.setIcon({path:icon}); + } } export default new BadgeManager();
\ No newline at end of file diff --git a/src/js/Manager/ControllerManager.js b/src/js/Manager/ControllerManager.js index 9bcf97c..20e4ec8 100644 --- a/src/js/Manager/ControllerManager.js +++ b/src/js/Manager/ControllerManager.js @@ -165,6 +165,20 @@ class ControllerManager { await this._executeController(module, message, reply); } ); + MessageService.listen( + 'theme.show', + async (message, reply) => { + let module = await import(/* webpackChunkName: "ThemeShow" */ '@js/Controller/Theme/Show'); + await this._executeController(module, message, reply); + } + ); + MessageService.listen( + 'theme.save', + async (message, reply) => { + let module = await import(/* webpackChunkName: "ThemeSave" */ '@js/Controller/Theme/Save'); + await this._executeController(module, message, reply); + } + ); } /** diff --git a/src/js/Manager/ConverterManager.js b/src/js/Manager/ConverterManager.js index 5ccd273..61df209 100644 --- a/src/js/Manager/ConverterManager.js +++ b/src/js/Manager/ConverterManager.js @@ -1,8 +1,9 @@ +import ErrorManager from '@js/Manager/ErrorManager'; import MessageService from '@js/Services/MessageService'; +import ThemeConverter from '@js/Converter/ThemeConverter'; +import FolderConverter from '@js/Converter/FolderConverter'; import ServerConverter from '@js/Converter/ServerConverter'; import PasswordConverter from '@js/Converter/PasswordConverter'; -import ErrorManager from '@js/Manager/ErrorManager'; -import FolderConverter from '@js/Converter/FolderConverter'; class ConverterManager { @@ -25,6 +26,12 @@ class ConverterManager { await this._executeConverter(FolderConverter, message); } ); + MessageService.convert( + ['theme.items', 'theme.item', 'theme.save', 'theme.preview'], + async (message) => { + await this._executeConverter(ThemeConverter, message); + } + ); } diff --git a/src/js/Models/Theme/Theme.js b/src/js/Models/Theme/Theme.js index b69e10e..6567197 100644 --- a/src/js/Models/Theme/Theme.js +++ b/src/js/Models/Theme/Theme.js @@ -1,5 +1,5 @@ import AbstractModel from 'passwords-client/src/Model/AbstractModel'; -import Properties from '@js/Definition/Theme' +import Properties from '@js/Definition/Theme'; export default class Theme extends AbstractModel { @@ -11,44 +11,207 @@ export default class Theme extends AbstractModel { * @return {Object} */ getId() { - return this.getProperty('id') + return this.getProperty('id'); } - + /** * @param {String} value * @return {this} */ setId(value) { - return this.setProperty('id', value) + return this.setProperty('id', value); } /** * @return {Object} */ getLabel() { - return this.getProperty('label') + return this.getProperty('label'); } - + /** * @param {String} value * @return {this} */ setLabel(value) { - return this.setProperty('label', value) + return this.setProperty('label', value); } /** * @return {Object} */ getColors() { - return this.getProperty('colors') + return this.getProperty('colors'); } - + /** * @param {Object} value * @return {this} */ setColors(value) { - return this.setProperty('colors', value) + return this.setProperty('colors', value); + } + + /** + * @return {Object} + */ + getVariables() { + return this.getProperty('variables'); + } + + /** + * @param {Object} value + * @return {this} + */ + setVariables(value) { + return this.setProperty('variables', value); + } + + /** + * @return {Object} + */ + getBadge() { + return this.getProperty('badge'); + } + + /** + * @param {Object} value + * @return {this} + */ + setBadge(value) { + return this.setProperty('badge', value); + } + + /** + * @return {String} + */ + getBadgeIcon() { + return this._getSubProperty('badge', 'icon'); + } + + /** + * @param {String} value + * @return {this} + */ + setBadgeIcon(value) { + return this._setSubProperty('badge', 'icon', value); + } + + /** + * @return {String} + */ + getBadgeBackgroundColor() { + return this._getSubProperty('badge', 'color-bg'); + } + + /** + * @param {String} value + * @return {this} + */ + setBadgeBackgroundColor(value) { + return this._setSubProperty('badge', 'color-bg', value); + } + + /** + * @return {String} + */ + getBadgeForegroundColor() { + return this._getSubProperty('badge', 'color-fg'); + } + + /** + * @param {String} value + * @return {this} + */ + setBadgeForegroundColor(value) { + return this._setSubProperty('badge', 'color-fg', value); + } + + /** + * @return {Object} + */ + getFont() { + return this.getProperty('font'); + } + + /** + * @param {Object} value + * @return {this} + */ + setFont(value) { + return this.setProperty('font', value); + } + + /** + * @return {String} + */ + getFontFamily() { + return this._getSubProperty('font', 'family'); + } + + /** + * @param {String} value + * @return {this} + */ + setFontFamily(value) { + return this._setSubProperty('font', 'family', value); + } + + /** + * @return {String} + */ + getFontSize() { + return this._getSubProperty('font', 'size'); + } + + /** + * @param {String} value + * @return {this} + */ + setFontSize(value) { + return this._setSubProperty('font', 'size', value); + } + + /** + * @return {Boolean} + */ + getStyle() { + return this.getProperty('style'); + } + + /** + * @param {Boolean} value + * @return {this} + */ + setStyle(value) { + return this.setProperty('style', value); + } + + /** + * @param {String} property + * @param {String} key + * @return {(null|String)} + * @private + */ + _getSubProperty(property, key) { + let data = this.getProperty(property); + if(data !== null && data !== undefined && data.hasOwnProperty(key)) return data[key]; + + return null; + } + + /** + * @param {String} property + * @param {String} key + * @param {String} value + * @return {this} + * @private + */ + _setSubProperty(property, key, value) { + let data = this.getProperty(property); + if(data === null || data === undefined) data = {}; + data[key] = value; + + return this.setProperty(property, data); } }
\ No newline at end of file diff --git a/src/js/Queue/Client/MiningClient.js b/src/js/Queue/Client/MiningClient.js index 630bd23..66e6666 100644 --- a/src/js/Queue/Client/MiningClient.js +++ b/src/js/Queue/Client/MiningClient.js @@ -1,14 +1,21 @@ import FeedbackClient from '@js/Queue/Client/FeedbackClient'; import MiningItem from '@js/Models/Queue/MiningItem'; +import EventQueue from '@js/Event/EventQueue'; +import ErrorManager from '@js/Manager/ErrorManager'; class MiningClient extends FeedbackClient { + get update() { + return this._event; + } + constructor() { let worker = (i) => { return this._worker(i); }; let feedbackWorker = (i) => { return this._feedbackWorker(i); }; super('mining', worker, feedbackWorker, MiningItem); this._items = {}; this._solvedItems = {}; + this._event = new EventQueue(); } /** @@ -54,6 +61,9 @@ class MiningClient extends FeedbackClient { resolve, reject }; + + this._event.emit(item) + .catch(ErrorManager.catch); }); } diff --git a/src/js/Queue/FeedbackQueue.js b/src/js/Queue/FeedbackQueue.js index 2a89852..98250e7 100644 --- a/src/js/Queue/FeedbackQueue.js +++ b/src/js/Queue/FeedbackQueue.js @@ -1,14 +1,13 @@ import Queue from '@js/Queue/Queue'; -import MessageService from '@js/Services/MessageService'; import FeedbackItem from '@js/Models/Queue/FeedbackItem'; export default class FeedbackQueue extends Queue { /** * - * @param {String} name + * @param {String} name * @param {(String|null)} [area=null] - * @param {QueueItem} [type=FeedbackItem] + * @param {QueueItem} [type=FeedbackItem] */ constructor(name, area, type = FeedbackItem) { super(name, area, type); diff --git a/src/js/Repositories/ThemeRepository.js b/src/js/Repositories/ThemeRepository.js index 2c4cd6f..3446ed5 100644 --- a/src/js/Repositories/ThemeRepository.js +++ b/src/js/Repositories/ThemeRepository.js @@ -1,12 +1,58 @@ import Theme from '@js/Models/Theme/Theme'; import DarkTheme from '@js/Themes/Dark'; import LightTheme from '@js/Themes/Light'; -import LocalisationService from '@js/Services/LocalisationService'; +import RGB from '@js/Themes/RGB'; +import Hacker from '@js/Themes/Hacker'; +import ArcDark from '@js/Themes/ArcDark'; +import ArcLight from '@js/Themes/ArcLight'; +import OledDarkTheme from '@js/Themes/OledDark'; +import AdaptaTealTheme from '@js/Themes/AdaptaTeal'; +import AdaptaLightTheme from '@js/Themes/AdaptaLight'; +import SettingsService from '@js/Services/SettingsService'; +import StorageService from '@js/Services/StorageService'; +import uuid from 'uuidv4'; +import BooleanState from 'passwords-client/src/State/BooleanState'; class ThemeRepository { + get STORAGE_KEY() { + return 'themes'; + } + constructor() { this._themes = null; + this._customThemes = null; + this._loading = new BooleanState(false); + } + + /** + * + * @param {Theme} theme + */ + async create(theme) { + if(theme.getId() === null || theme.getId() === undefined) { + theme.setId(uuid()); + } + + await this._saveCustomTheme(theme); + if(this._themes !== null) { + this._themes[theme.getId()] = theme; + } + } + + /** + * + * @param {Theme} theme + */ + async update(theme) { + if(theme.getId() === null || theme.getId() === undefined) { + return await this.create(theme); + } + + await this._saveCustomTheme(theme); + if(this._themes !== null) { + this._themes[theme.getId()] = theme; + } } /** @@ -44,29 +90,102 @@ class ThemeRepository { * @private */ async _listThemes() { + if(this._loading.get()) await this._loading.awaitFalse(); if(this._themes !== null) return this._themes; + this._loading.set(true); - this._themes = {}; - let systemThemes = [LightTheme, DarkTheme]; + let themes = {}; + let systemThemes = [LightTheme, DarkTheme, OledDarkTheme, AdaptaLightTheme, AdaptaTealTheme, ArcLight, ArcDark, Hacker, RGB]; for(let data of systemThemes) { - let theme = this._makeSystemTheme(data); + let theme = new Theme(data); - this._themes[theme.getId()] = theme; + themes[theme.getId()] = theme; + } + + let customThemes = await this._loadCustomThemes(); + if(customThemes.length === 0) { + themes.custom = await this._makeCustomTheme(); + } else { + for(let theme of customThemes) { + themes[theme.getId()] = theme; + } } - return this._themes; + this._themes = themes; + this._loading.set(false); + return themes; + } + + async _makeCustomTheme() { + let theme = await SettingsService.getValue('theme.custom'); + + if(theme === null) { + theme = LightTheme; + theme.label = 'ThemeCustom'; + } + + theme.id = 'custom'; + + return new Theme(theme); + } + + /** + * + * @returns {Promise<Theme[]>} + * @private + */ + async _loadCustomThemes() { + if(this._customThemes !== null) { + return this._customThemes; + } + + let themes = []; + if(await StorageService.has(this.STORAGE_KEY)) { + let data = await StorageService.get(this.STORAGE_KEY); + + for(let element of data) { + themes.push(new Theme(element)); + } + } + + this._customThemes = themes; + return themes; + } + + /** + * + * @param theme + * @returns {Promise<void>} + * @private + */ + async _saveCustomTheme(theme) { + let themes = await this._loadCustomThemes(); + + for(let i = 0; i < themes.length; i++) { + if(themes[i].getId() === theme.getId()) { + themes[i] = theme; + await this._saveCustomThemeList(themes); + return; + } + } + + themes.push(theme); + await this._saveCustomThemeList(themes); } /** * - * @param {Object} data - * @return {Theme} + * @param {Theme[]} themes + * @return {Promise<void>} * @private */ - _makeSystemTheme(data) { - data.label = LocalisationService.translate(data.label); + async _saveCustomThemeList(themes) { + let objects = []; - return new Theme(data); + for(let theme of themes) { + objects.push(theme.getProperties()); + } + await StorageService.set(this.STORAGE_KEY, objects); } } diff --git a/src/js/Services/SettingsService.js b/src/js/Services/SettingsService.js index 09880cc..717e0f6 100644 --- a/src/js/Services/SettingsService.js +++ b/src/js/Services/SettingsService.js @@ -31,9 +31,14 @@ class SettingsService { 'client.ext.theme.current', 'local.theme.current', 'sync.theme.current' + ], + 'theme.custom' : [ + 'sync.theme.custom', + 'local.theme.custom' ] }; this._defaults = { + 'theme.custom' : null, 'theme.current' : 'light', 'server.default' : null, 'password.autosubmit' : true, diff --git a/src/js/Services/SystemService.js b/src/js/Services/SystemService.js index ee8a836..32aac1f 100644 --- a/src/js/Services/SystemService.js +++ b/src/js/Services/SystemService.js @@ -94,6 +94,14 @@ class SystemService { } /** + * @param {String} path + * @return {Promise<String>} + */ + async getFileUrl(path) { + return await this.getBrowserApi().runtime.getURL(path); + } + + /** * @returns {Boolean} */ hasContextMenu() { diff --git a/src/js/Services/ThemeService.js b/src/js/Services/ThemeService.js index 37770df..105230f 100644 --- a/src/js/Services/ThemeService.js +++ b/src/js/Services/ThemeService.js @@ -1,17 +1,72 @@ import SettingsService from '@js/Services/SettingsService'; -import ThemeRepository from '@js/Repositories/ThemeRepository'; +import MessageService from '@js/Services/MessageService'; +import SystemService from '@js/Services/SystemService'; import ErrorManager from '@js/Manager/ErrorManager'; class ThemeService { + get FONT_MAPPING() { + return { + default : '-apple-system, BlinkMacSystemFont, Ubuntu, Calibri, "Helvetica Neue", sans-serif', + mono : 'FreeMono, "Courier New", monospace', + sans : 'Ubuntu, Calibri, "Helvetica Neue", sans-serif', + serif : '"Times New Roman", Numbus, serif', + light : '"Comfortaa Light","Lato Light","Corbel Light","Gill Sans Light", sans-serif', + nextcloud: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"', + dyslexic : 'OpenDyslexic, Dyslexie, sans-serif' + }; + } + + constructor() { + /** @type {(ThemeRepository|null)} **/ + this._repository = null; + this._style = null; + } + + /** + * + * @param {ThemeRepository} repository + */ + init(repository) { + this._repository = repository; + } + + + async getBadgeIcon() { + let theme = await this.getCurrentTheme(), + icon = theme.getBadgeIcon(); + + return await SystemService.getBrowserApi().runtime.getURL(`img/${icon}.svg`); + } + + async getBadgeTextColor() { + let theme = await this.getCurrentTheme(); + + return theme.getBadgeForegroundColor(); + } + + async getBadgeBackgroundColor() { + let theme = await this.getCurrentTheme(); + + return theme.getBadgeBackgroundColor(); + } + async apply() { - let theme = await this.getCurrentTheme(), - colors = theme.getColors(); + let theme = await this.getCurrentTheme(); + this.applyTheme(theme); + } - for(let color in colors) { - if(!colors.hasOwnProperty(color)) continue; - document.documentElement.style.setProperty(`--${color}-color`, colors[color]); - } + /** + * + * @param {Theme} theme + */ + applyTheme(theme) { + this._createStyleSheet( + theme, + this._applyFont(theme.getFont()), + this._applyColors(theme.getColors()), + this._applyVariables(theme.getVariables()) + ); } /** @@ -21,12 +76,118 @@ class ThemeService { async getCurrentTheme() { let current = await SettingsService.getValue('theme.current'); - try { - return await ThemeRepository.findById(current); - } catch(e) { - ErrorManager.logError(e); - return await ThemeRepository.findById('light'); + if(this._repository !== null) { + try { + return await this._repository.findById(current); + } catch(e) { + ErrorManager.logError(e); + return await this._repository.findById('light'); + } + } + + let reply = await MessageService.send({type: 'theme.show', payload: current}); + if(reply.getType() === 'theme.item') return reply.getPayload(); + + reply = await MessageService.send({type: 'theme.show', payload: 'light'}); + return reply.getPayload(); + } + + /** + * + * @param {Object} colors + * @return {{}} + * @private + */ + _applyColors(colors) { + if(!colors) return {}; + + let css = {}; + for(let color in colors) { + if(!colors.hasOwnProperty(color)) continue; + css[`--${color}-color`] = colors[color]; + } + + for(let toast of ['info', 'success', 'warning', 'error']) { + let color = `${toast}-fg`; + if(colors.hasOwnProperty(color)) { + css[`--${toast}-hv-color`] = `${colors[color]}40`; + } + } + + return css; + } + + /** + * + * @param {Object} font + * @return {{}} + * @private + */ + _applyFont(font) { + if(!font) return {}; + + let css = {}; + if(font.hasOwnProperty('family') && font.family) { + let mapping = this.FONT_MAPPING; + + if(mapping.hasOwnProperty(font.family)) { + css['--font-family'] = mapping[font.family]; + } else { + css['--font-family'] = font.family; + } } + + if(font.hasOwnProperty('size') && font.size) css['--font-size'] = font.size; + + return css; + } + + /** + * + * @param {Object} variables + * @return {{}} + * @private + */ + _applyVariables(variables) { + if(!variables) return {}; + + let css = {}; + for(let variable in variables) { + if(!variables.hasOwnProperty(variable)) continue; + css[`--${variable}`] = variables[variable]; + } + + return css; + } + + /** + * + * @param {Theme} theme + * @param {Object} variables + * @private + */ + _createStyleSheet(theme, ...variables) { + let css = ''; + variables = Object.assign(...variables); + for(let variable in variables) { + if(!variables.hasOwnProperty(variable)) continue; + + let value = variables[variable].replace(';', ''), + key = variable.replace(';', ''); + + css += `${key}: ${value};`; + } + + css = `:root { ${css} }`; + if(theme.getStyle()) css = `@import url("/css/themes/${theme.getId()}.css");\n${css}`; + + if(this._style === null) { + this._style = document.createElement('style'); + this._style.setAttribute('type', 'text/css'); + document.body.appendChild(this._style); + } + + this._style.innerHTML = css; } } diff --git a/src/js/Services/ToastService.js b/src/js/Services/ToastService.js index bb2bacc..088722b 100644 --- a/src/js/Services/ToastService.js +++ b/src/js/Services/ToastService.js @@ -40,15 +40,26 @@ class ToastService { } } + /** + * + * @param {(String|String[])} message The text of the toast + * @param {(String|String[])} [title=null] The title of the toast + * @param {Number} [ttl=3] Time before the toast is closed + * @return {Promise<String>} + */ + success(message, title = null, ttl = 3) { + return this.create({type: 'success', title, message, closeable: true, ttl}); + } /** * * @param {(String|String[])} message The text of the toast * @param {(String|String[])} [title=null] The title of the toast + * @param {Number} [ttl=10] Time before the toast is closed * @return {Promise<String>} */ - warning(message, title = null) { - return this.create({type: 'warning', title, message, closeable: true, ttl: 10}); + warning(message, title = null, ttl = 10) { + return this.create({type: 'warning', title, message, closeable: true, ttl}); } @@ -56,10 +67,11 @@ class ToastService { * * @param {(String|String[])} message The text of the toast * @param {(String|String[])} [title=null] The title of the toast + * @param {Number} [ttl=10] Time before the toast is closed * @return {Promise<String>} */ - error(message, title = null) { - return this.create({type: 'error', title, message, closeable: true, ttl: 10}); + error(message, title = null, ttl = 10) { + return this.create({type: 'error', title, message, closeable: true, ttl}); } /** @@ -80,16 +92,6 @@ class ToastService { return this.create(config); } - /** - * - * @param {(String|String[])} message The text of the toast - * @param {(String|String[])} [title=null] The title of the toast - * @return {Promise<String>} - */ - success(message, title = null) { - return this.create({type: 'success', title, message, closeable: true, ttl: 3}); - } - /** * diff --git a/src/js/Themes/Dark.json b/src/js/Themes/Dark.json index 0de7c67..5dc7752 100644 --- a/src/js/Themes/Dark.json +++ b/src/js/Themes/Dark.json @@ -1,6 +1,15 @@ { "id": "dark", "label": "ThemeDark", + "badge" : { + "color-bg": "#0c63db", + "color-fg": "#d8d8d8", + "icon" : "passwords-light" + }, + "font" : { + "family": "default", + "size" : "11pt" + }, "colors": { "element-bg" : "#181818", "element-fg" : "#d8d8d8", diff --git a/src/js/Themes/Light.json b/src/js/Themes/Light.json index bd3269f..cd4a39a 100644 --- a/src/js/Themes/Light.json +++ b/src/js/Themes/Light.json @@ -1,6 +1,15 @@ { "id" : "light", "label" : "ThemeLight", + "badge" : { + "color-bg": "#0652dd", + "color-fg": "#ffffff", + "icon" : "passwords-dark" + }, + "font" : { + "family": "default", + "size" : "11pt" + }, "colors": { "element-bg" : "#ffffff", "element-fg" : "#000000", @@ -10,8 +19,8 @@ "element-active-fg" : "#0996f8", "element-active-hover-bg": "#ededed", "element-active-hover-fg": "#0670cc", - "button-bg" : "#00000000", - "button-fg" : "#00000000", + "button-bg" : "inherit", + "button-fg" : "inherit", "button-hover-bg" : "#0996f8", "button-hover-fg" : "#ffffff", "info-bg" : "#0652dd", diff --git a/src/js/preview.js b/src/js/preview.js index 6297ff6..3bead7b 100644 --- a/src/js/preview.js +++ b/src/js/preview.js @@ -1,6 +1,5 @@ import Preview from '@js/App/Preview'; import '@js/Prototype/prototype'; -import '@scss/preview.scss' // noinspection JSUnresolvedVariable __webpack_public_path__ = `/`; diff --git a/src/platform/generic/_locales/de/messages.json b/src/platform/generic/_locales/de/messages.json index a2fcfc4..6d175d5 100644 --- a/src/platform/generic/_locales/de/messages.json +++ b/src/platform/generic/_locales/de/messages.json @@ -489,7 +489,27 @@ } } }, - "SettingsCurrentTheme" : { + "DemoInfoNotification" : { + "message" : "Infomeldung", + "description": "" + }, + "DemoSuccessNotification" : { + "message" : "Erfolgsmeldung", + "description": "" + }, + "DemoWarningNotification" : { + "message" : "Warnmeldung", + "description": "" + }, + "DemoErrorNotification" : { + "message" : "Fehlermeldung", + "description": "" + }, + "DemoText" : { + "message" : "Demo", + "description": "" + }, + "SettingsThemeId" : { "message" : "Aktives Thema", "description": "" }, @@ -497,64 +517,236 @@ "message" : "Dunkles Thema", "description": "" }, + "ThemeOledDark" : { + "message" : "Dunkles OLED Thema", + "description": "" + }, "ThemeLight" : { - "message" : "Helles Thema", + "message" : "Standard", "description": "" }, - "DemoInfoNotification" : { - "message" : "Info Meldung", + "ThemeAdaptaLight" : { + "message" : "Adapta", "description": "" }, - "DemoSuccessNotification" : { - "message" : "Erfolgsmeldung", + "ThemeAdaptaTeal" : { + "message" : "Adapta teal", "description": "" }, - "DemoWarningNotification" : { - "message" : "Warnmeldung", + "ThemeArcDark" : { + "message" : "Arc dark", "description": "" }, - "DemoErrorNotification" : { - "message" : "Fehlermeldung", + "ThemeArcLight" : { + "message" : "Arc", "description": "" }, - "DemoDataInsert" : { - "message" : "Demo-Daten einfügen", + "ThemeHacker" : { + "message" : "Hacker", "description": "" }, - "DemoText" : { - "message" : "Demo", + "ThemeRGB" : { + "message" : "Gaming RGB", "description": "" }, - "SettingsCustomTheme" : { + "ThemeCustom" : { "message" : "Eigenes Thema", "description": "" }, + "CustomFont" : { + "message" : "Schriftart", + "description": "" + }, "CustomDefaultElement" : { - "message" : "Normales Element", + "message" : "Normale Elemente", "description": "" }, "CustomActiveElement" : { - "message" : "Aktives Element", + "message" : "Aktive Elemente", "description": "" }, "CustomButtons" : { "message" : "Schaltflächen", "description": "" }, - "CustomColorsBackground" : { - "message" : "Farbe Hintergrund", + "CustomToasts" : { + "message" : "Mini-Benachrichtigungen", + "description": "" + }, + "CustomBadge" : { + "message" : "Browser-Symbol", + "description": "" + }, + "BackgroundColorLabel" : { + "message" : "Hintergrundfarbe", + "description": "" + }, + "ForegroundColorLabel" : { + "message" : "Vordergrundfarbe", + "description": "" + }, + "BackgroundInheritLabel" : { + "message" : "Hintergrund erben", + "description": "" + }, + "ForegroundInheritLabel" : { + "message" : "Vordergrund erben", + "description": "" + }, + "BackgroundColorBaseTitle" : { + "message" : "Diese Farbe wird im Normalfall für den Hintergrund verwendet", + "description": "" + }, + "BackgroundColorHoverTitle" : { + "message" : "Diese Farbe wird für den Hintergrund verwendet wenn das Element mit der Maus aktiviert wird. Diese Farbe kann auch für Umrandungen verwendet werden wenn das Element nicht aktiviert ist.", + "description": "" + }, + "ForegroundColorBaseTitle" : { + "message" : "Diese Farbe wird im Normalfall für den Vordergrund verwendet", + "description": "" + }, + "ForegroundColorHoverTitle" : { + "message" : "Diese Farbe wird für den Vordergrund und Umrandungen verwendet wenn das Element mit der Maus überfahren wird", + "description": "" + }, + "BackgroundInheritTitle": { + "message" : "Falls aktiviert wird die Farbe des übergeordneten Elements übernommen", + "description": "" + }, + "ForegroundInheritTitle": { + "message" : "Falls aktiviert wird die Farbe des übergeordneten Elements übernommen", + "description": "" + }, + "SettingsCustomFont" : { + "message" : "Schriftart", + "description": "" + }, + "FontDefault" : { + "message" : "Standard", + "description": "" + }, + "FontMono" : { + "message" : "Monospace-Schrift", + "description": "" + }, + "FontSerif" : { + "message" : "Serifenschrift", + "description": "" + }, + "FontSans" : { + "message" : "Serifenlose Schrift", + "description": "" + }, + "FontLight" : { + "message" : "Dünne Schrift", + "description": "" + }, + "FontNextcloud" : { + "message" : "Nextcloud", + "description": "" + }, + "FontOpenDyslexic" : { + "message" : "Open Dyslexic", + "description": "" + }, + "FontCustom" : { + "message" : "Eigene Schrift", + "description": "" + }, + "SettingsCustomFontSize" : { + "message" : "Schriftgröße", + "description": "" + }, + "FontSizeVerySmall" : { + "message" : "Sehr klein", + "description": "" + }, + "FontSizeSmall" : { + "message" : "Klein", + "description": "" + }, + "FontSizeDefault" : { + "message" : "Standard", + "description": "" + }, + "FontSizeMedium" : { + "message" : "Medium", + "description": "" + }, + "FontSizeLarge" : { + "message" : "Groß", + "description": "" + }, + "FontSizeVeryLarge" : { + "message" : "Sehr groß", + "description": "" + }, + "ToastInfoColors" : { + "message" : "Infomeldung", + "description": "" + }, + "ToastSuccessColors" : { + "message" : "Erfolgsmeldung", + "description": "" + }, + "ToastWarningColors" : { + "message" : "Warnmeldung", + "description": "" + }, + "ToastErrorColors" : { + "message" : "Fehlermeldung", + "description": "" + }, + "ToastBackgroundTitle" : { + "message" : "Hintergrundfarbe für die Mini-Benachrichtigung", + "description": "" + }, + "ToastForegroundTitle" : { + "message" : "Farbe für Texte und Schaltflächen", + "description": "" + }, + "SettingsBadgeIcon" : { + "message" : "Symbolleisten-Symbol", + "description": "" + }, + "BadgeIconAuto" : { + "message" : "Automatisch", + "description": "" + }, + "BadgeIconLight" : { + "message" : "Helles Symbol", + "description": "" + }, + "BadgeIconMedium" : { + "message" : "Graues Symbol", + "description": "" + }, + "BadgeIconDark" : { + "message" : "Dunkles Symbol", + "description": "" + }, + "BadgeIconNewLight" : { + "message" : "Helles modernes Symbol", + "description": "" + }, + "BadgeIconNewMedium" : { + "message" : "Graues modernes Symbol", + "description": "" + }, + "BadgeIconNewDark" : { + "message" : "Dunkles modernes Symbol", "description": "" }, - "CustomColorsForeground" : { - "message" : "Farbe Vordergrund", + "CustomBadgeColors" : { + "message" : "Abzeichenfarbe", "description": "" }, - "CustomAlphaBackground" : { - "message" : "Transparenz Hintergrund", + "BadgeBackgroundTitle" : { + "message" : "Hintergrundfarbe des Abzeichens", "description": "" }, - "CustomAlphaForeground" : { - "message" : "Transparenz Vordergrund", + "BadgeForegroundTitle" : { + "message" : "Textfarbe des Abzeichens", "description": "" } }
\ No newline at end of file diff --git a/src/platform/generic/_locales/en/messages.json b/src/platform/generic/_locales/en/messages.json index 5491456..29ad693 100644 --- a/src/platform/generic/_locales/en/messages.json +++ b/src/platform/generic/_locales/en/messages.json @@ -503,7 +503,27 @@ } } }, - "SettingsCurrentTheme" : { + "DemoInfoNotification" : { + "message" : "Info notification", + "description": "" + }, + "DemoSuccessNotification" : { + "message" : "Success notification", + "description": "" + }, + "DemoWarningNotification" : { + "message" : "Warning notification", + "description": "" + }, + "DemoErrorNotification" : { + "message" : "Error notification", + "description": "" + }, + "DemoText" : { + "message" : "Demo", + "description": "" + }, + "SettingsThemeId" : { "message" : "Active theme", "description": "" }, @@ -511,38 +531,46 @@ "message" : "Dark theme", "description": "" }, + "ThemeOledDark" : { + "message" : "Dark OLED", + "description": "" + }, "ThemeLight" : { - "message" : "Light theme", + "message" : "Default", "description": "" }, - "DemoInfoNotification" : { - "message" : "Info notification", + "ThemeAdaptaLight" : { + "message" : "Adapta", "description": "" }, - "DemoSuccessNotification" : { - "message" : "Success notification", + "ThemeAdaptaTeal" : { + "message" : "Adapta teal", "description": "" }, - "DemoWarningNotification" : { - "message" : "Warning notification", + "ThemeArcDark" : { + "message" : "Arc dark", "description": "" }, - "DemoErrorNotification" : { - "message" : "Error notification", + "ThemeArcLight" : { + "message" : "Arc", "description": "" }, - "DemoDataInsert" : { - "message" : "Insert demo data", + "ThemeHacker" : { + "message" : "Hacker", "description": "" }, - "DemoText" : { - "message" : "Demo", + "ThemeRGB" : { + "message" : "Gaming RGB", "description": "" }, - "SettingsCustomTheme" : { + "ThemeCustom" : { "message" : "Custom theme", "description": "" }, + "CustomFont" : { + "message" : "Font", + "description": "" + }, "CustomDefaultElement" : { "message" : "Default element", "description": "" @@ -555,156 +583,184 @@ "message" : "Buttons", "description": "" }, - "CustomColorsBackground" : { + "CustomToasts" : { + "message" : "Toast notifications", + "description": "" + }, + "CustomBadge" : { + "message" : "Browser icon", + "description": "" + }, + "BackgroundColorLabel" : { "message" : "Background color", "description": "" }, - "CustomColorsForeground" : { + "ForegroundColorLabel" : { "message" : "Foreground color", "description": "" }, - "CustomAlphaBackground" : { - "message" : "Background transparent", + "BackgroundInheritLabel" : { + "message" : "Inherit background", "description": "" }, - "CustomAlphaForeground" : { - "message" : "Foreground transparent", + "ForegroundInheritLabel" : { + "message" : "Inherit foreground", "description": "" }, - - - - - - - - - - - "PasswordRequestFailedTitle" : { - "message" : "Nextcloud password request failed", - "description": "Notification title when the password request fails" + "BackgroundColorBaseTitle" : { + "message" : "This color is usually used for the background", + "description": "" }, - "PasswordRequestFailedText" : { - "message" : "The password list could not be retrieved.\nError: $ERROR$", - "description" : "Notification text when the password request fails", - "placeholders": { - "error": { - "content": "$1", - "example": "Unknown Error" - } - } + "BackgroundColorHoverTitle" : { + "message" : "This color is used for the background if the element is active. It can also be used for the border of an inactive element", + "description": "" }, - "PasswordEncodingFailedTitle" : { - "message" : "Could not parse password properties", - "description": "Notification title when the password properties can not be parsed" + "ForegroundColorBaseTitle" : { + "message" : "This color is usually used for texts and borders", + "description": "" }, - "PasswordEncodingFailedText" : { - "message" : "The properties of password #$ID$ do not contain valid JSON data and could not be processed", - "description" : "Notification text when the password properties can not be parsed", - "placeholders": { - "id": { - "content": "$1", - "example": "0" - } - } + "ForegroundColorHoverTitle" : { + "message" : "This color is used for texts and borders if the element is active", + "description": "" }, - "PasswordCreatedTitle" : { - "message" : "Password saved", - "description": "Notification title when a password was saved" + "BackgroundInheritTitle": { + "message" : "If activated, the value will be inherited from the parent element", + "description": "" }, - "PasswordCreatedText" : { - "message" : "The password was stored sucessfully", - "description": "Notification text when a password was saved" + "ForegroundInheritTitle": { + "message" : "If activated, the value will be inherited from the parent element", + "description": "" }, - "CreatePasswordFailedTitle" : { - "message" : "Save password failed", - "description": "Notification title when saving a password failed" + "SettingsCustomFont" : { + "message" : "Font family", + "description": "" }, - "CreatePasswordFailedText" : { - "message" : "The password could not be saved", - "description": "Notification text when saving a password failed" + "FontDefault" : { + "message" : "Default", + "description": "" }, - "UpdatedLoginDetectedTitle" : { - "message" : "Updated login found", - "description": "Notification title when an updated login is detected" + "FontMono" : { + "message" : "Monospace", + "description": "" }, - "UpdatedLoginDetectedText" : { - "message" : "Click here to update the password for \"$USER$\"", - "description" : "Notification text when an updated login is detected", - "placeholders": { - "user": { - "content": "$1", - "example": "username" - } - } + "FontSerif" : { + "message" : "Serif", + "description": "" }, - "PasswordUpdatedTitle" : { - "message" : "Password updated", - "description": "Notification title when a password was updated" + "FontSans" : { + "message" : "Sans serif", + "description": "" }, - "PasswordUpdatedText" : { - "message" : "The password was updated sucessfully", - "description": "Notification text when a password was updated" + "FontLight" : { + "message" : "Light", + "description": "" }, - "UpdatePasswordFailedTitle" : { - "message" : "Update password failed", - "description": "Notification title when updating a password failed" + "FontNextcloud" : { + "message" : "Nextcloud", + "description": "" }, - "UpdatePasswordFailedText" : { - "message" : "The password could not be updated", - "description": "Notification text when updating a password failed" + "FontOpenDyslexic" : { + "message" : "Open Dyslexic", + "description": "" }, - "ApiLogin200" : { - "message" : "URL invalid or API disabled", - "description": "Error text when an api request could not be decoded" + "FontCustom" : { + "message" : "Custom", + "description": "" }, - "ApiLogin401" : { - "message" : "Invalid user credentials", - "description": "Error text when an api request got a 401 Unauthorized response" + "SettingsCustomFontSize" : { + "message" : "Font size", + "description": "" }, - "ApiLogin403" : { - "message" : "Access denied", - "description": "Error text when an api request got a 403 Forbidden response" + "FontSizeVerySmall" : { + "message" : "Very small", + "description": "" }, - "ApiLogin404" : { - "message" : "URL is invalid", - "description": "Error text when an api request got a 404 Not Found response" + "FontSizeSmall" : { + "message" : "Small", + "description": "" }, - "ApiLogin500" : { - "message" : "Server crashed during request", - "description": "Error text when an api request got a 50x Internal Server Error response" + "FontSizeDefault" : { + "message" : "Default", + "description": "" }, - "ApiLoginGeneric" : { - "message" : "Http Error $STATUS$: $MESSAGE$", - "description" : "Generic error message for failed requests", - "placeholders": { - "status" : { - "content": "$1", - "example": "HTTP status code" - }, - "message": { - "content": "$2", - "example": "HTTP status text" - } - } + "FontSizeMedium" : { + "message" : "Medium", + "description": "" }, - "SettingsInvalidUrl" : { - "message" : "The url $URL$ seems to be invalid.\n\n If the url of the app is\nhttps://example.com/index.php/apps/passwords\nyou need to enter\nhttps://example.com\n\nIf the url of the app is\nhttps://example.com/nextcloud/index.php/apps/passwords\nyou need to enter\nhttps://example.com/nextcloud", - "description" : "Generic error message for failed requests", - "placeholders": { - "url": { - "content": "$1", - "example": "The user input" - } - } + "FontSizeLarge" : { + "message" : "Large", + "description": "" + }, + "FontSizeVeryLarge" : { + "message" : "Very large", + "description": "" + }, + "ToastInfoColors" : { + "message" : "Info toast", + "description": "" + }, + "ToastSuccessColors" : { + "message" : "Success toast", + "description": "" + }, + "ToastWarningColors" : { + "message" : "Warning toast", + "description": "" + }, + "ToastErrorColors" : { + "message" : "Error toast", + "description": "" + }, + "ToastBackgroundTitle" : { + "message" : "Background color for popup messages", + "description": "" + }, + "ToastForegroundTitle" : { + "message" : "Color for text and button backgrounds", + "description": "" }, - "DarkMode" : { - "message" : "Dark Mode", - "description": "Lable of the Dark Mode setting" + "SettingsBadgeIcon" : { + "message" : "Toolbar icon", + "description": "" + }, + "BadgeIconAuto" : { + "message" : "Automatic", + "description": "" + }, + "BadgeIconLight" : { + "message" : "Light icon", + "description": "" + }, + "BadgeIconMedium" : { + "message" : "Grey icon", + "description": "" }, - "Url" : { "message": "Url" }, - "Password" : { "message": "Password" }, - "User" : { "message": "User" }, - "Save" : { "message": "Save" } + "BadgeIconDark" : { + "message" : "Dark icon", + "description": "" + }, + "BadgeIconNewLight" : { + "message" : "Light modern icon", + "description": "" + }, + "BadgeIconNewMedium" : { + "message" : "Grey modern icon", + "description": "" + }, + "BadgeIconNewDark" : { + "message" : "Dark modern icon", + "description": "" + }, + "CustomBadgeColors" : { + "message" : "Badge colors", + "description": "" + }, + "BadgeBackgroundTitle" : { + "message" : "Background color of the badge", + "description": "" + }, + "BadgeForegroundTitle" : { + "message" : "Text color of the badge", + "description": "" + } }
\ No newline at end of file diff --git a/src/scss/_base.scss b/src/scss/_base.scss index 1f625a8..cd15ae8 100644 --- a/src/scss/_base.scss +++ b/src/scss/_base.scss @@ -1,4 +1,11 @@ +:root { + font-family : var(--font-family); + font-size : var(--font-size); +} + body { + font-family : var(--font-family); + font-size : var(--font-size); background-color : var(--element-bg-color); color : var(--element-fg-color); margin : 0; @@ -10,6 +17,7 @@ body { input, select, button, { - font-size : 1rem; + font-family : var(--font-family); + font-size : 1rem; } }
\ No newline at end of file diff --git a/src/scss/_fonts.scss b/src/scss/_fonts.scss new file mode 100644 index 0000000..9a2365c --- /dev/null +++ b/src/scss/_fonts.scss @@ -0,0 +1,13 @@ +@font-face { + font-family : "OpenDyslexic"; + src : url('../../fonts/OpenDyslexic/OpenDyslexic-Regular.woff'); + font-weight : normal; + font-style : normal; +} + +@font-face { + font-family : "OpenDyslexic"; + src : url('../../fonts/OpenDyslexic/OpenDyslexic-Bold.woff'); + font-weight : bold; + font-style : normal; +}
\ No newline at end of file diff --git a/src/scss/_theme.scss b/src/scss/_theme.scss index 4d00cdb..b837253 100644 --- a/src/scss/_theme.scss +++ b/src/scss/_theme.scss @@ -1,4 +1,7 @@ :root { + --font-family : initial; + --font-size : 11pt; + /** * Generic content elements */ @@ -39,10 +42,14 @@ */ --info-bg-color : #0652dd; --info-fg-color : #fff; + --info-hv-color : #ffffff40; --warning-bg-color : #ffc312; --warning-fg-color : #fff; + --warning-hv-color : #ffffff40; --error-bg-color : #ff3f34; --error-fg-color : #fff; + --error-hv-color : #ffffff40; --success-bg-color : #05c46b; --success-fg-color : #fff; + --success-hv-color : #ffffff40; }
\ No newline at end of file diff --git a/src/scss/includes.scss b/src/scss/includes.scss index 8c7dde7..8ff7dde 100644 --- a/src/scss/includes.scss +++ b/src/scss/includes.scss @@ -1,2 +1,3 @@ @import "base"; +@import "fonts"; @import "theme";
\ No newline at end of file diff --git a/src/scss/preview.scss b/src/scss/preview.scss deleted file mode 100644 index 9b7c637..0000000 --- a/src/scss/preview.scss +++ /dev/null @@ -1,4 +0,0 @@ -body { - font-family : Ubuntu, Verdana, sans-serif; - font-size : 11pt; -}
\ No newline at end of file diff --git a/src/vue/App/Options.vue b/src/vue/App/Options.vue index f85cb4b..fb69700 100644 --- a/src/vue/App/Options.vue +++ b/src/vue/App/Options.vue @@ -11,9 +11,9 @@ <script> import Tabs from '@vue/Components/Tabs'; + import Theming from '@vue/Components/Options/Theming'; import Accounts from '@vue/Components/Options/Accounts'; import Settings from '@vue/Components/Options/Settings'; - import Theming from '@vue/Components/Options/Theming'; export default { el : '#app', @@ -38,6 +38,9 @@ </script> <style lang="scss"> + @import "@scss/includes"; + @import "@scssP/browser.scss"; + body { min-height : 600px; diff --git a/src/vue/App/Popup.vue b/src/vue/App/Popup.vue index ab4c8ee..39295be 100644 --- a/src/vue/App/Popup.vue +++ b/src/vue/App/Popup.vue @@ -1,10 +1,10 @@ <template> <div id="manager"> - <tabs :tabs="tabs" :initial-tab="tab" v-if="authorized" v-on:switch="saveTab($event)" ref="tabs"> - <related slot="related" ref="related"/> - <search slot="search" ref="search" :initial-status="search"/> - <browse slot="browse" ref="browse" :initial-status="browse"/> - <collected slot="collected" ref="collected" :initial-status="collected"/> + <tabs :tabs="tabs" :initial-tab="tab" v-if="authorized" v-on:switch="saveTab($event)" > + <related slot="related"/> + <search slot="search" :initial-status="search"/> + <browse slot="browse" :initial-status="browse"/> + <collected slot="collected" :initial-status="collected"/> </tabs> <authorisation v-if="!authorized"></authorisation> <div id="toasts"></div> @@ -112,6 +112,9 @@ </script> <style lang="scss"> + @import "@scss/includes"; + @import "@scssP/browser.scss"; + body { overflow : hidden; diff --git a/src/vue/Components/Collected/MinedProperty.vue b/src/vue/Components/Collected/MinedProperty.vue index eafc29d..09b9eb1 100644 --- a/src/vue/Components/Collected/MinedProperty.vue +++ b/src/vue/Components/Collected/MinedProperty.vue @@ -96,6 +96,7 @@ overflow : hidden; box-shadow : 0 0 0 1px transparent; transition : box-shadow .15s ease-in-out; + color : var(--element-fg-color); &:hover { box-shadow : 0 0 0 1px var(--element-hover-bg-color); @@ -103,13 +104,15 @@ } input { - width : 100%; - padding : .25rem; - box-sizing : border-box; - box-shadow : 0 0 0 1px var(--element-active-fg-color); - border-radius : 3px; - border : none; - line-height : 2rem; + width : 100%; + padding : .25rem; + box-sizing : border-box; + box-shadow : 0 0 0 1px var(--element-active-fg-color); + border-radius : 3px; + border : none; + line-height : 2rem; + background-color : var(--element-bg-color); + color : var(--element-fg-color); } &.password { diff --git a/src/vue/Components/Form/InputField.vue b/src/vue/Components/Form/InputField.vue index 7c07b58..73ecc4d 100644 --- a/src/vue/Components/Form/InputField.vue +++ b/src/vue/Components/Form/InputField.vue @@ -1,5 +1,11 @@ <template> - <input :placeholder="getPlaceholder" :title="getTitle" v-model="model"/> + <input :type="type" + :value="value" + :checked="isChecked" + :placeholder="getPlaceholder" + :title="getTitle" + @input="handleInput" + @change="handleChange"/> </template> <script> @@ -8,9 +14,17 @@ export default { props: { - value : { + type : { type : String, - default: '' + default: 'text' + }, + value : { + type : [String, Number, Boolean], + default: null + }, + checked : { + type : Boolean, + default: null }, placeholder: { type : String, @@ -22,13 +36,6 @@ } }, - data() { - return { - model: this.value, - emit : this.value - }; - }, - computed: { getPlaceholder() { if(this.placeholder.length === 0) return; @@ -39,19 +46,25 @@ if(this.title.length === 0) return; return LocalisationService.translate(this.title); + }, + isChecked() { + if(this.type !== 'checkbox' && this.type !== 'radio') return undefined; + + return this.checked || this.value; } }, - watch: { - model(value) { - if(this.emit !== value) { - this.emit = value; - this.$emit('input', value); + methods: { + handleInput($event) { + if(this.type !== 'checkbox' && this.type !== 'radio') { + this.$emit('input', $event.target.value); } }, - value(value) { - this.emit = value; - this.model = value; + handleChange($event) { + if(this.type === 'checkbox' || this.type === 'radio') { + this.$emit('change', $event.target.checked); + this.$emit('input', $event.target.checked); + } } } }; diff --git a/src/vue/Components/Form/SelectField.vue b/src/vue/Components/Form/SelectField.vue index 5ef325a..bfc3766 100644 --- a/src/vue/Components/Form/SelectField.vue +++ b/src/vue/Components/Form/SelectField.vue @@ -1,8 +1,10 @@ <template> - <select v-model="model"> + <select @change="handleChange"> <option v-for="option in optionList" :key="option.id" :value="option.id" + :disabled="option.disabled" + :selected="option.id === value" :title="getTranslated(option.description)">{{getTranslated(option.label)}} </option> </select> @@ -27,13 +29,6 @@ } }, - data() { - return { - model: this.value, - emit : this.value - }; - }, - computed: { optionList() { let options = []; @@ -43,11 +38,12 @@ let config = this.options[id]; if(typeof config === 'string') { - options.push({id, label: config, description: null}); + options.push({id, label: config, disabled: false, description: null}); } else { let option = { id : config.hasOwnProperty('id') ? config.id:id, label : config.label, + disabled : config.hasOwnProperty('disabled') ? config.disabled === true:false, description: config.hasOwnProperty('description') ? config.description:null }; @@ -64,19 +60,10 @@ if(!this.translate || !text) return text; return LocalisationService.translate(text); - } - }, - - watch: { - model(value) { - if(this.emit !== value) { - this.emit = value; - this.$emit('input', value); - } }, - value(value) { - this.emit = value; - this.model = value; + handleChange($event) { + this.$emit('change', $event.target.value); + this.$emit('input', $event.target.value); } } }; diff --git a/src/vue/Components/Options/Theming.vue b/src/vue/Components/Options/Theming.vue index 333610c..4477459 100644 --- a/src/vue/Components/Options/Theming.vue +++ b/src/vue/Components/Options/Theming.vue @@ -1,13 +1,13 @@ <template> - <div class="theming"> + <div class="theming" v-if="theme"> <div class="theme-settings"> <div class="setting"> - <translate tag="label" for="theme-current" say="SettingsCurrentTheme"/> - <select-field id="theme-current" :options="themeList" :translate="false" v-model="currentTheme"/> + <translate tag="label" for="theme-current" say="SettingsThemeId"/> + <select-field id="theme-current" :options="list" v-model="themeId"/> </div> - <custom-theme /> + <custom-theme :theme="customTheme" v-if="customTheme && themeId === 'custom'"/> </div> - <preview-theme :theme="currentTheme"/> + <preview-theme :theme="theme"/> </div> </template> @@ -18,29 +18,68 @@ import ToastService from '@js/Services/ToastService'; import PreviewTheme from '@vue/Components/Theming/PreviewTheme'; import CustomTheme from '@vue/Components/Theming/CustomTheme'; + import ErrorManager from '@js/Manager/ErrorManager'; export default { components: {CustomTheme, PreviewTheme, Translate, SelectField}, data() { return { - currentTheme: null, - themeList : {} + themeId: null, + theme : null, + themes : [] }; }, - mounted() { - MessageService.send({type: 'theme.list'}).then((r) => { this.themeList = r.getPayload(); }); - MessageService.send({type: 'setting.get', payload: 'theme.current'}).then((r) => { - this.currentTheme = - r.getPayload(); - }); + computed: { + list() { + let list = {}; + + for(let theme of this.themes) list[theme.getId()] = theme.getLabel(); + + return list; + }, + customTheme() { + for(let theme of this.themes) { + if(theme.getId() === 'custom') return theme; + } + } + }, + + async mounted() { + let reply = await MessageService.send({type: 'theme.list'}); + this.themes = reply.getPayload(); + + reply = await MessageService.send({type: 'setting.get', payload: 'theme.current'}); + this.themeId = reply.getPayload(); + + this.setCurrentTheme(this.themeId); + }, + + methods: { + setCurrentTheme(themeId) { + for(let theme of this.themes) { + if(theme.getId() === themeId) { + this.theme = theme; + return; + } + } + } }, watch: { - currentTheme(value) { + themeId(value) { MessageService.send({type: 'setting.set', payload: {setting: 'theme.current', value}}) .catch((e) => { ToastService.error(e.message); }); + + this.setCurrentTheme(this.themeId); + MessageService.send( + { + type : 'theme.preview', + payload : this.theme, + receiver: 'popup' + } + ).catch(ErrorManager.catch); } } }; @@ -53,15 +92,12 @@ grid-column-gap : 1rem; padding : 1rem; - .theme-preview { - background-color : grey; - } - .theme-settings { .setting { display : grid; - grid-template-areas : "label" "input"; - grid-template-columns : 2fr 1fr; + grid-template-areas : "label input"; + grid-template-columns : 3fr 2fr; + margin-bottom : .25rem; label { grid-area : label; @@ -70,6 +106,7 @@ select, input { grid-area : input; + width : 100%; } } } @@ -91,7 +128,7 @@ cursor : pointer; &[disabled] { - opacity: .25; + opacity : .25; } } } diff --git a/src/vue/Components/Popup/Collected.vue b/src/vue/Components/Popup/Collected.vue index fc66bd0..fa8c769 100644 --- a/src/vue/Components/Popup/Collected.vue +++ b/src/vue/Components/Popup/Collected.vue @@ -36,10 +36,19 @@ data() { return { items : MiningClient.getItems(), - current: null + current: null, + listener: (i) => { this.addItem(i); } }; }, + created() { + MiningClient.update.on(this.listener); + }, + + destroyed() { + MiningClient.update.off(this.listener); + }, + async mounted() { if(this.initialStatus.current !== null && this.tabs.hasOwnProperty(this.initialStatus.current)) { this.$refs.foldout.setActive(this.initialStatus.current); @@ -87,6 +96,13 @@ this.current = $event.tab; }, + /** + * @param {MiningItem} item + */ + addItem(item) { + this.items.push(item); + }, + sendStatus() { let status = { current: this.current diff --git a/src/vue/Components/Tabs.vue b/src/vue/Components/Tabs.vue index 797b100..d07ce0f 100644 --- a/src/vue/Components/Tabs.vue +++ b/src/vue/Components/Tabs.vue @@ -18,7 +18,7 @@ :class="`tab-content-${name}`" :style="{display: name===tab ? 'block':'none'}"> <keep-alive> - <slot :name="name"/> + <slot :name="name" v-if="name===tab"/> </keep-alive> </div> </div> diff --git a/src/vue/Components/Theming/BadgeIcon.vue b/src/vue/Components/Theming/BadgeIcon.vue new file mode 100644 index 0000000..6d5ef7a --- /dev/null +++ b/src/vue/Components/Theming/BadgeIcon.vue @@ -0,0 +1,61 @@ +<template> + <div class="setting"> + <translate tag="label" for="badge-icon" say="SettingsBadgeIcon"/> + <select-field id="badge-icon" :options="options" v-model="model"/> + </div> +</template> + +<script> + import SelectField from '@vue/Components/Form/SelectField'; + import Translate from '@vue/Components/Translate'; + + export default { + components: {Translate, SelectField}, + + props: ['value'], + + data() { + return { + model: this.mapValue(this.value) + }; + }, + + computed: { + options() { + return { + auto : 'BadgeIconAuto', + light : 'BadgeIconLight', + medium : 'BadgeIconMedium', + dark : 'BadgeIconDark', + 'new-light': 'BadgeIconNewLight', + 'new' : 'BadgeIconNewMedium', + 'new-dark' : 'BadgeIconNewDark' + }; + } + }, + + methods: { + mapValue(value) { + if(!value) return 'auto'; + if(value === 'passwords') return 'medium'; + + return value.substr(10); + }, + mapModel(value) { + if(value === 'auto') return null; + if(value === 'medium') return 'passwords'; + + return `passwords-${value}`; + } + }, + + watch: { + value(value) { + this.model = this.mapValue(value); + }, + model(value) { + this.$emit('input', this.mapModel(value)); + } + } + }; +</script>
\ No newline at end of file diff --git a/src/vue/Components/Theming/CustomColorAlpha.vue b/src/vue/Components/Theming/CustomColorAlpha.vue deleted file mode 100644 index e09d346..0000000 --- a/src/vue/Components/Theming/CustomColorAlpha.vue +++ /dev/null @@ -1,81 +0,0 @@ -<template> - <div class="color-setting"> - <translate :say="label"/> - <input type="checkbox" v-model="currentBase"/> - <input type="checkbox" v-model="currentHover"/> - </div> -</template> - -<script> - import Translate from '@vue/Components/Translate'; - - export default { - components: {Translate}, - - props: { - name : String, - type : String, - colors: Object - }, - - data() { - let keyBase = `${this.name}-${this.type}`, - keyHover = `${this.name}-hover-${this.type}`; - - return { - colorBase : this.colors[keyBase], - colorHover : this.colors[keyHover], - defaultBase : this.colors[keyBase].length > 7, - defaultHover: this.colors[keyHover].length > 7, - currentBase : this.colors[keyBase].length > 7, - currentHover: this.colors[keyHover].length > 7, - keyBase, - keyHover - }; - }, - - computed: { - label() { - return this.type === 'bg' ? 'CustomAlphaBackground':'CustomAlphaForeground'; - } - }, - - methods: { - update() { - let colors = {}; - - colors[this.keyBase] = this.colorBase.substr(0, 7); - colors[this.keyHover] = this.colorHover.substr(0, 7); - - if(this.currentBase) colors[this.keyBase] += '00'; - if(this.currentHover) colors[this.keyHover] += '00'; - - this.$emit('update', colors); - } - }, - - watch: { - colors: { - deep: true, - handler(value) { - this.colorBase = value[this.keyBase]; - this.colorHover = value[this.keyHover]; - this.defaultBase = this.colorBase.length > 7; - this.defaultHover = this.colorHover.length > 7; - this.currentBase = this.defaultBase; - this.currentHover = this.defaultHover; - } - }, - currentBase(value) { - if(this.defaultBase !== value) this.update(); - }, - currentHover(value) { - if(this.defaultHover !== value) this.update(); - } - } - }; -</script> - -<style lang="scss"> - -</style>
\ No newline at end of file diff --git a/src/vue/Components/Theming/CustomColorInherit.vue b/src/vue/Components/Theming/CustomColorInherit.vue new file mode 100644 index 0000000..092c763 --- /dev/null +++ b/src/vue/Components/Theming/CustomColorInherit.vue @@ -0,0 +1,87 @@ +<template> + <div class="color-setting"> + <translate :say="label"/> + <input-field type="checkbox" :title="title" v-model="currentBase"/> + <input-field type="checkbox" :title="title" v-model="currentHover"/> + </div> +</template> + +<script> + import Translate from '@vue/Components/Translate'; + import InputField from '@vue/Components/Form/InputField'; + + export default { + components: {InputField, Translate}, + + props: { + name : String, + type : String, + colors: Object + }, + + data() { + let keyBase = `${this.name}-${this.type}`, + keyHover = `${this.name}-hover-${this.type}`, + inheritBase = this.colors[keyBase] === 'inherit', + inheritHover = this.colors[keyHover] === 'inherit'; + + return { + colorBase : inheritBase ? '#000':this.colors[keyBase], + colorHover : inheritHover ? '#000':this.colors[keyHover], + defaultBase : inheritBase, + defaultHover: inheritHover, + currentBase : inheritBase, + currentHover: inheritHover, + keyBase, + keyHover + }; + }, + + computed: { + label() { + return this.type === 'bg' ? 'BackgroundInheritLabel':'ForegroundInheritLabel'; + }, + title() { + return this.type === 'bg' ? 'BackgroundInheritTitle':'ForegroundInheritTitle'; + } + }, + + methods: { + update() { + let colors = {}; + + colors[this.keyBase] = this.currentBase ? 'inherit':this.colorBase; + colors[this.keyHover] = this.currentHover ? 'inherit':this.colorHover; + + this.$emit('update', colors); + } + }, + + watch: { + colors: { + deep: true, + handler(value) { + let inheritBase = this.colors[this.keyBase] === 'inherit', + inheritHover = this.colors[this.keyHover] === 'inherit'; + + if(!inheritBase) this.colorBase = value[this.keyBase]; + if(!inheritHover) this.colorHover = value[this.keyHover]; + this.defaultBase = inheritBase; + this.defaultHover = inheritHover; + this.currentBase = inheritBase; + this.currentHover = inheritHover; + } + }, + currentBase() { + this.update(); + }, + currentHover() { + this.update(); + } + } + }; +</script> + +<style lang="scss"> + +</style>
\ No newline at end of file diff --git a/src/vue/Components/Theming/CustomColorSet.vue b/src/vue/Components/Theming/CustomColorSet.vue index 3960acd..8d0dbe8 100644 --- a/src/vue/Components/Theming/CustomColorSet.vue +++ b/src/vue/Components/Theming/CustomColorSet.vue @@ -1,16 +1,17 @@ <template> <div class="color-setting"> <translate :say="label"/> - <input type="color" v-model="currentBase" :disabled="!baseEnabled"/> - <input type="color" v-model="currentHover" :disabled="!hoverEnabled"/> + <input-field type="color" v-model="currentBase" :title="baseTitle" :disabled="baseDisabled"/> + <input-field type="color" v-model="currentHover" :title="hoverTitle" :disabled="hoverDisabled"/> </div> </template> <script> import Translate from '@vue/Components/Translate'; + import InputField from '@vue/Components/Form/InputField'; export default { - components: {Translate}, + components: {InputField, Translate}, props: { name : String, @@ -34,13 +35,19 @@ computed: { label() { - return this.type === 'bg' ? 'CustomColorsBackground':'CustomColorsForeground'; + return this.type === 'bg' ? 'BackgroundColorLabel':'ForegroundColorLabel'; }, - baseEnabled() { - return this.defaultBase.length === 7; + baseTitle() { + return this.type === 'bg' ? 'BackgroundColorBaseTitle':'ForegroundColorBaseTitle'; }, - hoverEnabled() { - return this.defaultHover.length === 7; + hoverTitle() { + return this.type === 'bg' ? 'BackgroundColorHoverTitle':'ForegroundColorHoverTitle'; + }, + baseDisabled() { + return this.defaultBase === 'inherit'; + }, + hoverDisabled() { + return this.defaultHover === 'inherit'; } }, @@ -65,11 +72,11 @@ this.currentHover = value[this.keyHover]; } }, - currentBase(value) { - if(this.defaultBase !== value) this.update(); + currentBase() { + this.update(); }, - currentHover(value) { - if(this.defaultHover !== value) this.update(); + currentHover() { + this.update(); } } }; diff --git a/src/vue/Components/Theming/CustomColorToast.vue b/src/vue/Components/Theming/CustomColorToast.vue new file mode 100644 index 0000000..dc133af --- /dev/null +++ b/src/vue/Components/Theming/CustomColorToast.vue @@ -0,0 +1,77 @@ +<template> + <div class="color-setting"> + <translate :say="label"/> + <input-field type="color" v-model="currentBackground" :title="titleBg"/> + <input-field type="color" v-model="currentForeground" :title="titleFg"/> + </div> +</template> + +<script> + import Translate from '@vue/Components/Translate'; + import InputField from '@vue/Components/Form/InputField'; + + export default { + components: {InputField, Translate}, + + props: { + name : String, + label : String, + colors : Object, + titleBg: { + type : String, + default: 'ToastBackgroundTitle' + }, + titleFg: { + type : String, + default: 'ToastForegroundTitle' + } + }, + + data() { + let keyBackground = `${this.name}-bg`, + keyForeground = `${this.name}-fg`; + + return { + defaultBackground: this.colors[keyBackground], + defaultForeground: this.colors[keyForeground], + currentBackground: this.colors[keyBackground], + currentForeground: this.colors[keyForeground], + keyBackground, + keyForeground + }; + }, + + methods: { + update() { + let colors = {}; + + colors[this.keyBackground] = this.currentBackground; + colors[this.keyForeground] = this.currentForeground; + + this.$emit('update', colors); + } + }, + + watch: { + colors: { + deep: true, + handler(value) { + this.defaultBackground = value[this.keyBackground]; + this.defaultForeground = value[this.keyForeground]; + this.currentBackground = value[this.keyBackground]; + this.currentForeground = value[this.keyForeground]; + } + }, + currentBackground() { + this.update(); + }, + currentForeground() { + this.update(); + } + } + }; +</script> + +<style lang="scss"> + +</style>
\ No newline at end of file diff --git a/src/vue/Components/Theming/CustomFontFamily.vue b/src/vue/Components/Theming/CustomFontFamily.vue new file mode 100644 index 0000000..4e521e0 --- /dev/null +++ b/src/vue/Components/Theming/CustomFontFamily.vue @@ -0,0 +1,52 @@ +<template> + <div class="setting"> + <translate tag="label" for="custom-font" say="SettingsCustomFont"/> + <select-field id="custom-font" :options="options" v-model="model"/> + </div> +</template> + +<script> + import SelectField from '@vue/Components/Form/SelectField'; + import Translate from '@vue/Components/Translate'; + + export default { + components: {Translate, SelectField}, + + props: ['value'], + + data() { + return { + model: this.value + }; + }, + + computed: { + options() { + let options = { + default : 'FontDefault', + mono : 'FontMono', + serif : 'FontSerif', + sans : 'FontSans', + light : 'FontLight', + nextcloud: 'FontNextcloud', + dyslexic : 'FontOpenDyslexic' + }; + + if(!options.hasOwnProperty(this.value)) { + options[this.value] = 'FontCustom'; + } + + return options; + } + }, + + watch: { + value(value) { + this.model = value; + }, + model(value) { + if(this.value !== value) this.$emit('input', value); + } + } + }; +</script>
\ No newline at end of file diff --git a/src/vue/Components/Theming/CustomFontSize.vue b/src/vue/Components/Theming/CustomFontSize.vue new file mode 100644 index 0000000..9414057 --- /dev/null +++ b/src/vue/Components/Theming/CustomFontSize.vue @@ -0,0 +1,59 @@ +<template> + <div class="setting"> + <translate tag="label" for="custom-font" say="SettingsCustomFontSize"/> + <select-field id="custom-font" :options="options" v-model="model"/> + </div> +</template> + +<script> + import SelectField from '@vue/Components/Form/SelectField'; + import Translate from '@vue/Components/Translate'; + + export default { + components: {Translate, SelectField}, + + props: { + value: { + default: '11pt' + } + }, + + data() { + return { + model: this.value ? this.value:'11pt' + }; + }, + + mounted() { + this.model = this.value; + }, + + computed: { + options() { + let options = { + '8pt' : 'FontSizeVerySmall', + '10pt': 'FontSizeSmall', + '11pt': 'FontSizeDefault', + '12pt': 'FontSizeMedium', + '14pt': 'FontSizeLarge', + '16pt': 'FontSizeVeryLarge' + }; + + if(!options.hasOwnProperty(this.value)) { + options[this.value] = this.value; + } + + return options; + } + }, + + watch: { + value(value) { + this.model = value; + }, + model(value) { + this.$emit('input', value); + } + } + }; +</script>
\ No newline at end of file diff --git a/src/vue/Components/Theming/CustomTheme.vue b/src/vue/Components/Theming/CustomTheme.vue index 8013b0a..f06b9e3 100644 --- a/src/vue/Components/Theming/CustomTheme.vue +++ b/src/vue/Components/Theming/CustomTheme.vue @@ -1,38 +1,113 @@ <template> - <div> - <translate tag="h4" say="SettingsCustomTheme"/> - <div class="theme-colors"> - <translate tag="h4" say="CustomDefaultElement" /> - <custom-color-set name="element" type="bg" :colors="themeColors" v-on:update="update" /> - <custom-color-set name="element" type="fg" :colors="themeColors" v-on:update="update" /> - <translate tag="h4" say="CustomActiveElement" /> - <custom-color-set name="element-active" type="bg" :colors="themeColors" v-on:update="update" /> - <custom-color-set name="element-active" type="fg" :colors="themeColors" v-on:update="update" /> - <translate tag="h4" say="CustomButtons" /> - <custom-color-alpha name="button" type="bg" :colors="themeColors" v-on:update="update" /> - <custom-color-set name="button" type="bg" :colors="themeColors" v-on:update="update" /> - <custom-color-alpha name="button" type="fg" :colors="themeColors" v-on:update="update" /> - <custom-color-set name="button" type="fg" :colors="themeColors" v-on:update="update"/> - </div> + <div class="theme-colors"> + <translate tag="h4" say="CustomFont"/> + <custom-font-family v-model="font.family"/> + <custom-font-size v-model="font.size"/> + <translate tag="h4" say="CustomDefaultElement"/> + <custom-color-set name="element" type="bg" :colors="colors" v-on:update="update"/> + <custom-color-set name="element" type="fg" :colors="colors" v-on:update="update"/> + <translate tag="h4" say="CustomActiveElement"/> + <custom-color-set name="element-active" type="bg" :colors="colors" v-on:update="update"/> + <custom-color-set name="element-active" type="fg" :colors="colors" v-on:update="update"/> + <translate tag="h4" say="CustomButtons"/> + <custom-color-inherit name="button" type="bg" :colors="colors" v-on:update="update"/> + <custom-color-set name="button" type="bg" :colors="colors" v-on:update="update"/> + <custom-color-inherit name="button" type="fg" :colors="colors" v-on:update="update"/> + <custom-color-set name="button" type="fg" :colors="colors" v-on:update="update"/> + <translate tag="h4" say="CustomToasts"/> + <custom-color-toast name="info" label="ToastInfoColors" :colors="colors" v-on:update="update"/> + <custom-color-toast name="success" label="ToastSuccessColors" :colors="colors" v-on:update="update"/> + <custom-color-toast name="warning" label="ToastWarningColors" :colors="colors" v-on:update="update"/> + <custom-color-toast name="error" label="ToastErrorColors" :colors="colors" v-on:update="update"/> + <translate tag="h4" say="CustomBadge"/> + <badge-icon v-model="badge.icon"/> + <custom-color-toast name="color" + label="CustomBadgeColors" + title-bg="BadgeBackgroundTitle" + title-fg="BadgeForegroundTitle" + :colors="badge" + v-on:update="updateBadge"/> </div> </template> <script> - import LightTheme from '@js/Themes/Light'; + import Theme from '@js/Models/Theme/Theme'; import Translate from '@vue/Components/Translate'; + import MessageService from '@js/Services/MessageService'; + import BadgeIcon from '@vue/Components/Theming/BadgeIcon'; + import CustomFontSize from '@vue/Components/Theming/CustomFontSize'; import CustomColorSet from '@vue/Components/Theming/CustomColorSet'; - import CustomColorAlpha from '@vue/Components/Theming/CustomColorAlpha'; + import CustomFontFamily from '@vue/Components/Theming/CustomFontFamily'; + import CustomColorToast from '@vue/Components/Theming/CustomColorToast'; + import CustomColorInherit from '@vue/Components/Theming/CustomColorInherit'; + import ToastService from '@js/Services/ToastService'; + import ErrorManager from '@js/Manager/ErrorManager'; export default { - components: {CustomColorAlpha, CustomColorSet, Translate}, + components: { + CustomFontFamily, + CustomFontSize, + BadgeIcon, + CustomColorToast, + CustomColorInherit, + CustomColorSet, + Translate + }, + + props: { + theme: Theme + }, + data() { return { - themeColors: LightTheme.colors - } + font : this.theme.getFont(), + badge : this.theme.getBadge(), + colors: this.theme.getColors() + }; }, methods: { update(colors) { - this.themeColors = Object.assign(this.themeColors, colors); + this.colors = Object.assign(this.colors, colors); + this.theme.setColors(this.colors); + this.updatePreview(); + this.saveTheme(); + }, + updateBadge(colors) { + this.badge = Object.assign(this.badge, colors); + this.theme.setBadge(this.badge); + this.saveTheme(); + }, + updatePreview() { + MessageService.send( + { + type : 'theme.preview', + payload : this.theme, + receiver: 'popup' + } + ); + }, + async saveTheme() { + let reply = await MessageService.send({type: 'theme.save', payload: this.theme}); + if(!reply.getPayload().success) { + ToastService.error(['ThemeSaveError', reply.getPayload().message]) + .catch(ErrorManager.catch); + } + } + }, + watch : { + 'font.family'(value) { + this.theme.setFontFamily(value); + this.updatePreview(); + this.saveTheme(); + }, + 'font.size'(value) { + this.theme.setFontSize(value); + this.updatePreview(); + this.saveTheme(); + }, + 'badge.icon'(value) { + this.theme.setBadgeIcon(value); + this.saveTheme(); } } }; diff --git a/src/vue/Components/Theming/PreviewTheme.vue b/src/vue/Components/Theming/PreviewTheme.vue index be3f210..54b5d50 100644 --- a/src/vue/Components/Theming/PreviewTheme.vue +++ b/src/vue/Components/Theming/PreviewTheme.vue @@ -1,28 +1,54 @@ <template> - <iframe :src="url" class="theme-preview"/> + <div class="theme-preview"> + <div class="badge-preview"> + <img :src="icon" alt=""/> + <div class="badge" :style="style">8</div> + </div> + <iframe :src="url" class="popup-preview"/> + </div> </template> <script> + import Theme from '@js/Models/Theme/Theme'; + import SystemService from '@js/Services/SystemService'; + import ErrorManager from '@js/Manager/ErrorManager'; + export default { props: { - theme: { - type: String - } + theme: Theme }, data() { return { - url: 'about:blank' + url : 'about:blank', + icon: null }; }, mounted() { this.url = 'preview.html'; + SystemService.getFileUrl(`/img/${this.theme.getBadgeIcon()}.svg`) + .then((r) => {this.icon = r;}) + .catch(ErrorManager.catch); + }, + + computed: { + style() { + return { + backgroundColor: this.theme.getBadgeBackgroundColor(), + color : this.theme.getBadgeForegroundColor() + }; + } }, watch: { - theme(value, oldValue) { - if(oldValue !== null) this.$el.contentWindow.location.reload(); + theme: { + deep: true, + handler(theme) { + SystemService.getFileUrl(`/img/${theme.getBadgeIcon()}.svg`) + .then((r) => {this.icon = r;}) + .catch(ErrorManager.catch); + } } } }; @@ -30,7 +56,43 @@ <style lang="scss"> .theme-preview { - height : 360px; - border : 1px solid var(--element-hover-bg-color); + .badge-preview { + border : 1px solid var(--element-hover-bg-color); + border-radius : 5px; + width : 40px; + height : 40px; + margin : .5rem auto; + padding : 7px; + position : relative; + + img { + width : 24px; + height : 24px; + } + + .badge { + position : absolute; + width : 1rem; + height : 1rem; + font-size : .8rem; + line-height : 1rem; + text-align : center; + border-radius : 3px; + right : -.4rem; + top : -.4rem; + + &.bottom { + top : auto; + bottom : -.4rem; + } + } + } + + .popup-preview { + height : 360px; + border : 1px solid var(--element-hover-bg-color); + width : 100%; + border-radius : 5px; + } } </style>
\ No newline at end of file diff --git a/src/vue/Components/Toast/Toast.vue b/src/vue/Components/Toast/Toast.vue index 3f64c21..38d16c5 100644 --- a/src/vue/Components/Toast/Toast.vue +++ b/src/vue/Components/Toast/Toast.vue @@ -37,7 +37,7 @@ return className; }, hasCloseButton() { - return this.toast.getCloseable() && this.toast.getDefault() !== 'close' + return this.toast.getCloseable() && this.toast.getDefault() !== 'close'; } }, @@ -72,49 +72,94 @@ } .icon { - float : right; - position : relative; - margin : -.25rem; - padding : .25rem; + float : right; + position : relative; + margin : -.25rem; + padding : .2rem; + text-align : center; + cursor : pointer; + transition : background-color .15s; + border-radius : 3px; + + &:hover { + background-color : var(--success-hv-color); + } + } + + .button { + padding : .5rem; + margin : 0 -.5rem; text-align : center; + border-top : 1px solid var(--info-hv-color); + cursor : pointer; - &:before { - position : relative; - z-index : 1; + &:hover { + border-top-color : transparent; + background-color : var(--info-hv-color); } - &:after { - content : ''; - background-color : var(--element-fg-color); - opacity : 0; - position : absolute; - top : 0; - left : 0; - right : 0; - bottom : 0; - border-radius : 3px; - transition : opacity .15s; - cursor : pointer; + &:first-of-type { + margin-top : .5rem; } - &:hover:after { - opacity : .25; + &:last-of-type { + margin-bottom : -.5rem; + border-radius : 0 0 .25rem .25rem; + } + } + + &.success .toast-content { + background-color : var(--success-bg-color); + color : var(--success-fg-color); + + .icon:hover { + background-color : var(--success-hv-color); + } + + .button { + border-top-color : var(--warning-hv-color); + + &:hover { + border-top-color : transparent; + background-color : var(--warning-hv-color); + } } } &.warning .toast-content { background-color : var(--warning-bg-color); - color: var(--warning-fg-color); + color : var(--warning-fg-color); + + .icon:hover { + background-color : var(--warning-hv-color); + } + + .button { + border-top-color : var(--warning-hv-color); + + &:hover { + border-top-color : transparent; + background-color : var(--warning-hv-color); + } + } } &.error .toast-content { background-color : var(--error-bg-color); - color: var(--error-fg-color); - } + color : var(--error-fg-color); - &.success .toast-content { - background-color : var(--success-bg-color); - color: var(--success-fg-color); + .icon:hover { + background-color : var(--error-hv-color); + } + + .button { + border-top-color : var(--error-hv-color); + + &:hover { + border-top-color : transparent; + background-color : var(--error-hv-color); + } + } } &.has-default { @@ -130,27 +175,5 @@ .message { display : block; } - - .button { - padding : .5rem; - margin : 0 -.5rem; - text-align : center; - border-top : 1px solid rgba(0, 0, 0, .25); - cursor : pointer; - - &:hover { - background-color : rgba(0, 0, 0, .25); - border-top : 1px solid rgba(0, 0, 0, 0); - } - - &:first-of-type { - margin-top : .5rem; - } - - &:last-of-type { - margin-bottom : -.5rem; - border-radius : 0 0 .25rem .25rem; - } - } } </style>
\ No newline at end of file diff --git a/webpack.config.js b/webpack.config.js index 205fee8..af7b166 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -42,7 +42,7 @@ module.exports = env => { ); } - let platformDir = platform === 'firefox' ? `${__dirname}/src/js/Platform`:`${__dirname}/src/platform/${platform}/js/Platform`; + let jsPlatformDir = platform === 'firefox' ? `${__dirname}/src/js/Platform`:`${__dirname}/src/platform/${platform}/js/Platform`; return { mode : production ? 'production':'development', devtool: 'none', @@ -64,9 +64,10 @@ module.exports = env => { alias : { 'vue$' : 'vue/dist/vue.esm.js', '@vue' : `${__dirname}/src/vue`, - '@js/Platform': platformDir, + '@js/Platform': jsPlatformDir, '@js' : `${__dirname}/src/js`, - '@scss' : `${__dirname}/src/scss` + '@scss' : `${__dirname}/src/scss`, + '@scssP' : `${__dirname}/src/platform/${platform}/scss` } }, module : { @@ -102,16 +103,6 @@ module.exports = env => { outputStyle: 'compressed' } } - }, - { - loader : 'sass-resources-loader', - options: { - sourceMap: true, - resources: [ - `${__dirname}/src/scss/includes.scss`, - `${__dirname}/src/platform/${platform}/scss/browser.scss` - ] - } } ] } |