forked from srxl/akkoma-fe
massively improved initial theme loading code, added checks and warnings when
loading theme files (import/localStorage/defaults)
This commit is contained in:
parent
93dfb4d352
commit
9336140486
8 changed files with 259 additions and 72 deletions
|
@ -5,6 +5,8 @@ import App from '../App.vue'
|
|||
import { windowWidth } from '../services/window_utils/window_utils'
|
||||
import { getOrCreateApp, getClientToken } from '../services/new_api/oauth.js'
|
||||
import backendInteractorService from '../services/backend_interactor_service/backend_interactor_service.js'
|
||||
import { CURRENT_VERSION } from '../services/theme_data/theme_data.service.js'
|
||||
import { applyTheme } from '../services/style_setter/style_setter.js'
|
||||
|
||||
const getStatusnetConfig = async ({ store }) => {
|
||||
try {
|
||||
|
@ -261,7 +263,7 @@ const checkOAuthToken = async ({ store }) => {
|
|||
try {
|
||||
await store.dispatch('loginUser', store.getters.getUserToken())
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
resolve()
|
||||
|
@ -269,23 +271,29 @@ const checkOAuthToken = async ({ store }) => {
|
|||
}
|
||||
|
||||
const afterStoreSetup = async ({ store, i18n }) => {
|
||||
if (store.state.config.customTheme) {
|
||||
// This is a hack to deal with async loading of config.json and themes
|
||||
// See: style_setter.js, setPreset()
|
||||
window.themeLoaded = true
|
||||
store.dispatch('setOption', {
|
||||
name: 'customTheme',
|
||||
value: store.state.config.customTheme
|
||||
})
|
||||
}
|
||||
|
||||
const width = windowWidth()
|
||||
store.dispatch('setMobileLayout', width <= 800)
|
||||
await setConfig({ store })
|
||||
|
||||
const { customTheme, customThemeSource } = store.state.config
|
||||
const { theme } = store.state.instance
|
||||
const customThemePresent = customThemeSource || customTheme
|
||||
|
||||
if (customThemePresent) {
|
||||
if (customThemeSource && customThemeSource.version === CURRENT_VERSION) {
|
||||
applyTheme(customThemeSource)
|
||||
} else {
|
||||
applyTheme(customTheme)
|
||||
}
|
||||
} else if (theme) {
|
||||
// do nothing, it will load asynchronously
|
||||
} else {
|
||||
console.error('Failed to load any theme!')
|
||||
}
|
||||
|
||||
// Now we can try getting the server settings and logging in
|
||||
await Promise.all([
|
||||
checkOAuthToken({ store }),
|
||||
setConfig({ store }),
|
||||
getTOS({ store }),
|
||||
getInstancePanel({ store }),
|
||||
getStickers({ store }),
|
||||
|
|
|
@ -57,6 +57,8 @@ export default {
|
|||
return {
|
||||
availableStyles: [],
|
||||
selected: this.$store.getters.mergedConfig.theme,
|
||||
themeWarning: undefined,
|
||||
tempImportFile: undefined,
|
||||
|
||||
previewShadows: {},
|
||||
previewColors: {},
|
||||
|
@ -120,12 +122,62 @@ export default {
|
|||
})
|
||||
},
|
||||
mounted () {
|
||||
this.normalizeLocalState(this.$store.getters.mergedConfig.customTheme)
|
||||
this.loadThemeFromLocalStorage()
|
||||
if (typeof this.shadowSelected === 'undefined') {
|
||||
this.shadowSelected = this.shadowsAvailable[0]
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
themeWarningHelp () {
|
||||
if (!this.themeWarning) return
|
||||
const t = this.$t
|
||||
const pre = 'settings.style.switcher.help.'
|
||||
const {
|
||||
origin,
|
||||
themeEngineVersion,
|
||||
type,
|
||||
noActionsPossible
|
||||
} = this.themeWarning
|
||||
if (origin === 'file') {
|
||||
// Loaded v2 theme from file
|
||||
if (themeEngineVersion === 2 && type === 'wrong_version') {
|
||||
return t(pre + 'v2_imported')
|
||||
}
|
||||
if (themeEngineVersion > CURRENT_VERSION) {
|
||||
return t(pre + 'future_version_imported') + ' ' +
|
||||
(
|
||||
noActionsPossible
|
||||
? t(pre + 'snapshot_missing')
|
||||
: t(pre + 'snapshot_present')
|
||||
)
|
||||
}
|
||||
if (themeEngineVersion < CURRENT_VERSION) {
|
||||
return t(pre + 'future_version_imported') + ' ' +
|
||||
(
|
||||
noActionsPossible
|
||||
? t(pre + 'snapshot_missing')
|
||||
: t(pre + 'snapshot_present')
|
||||
)
|
||||
}
|
||||
} else if (origin === 'localStorage') {
|
||||
// FE upgraded from v2
|
||||
if (themeEngineVersion === 2) {
|
||||
return 'upgraded_from_v2'
|
||||
}
|
||||
// Admin downgraded FE
|
||||
if (themeEngineVersion > CURRENT_VERSION) {
|
||||
return noActionsPossible
|
||||
? 'downgraded_theme'
|
||||
: 'downgraded_theme_missing_snapshot'
|
||||
}
|
||||
// Admin upgraded FE
|
||||
if (themeEngineVersion < CURRENT_VERSION) {
|
||||
return noActionsPossible
|
||||
? 'upgraded_theme'
|
||||
: 'upgraded_theme_missing_snapshot'
|
||||
}
|
||||
}
|
||||
},
|
||||
selectedVersion () {
|
||||
return Array.isArray(this.selected) ? 1 : 2
|
||||
},
|
||||
|
@ -308,10 +360,96 @@ export default {
|
|||
Checkbox
|
||||
},
|
||||
methods: {
|
||||
loadTheme (
|
||||
{
|
||||
theme,
|
||||
source,
|
||||
_pleroma_theme_version: fileVersion
|
||||
},
|
||||
origin,
|
||||
forceUseSource = false
|
||||
) {
|
||||
if (!source && !theme) {
|
||||
throw new Error('Can\'t load theme: empty')
|
||||
}
|
||||
const version = (origin === 'localstorage' && !theme.colors)
|
||||
? 'l1'
|
||||
: fileVersion
|
||||
const themeEngineVersion = (source || {}).themeEngineVersion || 2
|
||||
const versionsMatch = themeEngineVersion === CURRENT_VERSION
|
||||
// Force loading of source if user requested it or if snapshot
|
||||
// is unavailable
|
||||
const forcedSourceLoad = (source && forceUseSource) || !theme
|
||||
if (!versionsMatch &&
|
||||
!forcedSourceLoad &&
|
||||
version !== 'l1' &&
|
||||
origin !== 'defaults'
|
||||
) {
|
||||
if (!theme) {
|
||||
this.themeWarning = {
|
||||
origin,
|
||||
noActionsPossible: true,
|
||||
themeEngineVersion,
|
||||
type: 'no_snapshot_old_version'
|
||||
}
|
||||
} else if (!versionsMatch) {
|
||||
this.themeWarning = {
|
||||
origin,
|
||||
noActionsPossible: !source,
|
||||
themeEngineVersion,
|
||||
type: 'wrong_version'
|
||||
}
|
||||
}
|
||||
}
|
||||
this.normalizeLocalState(theme, version, source, forcedSourceLoad)
|
||||
},
|
||||
forceLoadLocalStorage () {
|
||||
this.loadThemeFromLocalStorage(true)
|
||||
},
|
||||
dismissWarning () {
|
||||
this.themeWarning = undefined
|
||||
this.tempImportFile = undefined
|
||||
},
|
||||
forceLoad () {
|
||||
const { origin } = this.themeWarning
|
||||
switch (origin) {
|
||||
case 'localstorage':
|
||||
this.loadThemeFromLocalStorage(true)
|
||||
break
|
||||
case 'file':
|
||||
this.onImport(this.tempImportFile, true)
|
||||
break
|
||||
}
|
||||
},
|
||||
loadThemeFromLocalStorage (confirmLoadSource = false) {
|
||||
const {
|
||||
customTheme: theme,
|
||||
customThemeSource: source
|
||||
} = this.$store.getters.mergedConfig
|
||||
if (!theme && !source) {
|
||||
// Anon user or never touched themes
|
||||
this.loadTheme(
|
||||
this.$store.state.instance.themeData,
|
||||
'defaults',
|
||||
confirmLoadSource
|
||||
)
|
||||
} else {
|
||||
this.loadTheme(
|
||||
{ theme, source },
|
||||
'localStorage',
|
||||
confirmLoadSource
|
||||
)
|
||||
}
|
||||
},
|
||||
setCustomTheme () {
|
||||
this.$store.dispatch('setOption', {
|
||||
name: 'customTheme',
|
||||
value: this.previewTheme
|
||||
})
|
||||
this.$store.dispatch('setOption', {
|
||||
name: 'customThemeSource',
|
||||
value: {
|
||||
themeEngineVersion: CURRENT_VERSION,
|
||||
shadows: this.shadowsLocal,
|
||||
fonts: this.fontsLocal,
|
||||
opacity: this.currentOpacity,
|
||||
|
@ -331,21 +469,16 @@ export default {
|
|||
this.previewColors.mod
|
||||
)
|
||||
},
|
||||
onImport (parsed) {
|
||||
if (parsed._pleroma_theme_version === 1) {
|
||||
this.normalizeLocalState(parsed, 1)
|
||||
} else if (parsed._pleroma_theme_version >= 2) {
|
||||
this.normalizeLocalState(parsed.theme, 2, parsed.source)
|
||||
}
|
||||
onImport (parsed, forceSource = false) {
|
||||
this.tempImportFile = parsed
|
||||
this.loadTheme(parsed, 'file', forceSource)
|
||||
},
|
||||
importValidator (parsed) {
|
||||
const version = parsed._pleroma_theme_version
|
||||
return version >= 1 || version <= 2
|
||||
},
|
||||
clearAll () {
|
||||
const state = this.$store.getters.mergedConfig.customTheme
|
||||
const version = state.colors ? 2 : 'l1'
|
||||
this.normalizeLocalState(this.$store.getters.mergedConfig.customTheme, version, this.$store.getters.mergedConfig.customThemeSource)
|
||||
this.loadThemeFromLocalStorage()
|
||||
},
|
||||
|
||||
// Clears all the extra stuff when loading V1 theme
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
@import '../../_variables.scss';
|
||||
.style-switcher {
|
||||
.theme-warning {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
}
|
||||
.preset-switcher {
|
||||
margin-right: 1em;
|
||||
}
|
||||
|
|
|
@ -1,31 +1,60 @@
|
|||
<template>
|
||||
<div class="style-switcher">
|
||||
<div class="presets-container">
|
||||
<div class="save-load">
|
||||
<ExportImport
|
||||
:export-object="exportedTheme"
|
||||
:export-label="$t("settings.export_theme")"
|
||||
:import-label="$t("settings.import_theme")"
|
||||
:import-failed-text="$t("settings.invalid_theme_imported")"
|
||||
:on-import="onImport"
|
||||
:validator="importValidator"
|
||||
>
|
||||
<template slot="before">
|
||||
<div class="presets">
|
||||
{{ $t('settings.presets') }}
|
||||
<label
|
||||
for="preset-switcher"
|
||||
class="select"
|
||||
<div class="style-switcher">
|
||||
<div class="presets-container">
|
||||
<div class="save-load">
|
||||
<div class="theme-warning" v-if="themeWarning">
|
||||
<div class="alert warning">
|
||||
{{ themeWarningHelp }}
|
||||
</div>
|
||||
<div class="buttons">
|
||||
<template v-if="themeWarning.noActionsPossible">
|
||||
<button
|
||||
class="btn"
|
||||
@click="dismissWarning"
|
||||
>
|
||||
{{ $t('general.dismiss') }}
|
||||
</button>
|
||||
</template>
|
||||
<template v-else>
|
||||
<button
|
||||
class="btn"
|
||||
@click="forceLoad"
|
||||
>
|
||||
{{ $t('settings.style.switcher.load_theme') }}
|
||||
</button>
|
||||
<button
|
||||
class="btn"
|
||||
@click="dismissWarning"
|
||||
>
|
||||
{{ $t('settings.style.switcher.use_snapshot') }}
|
||||
</button>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<ExportImport
|
||||
:export-object="exportedTheme"
|
||||
:export-label="$t("settings.export_theme")"
|
||||
:import-label="$t("settings.import_theme")"
|
||||
:import-failed-text="$t("settings.invalid_theme_imported")"
|
||||
:on-import="onImport"
|
||||
:validator="importValidator"
|
||||
>
|
||||
<template slot="before">
|
||||
<div class="presets">
|
||||
{{ $t('settings.presets') }}
|
||||
<label
|
||||
for="preset-switcher"
|
||||
class="select"
|
||||
>
|
||||
<select
|
||||
id="preset-switcher"
|
||||
v-model="selected"
|
||||
class="preset-switcher"
|
||||
<select
|
||||
id="preset-switcher"
|
||||
v-model="selected"
|
||||
class="preset-switcher"
|
||||
>
|
||||
<option
|
||||
v-for="style in availableStyles"
|
||||
:key="style.name"
|
||||
:value="style"
|
||||
<option
|
||||
v-for="style in availableStyles"
|
||||
:key="style.name"
|
||||
:value="style"
|
||||
:style="{
|
||||
backgroundColor: style[1] || (style.theme || style.source).colors.bg,
|
||||
color: style[3] || (style.theme || style.source).colors.text
|
||||
|
|
|
@ -46,6 +46,7 @@
|
|||
"optional": "optional",
|
||||
"show_more": "Show more",
|
||||
"show_less": "Show less",
|
||||
"dismiss": "Dismiss",
|
||||
"cancel": "Cancel",
|
||||
"disable": "Disable",
|
||||
"enable": "Enable",
|
||||
|
@ -394,7 +395,16 @@
|
|||
"save_load_hint": "\"Keep\" options preserve currently set options when selecting or loading themes, it also stores said options when exporting a theme. When all checkboxes unset, exporting theme will save everything.",
|
||||
"reset": "Reset",
|
||||
"clear_all": "Clear all",
|
||||
"clear_opacity": "Clear opacity"
|
||||
"clear_opacity": "Clear opacity",
|
||||
"load_theme": "Load theme",
|
||||
"use_snapshot": "Keep as is",
|
||||
"help": {
|
||||
"v2_imported": "File you imported was made for older FE. We try to maximize compatibility but there still could be inconsitencies.",
|
||||
"snapshot_present": "Theme snapshot is loaded, so all values are overriden. You can load theme's actual data instead.",
|
||||
"snapshot_missing": "No theme snapshot was in the file so it could look different than originally envisioned.",
|
||||
"future_version_imported": "File you imported was made in newer version of FE.",
|
||||
"older_version_imported": "File you imported was made in older version of FE."
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"color": "Color",
|
||||
|
|
|
@ -5,6 +5,9 @@ const browserLocale = (window.navigator.language || 'en').split('-')[0]
|
|||
|
||||
export const defaultState = {
|
||||
colors: {},
|
||||
theme: undefined,
|
||||
customTheme: undefined,
|
||||
customThemeSource: undefined,
|
||||
hideISP: false,
|
||||
// bad name: actually hides posts of muted USERS
|
||||
hideMutedPosts: undefined, // instance default
|
||||
|
@ -93,10 +96,10 @@ const config = {
|
|||
commit('setOption', { name, value })
|
||||
switch (name) {
|
||||
case 'theme':
|
||||
setPreset(value, commit)
|
||||
setPreset(value)
|
||||
break
|
||||
case 'customTheme':
|
||||
applyTheme(value, commit)
|
||||
applyTheme(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { set } from 'vue'
|
||||
import { setPreset } from '../services/style_setter/style_setter.js'
|
||||
import { getPreset, applyTheme } from '../services/style_setter/style_setter.js'
|
||||
import { instanceDefaultProperties } from './config.js'
|
||||
|
||||
const defaultState = {
|
||||
|
@ -10,6 +10,7 @@ const defaultState = {
|
|||
textlimit: 5000,
|
||||
server: 'http://localhost:4040/',
|
||||
theme: 'pleroma-dark',
|
||||
themeData: undefined,
|
||||
background: '/static/aurora_borealis.jpg',
|
||||
logo: '/static/logo.png',
|
||||
logoMask: true,
|
||||
|
@ -96,6 +97,9 @@ const instance = {
|
|||
dispatch('initializeSocket')
|
||||
}
|
||||
break
|
||||
case 'theme':
|
||||
dispatch('setTheme', value)
|
||||
break
|
||||
}
|
||||
},
|
||||
async getStaticEmoji ({ commit }) {
|
||||
|
@ -147,9 +151,16 @@ const instance = {
|
|||
}
|
||||
},
|
||||
|
||||
setTheme ({ commit }, themeName) {
|
||||
setTheme ({ commit, rootState }, themeName) {
|
||||
commit('setInstanceOption', { name: 'theme', value: themeName })
|
||||
return setPreset(themeName, commit)
|
||||
getPreset(themeName)
|
||||
.then(themeData => {
|
||||
commit('setInstanceOption', { name: 'themeData', value: themeData })
|
||||
// No need to apply theme if there's user theme already
|
||||
const { customTheme } = rootState.config
|
||||
if (customTheme) return
|
||||
applyTheme(themeData.theme)
|
||||
})
|
||||
},
|
||||
fetchEmoji ({ dispatch, state }) {
|
||||
if (!state.customEmojiFetched) {
|
||||
|
|
|
@ -7,7 +7,7 @@ import { getColors, computeDynamicColor } from '../theme_data/theme_data.service
|
|||
// styles that aren't just colors, so user can pick from a few different distinct
|
||||
// styles as well as set their own colors in the future.
|
||||
|
||||
export const setStyle = (href, commit) => {
|
||||
export const setStyle = (href) => {
|
||||
/***
|
||||
What's going on here?
|
||||
I want to make it easy for admins to style this application. To have
|
||||
|
@ -53,8 +53,8 @@ export const setStyle = (href, commit) => {
|
|||
cssEl.addEventListener('load', setDynamic)
|
||||
}
|
||||
|
||||
export const applyTheme = (input, commit) => {
|
||||
const { rules, theme } = generatePreset(input)
|
||||
export const applyTheme = (input) => {
|
||||
const { rules } = generatePreset(input)
|
||||
const head = document.head
|
||||
const body = document.body
|
||||
body.classList.add('hidden')
|
||||
|
@ -69,11 +69,6 @@ export const applyTheme = (input, commit) => {
|
|||
styleSheet.insertRule(`body { ${rules.shadows} }`, 'index-max')
|
||||
styleSheet.insertRule(`body { ${rules.fonts} }`, 'index-max')
|
||||
body.classList.remove('hidden')
|
||||
|
||||
// commit('setOption', { name: 'colors', value: htmlColors })
|
||||
// commit('setOption', { name: 'radii', value: radii })
|
||||
commit('setOption', { name: 'customTheme', value: input })
|
||||
commit('setOption', { name: 'colors', value: theme.colors })
|
||||
}
|
||||
|
||||
export const getCssShadow = (input, usesDropShadow) => {
|
||||
|
@ -387,7 +382,7 @@ export const getThemes = () => {
|
|||
*/
|
||||
export const shadows2to3 = (shadows) => {
|
||||
return Object.entries(shadows).reduce((shadowsAcc, [slotName, shadowDefs]) => {
|
||||
const isDynamic = ({ color }) => console.log(color) || color.startsWith('--')
|
||||
const isDynamic = ({ color }) => color.startsWith('--')
|
||||
const newShadow = shadowDefs.reduce((shadowAcc, def) => [
|
||||
...shadowAcc,
|
||||
{
|
||||
|
@ -399,7 +394,7 @@ export const shadows2to3 = (shadows) => {
|
|||
}, {})
|
||||
}
|
||||
|
||||
export const setPreset = (val, commit) => {
|
||||
export const getPreset = (val) => {
|
||||
return getThemes()
|
||||
.then((themes) => themes[val] ? themes[val] : themes['pleroma-dark'])
|
||||
.then((theme) => {
|
||||
|
@ -420,14 +415,8 @@ export const setPreset = (val, commit) => {
|
|||
data.colors = { bg, fg, text, link, cRed, cBlue, cGreen, cOrange }
|
||||
}
|
||||
|
||||
// This is a hack, this function is only called during initial load.
|
||||
// We want to cancel loading the theme from config.json if we're already
|
||||
// loading a theme from the persisted state.
|
||||
// Needed some way of dealing with the async way of things.
|
||||
// load config -> set preset -> wait for styles.json to load ->
|
||||
// load persisted state -> set colors -> styles.json loaded -> set colors
|
||||
if (!window.themeLoaded) {
|
||||
applyTheme(data, commit)
|
||||
}
|
||||
return { theme: data, source: theme.source }
|
||||
})
|
||||
}
|
||||
|
||||
export const setPreset = (val) => getPreset(val).then(data => applyTheme(data.theme))
|
||||
|
|
Loading…
Reference in a new issue