font selector with proper styles and functionality + local font selector

appearance-tab
Henry Jameson 3 months ago
parent eba3a43805
commit d5d37849ea

@ -375,7 +375,6 @@ nav {
user-select: none; user-select: none;
color: var(--text); color: var(--text);
border: none; border: none;
border-radius: var(--roundness);
cursor: pointer; cursor: pointer;
background-color: var(--background); background-color: var(--background);
box-shadow: var(--shadow); box-shadow: var(--shadow);
@ -511,7 +510,6 @@ textarea {
--_padding: 0.5em; --_padding: 0.5em;
border: none; border: none;
border-radius: var(--roundness);
background-color: var(--background); background-color: var(--background);
color: var(--text); color: var(--text);
box-shadow: var(--shadow); 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 // Textareas should have stock line-height + vertical padding instead of huge line-height
textarea.input { textarea.input {
padding: var(--_padding); padding: var(--_padding);
@ -662,22 +671,20 @@ option {
display: inline-flex; display: inline-flex;
vertical-align: middle; vertical-align: middle;
button, > * {
.button-dropdown { --_roundness-left: 0;
--_roundness-right: 0;
position: relative; position: relative;
flex: 1 1 auto; flex: 1 1 auto;
}
&:not(:last-child), > *:first-child {
&:not(:last-child) .button-default { --_roundness-left: var(--roundness);
border-top-right-radius: 0; }
border-bottom-right-radius: 0;
}
&:not(:first-child), > *:last-child {
&:not(:first-child) .button-default { --_roundness-right: var(--roundness);
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
} }
} }

@ -1,13 +1,19 @@
import { set } from 'lodash' import { set, clone } from 'lodash'
import Select from '../select/select.vue' import Select from '../select/select.vue'
import Checkbox from 'src/components/checkbox/checkbox.vue' import Checkbox from 'src/components/checkbox/checkbox.vue'
import Popover from 'src/components/popover/popover.vue' import Popover from 'src/components/popover/popover.vue'
import { library } from '@fortawesome/fontawesome-svg-core' 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( library.add(
faExclamationTriangle faExclamationTriangle,
faKeyboard,
faFont
) )
const PRESET_FONTS = new Set(['serif', 'sans-serif', 'monospace', 'inherit']) const PRESET_FONTS = new Set(['serif', 'sans-serif', 'monospace', 'inherit'])
@ -24,20 +30,38 @@ export default {
emits: ['update:modelValue'], emits: ['update:modelValue'],
data () { data () {
return { return {
localValue: this.modelValue, manualEntry: true,
customFamily: '', localValue: clone(this.modelValue),
familyCustomLocal: null,
availableOptions: [ availableOptions: [
this.noInherit ? '' : 'inherit', this.noInherit ? '' : 'inherit',
'local',
...(this.options || []),
'serif', 'serif',
'sans-serif',
'monospace', 'monospace',
'sans-serif' 'local',
...(this.options || [])
].filter(_ => _) ].filter(_ => _)
} }
}, },
beforeUpdate () { 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: { computed: {
present () { present () {
@ -46,32 +70,39 @@ export default {
defaultValue () { defaultValue () {
return this.localValue || this.fallback || {} return this.localValue || this.fallback || {}
}, },
fontsListCapable () {
return this.$store.state.interface.browserSupport.localFonts
},
fontsList () {
return this.$store.state.interface.localFonts
},
family: { family: {
get () { get () {
return this.defaultValue.family return this.defaultValue.family
}, },
set (v) { set (v) {
this.familyCustomLocal = ''
set(this.localValue, 'family', v) set(this.localValue, 'family', v)
this.$emit('update:modelValue', this.localValue) this.$emit('update:modelValue', this.localValue)
} }
}, },
familyCustom: { familyCustom: {
get () { get () {
return this.customFamily return this.familyCustomLocal
}, },
set (v) { set (v) {
this.customFamily = v this.familyCustomLocal = v
if (!PRESET_FONTS.has(this.customFamily)) { if (!this.isInvalidFamily(v)) {
set(this.localValue, 'family', v) set(this.localValue, 'family', v)
this.$emit('update:modelValue', this.customFamily) this.$emit('update:modelValue', this.localValue)
} }
} }
}, },
invalidCustom () { invalidCustom () {
return PRESET_FONTS.has(this.customFamily) return this.isInvalidFamily(this.familyCustomLocal)
}, },
isCustom () { isCustom () {
return !PRESET_FONTS.has(this.family) return !PRESET_FONTS.has(this.defaultValue.family)
}, },
preset: { preset: {
get () { get () {

@ -10,15 +10,6 @@
> >
{{ label }} {{ label }}
</label> </label>
{{ ' ' }}
<Checkbox
v-if="typeof fallback !== 'undefined'"
:id="name + '-o'"
:modelValue="present"
@change="$emit('update:modelValue', typeof modelValue === 'undefined' ? fallback : undefined)"
>
{{ $t('settings.style.themes3.define') }}
</Checkbox>
<p> <p>
<Select <Select
:id="name + '-font-switcher'" :id="name + '-font-switcher'"
@ -34,15 +25,89 @@
{{ $t('settings.style.themes3.font.' + option) }} {{ $t('settings.style.themes3.font.' + option) }}
</option> </option>
</Select> </Select>
{{ ' ' }}
<Checkbox
v-if="typeof fallback !== 'undefined'"
:id="name + '-o'"
:modelValue="present"
@change="$emit('update:modelValue', typeof modelValue === 'undefined' ? fallback : undefined)"
>
{{ $t('settings.style.themes3.define') }}
</Checkbox>
</p> </p>
<p> <p v-if="isCustom">
<input <label
v-if="isCustom" v-if="fontsList !== null && !manualEntry"
:id="name" :id="name + '-label'"
v-model="familyCustom" :for="preset === 'custom' ? name : name + '-font-switcher'"
class="input custom-font" class="label"
type="text"
> >
{{ $t('settings.style.themes3.font.select') }}
</label>
<label
v-else
:id="name + '-label'"
:for="preset === 'custom' ? name : name + '-font-switcher'"
class="label"
>
<i18n-t
keypath="settings.style.themes3.font.entry"
tag="span"
>
<template #fontFamily>
<code>font-family</code>
</template>
</i18n-t>
</label>
{{ ' ' }}
<span class="btn-group">
<button
v-if="fontsListCapable && (fontsList === null || manualEntry)"
class="btn button-default"
@click="lookupLocalFonts"
:title="$t('settings.style.themes3.font.lookup_local_fonts')"
>
<FAIcon
fixed-width
icon="font"
/>
</button>
<input
v-if="fontsLists === null || manualEntry"
:id="name"
v-model="familyCustom"
class="input custom-font"
type="text"
>
</span>
<span class="btn-group">
<button
v-if="fontsList !== null && !manualEntry"
class="btn button-default"
@click="toggleManualEntry"
:title="$t('settings.style.themes3.font.enter_manually')"
>
<FAIcon
fixed-width
icon="keyboard"
/>
</button>
<Select
v-if="fontsList !== null && !manualEntry"
:id="name + '-local-font-switcher'"
v-model="familyCustom"
class="custom-font"
>
<option
v-for="option in fontsList.values()"
:key="option"
:value="option"
:style="{ fontFamily: option }"
>
{{ option }}
</option>
</Select>
</span>
<span <span
v-if="invalidCustom" v-if="invalidCustom"
class="InvalidIndicator" class="InvalidIndicator"
@ -70,8 +135,9 @@
<style lang="scss"> <style lang="scss">
.font-control { .font-control {
input.custom-font { .custom-font {
min-width: 12em; min-width: 20em;
max-width: 20em;
} }
} }

@ -10,6 +10,10 @@
margin-right: 0.25em; margin-right: 0.25em;
} }
.btn-group .btn {
margin: 0;
}
.style-control { .style-control {
display: flex; display: flex;
align-items: baseline; align-items: baseline;

@ -746,14 +746,19 @@
"more_settings": "More settings", "more_settings": "More settings",
"style": { "style": {
"themes3": { "themes3": {
"define": "Include in theme", "define": "Override",
"font": { "font": {
"local": "Local font (must be installed on computer)", "local": "Local font (must be installed on computer)",
"serif": "Serif (browser default)", "serif": "Serif (browser default)",
"sans-serif": "Sans-serif (browser default)", "sans-serif": "Sans-serif (browser default)",
"monospace": "Monospace (browser default)", "monospace": "Monospace (browser default)",
"inherit": "Same as parent component", "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": { "switcher": {

@ -1,4 +1,5 @@
const defaultState = { const defaultState = {
localFonts: null,
themeApplied: false, themeApplied: false,
temporaryChangesTimeoutId: null, // used for temporary options that revert after a timeout temporaryChangesTimeoutId: null, // used for temporary options that revert after a timeout
temporaryChangesConfirm: () => {}, // used for applying temporary options temporaryChangesConfirm: () => {}, // used for applying temporary options
@ -17,7 +18,8 @@ const defaultState = {
cssFilter: window.CSS && window.CSS.supports && ( cssFilter: window.CSS && window.CSS.supports && (
window.CSS.supports('filter', 'drop-shadow(0 0)') || window.CSS.supports('filter', 'drop-shadow(0 0)') ||
window.CSS.supports('-webkit-filter', 'drop-shadow(0 0)') window.CSS.supports('-webkit-filter', 'drop-shadow(0 0)')
) ),
localFonts: typeof window.queryLocalFonts === 'function'
}, },
layoutType: 'normal', layoutType: 'normal',
globalNotices: [], globalNotices: [],
@ -104,6 +106,10 @@ const interfaceMod = {
}, },
setLastTimeline (state, value) { setLastTimeline (state, value) {
state.lastTimeline = value state.lastTimeline = value
},
setFontsList (state, value) {
console.log(value)
state.localFonts = new Set(value.map(font => font.family))
} }
}, },
actions: { actions: {
@ -178,6 +184,22 @@ const interfaceMod = {
commit('setLayoutType', wideLayout ? 'wide' : normalOrMobile) 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) { setLastTimeline ({ commit }, value) {
commit('setLastTimeline', value) commit('setLastTimeline', value)
} }

Loading…
Cancel
Save