font selector with proper styles and functionality + local font selector
This commit is contained in:
parent
eba3a43805
commit
d5d37849ea
35
src/App.scss
35
src/App.scss
|
@ -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>
|
||||||
</p>
|
{{ ' ' }}
|
||||||
<p>
|
<Checkbox
|
||||||
<input
|
v-if="typeof fallback !== 'undefined'"
|
||||||
v-if="isCustom"
|
:id="name + '-o'"
|
||||||
:id="name"
|
:modelValue="present"
|
||||||
v-model="familyCustom"
|
@change="$emit('update:modelValue', typeof modelValue === 'undefined' ? fallback : undefined)"
|
||||||
class="input custom-font"
|
|
||||||
type="text"
|
|
||||||
>
|
>
|
||||||
|
{{ $t('settings.style.themes3.define') }}
|
||||||
|
</Checkbox>
|
||||||
|
</p>
|
||||||
|
<p v-if="isCustom">
|
||||||
|
<label
|
||||||
|
v-if="fontsList !== null && !manualEntry"
|
||||||
|
:id="name + '-label'"
|
||||||
|
:for="preset === 'custom' ? name : name + '-font-switcher'"
|
||||||
|
class="label"
|
||||||
|
>
|
||||||
|
{{ $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…
Reference in a new issue