From 73fbe89a4b4e545796e9cc6aae707de0a4eed3a1 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Wed, 25 Oct 2023 18:58:33 +0300 Subject: initial work on showing notifications through serviceworkers --- src/boot/after_store.js | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'src/boot/after_store.js') diff --git a/src/boot/after_store.js b/src/boot/after_store.js index 395d4834..6489ef87 100644 --- a/src/boot/after_store.js +++ b/src/boot/after_store.js @@ -16,6 +16,7 @@ import backendInteractorService from '../services/backend_interactor_service/bac import { CURRENT_VERSION } from '../services/theme_data/theme_data.service.js' import { applyTheme, 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' let staticInitialResults = null @@ -344,6 +345,9 @@ const afterStoreSetup = async ({ store, i18n }) => { store.dispatch('setLayoutHeight', windowHeight()) FaviconService.initFaviconService() + initServiceWorker() + + window.addEventListener('focus', () => updateFocus()) const overrides = window.___pleromafe_dev_overrides || {} const server = (typeof overrides.target !== 'undefined') ? overrides.target : window.location.origin -- cgit v1.2.3-70-g09d2 From a17defc5abfe60b6aa0dc3275dac2cbec507472a Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Thu, 16 Nov 2023 20:41:41 +0200 Subject: handle desktop notifications clicks --- src/boot/after_store.js | 2 +- src/components/notifications/notifications.js | 12 +++++++----- src/modules/notifications.js | 15 +++++++++++++++ src/services/sw/sw.js | 10 ++++++++-- 4 files changed, 31 insertions(+), 8 deletions(-) (limited to 'src/boot/after_store.js') diff --git a/src/boot/after_store.js b/src/boot/after_store.js index 6489ef87..84fea954 100644 --- a/src/boot/after_store.js +++ b/src/boot/after_store.js @@ -345,7 +345,7 @@ const afterStoreSetup = async ({ store, i18n }) => { store.dispatch('setLayoutHeight', windowHeight()) FaviconService.initFaviconService() - initServiceWorker() + initServiceWorker(store) window.addEventListener('focus', () => updateFocus()) diff --git a/src/components/notifications/notifications.js b/src/components/notifications/notifications.js index a1088dff..a210e19d 100644 --- a/src/components/notifications/notifications.js +++ b/src/components/notifications/notifications.js @@ -159,14 +159,16 @@ const Notifications = { updateScrollPosition () { this.showScrollTop = this.$refs.root.offsetTop < this.scrollerRef.scrollTop }, + /* "Interacted" really refers to "actionable" notifications that require user input, + * everything else (likes/repeats/reacts) cannot be acted and therefore we just clear + * the "seen" status upon any clicks on them + */ notificationClicked (notification) { - // const { type, id, seen } = notification + const { id } = notification + this.$store.dispatch('notificationClicked', { id }) }, notificationInteracted (notification) { - const { id, seen } = notification - if (!seen) this.markOneAsSeen(id) - }, - markOneAsSeen (id) { + const { id } = notification this.$store.dispatch('markSingleNotificationAsSeen', { id }) }, markAsSeen () { diff --git a/src/modules/notifications.js b/src/modules/notifications.js index 9b91f291..febff488 100644 --- a/src/modules/notifications.js +++ b/src/modules/notifications.js @@ -113,6 +113,21 @@ export const notifications = { } }) }, + notificationClicked ({ state, commit }, id) { + const notification = state.idStore[id] + const { type, seen } = notification + + if (!seen) { + switch (type) { + case 'mention': + case 'pleroma:report': + case 'follow_request': + break + default: + commit('markSingleNotificationAsSeen', { id }) + } + } + }, setNotificationsLoading ({ rootState, commit }, { value }) { commit('setNotificationsLoading', { value }) }, diff --git a/src/services/sw/sw.js b/src/services/sw/sw.js index d4ae8f4f..e28f9168 100644 --- a/src/services/sw/sw.js +++ b/src/services/sw/sw.js @@ -82,12 +82,18 @@ function sendSubscriptionToBackEnd (subscription, token, notificationVisibility) return responseData }) } -export async function initServiceWorker () { +export async function initServiceWorker (store) { if (!isSWSupported()) return await getOrCreateServiceWorker() navigator.serviceWorker.addEventListener('message', (event) => { + const { dispatch } = store console.log('SW MESSAGE', event) - // TODO actually act upon click (open drawer on mobile, open chat/thread etc) + const { type, ...rest } = event + + switch (type) { + case 'notificationClicked': + dispatch('notificationClicked', { id: rest.id }) + } }) } -- cgit v1.2.3-70-g09d2 From 0110fd86c2f166de5be8d675ffa34ab815463b2d Mon Sep 17 00:00:00 2001 From: tusooa Date: Wed, 27 Dec 2023 22:30:19 -0500 Subject: Allow user to mark account as group --- src/boot/after_store.js | 1 + src/components/settings_modal/tabs/profile_tab.js | 10 ++++++++-- src/components/settings_modal/tabs/profile_tab.vue | 15 ++++++++++++--- src/i18n/en.json | 5 ++++- src/modules/instance.js | 1 + .../entity_normalizer/entity_normalizer.service.js | 1 + 6 files changed, 27 insertions(+), 6 deletions(-) (limited to 'src/boot/after_store.js') diff --git a/src/boot/after_store.js b/src/boot/after_store.js index 84fea954..7039f85a 100644 --- a/src/boot/after_store.js +++ b/src/boot/after_store.js @@ -261,6 +261,7 @@ const getNodeInfo = async ({ store }) => { store.dispatch('setInstanceOption', { name: 'pollLimits', value: metadata.pollLimits }) store.dispatch('setInstanceOption', { name: 'mailerEnabled', value: metadata.mailerEnabled }) store.dispatch('setInstanceOption', { name: 'quotingAvailable', value: features.includes('quote_posting') }) + store.dispatch('setInstanceOption', { name: 'groupActorAvailable', value: features.includes('pleroma:group_actors') }) const uploadLimits = metadata.uploadLimits store.dispatch('setInstanceOption', { name: 'uploadlimit', value: parseInt(uploadLimits.general) }) diff --git a/src/components/settings_modal/tabs/profile_tab.js b/src/components/settings_modal/tabs/profile_tab.js index eeacad48..3cb3ae45 100644 --- a/src/components/settings_modal/tabs/profile_tab.js +++ b/src/components/settings_modal/tabs/profile_tab.js @@ -9,6 +9,7 @@ import suggestor from 'src/components/emoji_input/suggestor.js' import Autosuggest from 'src/components/autosuggest/autosuggest.vue' import Checkbox from 'src/components/checkbox/checkbox.vue' import InterfaceLanguageSwitcher from 'src/components/interface_language_switcher/interface_language_switcher.vue' +import Select from 'src/components/select/select.vue' import BooleanSetting from '../helpers/boolean_setting.vue' import SharedComputedObject from '../helpers/shared_computed_object.js' import localeService from 'src/services/locale/locale.service.js' @@ -39,6 +40,7 @@ const ProfileTab = { showRole: this.$store.state.users.currentUser.show_role, role: this.$store.state.users.currentUser.role, bot: this.$store.state.users.currentUser.bot, + actorType: this.$store.state.users.currentUser.actor_type, pickAvatarBtnVisible: true, bannerUploading: false, backgroundUploading: false, @@ -57,7 +59,8 @@ const ProfileTab = { ProgressButton, Checkbox, BooleanSetting, - InterfaceLanguageSwitcher + InterfaceLanguageSwitcher, + Select }, computed: { user () { @@ -116,6 +119,9 @@ const ProfileTab = { bannerImgSrc () { const src = this.$store.state.users.currentUser.cover_photo return (!src) ? this.defaultBanner : src + }, + availableActorTypes () { + return this.$store.state.instance.groupActorAvailable ? ['Person', 'Service', 'Group'] : ['Person', 'Service'] } }, methods: { @@ -127,7 +133,7 @@ const ProfileTab = { /* eslint-disable camelcase */ display_name: this.newName, fields_attributes: this.newFields.filter(el => el != null), - bot: this.bot, + actor_type: this.actorType, show_role: this.showRole, birthday: this.newBirthday || '', show_birthday: this.showBirthday diff --git a/src/components/settings_modal/tabs/profile_tab.vue b/src/components/settings_modal/tabs/profile_tab.vue index 1cc850cb..e6dc5987 100644 --- a/src/components/settings_modal/tabs/profile_tab.vue +++ b/src/components/settings_modal/tabs/profile_tab.vue @@ -109,9 +109,18 @@

- - {{ $t('settings.bot') }} - +

{ output.show_role = data.source.pleroma.show_role output.discoverable = data.source.pleroma.discoverable output.show_birthday = data.pleroma.show_birthday + output.actor_type = data.source.pleroma.actor_type } } -- cgit v1.2.3-70-g09d2 From 8a21594dbc5075b92d245f4c83530c7dae71c62a Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Wed, 21 Feb 2024 22:18:56 +0200 Subject: shadow slots work + minor fixes --- src/boot/after_store.js | 1 + src/components/button.style.js | 43 ++++++-------- src/components/input.style.js | 12 ++-- .../settings_modal/tabs/theme_tab/theme_tab.js | 20 +++---- src/components/shadow_control/shadow_control.js | 2 +- src/services/style_setter/style_setter.js | 26 ++++++--- src/services/theme_data/css_utils.js | 20 +++++++ src/services/theme_data/theme2_to_theme3.js | 29 ++++++++-- src/services/theme_data/theme3_slot_functions.js | 22 ++++---- src/services/theme_data/theme_data_3.service.js | 66 ++++++++++++++-------- 10 files changed, 153 insertions(+), 88 deletions(-) (limited to 'src/boot/after_store.js') diff --git a/src/boot/after_store.js b/src/boot/after_store.js index 84fea954..49a8130c 100644 --- a/src/boot/after_store.js +++ b/src/boot/after_store.js @@ -359,6 +359,7 @@ const afterStoreSetup = async ({ store, i18n }) => { const { theme } = store.state.instance const customThemePresent = customThemeSource || customTheme + console.log({ ...customThemeSource }, { ...customTheme }) if (customThemePresent) { if (customThemeSource && customThemeSource.themeEngineVersion === CURRENT_VERSION) { applyTheme(customThemeSource) diff --git a/src/components/button.style.js b/src/components/button.style.js index 1404061c..4910a5ac 100644 --- a/src/components/button.style.js +++ b/src/components/button.style.js @@ -1,23 +1,3 @@ -const buttonInsetFakeBorders = ['$borderSide(#FFFFFF, top, 0.2)', '$borderSide(#000000, bottom, 0.2)'] -const inputInsetFakeBorders = ['$borderSide(#FFFFFF, bottom, 0.2)', '$borderSide(#000000, top, 0.2)'] -const buttonOuterShadow = { - x: 0, - y: 0, - blur: 2, - spread: 0, - color: '#000000', - alpha: 1 -} - -const hoverGlow = { - x: 0, - y: 0, - blur: 4, - spread: 0, - color: '--text', - alpha: 1 -} - export default { name: 'Button', // Name of the component selector: '.button-default', // CSS selector/prefix @@ -49,52 +29,61 @@ export default { ], // Default rules, used as "default theme", essentially. defaultRules: [ + { + component: 'Root', + directives: { + '--defaultButtonHoverGlow': 'shadow | 0 0 4 --text', + '--defaultButtonShadow': 'shadow | 0 0 2 #000000', + '--defaultButtonBevel': 'shadow | $borderSide(#FFFFFF, top, 0.2) | $borderSide(#000000, bottom, 0.2)', + '--pressedButtonBevel': 'shadow | $borderSide(#FFFFFF, bottom, 0.2)| $borderSide(#000000, top, 0.2)' + } + }, { // component: 'Button', // no need to specify components every time unless you're specifying how other component should look // like within it directives: { background: '--fg', - shadow: [buttonOuterShadow, ...buttonInsetFakeBorders], + shadow: ['--defaultButtonShadow', '--defaultButtonBevel'], roundness: 3 } }, { state: ['hover'], directives: { - shadow: [hoverGlow, ...buttonInsetFakeBorders] + shadow: ['--defaultButtonHoverGlow', '--defaultButtonBevel'] } }, { state: ['pressed'], directives: { - shadow: [buttonOuterShadow, ...inputInsetFakeBorders] + shadow: ['--defaultButtonShadow', '--pressedButtonBevel'] } }, { state: ['hover', 'pressed'], directives: { - shadow: [hoverGlow, ...inputInsetFakeBorders] + shadow: ['--defaultButtonHoverGlow', '--pressedButtonBevel'] } }, { state: ['toggled'], directives: { background: '--inheritedBackground,-24.2', - shadow: [buttonOuterShadow, ...inputInsetFakeBorders] + shadow: ['--defaultButtonShadow', '--pressedButtonBevel'] } }, { state: ['toggled', 'hover'], directives: { background: '--inheritedBackground,-24.2', - shadow: [hoverGlow, ...inputInsetFakeBorders] + shadow: ['--defaultButtonHoverGlow', '--pressedButtonBevel'] } }, { state: ['disabled'], directives: { background: '$blend(--inheritedBackground, 0.25, --parent)', - shadow: [...buttonInsetFakeBorders] + shadow: ['--defaultButtonBevel'] } }, { diff --git a/src/components/input.style.js b/src/components/input.style.js index b1c9f3db..70c775ad 100644 --- a/src/components/input.style.js +++ b/src/components/input.style.js @@ -1,5 +1,3 @@ -const inputInsetFakeBorders = ['$borderSide(#FFFFFF, bottom, 0.2)', '$borderSide(#000000, top, 0.2)'] - const hoverGlow = { x: 0, y: 0, @@ -25,6 +23,12 @@ export default { 'Text' ], defaultRules: [ + { + component: 'Root', + directives: { + '--defaultInputBevel': 'shadow | $borderSide(#FFFFFF, bottom, 0.2)| $borderSide(#000000, top, 0.2)' + } + }, { variant: 'checkbox', directives: { @@ -42,13 +46,13 @@ export default { spread: 0, color: '#000000', alpha: 1 - }, ...inputInsetFakeBorders] + }, '--defaultInputBevel'] } }, { state: ['hover'], directives: { - shadow: [hoverGlow, ...inputInsetFakeBorders] + shadow: [hoverGlow, '--defaultInputBevel'] } } ] 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 58f8d44a..dd525920 100644 --- a/src/components/settings_modal/tabs/theme_tab/theme_tab.js +++ b/src/components/settings_modal/tabs/theme_tab/theme_tab.js @@ -4,15 +4,7 @@ import { getContrastRatioLayers } from 'src/services/color_convert/color_convert.js' import { - DEFAULT_SHADOWS, - generateColors, - generateShadows, - generateRadii, - generateFonts, - composePreset, - getThemes, - shadows2to3, - colors2to3 + getThemes } from 'src/services/style_setter/style_setter.js' import { newImporter, @@ -25,7 +17,15 @@ import { CURRENT_VERSION, OPACITIES, getLayers, - getOpacitySlot + getOpacitySlot, + DEFAULT_SHADOWS, + generateColors, + generateShadows, + generateRadii, + generateFonts, + composePreset, + shadows2to3, + colors2to3 } from 'src/services/theme_data/theme_data.service.js' import ColorInput from 'src/components/color_input/color_input.vue' import RangeInput from 'src/components/range_input/range_input.vue' diff --git a/src/components/shadow_control/shadow_control.js b/src/components/shadow_control/shadow_control.js index a1d1012b..f8e12dbf 100644 --- a/src/components/shadow_control/shadow_control.js +++ b/src/components/shadow_control/shadow_control.js @@ -1,7 +1,7 @@ import ColorInput from '../color_input/color_input.vue' import OpacityInput from '../opacity_input/opacity_input.vue' import Select from '../select/select.vue' -import { getCssShadow } from '../../services/style_setter/style_setter.js' +import { getCssShadow } from '../../services/theme_data/theme_data.service.js' import { hex2rgb } from '../../services/color_convert/color_convert.js' import { library } from '@fortawesome/fontawesome-svg-core' import { diff --git a/src/services/style_setter/style_setter.js b/src/services/style_setter/style_setter.js index 1fb65e1c..1bc92584 100644 --- a/src/services/style_setter/style_setter.js +++ b/src/services/style_setter/style_setter.js @@ -6,12 +6,22 @@ import { getCssRules } from '../theme_data/css_utils.js' import { defaultState } from '../../modules/config.js' export const applyTheme = (input) => { - const t0 = performance.now() - const { rules, theme } = generatePreset(input) - console.log(rules, theme) + const { version, theme: inputTheme } = input + let extraRules + let fonts + if (version === 2) { + const t0 = performance.now() + const { rules, theme } = generatePreset(inputTheme) + fonts = rules.fonts + const t1 = performance.now() + console.log('Themes 2 initialization took ' + (t1 - t0) + 'ms') + extraRules = convertTheme2To3(theme) + } else { + console.log(input) + extraRules = convertTheme2To3(input) + } + const t1 = performance.now() - console.log('Themes 2 initialization took ' + (t1 - t0) + 'ms') - const extraRules = convertTheme2To3(theme) const themes3 = init(extraRules) const t2 = performance.now() console.log('Themes 3 initialization took ' + (t2 - t1) + 'ms') @@ -24,7 +34,7 @@ export const applyTheme = (input) => { const styleSheet = styleEl.sheet styleSheet.toString() - styleSheet.insertRule(`:root { ${rules.fonts} }`, 'index-max') + styleSheet.insertRule(`:root { ${fonts} }`, 'index-max') getCssRules(themes3.eager, themes3.staticVars).forEach(rule => { // Hack to support multiple selectors on same component if (rule.match(/::-webkit-scrollbar-button/)) { @@ -133,8 +143,8 @@ export const getPreset = (val) => { data.colors = { bg, fg, text, link, cRed, cBlue, cGreen, cOrange } } - return { theme: data, source: theme.source } + return { theme: data, source: theme.source, version: isV1 ? 1 : 2 } }) } -export const setPreset = (val) => getPreset(val).then(data => applyTheme(data.theme)) +export const setPreset = (val) => getPreset(val).then(data => applyTheme(data)) diff --git a/src/services/theme_data/css_utils.js b/src/services/theme_data/css_utils.js index 8395f6a7..412accf9 100644 --- a/src/services/theme_data/css_utils.js +++ b/src/services/theme_data/css_utils.js @@ -2,6 +2,26 @@ import { convert } from 'chromatism' import { rgba2css } from '../color_convert/color_convert.js' +export const parseCssShadow = (text) => { + const dimensions = /(\d[a-z]*\s?){2,4}/.exec(text)?.[0] + const inset = /inset/.exec(text)?.[0] + const color = text.replace(dimensions, '').replace(inset, '') + + const [x, y, blur = 0, spread = 0] = dimensions.split(/ /).filter(x => x).map(x => x.trim()) + const isInset = inset?.trim() === 'inset' + console.log(color.trim()) + const colorString = color.split(/ /).filter(x => x).map(x => x.trim())[0] + + return { + x, + y, + blur, + spread, + inset: isInset, + color: colorString + } +} + export const getCssColorString = (color, alpha) => rgba2css({ ...convert(color).rgb, a: alpha }) export const getCssShadow = (input, usesDropShadow) => { diff --git a/src/services/theme_data/theme2_to_theme3.js b/src/services/theme_data/theme2_to_theme3.js index 743bc386..b367af36 100644 --- a/src/services/theme_data/theme2_to_theme3.js +++ b/src/services/theme_data/theme2_to_theme3.js @@ -100,6 +100,8 @@ export const temporary = new Set([ export const temporaryColors = {} export const convertTheme2To3 = (data) => { + data.colors.accent = data.colors.accent || data.colors.link + data.colors.link = data.colors.link || data.colors.accent const generateRoot = () => { const directives = {} basePaletteKeys.forEach(key => { directives['--' + key] = 'color | ' + data.colors[key] }) @@ -111,7 +113,8 @@ export const convertTheme2To3 = (data) => { const convertRadii = () => { const newRules = [] - radiiKeys.forEach(key => { + Object.keys(data.radii).forEach(key => { + if (!radiiKeys.has(key) || data.radii[key] === undefined) return null const originalRadius = data.radii[key] const rule = {} @@ -150,13 +153,17 @@ export const convertTheme2To3 = (data) => { roundness: originalRadius } newRules.push(rule) + if (rule.component === 'Button') { + newRules.push({ ...rule, component: 'ScrollbarElement' }) + } }) return newRules } const convertShadows = () => { const newRules = [] - shadowsKeys.forEach(key => { + Object.keys(data.shadows).forEach(key => { + if (!shadowsKeys.has(key)) return const originalShadow = data.shadows[key] const rule = {} @@ -205,6 +212,10 @@ export const convertTheme2To3 = (data) => { if (key === 'buttonPressed') { newRules.push({ ...rule, state: ['toggled'] }) } + + if (rule.component === 'Button') { + newRules.push({ ...rule, component: 'ScrollbarElement' }) + } }) return newRules } @@ -234,10 +245,13 @@ export const convertTheme2To3 = (data) => { rule.component = 'ChatMessage' } else if (prefix === 'poll') { rule.component = 'PollGraph' + } else if (prefix === 'btn') { + rule.component = 'Button' } else { rule.component = prefix[0].toUpperCase() + prefix.slice(1).toLowerCase() } return keys.map((key) => { + if (!data.colors[key]) return null const leftoverKey = key.replace(prefix, '') const parts = (leftoverKey || 'Bg').match(/[A-Z][a-z]*/g) const last = parts.slice(-1)[0] @@ -335,12 +349,17 @@ export const convertTheme2To3 = (data) => { newRule.variant = variantArray[0].toLowerCase() } } - console.log(key, newRule) - return newRule + + if (newRule.component === 'Button') { + console.log([newRule, { ...newRule, component: 'ScrollbarElement' }]) + return [newRule, { ...newRule, component: 'ScrollbarElement' }] + } else { + return [newRule] + } }) }) - const flatExtRules = extendedRules.filter(x => x).reduce((acc, x) => [...acc, ...x], []).filter(x => x) + const flatExtRules = extendedRules.filter(x => x).reduce((acc, x) => [...acc, ...x], []).filter(x => x).reduce((acc, x) => [...acc, ...x], []) return [generateRoot(), ...convertShadows(), ...convertRadii(), ...flatExtRules] } diff --git a/src/services/theme_data/theme3_slot_functions.js b/src/services/theme_data/theme3_slot_functions.js index 2324e121..2715c827 100644 --- a/src/services/theme_data/theme3_slot_functions.js +++ b/src/services/theme_data/theme3_slot_functions.js @@ -1,7 +1,7 @@ import { convert, brightness } from 'chromatism' import { alphaBlend, relativeLuminance } from '../color_convert/color_convert.js' -export const process = (text, functions, findColor, dynamicVars, staticVars) => { +export const process = (text, functions, { findColor, findShadow }, { dynamicVars, staticVars }) => { const { funcName, argsString } = /\$(?\w+)\((?[#a-zA-Z0-9-,.'"\s]*)\)/.exec(text).groups const args = argsString.split(/,/g).map(a => a.trim()) @@ -9,27 +9,27 @@ export const process = (text, functions, findColor, dynamicVars, staticVars) => if (args.length < func.argsNeeded) { throw new Error(`$${funcName} requires at least ${func.argsNeeded} arguments, but ${args.length} were provided`) } - return func.exec(args, findColor, dynamicVars, staticVars) + return func.exec(args, { findColor, findShadow }, { dynamicVars, staticVars }) } export const colorFunctions = { alpha: { argsNeeded: 2, - exec: (args, findColor, dynamicVars, staticVars) => { + exec: (args, { findColor }, { dynamicVars, staticVars }) => { const [color, amountArg] = args - const colorArg = convert(findColor(color, dynamicVars, staticVars)).rgb + const colorArg = convert(findColor(color, { dynamicVars, staticVars })).rgb const amount = Number(amountArg) return { ...colorArg, a: amount } } }, blend: { argsNeeded: 3, - exec: (args, findColor, dynamicVars, staticVars) => { + exec: (args, { findColor }, { dynamicVars, staticVars }) => { const [backgroundArg, amountArg, foregroundArg] = args - const background = convert(findColor(backgroundArg, dynamicVars, staticVars)).rgb - const foreground = convert(findColor(foregroundArg, dynamicVars, staticVars)).rgb + const background = convert(findColor(backgroundArg, { dynamicVars, staticVars })).rgb + const foreground = convert(findColor(foregroundArg, { dynamicVars, staticVars })).rgb const amount = Number(amountArg) return alphaBlend(background, amount, foreground) @@ -37,10 +37,10 @@ export const colorFunctions = { }, mod: { argsNeeded: 2, - exec: (args, findColor, dynamicVars, staticVars) => { + exec: (args, { findColor }, { dynamicVars, staticVars }) => { const [colorArg, amountArg] = args - const color = convert(findColor(colorArg, dynamicVars, staticVars)).rgb + const color = convert(findColor(colorArg, { dynamicVars, staticVars })).rgb const amount = Number(amountArg) const effectiveBackground = dynamicVars.lowerLevelBackground @@ -54,7 +54,7 @@ export const colorFunctions = { export const shadowFunctions = { borderSide: { argsNeeded: 3, - exec: (args, findColor, dynamicVars, staticVars) => { + exec: (args, { findColor }) => { const [color, side, alpha = '1', widthArg = '1', inset = 'inset'] = args const width = Number(widthArg) @@ -86,7 +86,7 @@ export const shadowFunctions = { break } }) - return targetShadow + return [targetShadow] } } } diff --git a/src/services/theme_data/theme_data_3.service.js b/src/services/theme_data/theme_data_3.service.js index 8196415b..91bda11e 100644 --- a/src/services/theme_data/theme_data_3.service.js +++ b/src/services/theme_data/theme_data_3.service.js @@ -1,4 +1,5 @@ import { convert, brightness } from 'chromatism' +import { flattenDeep } from 'lodash' import { alphaBlend, getTextColor, @@ -20,6 +21,7 @@ import { normalizeCombination, findRules } from './iss_utils.js' +import { parseCssShadow } from './css_utils.js' const DEBUG = false @@ -36,7 +38,32 @@ const components = { ChatMessage: null } -const findColor = (color, dynamicVars, staticVars) => { +const findShadow = (shadows, { dynamicVars, staticVars }) => { + return (shadows || []).map(shadow => { + let targetShadow + if (typeof shadow === 'string') { + if (shadow.startsWith('$')) { + targetShadow = process(shadow, shadowFunctions, { findColor, findShadow }, { dynamicVars, staticVars }) + } else if (shadow.startsWith('--')) { + const [variable] = shadow.split(/,/g).map(str => str.trim()) // discarding modifier since it's not supported + const variableSlot = variable.substring(2) + return findShadow(staticVars[variableSlot], { dynamicVars, staticVars }) + } else { + targetShadow = parseCssShadow(shadow) + } + } else { + targetShadow = shadow + } + + const shadowArray = Array.isArray(targetShadow) ? targetShadow : [targetShadow] + return shadowArray.map(s => ({ + ...s, + color: findColor(s.color, { dynamicVars, staticVars }) + })) + }) +} + +const findColor = (color, { dynamicVars, staticVars }) => { if (typeof color !== 'string' || (!color.startsWith('--') && !color.startsWith('$'))) return color let targetColor = null if (color.startsWith('--')) { @@ -76,7 +103,7 @@ const findColor = (color, dynamicVars, staticVars) => { if (color.startsWith('$')) { try { - targetColor = process(color, colorFunctions, findColor, dynamicVars, staticVars) + targetColor = process(color, colorFunctions, { findColor }, { dynamicVars, staticVars }) } catch (e) { console.error('Failure executing color function', e) targetColor = '#FF00FF' @@ -89,7 +116,7 @@ const findColor = (color, dynamicVars, staticVars) => { const getTextColorAlpha = (directives, intendedTextColor, dynamicVars, staticVars) => { const opacity = directives.textOpacity const backgroundColor = convert(dynamicVars.lowerLevelBackground).rgb - const textColor = convert(findColor(intendedTextColor, dynamicVars, staticVars)).rgb + const textColor = convert(findColor(intendedTextColor, { dynamicVars, staticVars })).rgb if (opacity === null || opacity === undefined || opacity >= 1) { return convert(textColor).hex } @@ -288,7 +315,7 @@ export const init = (extraRuleset) => { dynamicVars.inheritedBackground = lowerLevelBackground dynamicVars.stacked = convert(stacked[lowerLevelSelector]).rgb - const intendedTextColor = convert(findColor(inheritedTextColor, dynamicVars, staticVars)).rgb + const intendedTextColor = convert(findColor(inheritedTextColor, { dynamicVars, staticVars })).rgb const textColor = newTextRule.directives.textAuto === 'no-auto' ? intendedTextColor : getTextColor( @@ -355,7 +382,7 @@ export const init = (extraRuleset) => { dynamicVars.inheritedBackground = inheritedBackground - const rgb = convert(findColor(computedDirectives.background, dynamicVars, staticVars)).rgb + const rgb = convert(findColor(computedDirectives.background, { dynamicVars, staticVars })).rgb if (!stacked[selector]) { let blend @@ -373,21 +400,7 @@ export const init = (extraRuleset) => { } if (computedDirectives.shadow) { - dynamicVars.shadow = (computedDirectives.shadow || []).map(shadow => { - let targetShadow - if (typeof shadow === 'string') { - if (shadow.startsWith('$')) { - targetShadow = process(shadow, shadowFunctions, findColor, dynamicVars, staticVars) - } - } else { - targetShadow = shadow - } - - return { - ...targetShadow, - color: findColor(targetShadow.color, dynamicVars, staticVars) - } - }) + dynamicVars.shadow = flattenDeep(findShadow(flattenDeep(computedDirectives.shadow), { dynamicVars, staticVars })) } if (!stacked[selector]) { @@ -403,14 +416,23 @@ export const init = (extraRuleset) => { const dynamicSlots = Object.entries(computedDirectives).filter(([k, v]) => k.startsWith('--')) dynamicSlots.forEach(([k, v]) => { - const [type, value] = v.split('|').map(x => x.trim()) // woah, Extreme! + const [type, ...value] = v.split('|').map(x => x.trim()) // woah, Extreme! switch (type) { case 'color': { - const color = findColor(value, dynamicVars, staticVars) + const color = findColor(value[0], { dynamicVars, staticVars }) dynamicVars[k] = color if (component.name === 'Root') { staticVars[k.substring(2)] = color } + break + } + case 'shadow': { + const shadow = value + dynamicVars[k] = shadow + if (component.name === 'Root') { + staticVars[k.substring(2)] = shadow + } + break } } }) -- cgit v1.2.3-70-g09d2 From 879f520b75bcb379790317627bc2a8d2b739a2fb Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Thu, 22 Feb 2024 00:02:24 +0200 Subject: tabs support + cleanup --- src/boot/after_store.js | 1 - src/components/alert.style.js | 3 +- src/components/button_unstyled.style.js | 2 -- src/components/panel.style.js | 3 +- src/components/tab_switcher/tab.style.js | 52 +++++++++++++++++++++++++++ src/components/tab_switcher/tab_switcher.jsx | 2 +- src/components/tab_switcher/tab_switcher.scss | 14 +++++--- src/services/theme_data/css_utils.js | 1 - src/services/theme_data/theme2_to_theme3.js | 10 ++++-- 9 files changed, 74 insertions(+), 14 deletions(-) create mode 100644 src/components/tab_switcher/tab.style.js (limited to 'src/boot/after_store.js') diff --git a/src/boot/after_store.js b/src/boot/after_store.js index 49a8130c..84fea954 100644 --- a/src/boot/after_store.js +++ b/src/boot/after_store.js @@ -359,7 +359,6 @@ const afterStoreSetup = async ({ store, i18n }) => { const { theme } = store.state.instance const customThemePresent = customThemeSource || customTheme - console.log({ ...customThemeSource }, { ...customTheme }) if (customThemePresent) { if (customThemeSource && customThemeSource.themeEngineVersion === CURRENT_VERSION) { applyTheme(customThemeSource) diff --git a/src/components/alert.style.js b/src/components/alert.style.js index 5881e833..bb97bd64 100644 --- a/src/components/alert.style.js +++ b/src/components/alert.style.js @@ -5,7 +5,8 @@ export default { 'Text', 'Icon', 'Link', - 'Border' + 'Border', + 'ButtonUnstyled' ], variants: { normal: '.neutral', diff --git a/src/components/button_unstyled.style.js b/src/components/button_unstyled.style.js index 60dd0d47..a2d854aa 100644 --- a/src/components/button_unstyled.style.js +++ b/src/components/button_unstyled.style.js @@ -3,8 +3,6 @@ export default { selector: '.button-unstyled', states: { disabled: ':disabled', - toggled: '.toggled', - pressed: ':active', hover: ':hover:not(:disabled)', focused: ':focus-within' }, diff --git a/src/components/panel.style.js b/src/components/panel.style.js index 95a10366..504ecbcb 100644 --- a/src/components/panel.style.js +++ b/src/components/panel.style.js @@ -16,7 +16,8 @@ export default { 'Alert', 'UserCard', 'Chat', - 'Attachment' + 'Attachment', + 'Tab' ], defaultRules: [ { diff --git a/src/components/tab_switcher/tab.style.js b/src/components/tab_switcher/tab.style.js new file mode 100644 index 00000000..563da87c --- /dev/null +++ b/src/components/tab_switcher/tab.style.js @@ -0,0 +1,52 @@ +export default { + name: 'Tab', // Name of the component + selector: '.tab', // CSS selector/prefix + states: { + active: '.active', + hover: ':hover:not(:disabled)', + disabled: '.disabled' + }, + validInnerComponents: [ + 'Text', + 'Icon' + ], + defaultRules: [ + { + directives: { + background: '--fg', + shadow: ['--defaultButtonShadow', '--defaultButtonBevel'], + roundness: 3 + } + }, + { + state: ['hover'], + directives: { + shadow: ['--defaultButtonHoverGlow', '--defaultButtonBevel'] + } + }, + { + state: ['disabled'], + directives: { + background: '$blend(--inheritedBackground, 0.25, --parent)', + shadow: ['--defaultButtonBevel'] + } + }, + { + state: ['active'], + directives: { + opacity: 0 + } + }, + { + component: 'Text', + parent: { + component: 'Tab', + state: ['disabled'] + }, + directives: { + textOpacity: 0.25, + textOpacityMode: 'blend' + } + } + ] +} diff --git a/src/components/tab_switcher/tab_switcher.jsx b/src/components/tab_switcher/tab_switcher.jsx index b444da43..027a380a 100644 --- a/src/components/tab_switcher/tab_switcher.jsx +++ b/src/components/tab_switcher/tab_switcher.jsx @@ -97,7 +97,7 @@ export default { .map((slot, index) => { const props = slot.props if (!props) return - const classesTab = ['tab', 'button-default'] + const classesTab = ['tab'] const classesWrapper = ['tab-wrapper'] if (this.activeIndex === index) { classesTab.push('active') diff --git a/src/components/tab_switcher/tab_switcher.scss b/src/components/tab_switcher/tab_switcher.scss index 705925c8..a90a13ed 100644 --- a/src/components/tab_switcher/tab_switcher.scss +++ b/src/components/tab_switcher/tab_switcher.scss @@ -25,8 +25,7 @@ content: ""; flex: 1 1 auto; border-bottom: 1px solid; - border-bottom-color: $fallback--border; - border-bottom-color: var(--border, $fallback--border); + border-bottom-color: var(--border); } .tab-wrapper { @@ -37,8 +36,7 @@ right: 0; bottom: 0; border-bottom: 1px solid; - border-bottom-color: $fallback--border; - border-bottom-color: var(--border, $fallback--border); + border-bottom-color: var(--border); } } @@ -173,6 +171,14 @@ } .tab { + user-select: none; + color: var(--text); + border: none; + cursor: pointer; + box-shadow: var(--shadow); + font-size: 1em; + font-family: var(--interfaceFont, sans-serif); + border-radius: var(--roundness); position: relative; white-space: nowrap; padding: 6px 1em; diff --git a/src/services/theme_data/css_utils.js b/src/services/theme_data/css_utils.js index 412accf9..3bbee6af 100644 --- a/src/services/theme_data/css_utils.js +++ b/src/services/theme_data/css_utils.js @@ -9,7 +9,6 @@ export const parseCssShadow = (text) => { const [x, y, blur = 0, spread = 0] = dimensions.split(/ /).filter(x => x).map(x => x.trim()) const isInset = inset?.trim() === 'inset' - console.log(color.trim()) const colorString = color.split(/ /).filter(x => x).map(x => x.trim())[0] return { diff --git a/src/services/theme_data/theme2_to_theme3.js b/src/services/theme_data/theme2_to_theme3.js index b367af36..ef9506a6 100644 --- a/src/services/theme_data/theme2_to_theme3.js +++ b/src/services/theme_data/theme2_to_theme3.js @@ -55,7 +55,7 @@ export const extendedBasePrefixes = [ 'panel', 'topBar', - // 'tab', // TODO: not implemented yet + 'tab', 'btn', 'input', 'selectedMenu', @@ -122,6 +122,9 @@ export const convertTheme2To3 = (data) => { case 'btn': rule.component = 'Button' break + case 'tab': + rule.component = 'Tab' + break case 'input': rule.component = 'Input' break @@ -155,6 +158,7 @@ export const convertTheme2To3 = (data) => { newRules.push(rule) if (rule.component === 'Button') { newRules.push({ ...rule, component: 'ScrollbarElement' }) + newRules.push({ ...rule, component: 'Tab' }) } }) return newRules @@ -215,6 +219,7 @@ export const convertTheme2To3 = (data) => { if (rule.component === 'Button') { newRules.push({ ...rule, component: 'ScrollbarElement' }) + newRules.push({ ...rule, component: 'Tab' }) } }) return newRules @@ -351,8 +356,7 @@ export const convertTheme2To3 = (data) => { } if (newRule.component === 'Button') { - console.log([newRule, { ...newRule, component: 'ScrollbarElement' }]) - return [newRule, { ...newRule, component: 'ScrollbarElement' }] + return [newRule, { ...newRule, component: 'Tab' }, { ...newRule, component: 'ScrollbarElement' }] } else { return [newRule] } -- cgit v1.2.3-70-g09d2 From dc22386599c77fdd5a8b88ccfd167cff36d14c67 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Mon, 26 Feb 2024 21:37:40 +0200 Subject: optimization and refactoring, rules are first flattened and then processed, letting us to set individual rules as "lazy" --- src/boot/after_store.js | 17 +- src/services/style_setter/style_setter.js | 26 +- src/services/theme_data/theme_data_3.service.js | 450 ++++++++++++------------ 3 files changed, 243 insertions(+), 250 deletions(-) (limited to 'src/boot/after_store.js') diff --git a/src/boot/after_store.js b/src/boot/after_store.js index 84fea954..c5de888b 100644 --- a/src/boot/after_store.js +++ b/src/boot/after_store.js @@ -327,17 +327,14 @@ const setConfig = async ({ store }) => { } const checkOAuthToken = async ({ store }) => { - // eslint-disable-next-line no-async-promise-executor - return new Promise(async (resolve, reject) => { - if (store.getters.getUserToken()) { - try { - await store.dispatch('loginUser', store.getters.getUserToken()) - } catch (e) { - console.error(e) - } + if (store.getters.getUserToken()) { + try { + await store.dispatch('loginUser', store.getters.getUserToken()) + } catch (e) { + console.error(e) } - resolve() - }) + } + return Promise.resolve() } const afterStoreSetup = async ({ store, i18n }) => { diff --git a/src/services/style_setter/style_setter.js b/src/services/style_setter/style_setter.js index 1cda7213..b1722295 100644 --- a/src/services/style_setter/style_setter.js +++ b/src/services/style_setter/style_setter.js @@ -5,23 +5,16 @@ import { convertTheme2To3 } from '../theme_data/theme2_to_theme3.js' import { getCssRules } from '../theme_data/css_utils.js' import { defaultState } from '../../modules/config.js' -export const applyTheme = (input) => { +export const applyTheme = async (input) => { let extraRules if (input.themeType !== 1) { - const t0 = performance.now() const { theme } = generatePreset(input) - const t1 = performance.now() - console.debug('Themes 2 initialization took ' + (t1 - t0) + 'ms') extraRules = convertTheme2To3(theme) } else { - console.debug(input) extraRules = convertTheme2To3(input) } - const t1 = performance.now() const themes3 = init(extraRules, '#FFFFFF') - const t2 = performance.now() - console.debug('Themes 3 (eager) initialization took ' + (t2 - t1) + 'ms') const head = document.head const body = document.body body.classList.add('hidden') @@ -47,14 +40,21 @@ export const applyTheme = (input) => { styleSheet.insertRule(rule, 'index-max') } }) + body.classList.remove('hidden') - themes3.lazy.then(lazyRules => { - getCssRules(lazyRules, themes3.staticVars).forEach(rule => { - styleSheet.insertRule(rule, 'index-max') + + setTimeout(() => { + themes3.lazy().then(lazyRules => { + const t2 = performance.now() + getCssRules(lazyRules, themes3.staticVars).forEach(rule => { + styleSheet.insertRule(rule, 'index-max') + }) + const t3 = performance.now() + console.debug('Themes 3 finalization (lazy) took ' + (t3 - t2) + 'ms') }) - const t3 = performance.now() - console.debug('Themes 3 finalization (lazy) took ' + (t3 - t2) + 'ms') }) + + return Promise.resolve() } const configColumns = ({ sidebarColumnWidth, contentColumnWidth, notifsColumnWidth, emojiReactionsScale }) => diff --git a/src/services/theme_data/theme_data_3.service.js b/src/services/theme_data/theme_data_3.service.js index 13caef9e..926988f7 100644 --- a/src/services/theme_data/theme_data_3.service.js +++ b/src/services/theme_data/theme_data_3.service.js @@ -151,6 +151,7 @@ export const init = (extraRuleset, ultimateBackgroundColor) => { const eagerRules = [] const lazyRules = [] + const lazyPromises = [] const rulesetUnsorted = [ ...Object.values(components) @@ -187,25 +188,210 @@ export const init = (extraRuleset, ultimateBackgroundColor) => { const virtualComponents = new Set(Object.values(components).filter(c => c.virtual).map(c => c.name)) - let counter = 0 - const promises = [] - const processInnerComponent = (component, rules, parent) => { + const processCombination = (combination, rules) => { const addRule = (rule) => { rules.push(rule) } - const parentSelector = ruleToSelector(parent, true) - // const parentList = parent ? unroll(parent).reverse().map(c => c.component) : [] - // if (!component.virtual) { - // const path = [...parentList, component.name].join(' > ') - // console.log('Component ' + path + ' process starting') - // } - // const t0 = performance.now() + const selector = ruleToSelector(combination, true) + const cssSelector = ruleToSelector(combination) + + const parentSelector = selector.split(/ /g).slice(0, -1).join(' ') + const soloSelector = selector.split(/ /g).slice(-1)[0] + + const lowerLevelSelector = parentSelector + const lowerLevelBackground = computed[lowerLevelSelector]?.background + const lowerLevelVirtualDirectives = computed[lowerLevelSelector]?.virtualDirectives + const lowerLevelVirtualDirectivesRaw = computed[lowerLevelSelector]?.virtualDirectivesRaw + + const dynamicVars = computed[selector] || { + lowerLevelBackground, + lowerLevelVirtualDirectives, + lowerLevelVirtualDirectivesRaw + } + + // Inheriting all of the applicable rules + const existingRules = ruleset.filter(findRules(combination)) + const computedDirectives = existingRules.map(r => r.directives).reduce((acc, directives) => ({ ...acc, ...directives }), {}) + const computedRule = { + ...combination, + directives: computedDirectives + } + + computed[selector] = computed[selector] || {} + computed[selector].computedRule = computedRule + computed[selector].dynamicVars = dynamicVars + + if (virtualComponents.has(combination.component)) { + const virtualName = [ + '--', + combination.component.toLowerCase(), + combination.variant === 'normal' + ? '' + : combination.variant[0].toUpperCase() + combination.variant.slice(1).toLowerCase(), + ...combination.state.filter(x => x !== 'normal').toSorted().map(state => state[0].toUpperCase() + state.slice(1).toLowerCase()) + ].join('') + + let inheritedTextColor = computedDirectives.textColor + let inheritedTextAuto = computedDirectives.textAuto + let inheritedTextOpacity = computedDirectives.textOpacity + let inheritedTextOpacityMode = computedDirectives.textOpacityMode + const lowerLevelTextSelector = [...selector.split(/ /g).slice(0, -1), soloSelector].join(' ') + const lowerLevelTextRule = computed[lowerLevelTextSelector] + + if (inheritedTextColor == null || inheritedTextOpacity == null || inheritedTextOpacityMode == null) { + inheritedTextColor = computedDirectives.textColor ?? lowerLevelTextRule.textColor + inheritedTextAuto = computedDirectives.textAuto ?? lowerLevelTextRule.textAuto + inheritedTextOpacity = computedDirectives.textOpacity ?? lowerLevelTextRule.textOpacity + inheritedTextOpacityMode = computedDirectives.textOpacityMode ?? lowerLevelTextRule.textOpacityMode + } + + const newTextRule = { + ...computedRule, + directives: { + ...computedRule.directives, + textColor: inheritedTextColor, + textAuto: inheritedTextAuto ?? 'preserve', + textOpacity: inheritedTextOpacity, + textOpacityMode: inheritedTextOpacityMode + } + } + + dynamicVars.inheritedBackground = lowerLevelBackground + dynamicVars.stacked = convert(stacked[lowerLevelSelector]).rgb + + const intendedTextColor = convert(findColor(inheritedTextColor, { dynamicVars, staticVars })).rgb + const textColor = newTextRule.directives.textAuto === 'no-auto' + ? intendedTextColor + : getTextColor( + convert(stacked[lowerLevelSelector]).rgb, + intendedTextColor, + newTextRule.directives.textAuto === 'preserve' + ) + + // Updating previously added rule + const earlyLowerLevelRules = rules.filter(findRules(combination.parent, true)) + const earlyLowerLevelRule = earlyLowerLevelRules.slice(-1)[0] + + const virtualDirectives = earlyLowerLevelRule.virtualDirectives || {} + const virtualDirectivesRaw = earlyLowerLevelRule.virtualDirectivesRaw || {} + + // Storing color data in lower layer to use as custom css properties + virtualDirectives[virtualName] = getTextColorAlpha(newTextRule.directives, textColor, dynamicVars) + virtualDirectivesRaw[virtualName] = textColor + earlyLowerLevelRule.virtualDirectives = virtualDirectives + earlyLowerLevelRule.virtualDirectivesRaw = virtualDirectivesRaw + computed[lowerLevelSelector].virtualDirectives = virtualDirectives + computed[lowerLevelSelector].virtualDirectivesRaw = virtualDirectivesRaw + } else { + computed[selector] = computed[selector] || {} + + // TODO: DEFAULT TEXT COLOR + const lowerLevelStackedBackground = stacked[lowerLevelSelector] || convert(ultimateBackgroundColor).rgb + + if (computedDirectives.background) { + let inheritRule = null + const variantRules = ruleset.filter( + findRules({ + component: combination.component, + variant: combination.variant, + parent: combination.parent + }) + ) + const lastVariantRule = variantRules[variantRules.length - 1] + if (lastVariantRule) { + inheritRule = lastVariantRule + } else { + const normalRules = ruleset.filter(findRules({ + component: combination.component, + parent: combination.parent + })) + const lastNormalRule = normalRules[normalRules.length - 1] + inheritRule = lastNormalRule + } + + const inheritSelector = ruleToSelector({ ...inheritRule, parent: combination.parent }, true) + const inheritedBackground = computed[inheritSelector].background + + dynamicVars.inheritedBackground = inheritedBackground + + const rgb = convert(findColor(computedDirectives.background, { dynamicVars, staticVars })).rgb + + if (!stacked[selector]) { + let blend + const alpha = computedDirectives.opacity ?? 1 + if (alpha >= 1) { + blend = rgb + } else if (alpha <= 0) { + blend = lowerLevelStackedBackground + } else { + blend = alphaBlend(rgb, computedDirectives.opacity, lowerLevelStackedBackground) + } + stacked[selector] = blend + computed[selector].background = { ...rgb, a: computedDirectives.opacity ?? 1 } + } + } + + if (computedDirectives.shadow) { + dynamicVars.shadow = flattenDeep(findShadow(flattenDeep(computedDirectives.shadow), { dynamicVars, staticVars })) + } + + if (!stacked[selector]) { + computedDirectives.background = 'transparent' + computedDirectives.opacity = 0 + stacked[selector] = lowerLevelStackedBackground + computed[selector].background = { ...lowerLevelStackedBackground, a: 0 } + } + + dynamicVars.stacked = stacked[selector] + dynamicVars.background = computed[selector].background + + const dynamicSlots = Object.entries(computedDirectives).filter(([k, v]) => k.startsWith('--')) + + dynamicSlots.forEach(([k, v]) => { + const [type, ...value] = v.split('|').map(x => x.trim()) // woah, Extreme! + switch (type) { + case 'color': { + const color = findColor(value[0], { dynamicVars, staticVars }) + dynamicVars[k] = color + if (combination.component === 'Root') { + staticVars[k.substring(2)] = color + } + break + } + case 'shadow': { + const shadow = value + dynamicVars[k] = shadow + if (combination.component === 'Root') { + staticVars[k.substring(2)] = shadow + } + break + } + case 'generic': { + dynamicVars[k] = value + if (combination.component === 'Root') { + staticVars[k.substring(2)] = value + } + break + } + } + }) + + addRule({ + dynamicVars, + selector: cssSelector, + ...combination, + directives: computedDirectives + }) + } + } + + const processInnerComponent = (component, parent) => { + const combinations = [] const { validInnerComponents = [], states: originalStates = {}, - variants: originalVariants = {}, - name + variants: originalVariants = {} } = component // Normalizing states and variants to always include "normal" @@ -241,233 +427,43 @@ export const init = (extraRuleset, ultimateBackgroundColor) => { }).reduce((acc, x) => [...acc, ...x], []) stateVariantCombination.forEach(combination => { - counter++ - // const tt0 = performance.now() - combination.component = component.name - const soloSelector = ruleToSelector(combination, true) - const soloCssSelector = ruleToSelector(combination) - const selector = [parentSelector, soloSelector].filter(x => x).join(' ') - const cssSelector = [parentSelector, soloCssSelector].filter(x => x).join(' ') - - const lowerLevelSelector = parentSelector - const lowerLevelBackground = computed[lowerLevelSelector]?.background - const lowerLevelVirtualDirectives = computed[lowerLevelSelector]?.virtualDirectives - const lowerLevelVirtualDirectivesRaw = computed[lowerLevelSelector]?.virtualDirectivesRaw - - const dynamicVars = computed[selector] || { - lowerLevelBackground, - lowerLevelVirtualDirectives, - lowerLevelVirtualDirectivesRaw - } - - // Inheriting all of the applicable rules - const existingRules = ruleset.filter(findRules({ component: component.name, ...combination, parent })) - const computedDirectives = existingRules.map(r => r.directives).reduce((acc, directives) => ({ ...acc, ...directives }), {}) - const computedRule = { - component: component.name, - ...combination, - parent, - directives: computedDirectives + combination.lazy = component.lazy || parent?.lazy + combination.parent = parent + if (combination.state.indexOf('hover') >= 0) { + combination.lazy = true } - computed[selector] = computed[selector] || {} - computed[selector].computedRule = computedRule - computed[selector].dynamicVars = dynamicVars - - if (virtualComponents.has(component.name)) { - const virtualName = [ - '--', - component.name.toLowerCase(), - combination.variant === 'normal' - ? '' - : combination.variant[0].toUpperCase() + combination.variant.slice(1).toLowerCase(), - ...combination.state.filter(x => x !== 'normal').toSorted().map(state => state[0].toUpperCase() + state.slice(1).toLowerCase()) - ].join('') - - let inheritedTextColor = computedDirectives.textColor - let inheritedTextAuto = computedDirectives.textAuto - let inheritedTextOpacity = computedDirectives.textOpacity - let inheritedTextOpacityMode = computedDirectives.textOpacityMode - const lowerLevelTextSelector = [...selector.split(/ /g).slice(0, -1), soloSelector].join(' ') - const lowerLevelTextRule = computed[lowerLevelTextSelector] - - if (inheritedTextColor == null || inheritedTextOpacity == null || inheritedTextOpacityMode == null) { - inheritedTextColor = computedDirectives.textColor ?? lowerLevelTextRule.textColor - inheritedTextAuto = computedDirectives.textAuto ?? lowerLevelTextRule.textAuto - inheritedTextOpacity = computedDirectives.textOpacity ?? lowerLevelTextRule.textOpacity - inheritedTextOpacityMode = computedDirectives.textOpacityMode ?? lowerLevelTextRule.textOpacityMode - } - - const newTextRule = { - ...computedRule, - directives: { - ...computedRule.directives, - textColor: inheritedTextColor, - textAuto: inheritedTextAuto ?? 'preserve', - textOpacity: inheritedTextOpacity, - textOpacityMode: inheritedTextOpacityMode - } - } - - dynamicVars.inheritedBackground = lowerLevelBackground - dynamicVars.stacked = convert(stacked[lowerLevelSelector]).rgb - - const intendedTextColor = convert(findColor(inheritedTextColor, { dynamicVars, staticVars })).rgb - const textColor = newTextRule.directives.textAuto === 'no-auto' - ? intendedTextColor - : getTextColor( - convert(stacked[lowerLevelSelector]).rgb, - intendedTextColor, - newTextRule.directives.textAuto === 'preserve' - ) - - // Updating previously added rule - const earlyLowerLevelRules = rules.filter(findRules(parent, true)) - const earlyLowerLevelRule = earlyLowerLevelRules.slice(-1)[0] - - const virtualDirectives = earlyLowerLevelRule.virtualDirectives || {} - const virtualDirectivesRaw = earlyLowerLevelRule.virtualDirectivesRaw || {} - - // Storing color data in lower layer to use as custom css properties - virtualDirectives[virtualName] = getTextColorAlpha(newTextRule.directives, textColor, dynamicVars) - virtualDirectivesRaw[virtualName] = textColor - earlyLowerLevelRule.virtualDirectives = virtualDirectives - earlyLowerLevelRule.virtualDirectivesRaw = virtualDirectivesRaw - computed[lowerLevelSelector].virtualDirectives = virtualDirectives - computed[lowerLevelSelector].virtualDirectivesRaw = virtualDirectivesRaw - } else { - computed[selector] = computed[selector] || {} - - // TODO: DEFAULT TEXT COLOR - const lowerLevelStackedBackground = stacked[lowerLevelSelector] || convert(ultimateBackgroundColor).rgb - - if (computedDirectives.background) { - let inheritRule = null - const variantRules = ruleset.filter(findRules({ component: component.name, variant: combination.variant, parent })) - const lastVariantRule = variantRules[variantRules.length - 1] - if (lastVariantRule) { - inheritRule = lastVariantRule - } else { - const normalRules = ruleset.filter(findRules({ component: component.name, parent })) - const lastNormalRule = normalRules[normalRules.length - 1] - inheritRule = lastNormalRule - } - - const inheritSelector = ruleToSelector({ ...inheritRule, parent }, true) - const inheritedBackground = computed[inheritSelector].background - - dynamicVars.inheritedBackground = inheritedBackground - - const rgb = convert(findColor(computedDirectives.background, { dynamicVars, staticVars })).rgb - - if (!stacked[selector]) { - let blend - const alpha = computedDirectives.opacity ?? 1 - if (alpha >= 1) { - blend = rgb - } else if (alpha <= 0) { - blend = lowerLevelStackedBackground - } else { - blend = alphaBlend(rgb, computedDirectives.opacity, lowerLevelStackedBackground) - } - stacked[selector] = blend - computed[selector].background = { ...rgb, a: computedDirectives.opacity ?? 1 } - } - } - - if (computedDirectives.shadow) { - dynamicVars.shadow = flattenDeep(findShadow(flattenDeep(computedDirectives.shadow), { dynamicVars, staticVars })) - } - - if (!stacked[selector]) { - computedDirectives.background = 'transparent' - computedDirectives.opacity = 0 - stacked[selector] = lowerLevelStackedBackground - computed[selector].background = { ...lowerLevelStackedBackground, a: 0 } - } - - dynamicVars.stacked = stacked[selector] - dynamicVars.background = computed[selector].background - - const dynamicSlots = Object.entries(computedDirectives).filter(([k, v]) => k.startsWith('--')) - - dynamicSlots.forEach(([k, v]) => { - const [type, ...value] = v.split('|').map(x => x.trim()) // woah, Extreme! - switch (type) { - case 'color': { - const color = findColor(value[0], { dynamicVars, staticVars }) - dynamicVars[k] = color - if (component.name === 'Root') { - staticVars[k.substring(2)] = color - } - break - } - case 'shadow': { - const shadow = value - dynamicVars[k] = shadow - if (component.name === 'Root') { - staticVars[k.substring(2)] = shadow - } - break - } - case 'generic': { - dynamicVars[k] = value - if (component.name === 'Root') { - staticVars[k.substring(2)] = value - } - break - } - } - }) - - addRule({ - dynamicVars, - selector: cssSelector, - component: component.name, - ...combination, - parent, - directives: computedDirectives - }) - } + combinations.push(combination) innerComponents.forEach(innerComponent => { - if (innerComponent.lazy) { - promises.push(new Promise((resolve, reject) => { - setTimeout(() => { - try { - processInnerComponent(innerComponent, lazyRules, { parent, component: name, ...combination }) - resolve() - } catch (e) { - reject(e) - } - }, 0) - })) - } else { - processInnerComponent(innerComponent, rules, { parent, component: name, ...combination }) - } + combinations.push(...processInnerComponent(innerComponent, combination)) }) - // const tt1 = performance.now() - // if (!component.virtual) { - // console.log('State-variant ' + combination.variant + ' : ' + combination.state.join('+') + ' procession time: ' + (tt1 - tt0) + 'ms') - // } }) - // const t1 = performance.now() - // if (!component.virtual) { - // const path = [...parentList, component.name].join(' > ') - // console.log('Component ' + path + ' procession time: ' + (t1 - t0) + 'ms') - // } + return combinations } - processInnerComponent(components.Root, eagerRules) - console.debug('Eager combinations processed:' + counter) - const lazyExec = Promise.all(promises).then(() => { - console.debug('Total combinations processed: ' + counter) - }).then(() => lazyRules) + const t0 = performance.now() + const combinations = processInnerComponent(components.Root) + const t1 = performance.now() + console.debug('Tree tranveral took ' + (t1 - t0) + ' ms') + + combinations.forEach((combination) => { + if (combination.lazy) { + lazyPromises.push(async () => processCombination(combination, lazyRules)) + } else { + processCombination(combination, eagerRules) + } + }) + const t2 = performance.now() + console.debug('Eager processing took ' + (t2 - t1) + ' ms') return { - lazy: lazyExec, + lazy: async () => { + await Promise.all(lazyPromises.map(x => x())) + return lazyRules + }, eager: eagerRules, staticVars } -- cgit v1.2.3-70-g09d2 From 9806eea12ea4cde8b78741cfb1ddfe1d3e64ba9b Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Wed, 3 Apr 2024 22:52:12 +0300 Subject: only show interface after theme has been applied --- src/App.vue | 1 + src/boot/after_store.js | 1 + src/modules/instance.js | 1 + src/modules/interface.js | 4 ++++ src/services/style_setter/style_setter.js | 5 ----- 5 files changed, 7 insertions(+), 5 deletions(-) (limited to 'src/boot/after_store.js') diff --git a/src/App.vue b/src/App.vue index fe214ce7..e12f919d 100644 --- a/src/App.vue +++ b/src/App.vue @@ -2,6 +2,7 @@

{ } else { applyTheme(customTheme) } + store.commit('setThemeApplied') } else if (theme) { // do nothing, it will load asynchronously } else { diff --git a/src/modules/instance.js b/src/modules/instance.js index c7a2cad1..76269ece 100644 --- a/src/modules/instance.js +++ b/src/modules/instance.js @@ -386,6 +386,7 @@ const instance = { } else { applyTheme(themeData.theme) } + commit('setThemeApplied') }) }, fetchEmoji ({ dispatch, state }) { diff --git a/src/modules/interface.js b/src/modules/interface.js index f8d62d87..39242b9d 100644 --- a/src/modules/interface.js +++ b/src/modules/interface.js @@ -1,4 +1,5 @@ const defaultState = { + themeApplied: false, settingsModalState: 'hidden', settingsModalLoadedUser: false, settingsModalLoadedAdmin: false, @@ -35,6 +36,9 @@ const interfaceMod = { state.settings.currentSaveStateNotice = { error: true, errorData: error } } }, + setThemeApplied (state) { + state.themeApplied = true + }, setNotificationPermission (state, permission) { state.notificationPermission = permission }, diff --git a/src/services/style_setter/style_setter.js b/src/services/style_setter/style_setter.js index 3c48bf89..e6724d60 100644 --- a/src/services/style_setter/style_setter.js +++ b/src/services/style_setter/style_setter.js @@ -88,9 +88,6 @@ export const generateTheme = async (input, callbacks) => { } export const applyTheme = async (input) => { - const body = document.body - body.classList.add('hidden') - const styleSheet = new CSSStyleSheet() document.adoptedStyleSheets = [styleSheet] @@ -103,8 +100,6 @@ export const applyTheme = async (input) => { } ) - body.classList.remove('hidden') - setTimeout(lazyProcessFunc, 0) return Promise.resolve() -- cgit v1.2.3-70-g09d2 From 5505a89e8aad717a0b4a7665b23a317110d38cb5 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Mon, 22 Apr 2024 23:40:39 +0300 Subject: implement a simple caching system for themes 3 --- package.json | 1 + src/boot/after_store.js | 26 ++++++++------- src/components/settings_modal/tabs/general_tab.vue | 8 +++++ src/i18n/en.json | 1 + src/modules/config.js | 1 + src/modules/instance.js | 3 +- src/services/style_setter/style_setter.js | 37 ++++++++++++++++++++-- src/services/theme_data/theme_data_3.service.js | 8 ++++- 8 files changed, 70 insertions(+), 15 deletions(-) (limited to 'src/boot/after_store.js') diff --git a/package.json b/package.json index cbbcb170..fb157fae 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "click-outside-vue3": "4.0.1", "cropperjs": "1.5.13", "escape-html": "1.0.3", + "hash-sum": "^2.0.0", "js-cookie": "3.0.5", "localforage": "1.10.0", "parse-link-header": "2.0.0", diff --git a/src/boot/after_store.js b/src/boot/after_store.js index bfb671ed..bcab7a66 100644 --- a/src/boot/after_store.js +++ b/src/boot/after_store.js @@ -14,7 +14,7 @@ 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 } from '../services/style_setter/style_setter.js' +import { applyTheme, applyConfig, tryLoadCache } from '../services/style_setter/style_setter.js' import FaviconService from '../services/favicon_service/favicon_service.js' import { initServiceWorker, updateFocus } from '../services/sw/sw.js' @@ -353,21 +353,25 @@ const afterStoreSetup = async ({ store, i18n }) => { await setConfig({ store }) - const { customTheme, customThemeSource } = store.state.config + const { customTheme, customThemeSource, forceThemeRecompilation } = store.state.config const { theme } = store.state.instance const customThemePresent = customThemeSource || customTheme - if (customThemePresent) { - if (customThemeSource && customThemeSource.themeEngineVersion === CURRENT_VERSION) { - applyTheme(customThemeSource) - } else { - applyTheme(customTheme) - } + if (!forceThemeRecompilation && tryLoadCache()) { store.commit('setThemeApplied') - } else if (theme) { - // do nothing, it will load asynchronously } else { - console.error('Failed to load any theme!') + if (customThemePresent) { + if (customThemeSource && customThemeSource.themeEngineVersion === CURRENT_VERSION) { + applyTheme(customThemeSource) + } else { + applyTheme(customTheme) + } + store.commit('setThemeApplied') + } else if (theme) { + // do nothing, it will load asynchronously + } else { + console.error('Failed to load any theme!') + } } applyConfig(store.state.config) diff --git a/src/components/settings_modal/tabs/general_tab.vue b/src/components/settings_modal/tabs/general_tab.vue index f56fa8e0..424ee7e4 100644 --- a/src/components/settings_modal/tabs/general_tab.vue +++ b/src/components/settings_modal/tabs/general_tab.vue @@ -200,6 +200,14 @@

{{ $t('settings.post_look_feel') }}

    +
  • + + {{ $t('settings.force_theme_recompilation_debug') }} + +
  • { return { lazyProcessFunc: processChunk } } -export const applyTheme = async (input) => { +export const tryLoadCache = () => { + const json = localStorage.getItem('pleroma-fe-theme-cache') + if (!json) return null + let cache + try { + cache = JSON.parse(json) + } catch (e) { + console.error('Failed to decode theme cache:', e) + return false + } + if (cache.checksum === getChecksum()) { + const styleSheet = new CSSStyleSheet() + const lazyStyleSheet = new CSSStyleSheet() + + cache.data[0].forEach(rule => styleSheet.insertRule(rule, 'index-max')) + cache.data[1].forEach(rule => lazyStyleSheet.insertRule(rule, 'index-max')) + + document.adoptedStyleSheets = [styleSheet, lazyStyleSheet] + + return true + } else { + console.warn('Checksum doesn\'t match, cache not usable, clearing') + localStorage.removeItem('pleroma-fe-theme-cache') + } +} + +export const applyTheme = async (input, onFinish = (data) => {}) => { const styleSheet = new CSSStyleSheet() + const styleArray = [] const lazyStyleSheet = new CSSStyleSheet() + const lazyStyleArray = [] const { lazyProcessFunc } = await generateTheme( input, @@ -97,8 +125,10 @@ export const applyTheme = async (input) => { onNewRule (rule, isLazy) { if (isLazy) { lazyStyleSheet.insertRule(rule, 'index-max') + lazyStyleArray.push(rule) } else { styleSheet.insertRule(rule, 'index-max') + styleArray.push(rule) } }, onEagerFinished () { @@ -106,6 +136,9 @@ export const applyTheme = async (input) => { }, onLazyFinished () { document.adoptedStyleSheets = [styleSheet, lazyStyleSheet] + const cache = { checksum: getChecksum(), data: [styleArray, lazyStyleArray] } + onFinish(cache) + localStorage.setItem('pleroma-fe-theme-cache', JSON.stringify(cache)) } } ) diff --git a/src/services/theme_data/theme_data_3.service.js b/src/services/theme_data/theme_data_3.service.js index 7457ab99..844e951a 100644 --- a/src/services/theme_data/theme_data_3.service.js +++ b/src/services/theme_data/theme_data_3.service.js @@ -1,4 +1,5 @@ import { convert, brightness } from 'chromatism' +import sum from 'hash-sum' import { flattenDeep } from 'lodash' import { alphaBlend, @@ -142,8 +143,12 @@ componentsContext.keys().forEach(key => { components[component.name] = component }) +const checksum = sum(components) + const ruleToSelector = genericRuleToSelector(components) +export const getChecksum = () => checksum + export const init = (extraRuleset, ultimateBackgroundColor) => { const staticVars = {} const stacked = {} @@ -463,6 +468,7 @@ export const init = (extraRuleset, ultimateBackgroundColor) => { return { lazy: result.filter(x => typeof x === 'function'), eager: result.filter(x => typeof x !== 'function'), - staticVars + staticVars, + checksum } } -- cgit v1.2.3-70-g09d2 From f9e407de639ce4d20beacd3eca3501639ae161bd Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Fri, 21 Jun 2024 23:28:24 +0300 Subject: made theme debug easier by making it an actual option --- src/boot/after_store.js | 10 ++++++---- src/components/settings_modal/tabs/appearance_tab.vue | 16 ++++++++++++++++ src/components/settings_modal/tabs/general_tab.vue | 8 -------- src/i18n/en.json | 1 + src/modules/config.js | 12 ++++++++++-- src/modules/instance.js | 6 +++--- src/services/style_setter/style_setter.js | 17 +++++++++++------ src/services/theme_data/css_utils.js | 12 +++++------- src/services/theme_data/theme_data_3.service.js | 2 +- 9 files changed, 53 insertions(+), 31 deletions(-) (limited to 'src/boot/after_store.js') diff --git a/src/boot/after_store.js b/src/boot/after_store.js index bcab7a66..b93e28a3 100644 --- a/src/boot/after_store.js +++ b/src/boot/after_store.js @@ -353,18 +353,20 @@ const afterStoreSetup = async ({ store, i18n }) => { await setConfig({ store }) - const { customTheme, customThemeSource, forceThemeRecompilation } = store.state.config + const { customTheme, customThemeSource, forceThemeRecompilation, themeDebug } = store.state.config const { theme } = store.state.instance const customThemePresent = customThemeSource || customTheme - if (!forceThemeRecompilation && tryLoadCache()) { + console.log('DEBUG INITIAL', themeDebug, forceThemeRecompilation) + + if (!forceThemeRecompilation && !themeDebug && tryLoadCache()) { store.commit('setThemeApplied') } else { if (customThemePresent) { if (customThemeSource && customThemeSource.themeEngineVersion === CURRENT_VERSION) { - applyTheme(customThemeSource) + applyTheme(customThemeSource, () => {}, themeDebug) } else { - applyTheme(customTheme) + applyTheme(customTheme, () => {}, themeDebug) } store.commit('setThemeApplied') } else if (theme) { diff --git a/src/components/settings_modal/tabs/appearance_tab.vue b/src/components/settings_modal/tabs/appearance_tab.vue index dd9e8070..5356292e 100644 --- a/src/components/settings_modal/tabs/appearance_tab.vue +++ b/src/components/settings_modal/tabs/appearance_tab.vue @@ -142,6 +142,22 @@ {{ $t('settings.hide_wallpaper') }}
  • +
  • + + {{ $t('settings.force_theme_recompilation_debug') }} + +
  • +
  • + + {{ $t('settings.theme_debug') }} + +
diff --git a/src/components/settings_modal/tabs/general_tab.vue b/src/components/settings_modal/tabs/general_tab.vue index 496d0d56..4ece6cf4 100644 --- a/src/components/settings_modal/tabs/general_tab.vue +++ b/src/components/settings_modal/tabs/general_tab.vue @@ -148,14 +148,6 @@

{{ $t('settings.post_look_feel') }}

    -
  • - - {{ $t('settings.force_theme_recompilation_debug') }} - -
  • {}, themeDebug) break + } + case 'themeDebug': { + const { customTheme, customThemeSource } = state + applyTheme(customTheme || customThemeSource, () => {}, value) + break + } case 'interfaceLanguage': messages.setLanguage(this.getters.i18n, value) dispatch('loadUnicodeEmojiData', value) diff --git a/src/modules/instance.js b/src/modules/instance.js index e28837b3..6997e75d 100644 --- a/src/modules/instance.js +++ b/src/modules/instance.js @@ -383,16 +383,16 @@ const instance = { .then(themeData => { commit('setInstanceOption', { name: 'themeData', value: themeData }) // No need to apply theme if there's user theme already - const { customTheme } = rootState.config + 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) + applyTheme(themeSource, null, themeDebug) } else { - applyTheme(themeData.theme) + applyTheme(themeData.theme, null, themeDebug) } commit('setThemeApplied') }) diff --git a/src/services/style_setter/style_setter.js b/src/services/style_setter/style_setter.js index ee0f8607..126b6946 100644 --- a/src/services/style_setter/style_setter.js +++ b/src/services/style_setter/style_setter.js @@ -6,7 +6,7 @@ import { getCssRules } from '../theme_data/css_utils.js' import { defaultState } from '../../modules/config.js' import { chunk } from 'lodash' -export const generateTheme = async (input, callbacks) => { +export const generateTheme = async (input, callbacks, debug) => { const { onNewRule = (rule, isLazy) => {}, onLazyFinished = () => {}, @@ -22,9 +22,11 @@ export const generateTheme = async (input, callbacks) => { } // Assuming that "worst case scenario background" is panel background since it's the most likely one - const themes3 = init(extraRules, extraRules[0].directives['--bg'].split('|')[1].trim()) + const themes3 = init(extraRules, extraRules[0].directives['--bg'].split('|')[1].trim(), debug) - getCssRules(themes3.eager, themes3.staticVars).forEach(rule => { + 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/)) { const parts = rule.split(/[{}]/g) @@ -54,7 +56,7 @@ export const generateTheme = async (input, callbacks) => { const processChunk = () => { const chunk = chunks[counter] Promise.all(chunk.map(x => x())).then(result => { - getCssRules(result.filter(x => x), themes3.staticVars).forEach(rule => { + getCssRules(result.filter(x => x), debug).forEach(rule => { if (rule.match(/\.modal-view/)) { const parts = rule.split(/[{}]/g) const newRule = [ @@ -113,12 +115,14 @@ export const tryLoadCache = () => { } } -export const applyTheme = async (input, onFinish = (data) => {}) => { +export const applyTheme = async (input, onFinish = (data) => {}, debug) => { const styleSheet = new CSSStyleSheet() const styleArray = [] const lazyStyleSheet = new CSSStyleSheet() const lazyStyleArray = [] + console.log('DEBUG IS', debug) + const { lazyProcessFunc } = await generateTheme( input, { @@ -140,7 +144,8 @@ export const applyTheme = async (input, onFinish = (data) => {}) => { onFinish(cache) localStorage.setItem('pleroma-fe-theme-cache', JSON.stringify(cache)) } - } + }, + debug ) setTimeout(lazyProcessFunc, 0) diff --git a/src/services/theme_data/css_utils.js b/src/services/theme_data/css_utils.js index a89eac3b..8423e8ac 100644 --- a/src/services/theme_data/css_utils.js +++ b/src/services/theme_data/css_utils.js @@ -2,11 +2,6 @@ import { convert } from 'chromatism' import { hex2rgb, rgba2css } from '../color_convert/color_convert.js' -// This changes what backgrounds are used to "stacked" solid colors so you can see -// what theme engine "thinks" is actual background color is for purposes of text color -// generation and for when --stacked variable is used -const DEBUG = false - export const parseCssShadow = (text) => { const dimensions = /(\d[a-z]*\s?){2,4}/.exec(text)?.[0] const inset = /inset/.exec(text)?.[0] @@ -66,7 +61,10 @@ export const getCssShadowFilter = (input) => { .join(' ') } -export const getCssRules = (rules) => rules.map(rule => { +// `debug` changes what backgrounds are used to "stacked" solid colors so you can see +// what theme engine "thinks" is actual background color is for purposes of text color +// generation and for when --stacked variable is used +export const getCssRules = (rules, debug) => rules.map(rule => { let selector = rule.selector if (!selector) { selector = 'html' @@ -93,7 +91,7 @@ export const getCssRules = (rules) => rules.map(rule => { ].join(';\n ') } case 'background': { - if (DEBUG) { + if (debug) { return ` --background: ${getCssColorString(rule.dynamicVars.stacked)}; background-color: ${getCssColorString(rule.dynamicVars.stacked)}; diff --git a/src/services/theme_data/theme_data_3.service.js b/src/services/theme_data/theme_data_3.service.js index 15b4493e..51ecc051 100644 --- a/src/services/theme_data/theme_data_3.service.js +++ b/src/services/theme_data/theme_data_3.service.js @@ -149,7 +149,7 @@ const ruleToSelector = genericRuleToSelector(components) export const getEngineChecksum = () => engineChecksum -export const init = (extraRuleset, ultimateBackgroundColor) => { +export const init = (extraRuleset, ultimateBackgroundColor, debug) => { const staticVars = {} const stacked = {} const computed = {} -- cgit v1.2.3-70-g09d2 From c6ccab778f78bf65cebcad1c5e0943d427254098 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Wed, 10 Jul 2024 22:49:56 +0300 Subject: 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 ++ .../settings_modal/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 + src/services/theme_data/theme_data_3.service.js | 5 +- .../specs/services/theme_data/theme_data3.spec.js | 44 +++++---- 10 files changed, 159 insertions(+), 111 deletions(-) (limited to 'src/boot/after_store.js') 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') -- cgit v1.2.3-70-g09d2