From eba3a43805b5777aebb677df88bf5c8533743ead Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Wed, 26 Jun 2024 14:17:22 +0300 Subject: [PATCH 1/2] better font control custom input --- src/components/font_control/font_control.js | 60 ++++++++--- src/components/font_control/font_control.vue | 103 ++++++++++--------- src/i18n/en.json | 11 ++ 3 files changed, 110 insertions(+), 64 deletions(-) diff --git a/src/components/font_control/font_control.js b/src/components/font_control/font_control.js index 92ee3f306..52c3e70a6 100644 --- a/src/components/font_control/font_control.js +++ b/src/components/font_control/font_control.js @@ -1,9 +1,22 @@ import { set } from 'lodash' import Select from '../select/select.vue' +import Checkbox from 'src/components/checkbox/checkbox.vue' +import Popover from 'src/components/popover/popover.vue' + +import { library } from '@fortawesome/fontawesome-svg-core' +import { faExclamationTriangle } from '@fortawesome/free-solid-svg-icons' + +library.add( + faExclamationTriangle +) + +const PRESET_FONTS = new Set(['serif', 'sans-serif', 'monospace', 'inherit']) export default { components: { - Select + Select, + Checkbox, + Popover }, props: [ 'name', 'label', 'modelValue', 'fallback', 'options', 'no-inherit' @@ -11,10 +24,11 @@ export default { emits: ['update:modelValue'], data () { return { - lValue: this.modelValue, + localValue: this.modelValue, + customFamily: '', availableOptions: [ this.noInherit ? '' : 'inherit', - 'custom', + 'local', ...(this.options || []), 'serif', 'monospace', @@ -23,40 +37,52 @@ export default { } }, beforeUpdate () { - this.lValue = this.modelValue + this.localValue = this.modelValue }, computed: { present () { - return typeof this.lValue !== 'undefined' + return typeof this.localValue !== 'undefined' }, - dValue () { - return this.lValue || this.fallback || {} + defaultValue () { + return this.localValue || this.fallback || {} }, family: { get () { - return this.dValue.family + return this.defaultValue.family }, set (v) { - set(this.lValue, 'family', v) - this.$emit('update:modelValue', this.lValue) + set(this.localValue, 'family', v) + this.$emit('update:modelValue', this.localValue) } }, + familyCustom: { + get () { + return this.customFamily + }, + set (v) { + this.customFamily = v + if (!PRESET_FONTS.has(this.customFamily)) { + set(this.localValue, 'family', v) + this.$emit('update:modelValue', this.customFamily) + } + } + }, + invalidCustom () { + return PRESET_FONTS.has(this.customFamily) + }, isCustom () { - return this.preset === 'custom' + return !PRESET_FONTS.has(this.family) }, preset: { get () { - if (this.family === 'serif' || - this.family === 'sans-serif' || - this.family === 'monospace' || - this.family === 'inherit') { + if (PRESET_FONTS.has(this.family)) { return this.family } else { - return 'custom' + return 'local' } }, set (v) { - this.family = v === 'custom' ? '' : v + this.family = v === 'local' ? '' : v } } } diff --git a/src/components/font_control/font_control.vue b/src/components/font_control/font_control.vue index d2d1b3884..e73862a05 100644 --- a/src/components/font_control/font_control.vue +++ b/src/components/font_control/font_control.vue @@ -1,6 +1,6 @@ @@ -55,20 +71,13 @@ diff --git a/src/i18n/en.json b/src/i18n/en.json index 3116843d2..bd18c6ef3 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -745,6 +745,17 @@ "enable_web_push_always_show_tip": "Some browsers (Chromium, Chrome) require that push messages always result in a notification, otherwise generic 'Website was updated in background' is shown, enable this to prevent this notification from showing, as Chrome seem to hide push notifications if tab is in focus. Can result in showing duplicate notifications on other browsers.", "more_settings": "More settings", "style": { + "themes3": { + "define": "Include in theme", + "font": { + "local": "Local font (must be installed on computer)", + "serif": "Serif (browser default)", + "sans-serif": "Sans-serif (browser default)", + "monospace": "Monospace (browser default)", + "inherit": "Same as parent component", + "invalid_custom_reserved": "This is a reserved font name, it will not be saved as custom font - use dropdown instead" + } + }, "switcher": { "keep_color": "Keep colors", "keep_shadows": "Keep shadows", From d5d37849ea6712fa00a077cb219800553b5689ab Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Wed, 26 Jun 2024 17:05:59 +0300 Subject: [PATCH 2/2] font selector with proper styles and functionality + local font selector --- src/App.scss | 35 +++--- src/components/font_control/font_control.js | 61 +++++++--- src/components/font_control/font_control.vue | 104 ++++++++++++++---- .../tabs/theme_tab/theme_tab.scss | 4 + src/i18n/en.json | 9 +- src/modules/interface.js | 24 +++- 6 files changed, 186 insertions(+), 51 deletions(-) diff --git a/src/App.scss b/src/App.scss index 6b8875bf4..9225075b6 100644 --- a/src/App.scss +++ b/src/App.scss @@ -375,7 +375,6 @@ nav { user-select: none; color: var(--text); border: none; - border-radius: var(--roundness); cursor: pointer; background-color: var(--background); box-shadow: var(--shadow); @@ -511,7 +510,6 @@ textarea { --_padding: 0.5em; border: none; - border-radius: var(--roundness); background-color: var(--background); color: var(--text); box-shadow: var(--shadow); @@ -617,6 +615,17 @@ textarea { } } +.input, +.button-default { + --_roundness-left: var(--roundness); + --_roundness-right: var(--roundness); + + border-top-left-radius: var(--_roundness-left); + border-bottom-left-radius: var(--_roundness-left); + border-top-right-radius: var(--_roundness-right); + border-bottom-right-radius: var(--_roundness-right); +} + // Textareas should have stock line-height + vertical padding instead of huge line-height textarea.input { padding: var(--_padding); @@ -662,22 +671,20 @@ option { display: inline-flex; vertical-align: middle; - button, - .button-dropdown { + > * { + --_roundness-left: 0; + --_roundness-right: 0; + position: relative; flex: 1 1 auto; + } - &:not(:last-child), - &:not(:last-child) .button-default { - border-top-right-radius: 0; - border-bottom-right-radius: 0; - } + > *:first-child { + --_roundness-left: var(--roundness); + } - &:not(:first-child), - &:not(:first-child) .button-default { - border-top-left-radius: 0; - border-bottom-left-radius: 0; - } + > *:last-child { + --_roundness-right: var(--roundness); } } diff --git a/src/components/font_control/font_control.js b/src/components/font_control/font_control.js index 52c3e70a6..1e33338fa 100644 --- a/src/components/font_control/font_control.js +++ b/src/components/font_control/font_control.js @@ -1,13 +1,19 @@ -import { set } from 'lodash' +import { set, clone } from 'lodash' import Select from '../select/select.vue' import Checkbox from 'src/components/checkbox/checkbox.vue' import Popover from 'src/components/popover/popover.vue' import { library } from '@fortawesome/fontawesome-svg-core' -import { faExclamationTriangle } from '@fortawesome/free-solid-svg-icons' +import { + faExclamationTriangle, + faKeyboard, + faFont +} from '@fortawesome/free-solid-svg-icons' library.add( - faExclamationTriangle + faExclamationTriangle, + faKeyboard, + faFont ) const PRESET_FONTS = new Set(['serif', 'sans-serif', 'monospace', 'inherit']) @@ -24,20 +30,38 @@ export default { emits: ['update:modelValue'], data () { return { - localValue: this.modelValue, - customFamily: '', + manualEntry: true, + localValue: clone(this.modelValue), + familyCustomLocal: null, availableOptions: [ this.noInherit ? '' : 'inherit', - 'local', - ...(this.options || []), 'serif', + 'sans-serif', 'monospace', - 'sans-serif' + 'local', + ...(this.options || []) ].filter(_ => _) } }, beforeUpdate () { - this.localValue = this.modelValue + this.localValue = clone(this.modelValue) + if (this.familyCustomLocal === null && !this.isInvalidFamily(this.modelValue?.family)) { + this.familyCustomLocal = this.modelValue?.family + } + }, + methods: { + lookupLocalFonts () { + if (!this.fontsList) { + this.$store.dispatch('queryLocalFonts') + } + this.toggleManualEntry() + }, + isInvalidFamily (value) { + return PRESET_FONTS.has(value) || (value === '') + }, + toggleManualEntry () { + this.manualEntry = !this.manualEntry + } }, computed: { present () { @@ -46,32 +70,39 @@ export default { defaultValue () { return this.localValue || this.fallback || {} }, + fontsListCapable () { + return this.$store.state.interface.browserSupport.localFonts + }, + fontsList () { + return this.$store.state.interface.localFonts + }, family: { get () { return this.defaultValue.family }, set (v) { + this.familyCustomLocal = '' set(this.localValue, 'family', v) this.$emit('update:modelValue', this.localValue) } }, familyCustom: { get () { - return this.customFamily + return this.familyCustomLocal }, set (v) { - this.customFamily = v - if (!PRESET_FONTS.has(this.customFamily)) { + this.familyCustomLocal = v + if (!this.isInvalidFamily(v)) { set(this.localValue, 'family', v) - this.$emit('update:modelValue', this.customFamily) + this.$emit('update:modelValue', this.localValue) } } }, invalidCustom () { - return PRESET_FONTS.has(this.customFamily) + return this.isInvalidFamily(this.familyCustomLocal) }, isCustom () { - return !PRESET_FONTS.has(this.family) + return !PRESET_FONTS.has(this.defaultValue.family) }, preset: { get () { diff --git a/src/components/font_control/font_control.vue b/src/components/font_control/font_control.vue index e73862a05..f1b126be0 100644 --- a/src/components/font_control/font_control.vue +++ b/src/components/font_control/font_control.vue @@ -10,15 +10,6 @@ > {{ label }} - {{ ' ' }} - - {{ $t('settings.style.themes3.define') }} -

-

-

- + {{ $t('settings.style.themes3.define') }} + +

+

+ + + {{ ' ' }} + + + + + + + + .font-control { - input.custom-font { - min-width: 12em; + .custom-font { + min-width: 20em; + max-width: 20em; } } diff --git a/src/components/settings_modal/tabs/theme_tab/theme_tab.scss b/src/components/settings_modal/tabs/theme_tab/theme_tab.scss index 5e6331204..4cb37c1e8 100644 --- a/src/components/settings_modal/tabs/theme_tab/theme_tab.scss +++ b/src/components/settings_modal/tabs/theme_tab/theme_tab.scss @@ -10,6 +10,10 @@ margin-right: 0.25em; } + .btn-group .btn { + margin: 0; + } + .style-control { display: flex; align-items: baseline; diff --git a/src/i18n/en.json b/src/i18n/en.json index bd18c6ef3..4b2161651 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -746,14 +746,19 @@ "more_settings": "More settings", "style": { "themes3": { - "define": "Include in theme", + "define": "Override", "font": { "local": "Local font (must be installed on computer)", "serif": "Serif (browser default)", "sans-serif": "Sans-serif (browser default)", "monospace": "Monospace (browser default)", "inherit": "Same as parent component", - "invalid_custom_reserved": "This is a reserved font name, it will not be saved as custom font - use dropdown instead" + "invalid_custom_reserved": "Empty or reserved font name, it will not be saved as custom font - use dropdown instead", + "font_list_unavailable": "Couldn't get locally installed fonts: {error}", + "lookup_local_fonts": "Load list of fonts installed on this computer", + "enter_manually": "Enter font name family manually", + "entry": "Font's {fontFamily}", + "select": "Select local font" } }, "switcher": { diff --git a/src/modules/interface.js b/src/modules/interface.js index e21b4204b..bee503c59 100644 --- a/src/modules/interface.js +++ b/src/modules/interface.js @@ -1,4 +1,5 @@ const defaultState = { + localFonts: null, themeApplied: false, temporaryChangesTimeoutId: null, // used for temporary options that revert after a timeout temporaryChangesConfirm: () => {}, // used for applying temporary options @@ -17,7 +18,8 @@ const defaultState = { cssFilter: window.CSS && window.CSS.supports && ( window.CSS.supports('filter', 'drop-shadow(0 0)') || window.CSS.supports('-webkit-filter', 'drop-shadow(0 0)') - ) + ), + localFonts: typeof window.queryLocalFonts === 'function' }, layoutType: 'normal', globalNotices: [], @@ -104,6 +106,10 @@ const interfaceMod = { }, setLastTimeline (state, value) { state.lastTimeline = value + }, + setFontsList (state, value) { + console.log(value) + state.localFonts = new Set(value.map(font => font.family)) } }, actions: { @@ -178,6 +184,22 @@ const interfaceMod = { commit('setLayoutType', wideLayout ? 'wide' : normalOrMobile) } }, + queryLocalFonts ({ commit, dispatch }) { + window + .queryLocalFonts() + .then((fonts) => { + commit('setFontsList', fonts) + }) + .catch((e) => { + dispatch('pushGlobalNotice', { + messageKey: 'settings.style.themes3.font.font_list_unavailable', + messageArgs: { + error: e + }, + level: 'error' + }) + }) + }, setLastTimeline ({ commit }, value) { commit('setLastTimeline', value) }