commit: ba4be2cb22aa508589f89fe0c44e8da241e91321
parent f0957bdb4ff1fed060696ac953679cef241e92be
Author: Henry Jameson <me@hjkos.com>
Date: Wed, 2 Oct 2024 02:35:52 +0300
yet another massive overhaul on how themes are loaded/applied
Diffstat:
7 files changed, 380 insertions(+), 258 deletions(-)
diff --git a/src/boot/after_store.js b/src/boot/after_store.js
@@ -350,7 +350,7 @@ const afterStoreSetup = async ({ store, i18n }) => {
store.dispatch('setInstanceOption', { name: 'server', value: server })
await setConfig({ store })
- await store.dispatch('setTheme')
+ await store.dispatch('applyTheme', { recompile: false })
applyConfig(store.state.config)
diff --git a/src/components/settings_modal/tabs/appearance_tab.js b/src/components/settings_modal/tabs/appearance_tab.js
@@ -8,9 +8,6 @@ import FontControl from 'src/components/font_control/font_control.vue'
import { normalizeThemeData } from 'src/modules/interface'
-import {
- getThemeResources
-} from 'src/services/style_setter/style_setter.js'
import { convertTheme2To3 } from 'src/services/theme_data/theme2_to_theme3.js'
import { init } from 'src/services/theme_data/theme_data_3.service.js'
import {
@@ -42,8 +39,8 @@ const AppearanceTab = {
'link',
'text',
'cRed',
- 'cBlue',
'cGreen',
+ 'cBlue',
'cOrange'
],
intersectionObserver: null,
@@ -75,37 +72,50 @@ const AppearanceTab = {
Preview
},
mounted () {
- getThemeResources('/static/styles.json')
- .then((themes) => {
- this.availableStyles = Object
- .entries(themes)
- .map(([key, data]) => ({ key, data, name: data.name || data[0], version: 'v2' }))
- })
+ const updateIndex = (resource) => {
+ const capitalizedResource = resource[0].toUpperCase() + resource.slice(1)
+ const currentIndex = this.$store.state.instance[`${resource}sIndex`]
- getThemeResources('/static/palettes/index.json')
- .then((palettes) => {
- const result = {}
- console.log(palettes)
- Object.entries(palettes).forEach(([k, v]) => {
- if (Array.isArray(v)) {
- const [
- name,
- background,
- foreground,
- text,
- link,
- cRed = '#FF0000',
- cBlue = '#0000FF',
- cGreen = '#00FF00',
- cOrange = '#E3FF00'
- ] = v
- result[k] = { name, background, foreground, text, link, cRed, cBlue, cGreen, cOrange }
- } else {
- result[k] = v
- }
- })
- this.availablePalettes = result
+ let promise
+ if (currentIndex) {
+ promise = Promise.resolve(currentIndex)
+ } else {
+ promise = this.$store.dispatch(`fetch${capitalizedResource}sIndex`)
+ }
+
+ return promise.then(index => {
+ return Object
+ .entries(index)
+ .map(([k, func]) => [k, func()])
})
+ }
+
+ updateIndex('theme').then(themes => {
+ themes.forEach(([key, themePromise]) => themePromise.then(data => {
+ this.availableStyles.push({ key, data, name: data.name, version: 'v2' })
+ }))
+ })
+
+ updateIndex('palette').then(palettes => {
+ palettes.forEach(([key, palettePromise]) => palettePromise.then(v => {
+ if (Array.isArray(v)) {
+ const [
+ name,
+ background,
+ foreground,
+ text,
+ link,
+ cRed = '#FF0000',
+ cGreen = '#00FF00',
+ cBlue = '#0000FF',
+ cOrange = '#E3FF00'
+ ] = v
+ this.availablePalettes.push({ key, name, background, foreground, text, link, cRed, cBlue, cGreen, cOrange })
+ } else {
+ this.availablePalettes.push({ key, ...v })
+ }
+ }))
+ })
if (window.IntersectionObserver) {
this.intersectionObserver = new IntersectionObserver((entries, observer) => {
@@ -186,11 +196,13 @@ const AppearanceTab = {
const { theme } = this.mergedConfig
return key === theme
},
- setTheme (name) {
- this.$store.dispatch('setTheme', { themeName: name, saveData: true, recompile: true })
+ async setTheme (name) {
+ await this.$store.dispatch('setTheme', name)
+ this.$store.dispatch('applyTheme')
},
- setPalette (name) {
- this.$store.dispatch('setPalette', { paletteData: name })
+ async setPalette (name) {
+ await this.$store.dispatch('setPalette', name)
+ this.$store.dispatch('applyTheme')
},
previewTheme (key, input) {
let theme3
diff --git a/src/components/settings_modal/tabs/theme_tab/theme_tab.js b/src/components/settings_modal/tabs/theme_tab/theme_tab.js
@@ -5,9 +5,6 @@ import {
relativeLuminance
} from 'src/services/color_convert/color_convert.js'
import {
- getThemeResources
-} from 'src/services/style_setter/style_setter.js'
-import {
newImporter,
newExporter
} from 'src/services/export_import/export_import.js'
@@ -123,12 +120,22 @@ export default {
}
},
created () {
- const self = this
+ const currentIndex = this.$store.state.instance.themesIndex
- getThemeResources('/static/styles.json')
- .then((themesComplete) => {
- self.availableStyles = Object.values(themesComplete)
- })
+ let promise
+ if (currentIndex) {
+ promise = Promise.resolve(currentIndex)
+ } else {
+ promise = this.$store.dispatch('fetchThemesIndex')
+ }
+
+ promise.then(themesIndex => {
+ Object
+ .values(themesIndex)
+ .forEach(themeFunc => {
+ themeFunc().then(themeData => this.availableStyles.push(themeData))
+ })
+ })
},
mounted () {
this.loadThemeFromLocalStorage()
diff --git a/src/modules/instance.js b/src/modules/instance.js
@@ -42,6 +42,9 @@ const defaultState = {
registrationOpen: true,
server: 'http://localhost:4040/',
textlimit: 5000,
+ themesIndex: undefined,
+ stylesIndex: undefined,
+ palettesIndex: undefined,
themeData: undefined, // used for theme editor v2
vapidPublicKey: undefined,
diff --git a/src/modules/interface.js b/src/modules/interface.js
@@ -1,4 +1,4 @@
-import { getPreset, applyTheme, tryLoadCache } from '../services/style_setter/style_setter.js'
+import { getResourcesIndex, applyTheme, tryLoadCache } from '../services/style_setter/style_setter.js'
import { CURRENT_VERSION, generatePreset } from 'src/services/theme_data/theme_data.service.js'
import { convertTheme2To3 } from 'src/services/theme_data/theme2_to_theme3.js'
@@ -212,35 +212,229 @@ const interfaceMod = {
setLastTimeline ({ commit }, value) {
commit('setLastTimeline', value)
},
- setPalette ({ dispatch, commit }, { paletteData }) {
- console.log('PAL', paletteData)
- commit('setOption', { name: 'userPalette', value: paletteData })
- dispatch('setTheme', { themeName: null, recompile: true })
+ async fetchPalettesIndex ({ commit, state }) {
+ try {
+ const value = await getResourcesIndex('/static/palettes/index.json')
+ commit('setInstanceOption', { name: 'palettesIndex', value })
+ return value
+ } catch (e) {
+ console.error('Could not fetch palettes index', e)
+ return {}
+ }
+ },
+ setPalette ({ dispatch, commit }, value) {
+ dispatch('resetV3')
+ dispatch('resetV2')
+
+ commit('setOption', { name: 'palette', value })
+
+ dispatch('applyTheme')
+ },
+ setPaletteCustom ({ dispatch, commit }, value) {
+ dispatch('resetV3')
+ dispatch('resetV2')
+
+ commit('setOption', { name: 'paletteCustomData', value })
+
+ dispatch('applyTheme')
+ },
+ async fetchStylesIndex ({ commit, state }) {
+ try {
+ const value = await getResourcesIndex('/static/styles/index.json')
+ commit('setInstanceOption', { name: 'stylesIndex', value })
+ return value
+ } catch (e) {
+ console.error('Could not fetch styles index', e)
+ return Promise.resolve({})
+ }
+ },
+ setStyle ({ dispatch, commit }, value) {
+ dispatch('resetV3')
+ dispatch('resetV2')
+
+ commit('setOption', { name: 'style', value })
+
+ dispatch('applyTheme')
+ },
+ setStyleCustom ({ dispatch, commit }, value) {
+ dispatch('resetV3')
+ dispatch('resetV2')
+
+ commit('setOption', { name: 'styleCustomData', value })
+
+ dispatch('applyTheme')
+ },
+ async fetchThemesIndex ({ commit, state }) {
+ try {
+ const value = await getResourcesIndex('/static/styles.json')
+ commit('setInstanceOption', { name: 'themesIndex', value })
+ return value
+ } catch (e) {
+ console.error('Could not fetch themes index', e)
+ return Promise.resolve({})
+ }
+ },
+ setTheme ({ dispatch, commit }, value) {
+ dispatch('resetV3')
+ dispatch('resetV2')
+
+ commit('setOption', { name: 'theme', value })
+
+ dispatch('applyTheme')
},
- setTheme ({ commit, rootState }, { themeName, themeData, recompile, saveData } = {}) {
+ setThemeCustom ({ dispatch, commit }, value) {
+ dispatch('resetV3')
+ dispatch('resetV2')
+
+ commit('setOption', { name: 'customTheme', value })
+ commit('setOption', { name: 'customThemeSource', value })
+
+ dispatch('applyTheme')
+ },
+ resetV3 ({ dispatch, commit }) {
+ commit('setOption', { name: 'style', value: null })
+ commit('setOption', { name: 'styleCustomData', value: null })
+ commit('setOption', { name: 'palette', value: null })
+ commit('setOption', { name: 'paletteCustomData', value: null })
+ },
+ resetV2 ({ dispatch, commit }) {
+ commit('setOption', { name: 'theme', value: null })
+ commit('setOption', { name: 'customTheme', value: null })
+ commit('setOption', { name: 'customThemeSource', value: null })
+ },
+ async applyTheme (
+ { dispatch, commit, rootState },
+ { recompile = true } = {}
+ ) {
+ // If we're not not forced to recompile try using
+ // cache (tryLoadCache return true if load successful)
+
const {
- theme: instanceThemeName
+ style: instanceStyleName,
+ palette: instancePaletteName
+ } = rootState.instance
+ let {
+ theme: instanceThemeV2Name,
+ themesIndex,
+ stylesIndex,
+ palettesIndex
} = rootState.instance
const {
- theme: userThemeName,
- customTheme: userThemeSnapshot,
- customThemeSource: userThemeSource,
- userPalette,
+ style: userStyleName,
+ styleCustomData: userStyleCustomData,
+ palette: userPaletteName,
+ paletteCustomData: userPaletteCustomData,
forceThemeRecompilation,
themeDebug,
theme3hacks
} = rootState.config
+ let {
+ theme: userThemeV2Name,
+ customTheme: userThemeV2Snapshot,
+ customThemeSource: userThemeV2Source
- const userPaletteIss = (() => {
- if (!userPalette) return null
+ } = rootState.config
+
+ const forceRecompile = forceThemeRecompilation || recompile
+ if (!forceRecompile && !themeDebug && tryLoadCache()) {
+ return commit('setThemeApplied')
+ }
+
+ let majorVersionUsed
+
+ if (userPaletteName || userPaletteCustomData ||
+ userStyleName || userStyleCustomData ||
+ instancePaletteName ||
+ instanceStyleName
+ ) {
+ // Palette and/or style overrides V2 themes
+ instanceThemeV2Name = null
+ userThemeV2Name = null
+ userThemeV2Source = null
+ userThemeV2Snapshot = null
+
+ majorVersionUsed = 'v3'
+ if (!palettesIndex || !stylesIndex) {
+ const result = await Promise.all([
+ dispatch('fetchPalettesIndex'),
+ dispatch('fetchStylesIndex')
+ ])
+
+ palettesIndex = result[0]
+ stylesIndex = result[1]
+ }
+ } else {
+ majorVersionUsed = 'v2'
+ // Promise.all just to be uniform with v3
+ const result = await Promise.all([
+ dispatch('fetchThemesIndex')
+ ])
+ themesIndex = result[0]
+ }
+
+ let styleDataUsed = null
+ let styleNameUsed = null
+ let paletteDataUsed = null
+ let paletteNameUsed = null
+ let themeNameUsed = null
+ let themeDataUsed = null
+
+ if (majorVersionUsed === 'v3') {
+ if (userStyleCustomData) {
+ styleNameUsed = 'custom' // custom data overrides name
+ styleDataUsed = userStyleCustomData
+ } else {
+ styleNameUsed = userStyleName || instanceStyleName
+ let styleFetchFunc = stylesIndex[themeNameUsed]
+ if (!styleFetchFunc) {
+ const newName = Object.keys(stylesIndex)[0]
+ styleFetchFunc = stylesIndex[newName]
+ console.warn(`Style with id '${styleNameUsed}' not found, falling back to '${newName}'`)
+ }
+ styleDataUsed = await styleFetchFunc?.()
+ }
+
+ if (userPaletteCustomData) {
+ paletteNameUsed = 'custom' // custom data overrides name
+ paletteDataUsed = userPaletteCustomData
+ } else {
+ paletteNameUsed = userPaletteName || instanceStyleName
+ let paletteFetchFunc = palettesIndex[themeNameUsed]
+ if (!paletteFetchFunc) {
+ const newName = Object.keys(palettesIndex)[0]
+ paletteFetchFunc = palettesIndex[newName]
+ console.warn(`Palette with id '${paletteNameUsed}' not found, falling back to '${newName}'`)
+ }
+ paletteDataUsed = await paletteFetchFunc?.()
+ }
+ } else {
+ if (userThemeV2Snapshot || userThemeV2Source) {
+ themeNameUsed = 'custom' // custom data overrides name
+ themeDataUsed = userThemeV2Snapshot || userThemeV2Source
+ } else {
+ themeNameUsed = userThemeV2Name || instanceThemeV2Name
+ let themeFetchFunc = themesIndex[themeNameUsed]
+ if (!themeFetchFunc) {
+ const newName = Object.keys(themesIndex)[0]
+ themeFetchFunc = themesIndex[newName]
+ console.warn(`Theme with id '${themeNameUsed}' not found, falling back to '${newName}'`)
+ }
+ themeDataUsed = await themeFetchFunc?.()
+ }
+ // Themes v2 editor support
+ commit('setInstanceOption', { name: 'themeData', value: themeDataUsed })
+ }
+
+ const paletteIss = (() => {
+ if (!paletteDataUsed) return null
const result = {
component: 'Root',
directives: {}
}
Object
- .entries(userPalette)
+ .entries(paletteDataUsed)
.filter(([k]) => k !== 'name')
.forEach(([k, v]) => {
let issRootDirectiveName
@@ -258,139 +452,84 @@ const interfaceMod = {
})
return result
})()
- const actualThemeName = userThemeName || instanceThemeName
-
- const forceRecompile = forceThemeRecompilation || recompile
-
- let promise = null
-
- console.log('TEST', actualThemeName, themeData)
-
- if (themeData) {
- promise = Promise.resolve(normalizeThemeData(themeData))
- } else if (themeName) {
- promise = getPreset(themeName).then(themeData => normalizeThemeData(themeData))
- } else if (themeName === null) {
- promise = Promise.resolve(null)
- } else if (userThemeSource || userThemeSnapshot) {
- promise = Promise.resolve(normalizeThemeData({
- _pleroma_theme_version: 2,
- theme: userThemeSnapshot,
- source: userThemeSource
- }))
- } else if (actualThemeName && actualThemeName !== 'custom') {
- promise = getPreset(actualThemeName).then(themeData => {
- const realThemeData = normalizeThemeData(themeData)
- if (actualThemeName === instanceThemeName) {
- // This sole line is the reason why this whole block is above the recompilation check
- commit('setInstanceOption', { name: 'themeData', value: { theme: realThemeData } })
- }
- return realThemeData
- })
- } else {
- throw new Error('Cannot load any theme!')
- }
-
- // If we're not not forced to recompile try using
- // cache (tryLoadCache return true if load successful)
- if (!forceRecompile && !themeDebug && tryLoadCache()) {
- commit('setThemeApplied')
- return
- }
- promise
- .then(realThemeData => {
- const theme2ruleset = realThemeData ? convertTheme2To3(realThemeData) : null
+ const theme2ruleset = themeDataUsed && convertTheme2To3(normalizeThemeData(themeDataUsed))
+ const hacks = []
- if (saveData) {
- commit('setOption', { name: 'theme', value: themeName || actualThemeName })
- commit('setOption', { name: 'customTheme', value: realThemeData })
- commit('setOption', { name: 'customThemeSource', value: realThemeData })
+ Object.entries(theme3hacks).forEach(([key, value]) => {
+ switch (key) {
+ case 'fonts': {
+ Object.entries(theme3hacks.fonts).forEach(([fontKey, font]) => {
+ if (!font?.family) return
+ switch (fontKey) {
+ case 'interface':
+ hacks.push({
+ component: 'Root',
+ directives: {
+ '--font': 'generic | ' + font.family
+ }
+ })
+ break
+ case 'input':
+ hacks.push({
+ component: 'Input',
+ directives: {
+ '--font': 'generic | ' + font.family
+ }
+ })
+ break
+ case 'post':
+ hacks.push({
+ component: 'RichContent',
+ directives: {
+ '--font': 'generic | ' + font.family
+ }
+ })
+ break
+ case 'monospace':
+ hacks.push({
+ component: 'Root',
+ directives: {
+ '--monoFont': 'generic | ' + font.family
+ }
+ })
+ break
+ }
+ })
+ break
}
-
- const hacks = []
-
- Object.entries(theme3hacks).forEach(([key, value]) => {
- switch (key) {
- case 'fonts': {
- Object.entries(theme3hacks.fonts).forEach(([fontKey, font]) => {
- if (!font?.family) return
- switch (fontKey) {
- case 'interface':
- hacks.push({
- component: 'Root',
- directives: {
- '--font': 'generic | ' + font.family
- }
- })
- break
- case 'input':
- hacks.push({
- component: 'Input',
- directives: {
- '--font': 'generic | ' + font.family
- }
- })
- break
- case 'post':
- hacks.push({
- component: 'RichContent',
- directives: {
- '--font': 'generic | ' + font.family
- }
- })
- break
- case 'monospace':
- hacks.push({
- component: 'Root',
- directives: {
- '--monoFont': 'generic | ' + font.family
- }
- })
- break
- }
- })
- break
+ case 'underlay': {
+ if (value !== 'none') {
+ const newRule = {
+ component: 'Underlay',
+ directives: {}
}
- case 'underlay': {
- if (value !== 'none') {
- const newRule = {
- component: 'Underlay',
- directives: {}
- }
- if (value === 'opaque') {
- newRule.directives.opacity = 1
- newRule.directives.background = '--wallpaper'
- }
- if (value === 'transparent') {
- newRule.directives.opacity = 0
- }
- hacks.push(newRule)
- }
- break
+ if (value === 'opaque') {
+ newRule.directives.opacity = 1
+ newRule.directives.background = '--wallpaper'
+ }
+ if (value === 'transparent') {
+ newRule.directives.opacity = 0
}
+ hacks.push(newRule)
}
- })
-
- const ruleset = [
- ...hacks
- ]
- if (!theme2ruleset && userPaletteIss) {
- ruleset.unshift(userPaletteIss)
- }
-
- if (theme2ruleset) {
- ruleset.unshift(...theme2ruleset)
+ break
}
+ }
+ })
- applyTheme(
- ruleset,
- () => commit('setThemeApplied'),
- themeDebug
- )
- })
+ const rulesetArray = [
+ theme2ruleset,
+ styleDataUsed,
+ paletteIss,
+ hacks
+ ].filter(x => x)
- return promise
+ return applyTheme(
+ rulesetArray.flat(),
+ () => commit('setThemeApplied'),
+ themeDebug
+ )
}
}
}
@@ -398,7 +537,6 @@ const interfaceMod = {
export default interfaceMod
export const normalizeThemeData = (input) => {
- console.log(input)
if (Array.isArray(input)) {
const themeData = { colors: {} }
themeData.colors.bg = input[1]
diff --git a/src/services/style_setter/style_setter.js b/src/services/style_setter/style_setter.js
@@ -1,4 +1,3 @@
-import { hex2rgb } from '../color_convert/color_convert.js'
import { init, getEngineChecksum } from '../theme_data/theme_data_3.service.js'
import { getCssRules } from '../theme_data/css_utils.js'
import { defaultState } from '../../modules/config.js'
@@ -53,7 +52,7 @@ export const generateTheme = async (inputRuleset, callbacks, debug) => {
// Assuming that "worst case scenario background" is panel background since it's the most likely one
const themes3 = init({
inputRuleset,
- ultimateBackgroundColor: inputRuleset[0].directives['--bg'].split('|')[1].trim(),
+ ultimateBackgroundColor: inputRuleset[0].directives['--bg']?.split('|')[1].trim() || '#000000',
debug
})
@@ -256,73 +255,36 @@ export const applyConfig = (input) => {
body.classList.remove('hidden')
}
-export const getThemeResources = (url) => {
+export const getResourcesIndex = async (url) => {
const cache = 'no-store'
- return window.fetch(url, { cache })
- .then((data) => data.json())
- .then((resources) => {
- return Object.entries(resources).map(([k, v]) => {
- let promise = null
- if (typeof v === 'object') {
- promise = Promise.resolve(v)
- } else if (typeof v === 'string') {
- promise = window.fetch(v, { cache })
- .then((data) => data.json())
- .catch((e) => {
- console.error(e)
- return null
- })
- }
- return [k, promise]
- })
- })
- .then((promises) => {
- return promises
- .reduce((acc, [k, v]) => {
- acc[k] = v
- return acc
- }, {})
- })
- .then((promises) => {
- return Promise.all(
- Object.entries(promises)
- .map(([k, v]) => v.then(res => [k, res]))
- )
- })
- .then(themes => themes.reduce((acc, [k, v]) => {
- if (v) {
- return {
- ...acc,
- [k]: v
- }
- } else {
- return acc
- }
- }, {}))
-}
-
-export const getPreset = (val) => {
- return getThemeResources('/static/styles.json')
- .then((themes) => themes[val] ? themes[val] : themes['pleroma-dark'])
- .then((theme) => {
- const isV1 = Array.isArray(theme)
- const data = isV1 ? {} : theme.theme
-
- if (isV1) {
- const bg = hex2rgb(theme[1])
- const fg = hex2rgb(theme[2])
- const text = hex2rgb(theme[3])
- const link = hex2rgb(theme[4])
-
- const cRed = hex2rgb(theme[5] || '#FF0000')
- const cGreen = hex2rgb(theme[6] || '#00FF00')
- const cBlue = hex2rgb(theme[7] || '#0000FF')
- const cOrange = hex2rgb(theme[8] || '#E3FF00')
-
- data.colors = { bg, fg, text, link, cRed, cBlue, cGreen, cOrange }
- }
-
- return { theme: data, source: theme.source }
- })
+ try {
+ const data = await window.fetch(url, { cache })
+ const resources = await data.json()
+ return Object.fromEntries(
+ Object
+ .entries(resources)
+ .map(([k, v]) => {
+ if (typeof v === 'object') {
+ return [k, () => Promise.resolve(v)]
+ } else if (typeof v === 'string') {
+ return [
+ k,
+ () => window
+ .fetch(v, { cache })
+ .then((data) => data.json())
+ .catch((e) => {
+ console.error(e)
+ return null
+ })
+ ]
+ } else {
+ console.error(`Unknown resource format - ${k} is a ${typeof v}`)
+ return [k, null]
+ }
+ })
+ )
+ } catch (e) {
+ return Promise.reject(e)
+ }
}
diff --git a/static/palettes/index.json b/static/palettes/index.json
@@ -8,8 +8,8 @@
"text": "#b9b9b9",
"link": "#baaa9c",
"cRed": "#d31014",
- "cBlue": "#0fa00f",
- "cGreen": "#0095ff",
+ "cGreen": "#0fa00f",
+ "cBlue": "#0095ff",
"cOrange": "#ffa500"
},
"pleroma-amoled": [ "Pleroma Dark AMOLED", "#000000", "#111111", "#b0b0b1", "#d8a070", "#aa0000", "#0fa00f", "#0095ff", "#d59500"],
@@ -20,8 +20,8 @@
"link": "#81a2be",
"text": "#c5c8c6",
"cRed": "#cc6666",
- "cBlue": "#8abeb7",
- "cGreen": "#b5bd68",
+ "cGreen": "#8abeb7",
+ "cBlue": "#b5bd68",
"cOrange": "#de935f",
"_cYellow": "#f0c674",
"_cPurple": "#b294bb"