From c6ccab778f78bf65cebcad1c5e0943d427254098 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Wed, 10 Jul 2024 22:49:56 +0300 Subject: [PATCH] MASSIVELY streamlined theme setting process, now EVERYTHING happens in a vuex action "setTheme" instead of several different applyTheme()s scattered around --- src/boot/after_store.js | 29 +---- .../settings_modal/tabs/appearance_tab.js | 5 + .../settings_modal/tabs/appearance_tab.vue | 9 ++ .../tabs/theme_tab/theme_tab.js | 9 +- src/modules/config.js | 44 ++++---- src/modules/instance.js | 101 ++++++++++++++---- src/services/style_setter/style_setter.js | 23 +--- src/services/theme_data/theme2_to_theme3.js | 1 + .../theme_data/theme_data_3.service.js | 5 +- .../services/theme_data/theme_data3.spec.js | 44 ++++---- 10 files changed, 159 insertions(+), 111 deletions(-) diff --git a/src/boot/after_store.js b/src/boot/after_store.js index b93e28a3..a486bd4c 100644 --- a/src/boot/after_store.js +++ b/src/boot/after_store.js @@ -13,8 +13,7 @@ import VBodyScrollLock from 'src/directives/body_scroll_lock' import { windowWidth, windowHeight } 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, applyConfig, tryLoadCache } from '../services/style_setter/style_setter.js' +import { applyConfig } from '../services/style_setter/style_setter.js' import FaviconService from '../services/favicon_service/favicon_service.js' import { initServiceWorker, updateFocus } from '../services/sw/sw.js' @@ -160,8 +159,6 @@ const setSettings = async ({ apiConfig, staticConfig, store }) => { copyInstanceOption('showFeaturesPanel') copyInstanceOption('hideSitename') copyInstanceOption('sidebarRight') - - return store.dispatch('setTheme', config.theme) } const getTOS = async ({ store }) => { @@ -352,29 +349,7 @@ const afterStoreSetup = async ({ store, i18n }) => { store.dispatch('setInstanceOption', { name: 'server', value: server }) await setConfig({ store }) - - const { customTheme, customThemeSource, forceThemeRecompilation, themeDebug } = store.state.config - const { theme } = store.state.instance - const customThemePresent = customThemeSource || customTheme - - console.log('DEBUG INITIAL', themeDebug, forceThemeRecompilation) - - if (!forceThemeRecompilation && !themeDebug && tryLoadCache()) { - store.commit('setThemeApplied') - } else { - if (customThemePresent) { - if (customThemeSource && customThemeSource.themeEngineVersion === CURRENT_VERSION) { - applyTheme(customThemeSource, () => {}, themeDebug) - } else { - applyTheme(customTheme, () => {}, themeDebug) - } - store.commit('setThemeApplied') - } else if (theme) { - // do nothing, it will load asynchronously - } else { - console.error('Failed to load any theme!') - } - } + await store.dispatch('setTheme') applyConfig(store.state.config) diff --git a/src/components/settings_modal/tabs/appearance_tab.js b/src/components/settings_modal/tabs/appearance_tab.js index 3776464a..d340fd00 100644 --- a/src/components/settings_modal/tabs/appearance_tab.js +++ b/src/components/settings_modal/tabs/appearance_tab.js @@ -29,6 +29,11 @@ const AppearanceTab = { key: mode, value: i - 1, label: this.$t(`settings.forced_roundness_mode_${mode}`) + })), + underlayOverrideModes: ['none', 'opaque', 'transparent'].map((mode, i) => ({ + key: mode, + value: mode, + label: this.$t(`settings.underlay_override_mode_${mode}`) })) } }, diff --git a/src/components/settings_modal/tabs/appearance_tab.vue b/src/components/settings_modal/tabs/appearance_tab.vue index fb24cc6b..d3df76a1 100644 --- a/src/components/settings_modal/tabs/appearance_tab.vue +++ b/src/components/settings_modal/tabs/appearance_tab.vue @@ -182,6 +182,15 @@ {{ $t('settings.force_interface_roundness') }} +
  • + + {{ $t('settings.underlay_overrides') }} + +
  • {{ $t('settings.hide_wallpaper') }} diff --git a/src/components/settings_modal/tabs/theme_tab/theme_tab.js b/src/components/settings_modal/tabs/theme_tab/theme_tab.js index 72e2b625..7ca3b066 100644 --- a/src/components/settings_modal/tabs/theme_tab/theme_tab.js +++ b/src/components/settings_modal/tabs/theme_tab/theme_tab.js @@ -502,6 +502,7 @@ export default { this.$store.dispatch('setOption', { name: 'customTheme', value: { + ignore: true, themeFileVersion: this.selectedVersion, themeEngineVersion: CURRENT_VERSION, ...this.previewTheme @@ -699,13 +700,13 @@ export default { } }, updateTheme3Preview () { - console.log(this.previewTheme) const theme2 = convertTheme2To3(this.previewTheme) const theme3 = init({ - extraRuleset: theme2, + inputRuleset: theme2, ultimateBackgroundColor: '#000000', liteMode: true }) + this.themeV3Preview = getCssRules(theme3.eager) .map(x => { if (x.startsWith('html')) { @@ -722,7 +723,7 @@ export default { watch: { currentRadii () { try { - this.previewTheme.radii = generateRadii({ radii: this.currentRadii }).theme + this.previewTheme.radii = generateRadii({ radii: this.currentRadii }).theme.radii this.radiiInvalid = false } catch (e) { this.radiiInvalid = true @@ -744,7 +745,7 @@ export default { fontsLocal: { handler () { try { - this.previewTheme.fonts = generateFonts({ fonts: this.fontsLocal }).theme + this.previewTheme.fonts = generateFonts({ fonts: this.fontsLocal }).theme.fonts this.fontsInvalid = false } catch (e) { this.fontsInvalid = true diff --git a/src/modules/config.js b/src/modules/config.js index 59d056d9..56151d2a 100644 --- a/src/modules/config.js +++ b/src/modules/config.js @@ -1,10 +1,21 @@ import Cookies from 'js-cookie' -import { setPreset, applyTheme, applyConfig } from '../services/style_setter/style_setter.js' +import { applyConfig } from '../services/style_setter/style_setter.js' import messages from '../i18n/messages' import { set } from 'lodash' import localeService from '../services/locale/locale.service.js' const BACKEND_LANGUAGE_COOKIE_NAME = 'userLanguage' +const APPEARANCE_SETTINGS_KEYS = new Set([ + 'sidebarColumnWidth', + 'contentColumnWidth', + 'notifsColumnWidth', + 'textSize', + 'navbarSize', + 'panelHeaderSize', + 'forcedRoundness', + 'emojiSize', + 'emojiReactionsScale' +]) const browserLocale = (window.navigator.language || 'en').split('-')[0] @@ -81,6 +92,11 @@ export const defaultState = { chatMention: true, polls: true }, + palette: null, + theme3hacks: { + underlay: 'none', + badgeColor: null + }, webPushNotifications: false, webPushAlwaysShowNotifications: false, muteWords: [], @@ -185,7 +201,6 @@ const config = { applyConfig(state) }, setOption (state, { name, value }) { - console.log('SET OPTION', state, name, value) set(state, name, value) }, setHighlight (state, { user, color, type }) { @@ -261,30 +276,23 @@ const config = { } } else { commit('setOption', { name, value }) + if ( + name.startsWith('theme3hacks') || + APPEARANCE_SETTINGS_KEYS.has(name) + ) { + applyConfig(state) + } switch (name) { case 'theme': - setPreset(value) - break - case 'sidebarColumnWidth': - case 'contentColumnWidth': - case 'notifsColumnWidth': - case 'textSize': - case 'navbarSize': - case 'panelHeaderSize': - case 'forcedRoundness': - case 'emojiSize': - case 'emojiReactionsScale': - applyConfig(state) + dispatch('setTheme', { themeName: value, recompile: true }) break case 'customTheme': case 'customThemeSource': { - const { themeDebug } = state - applyTheme(value, () => {}, themeDebug) + if (!value.ignore) dispatch('setTheme', { themeData: value }) break } case 'themeDebug': { - const { customTheme, customThemeSource } = state - applyTheme(customTheme || customThemeSource, () => {}, value) + dispatch('setTheme', { recompile: true }) break } case 'interfaceLanguage': diff --git a/src/modules/instance.js b/src/modules/instance.js index 602503ed..85a966b8 100644 --- a/src/modules/instance.js +++ b/src/modules/instance.js @@ -1,5 +1,6 @@ -import { getPreset, applyTheme } from '../services/style_setter/style_setter.js' -import { CURRENT_VERSION } from '../services/theme_data/theme_data.service.js' +import { getPreset, 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' import apiService from '../services/api/api.service.js' import { instanceDefaultProperties } from './config.js' import { langCodeToCldrName, ensureFinalFallback } from '../i18n/languages.js' @@ -286,9 +287,6 @@ const instance = { dispatch('initializeSocket') } break - case 'theme': - dispatch('setTheme', value) - break } }, async getStaticEmoji ({ commit }) { @@ -378,25 +376,86 @@ const instance = { } }, - setTheme ({ commit, rootState }, themeName) { - commit('setInstanceOption', { name: 'theme', value: themeName }) - getPreset(themeName) - .then(themeData => { - commit('setInstanceOption', { name: 'themeData', value: themeData }) - // No need to apply theme if there's user theme already - const { customTheme, themeDebug } = rootState.config - const { themeApplied } = rootState.interface - if (customTheme || themeApplied) return - - // New theme presets don't have 'theme' property, they use 'source' - const themeSource = themeData.source - if (!themeData.theme || (themeSource && themeSource.themeEngineVersion === CURRENT_VERSION)) { - applyTheme(themeSource, null, themeDebug) + setTheme ({ commit, state, rootState }, { themeName, themeData, recompile } = {}) { + // const { + // themeApplied + // } = rootState.interface + const { + theme: instanceThemeName + } = state + + const { + customTheme: userThemeSnapshot, + customThemeSource: userThemeSource, + forceThemeRecompilation, + themeDebug + } = rootState.config + + const forceRecompile = forceThemeRecompilation || recompile + + // If we're not not forced to recompile try using + // cache (tryLoadCache return true if load successful) + if (!forceRecompile && !themeDebug && tryLoadCache()) { + commit('setThemeApplied') + } + + const normalizeThemeData = (themeData) => { + console.log('NORMAL', themeData) + if (themeData.themeFileVerison === 1) { + return generatePreset(themeData).theme + } + // New theme presets don't have 'theme' property, they use 'source' + const themeSource = themeData.source + + let out // shout, shout let it all out + if (!themeData.theme || (themeSource && themeSource.themeEngineVersion === CURRENT_VERSION)) { + out = themeSource || themeData + } else { + out = themeData.theme + } + + // generatePreset here basically creates/updates "snapshot", + // while also fixing the 2.2 -> 2.3 colors/shadows/etc + return generatePreset(out).theme + } + + let promise = null + + if (themeName) { + // commit('setInstanceOption', { name: 'theme', value: themeName }) + promise = getPreset(themeName) + .then(themeData => { + // commit('setInstanceOption', { name: 'themeData', value: themeData }) + return normalizeThemeData(themeData) + }) + } else if (themeData) { + promise = Promise.resolve(normalizeThemeData(themeData)) + } else { + if (userThemeSource || userThemeSnapshot) { + if (userThemeSource && userThemeSource.themeEngineVersion === CURRENT_VERSION) { + promise = Promise.resolve(normalizeThemeData(userThemeSource)) } else { - applyTheme(themeData.theme, null, themeDebug) + promise = Promise.resolve(normalizeThemeData(userThemeSnapshot)) } - commit('setThemeApplied') + } else if (instanceThemeName) { + promise = getPreset(themeName).then(themeData => normalizeThemeData(themeData)) + } + } + + promise + .then(realThemeData => { + console.log('FR FR 1', realThemeData) + const ruleset = convertTheme2To3(realThemeData) + console.log('FR FR 2', ruleset) + + applyTheme( + ruleset, + () => commit('setThemeApplied'), + themeDebug + ) }) + + return promise }, fetchEmoji ({ dispatch, state }) { if (!state.customEmojiFetched) { diff --git a/src/services/style_setter/style_setter.js b/src/services/style_setter/style_setter.js index 78e7428d..e54a95bf 100644 --- a/src/services/style_setter/style_setter.js +++ b/src/services/style_setter/style_setter.js @@ -1,7 +1,5 @@ import { hex2rgb } from '../color_convert/color_convert.js' -import { generatePreset } from '../theme_data/theme_data.service.js' import { init, getEngineChecksum } from '../theme_data/theme_data_3.service.js' -import { convertTheme2To3 } from '../theme_data/theme2_to_theme3.js' import { getCssRules } from '../theme_data/css_utils.js' import { defaultState } from '../../modules/config.js' import { chunk } from 'lodash' @@ -45,30 +43,20 @@ const adoptStyleSheets = (styles) => { // is nothing to do here. } -export const generateTheme = async (input, callbacks, debug) => { +export const generateTheme = async (inputRuleset, callbacks, debug) => { const { onNewRule = (rule, isLazy) => {}, onLazyFinished = () => {}, onEagerFinished = () => {} } = callbacks - let extraRules - if (input.themeFileVersion === 1) { - extraRules = convertTheme2To3(input) - } else { - const { theme } = generatePreset(input) - extraRules = convertTheme2To3(theme) - } - // Assuming that "worst case scenario background" is panel background since it's the most likely one const themes3 = init({ - extraRules, - ultimateBackgroundColor: extraRules[0].directives['--bg'].split('|')[1].trim(), + inputRuleset, + ultimateBackgroundColor: inputRuleset[0].directives['--bg'].split('|')[1].trim(), debug }) - console.log('DEBUG 2 IS', debug) - getCssRules(themes3.eager, debug).forEach(rule => { // Hacks to support multiple selectors on same component if (rule.match(/::-webkit-scrollbar-button/)) { @@ -162,8 +150,6 @@ export const applyTheme = async (input, onFinish = (data) => {}, debug) => { const eagerStyles = createStyleSheet(EAGER_STYLE_ID) const lazyStyles = createStyleSheet(LAZY_STYLE_ID) - console.log('DEBUG IS', debug) - const { lazyProcessFunc } = await generateTheme( input, { @@ -216,7 +202,6 @@ const extractStyleConfig = ({ textSize } - console.log(forcedRoundness) switch (forcedRoundness) { case 'disable': break @@ -325,5 +310,3 @@ export const getPreset = (val) => { return { theme: data, source: theme.source } }) } - -export const setPreset = (val) => getPreset(val).then(data => applyTheme(data)) diff --git a/src/services/theme_data/theme2_to_theme3.js b/src/services/theme_data/theme2_to_theme3.js index 2c97d18b..b54366bd 100644 --- a/src/services/theme_data/theme2_to_theme3.js +++ b/src/services/theme_data/theme2_to_theme3.js @@ -265,6 +265,7 @@ export const convertTheme2To3 = (data) => { const newRules = [] Object.keys(data.fonts || {}).forEach(key => { if (!fontsKeys.has(key)) return + if (!data.fonts[key]) return const originalFont = data.fonts[key].family const rule = { source: '2to3' } diff --git a/src/services/theme_data/theme_data_3.service.js b/src/services/theme_data/theme_data_3.service.js index e98b19a7..e802a893 100644 --- a/src/services/theme_data/theme_data_3.service.js +++ b/src/services/theme_data/theme_data_3.service.js @@ -150,12 +150,13 @@ const ruleToSelector = genericRuleToSelector(components) export const getEngineChecksum = () => engineChecksum export const init = ({ - extraRuleset, + inputRuleset, ultimateBackgroundColor, debug = false, liteMode = false, rootComponentName = 'Root' }) => { + if (!inputRuleset) throw new Error('Ruleset is null or undefined!') const staticVars = {} const stacked = {} const computed = {} @@ -164,7 +165,7 @@ export const init = ({ ...Object.values(components) .map(c => (c.defaultRules || []).map(r => ({ component: c.name, ...r, source: 'Built-in' }))) .reduce((acc, arr) => [...acc, ...arr], []), - ...extraRuleset + ...inputRuleset ].map(rule => { normalizeCombination(rule) let currentParent = rule.parent diff --git a/test/unit/specs/services/theme_data/theme_data3.spec.js b/test/unit/specs/services/theme_data/theme_data3.spec.js index bb8d785c..92a87de9 100644 --- a/test/unit/specs/services/theme_data/theme_data3.spec.js +++ b/test/unit/specs/services/theme_data/theme_data3.spec.js @@ -66,7 +66,7 @@ describe('Theme Data 3', () => { this.timeout(5000) it('Test initialization without anything', () => { - const out = init([], '#DEADAF') + const out = init({ ruleset: [], ultimateBackgroundColor: '#DEADAF' }) expect(out).to.have.property('eager') expect(out).to.have.property('lazy') @@ -85,13 +85,16 @@ describe('Theme Data 3', () => { }) it('Test initialization with a basic palette', () => { - const out = init([{ - component: 'Root', - directives: { - '--bg': 'color | #008080', - '--fg': 'color | #00C0A0' - } - }], '#DEADAF') + const out = init({ + ruleset: [{ + component: 'Root', + directives: { + '--bg': 'color | #008080', + '--fg': 'color | #00C0A0' + } + }], + ultimateBackgroundColor: '#DEADAF' + }) expect(out.staticVars).to.have.property('bg').equal('#008080') expect(out.staticVars).to.have.property('fg').equal('#00C0A0') @@ -105,17 +108,20 @@ describe('Theme Data 3', () => { }) it('Test initialization with opacity', () => { - const out = init([{ - component: 'Root', - directives: { - '--bg': 'color | #008080' - } - }, { - component: 'Panel', - directives: { - opacity: 0.5 - } - }], '#DEADAF') + const out = init({ + ruleset: [{ + component: 'Root', + directives: { + '--bg': 'color | #008080' + } + }, { + component: 'Panel', + directives: { + opacity: 0.5 + } + }], + ultimateBackgroundColor: '#DEADAF' + }) expect(out.staticVars).to.have.property('bg').equal('#008080')