pleroma-fe/src/stores/interface.js

740 lines
22 KiB
JavaScript
Raw Normal View History

2023-04-05 21:06:37 -06:00
import { defineStore } from 'pinia'
2026-01-06 16:22:52 +02:00
import {
CURRENT_VERSION,
generatePreset,
} from 'src/services/theme_data/theme_data.service.js'
2026-01-06 16:23:17 +02:00
import { convertTheme2To3 } from 'src/services/theme_data/theme2_to_theme3.js'
import { useInstanceStore } from 'src/stores/instance.js'
2026-01-06 16:22:52 +02:00
import {
applyTheme,
2026-01-06 16:23:17 +02:00
getResourcesIndex,
2026-01-06 16:22:52 +02:00
tryLoadCache,
} from '../services/style_setter/style_setter.js'
2025-01-30 21:56:07 +02:00
import { deserialize } from '../services/theme_data/iss_deserializer.js'
2023-04-05 21:06:37 -06:00
export const useInterfaceStore = defineStore('interface', {
state: () => ({
2025-01-30 21:56:07 +02:00
localFonts: null,
themeApplied: false,
themeChangeInProgress: false,
themeVersion: 'v3',
styleNameUsed: null,
styleDataUsed: null,
useStylePalette: false, // hack for applying styles from appearance tab
paletteNameUsed: null,
paletteDataUsed: null,
themeNameUsed: null,
themeDataUsed: null,
2025-12-15 22:56:04 +02:00
temporaryChangesTimeoutId: null,
2026-01-06 16:22:52 +02:00
temporaryChangesCountdown: -1, // used for temporary options that revert after a timeout
2026-01-06 17:32:22 +02:00
temporaryChangesConfirm: () => {
/* no-op */
}, // used for applying temporary options
temporaryChangesRevert: () => {
/* no-op */
}, // used for reverting temporary options
2023-04-05 21:06:37 -06:00
settingsModalState: 'hidden',
2025-01-30 21:56:07 +02:00
settingsModalLoadedUser: false,
settingsModalLoadedAdmin: false,
2023-04-05 21:06:37 -06:00
settingsModalTargetTab: null,
2025-01-30 21:56:07 +02:00
settingsModalMode: 'user',
2023-04-05 21:06:37 -06:00
settings: {
currentSaveStateNotice: null,
noticeClearTimeout: null,
2026-01-06 16:22:52 +02:00
notificationPermission: null,
2023-04-05 21:06:37 -06:00
},
browserSupport: {
2026-01-06 16:22:52 +02:00
cssFilter:
window.CSS &&
window.CSS.supports &&
(window.CSS.supports('filter', 'drop-shadow(0 0)') ||
window.CSS.supports('-webkit-filter', 'drop-shadow(0 0)')),
localFonts: typeof window.queryLocalFonts === 'function',
2023-04-05 21:06:37 -06:00
},
layoutType: 'normal',
globalNotices: [],
layoutHeight: 0,
2026-01-06 16:22:52 +02:00
lastTimeline: null,
2023-04-05 21:06:37 -06:00
}),
actions: {
2026-01-06 16:22:52 +02:00
setTemporaryChanges({ confirm, revert }) {
2025-12-15 22:56:04 +02:00
this.temporaryChangesCountdown = 10
2025-03-19 03:33:05 +02:00
this.temporaryChangesConfirm = confirm
this.temporaryChangesRevert = revert
2025-12-15 22:56:04 +02:00
const countdownFunc = () => {
if (this.temporaryChangesCountdown === 1) {
this.temporaryChangesRevert()
this.clearTemporaryChanges()
} else {
this.temporaryChangesCountdown--
this.temporaryChangesTimeoutId = setTimeout(countdownFunc, 1000)
}
}
this.temporaryChangesTimeoutId = setTimeout(countdownFunc, 1000)
2025-03-19 03:33:05 +02:00
},
2026-01-06 16:22:52 +02:00
clearTemporaryChanges() {
this.temporaryChangesTimeoutId ??
clearTimeout(this.temporaryChangesTimeoutId)
2025-03-19 03:33:05 +02:00
this.temporaryChangesTimeoutId = null
2025-12-15 22:56:04 +02:00
this.temporaryChangesCountdown = -1
2026-01-06 17:32:22 +02:00
this.temporaryChangesConfirm = () => {
/* no-op */
}
this.temporaryChangesRevert = () => {
/* no-op */
}
2025-03-19 03:33:05 +02:00
},
2026-01-06 16:22:52 +02:00
setPageTitle(option = '') {
2023-04-05 21:06:37 -06:00
try {
document.title = `${option} ${window.vuex.useInstanceStore().name}`
2023-04-05 21:06:37 -06:00
} catch (error) {
console.error(`${error}`)
}
},
2026-01-06 16:22:52 +02:00
settingsSaved({ success, error }) {
2023-04-05 21:06:37 -06:00
if (success) {
if (this.noticeClearTimeout) {
clearTimeout(this.noticeClearTimeout)
}
this.settings.currentSaveStateNotice = { error: false, data: success }
2026-01-06 16:22:52 +02:00
this.settings.noticeClearTimeout = setTimeout(
() => delete this.settings.currentSaveStateNotice,
2000,
)
2023-04-05 21:06:37 -06:00
} else {
this.settings.currentSaveStateNotice = { error: true, errorData: error }
}
},
2026-01-06 16:22:52 +02:00
setNotificationPermission(permission) {
2023-04-05 21:06:37 -06:00
this.notificationPermission = permission
},
2026-01-06 16:22:52 +02:00
closeSettingsModal() {
2023-04-05 21:06:37 -06:00
this.settingsModalState = 'hidden'
},
2026-01-06 16:22:52 +02:00
openSettingsModal(value) {
2025-01-30 21:56:07 +02:00
this.settingsModalMode = value
2023-04-05 21:06:37 -06:00
this.settingsModalState = 'visible'
2025-01-30 21:56:07 +02:00
if (value === 'user') {
if (!this.settingsModalLoadedUser) {
this.settingsModalLoadedUser = true
}
} else if (value === 'admin') {
if (!this.settingsModalLoadedAdmin) {
this.settingsModalLoadedAdmin = true
}
2023-04-05 21:06:37 -06:00
}
},
2026-01-06 16:22:52 +02:00
togglePeekSettingsModal() {
2023-04-05 21:06:37 -06:00
switch (this.settingsModalState) {
case 'minimized':
this.settingsModalState = 'visible'
return
case 'visible':
this.settingsModalState = 'minimized'
return
default:
throw new Error('Illegal minimization state of settings modal')
}
},
2026-01-06 16:22:52 +02:00
clearSettingsModalTargetTab() {
2023-04-05 21:06:37 -06:00
this.settingsModalTargetTab = null
},
2026-01-06 16:22:52 +02:00
openSettingsModalTab(value, mode = 'user') {
2023-04-05 21:06:37 -06:00
this.settingsModalTargetTab = value
2025-02-03 00:14:44 +02:00
this.openSettingsModal(mode)
2023-04-05 21:06:37 -06:00
},
2026-01-06 16:22:52 +02:00
removeGlobalNotice(notice) {
this.globalNotices = this.globalNotices.filter((n) => n !== notice)
2023-04-05 21:06:37 -06:00
},
2026-01-06 16:22:52 +02:00
pushGlobalNotice({
messageKey,
messageArgs = {},
level = 'error',
timeout = 0,
}) {
2023-04-05 21:06:37 -06:00
const notice = {
messageKey,
messageArgs,
2026-01-06 16:22:52 +02:00
level,
2023-04-05 21:06:37 -06:00
}
this.globalNotices.push(notice)
// Adding a new element to array wraps it in a Proxy, which breaks the comparison
// TODO: Generate UUID or something instead or relying on !== operator?
const newNotice = this.globalNotices[this.globalNotices.length - 1]
if (timeout) {
setTimeout(() => this.removeGlobalNotice(newNotice), timeout)
}
return newNotice
},
2026-01-06 16:22:52 +02:00
setLayoutHeight(value) {
2023-04-05 21:06:37 -06:00
this.layoutHeight = value
},
2026-01-06 16:22:52 +02:00
setLayoutWidth(value) {
2023-04-05 21:06:37 -06:00
let width = value
if (value !== undefined) {
this.layoutWidth = value
} else {
width = this.layoutWidth
}
const mobileLayout = width <= 800
const normalOrMobile = mobileLayout ? 'mobile' : 'normal'
const { thirdColumnMode } = window.vuex.getters.mergedConfig
if (thirdColumnMode === 'none' || !window.vuex.state.users.currentUser) {
this.layoutType = normalOrMobile
} else {
const wideLayout = width >= 1300
this.layoutType = wideLayout ? 'wide' : normalOrMobile
}
},
2026-01-06 16:22:52 +02:00
setFontsList(value) {
this.localFonts = [...new Set(value.map((font) => font.family)).values()]
2025-02-03 00:14:44 +02:00
},
2026-01-06 16:22:52 +02:00
queryLocalFonts() {
2025-02-03 00:14:44 +02:00
if (this.localFonts !== null) return
this.setFontsList([])
if (!this.browserSupport.localFonts) {
return
}
window
.queryLocalFonts()
.then((fonts) => {
this.setFontsList(fonts)
})
.catch((e) => {
this.pushGlobalNotice({
messageKey: 'settings.style.themes3.font.font_list_unavailable',
messageArgs: {
2026-01-06 16:22:52 +02:00
error: e,
2025-02-03 00:14:44 +02:00
},
2026-01-06 16:22:52 +02:00
level: 'error',
2025-02-03 00:14:44 +02:00
})
})
},
2026-01-06 16:22:52 +02:00
setLastTimeline(value) {
2023-04-05 21:06:37 -06:00
this.lastTimeline = value
2025-01-30 21:56:07 +02:00
},
2026-01-06 16:22:52 +02:00
async fetchPalettesIndex() {
2025-01-30 21:56:07 +02:00
try {
const value = await getResourcesIndex('/static/palettes/index.json')
2026-01-06 16:22:52 +02:00
window.vuex.commit('setInstanceOption', {
name: 'palettesIndex',
value,
})
2025-01-30 21:56:07 +02:00
return value
} catch (e) {
console.error('Could not fetch palettes index', e)
2026-01-06 16:22:52 +02:00
window.vuex.commit('setInstanceOption', {
name: 'palettesIndex',
value: { _error: e },
})
2025-01-30 21:56:07 +02:00
return Promise.resolve({})
}
},
2026-01-06 16:22:52 +02:00
setPalette(value) {
2025-01-30 21:56:07 +02:00
this.resetThemeV3Palette()
this.resetThemeV2()
window.vuex.commit('setOption', { name: 'palette', value })
this.applyTheme({ recompile: true })
},
2026-01-06 16:22:52 +02:00
setPaletteCustom(value) {
2025-01-30 21:56:07 +02:00
this.resetThemeV3Palette()
this.resetThemeV2()
window.vuex.commit('setOption', { name: 'paletteCustomData', value })
this.applyTheme({ recompile: true })
},
2026-01-06 16:22:52 +02:00
async fetchStylesIndex() {
2025-01-30 21:56:07 +02:00
try {
const value = await getResourcesIndex(
'/static/styles/index.json',
2026-01-06 16:22:52 +02:00
deserialize,
2025-01-30 21:56:07 +02:00
)
window.vuex.commit('setInstanceOption', { name: 'stylesIndex', value })
return value
} catch (e) {
console.error('Could not fetch styles index', e)
2026-01-06 16:22:52 +02:00
window.vuex.commit('setInstanceOption', {
name: 'stylesIndex',
value: { _error: e },
})
2025-01-30 21:56:07 +02:00
return Promise.resolve({})
}
},
2026-01-06 16:22:52 +02:00
setStyle(value) {
2025-01-30 21:56:07 +02:00
this.resetThemeV3()
this.resetThemeV2()
this.resetThemeV3Palette()
window.vuex.commit('setOption', { name: 'style', value })
this.useStylePalette = true
this.applyTheme({ recompile: true }).then(() => {
this.useStylePalette = false
})
},
2026-01-06 16:22:52 +02:00
setStyleCustom(value) {
2025-01-30 21:56:07 +02:00
this.resetThemeV3()
this.resetThemeV2()
this.resetThemeV3Palette()
window.vuex.commit('setOption', { name: 'styleCustomData', value })
this.useStylePalette = true
this.applyTheme({ recompile: true }).then(() => {
this.useStylePalette = false
})
},
2026-01-06 16:22:52 +02:00
async fetchThemesIndex() {
2025-01-30 21:56:07 +02:00
try {
const value = await getResourcesIndex('/static/styles.json')
window.vuex.commit('setInstanceOption', { name: 'themesIndex', value })
return value
} catch (e) {
console.error('Could not fetch themes index', e)
2026-01-06 16:22:52 +02:00
window.vuex.commit('setInstanceOption', {
name: 'themesIndex',
value: { _error: e },
})
2025-01-30 21:56:07 +02:00
return Promise.resolve({})
}
},
2026-01-06 16:22:52 +02:00
setTheme(value) {
2025-01-30 21:56:07 +02:00
this.resetThemeV3()
this.resetThemeV3Palette()
this.resetThemeV2()
window.vuex.commit('setOption', { name: 'theme', value })
this.applyTheme({ recompile: true })
},
2026-01-06 16:22:52 +02:00
setThemeCustom(value) {
2025-01-30 21:56:07 +02:00
this.resetThemeV3()
this.resetThemeV3Palette()
this.resetThemeV2()
window.vuex.commit('setOption', { name: 'customTheme', value })
window.vuex.commit('setOption', { name: 'customThemeSource', value })
this.applyTheme({ recompile: true })
},
2026-01-06 16:22:52 +02:00
resetThemeV3() {
2025-01-30 21:56:07 +02:00
window.vuex.commit('setOption', { name: 'style', value: null })
window.vuex.commit('setOption', { name: 'styleCustomData', value: null })
},
2026-01-06 16:22:52 +02:00
resetThemeV3Palette() {
2025-01-30 21:56:07 +02:00
window.vuex.commit('setOption', { name: 'palette', value: null })
2026-01-06 16:22:52 +02:00
window.vuex.commit('setOption', {
name: 'paletteCustomData',
value: null,
})
2025-01-30 21:56:07 +02:00
},
2026-01-06 16:22:52 +02:00
resetThemeV2() {
2025-01-30 21:56:07 +02:00
window.vuex.commit('setOption', { name: 'theme', value: null })
window.vuex.commit('setOption', { name: 'customTheme', value: null })
2026-01-06 16:22:52 +02:00
window.vuex.commit('setOption', {
name: 'customThemeSource',
value: null,
})
2025-01-30 21:56:07 +02:00
},
2026-01-06 16:22:52 +02:00
async getThemeData() {
2025-01-30 21:56:07 +02:00
const getData = async (resource, index, customData, name) => {
2026-01-06 16:22:52 +02:00
const capitalizedResource =
resource[0].toUpperCase() + resource.slice(1)
2025-01-30 21:56:07 +02:00
const result = {}
if (customData) {
result.nameUsed = 'custom' // custom data overrides name
result.dataUsed = customData
} else {
result.nameUsed = name
if (result.nameUsed == null) {
result.dataUsed = null
return result
}
let fetchFunc = index[result.nameUsed]
// Fallbacks
if (!fetchFunc) {
if (resource === 'style' || resource === 'palette') {
return result
}
const newName = Object.keys(index)[0]
fetchFunc = index[newName]
2026-01-06 16:22:52 +02:00
console.warn(
`${capitalizedResource} with id '${this.styleNameUsed}' not found, trying back to '${newName}'`,
)
2025-01-30 21:56:07 +02:00
if (!fetchFunc) {
2026-01-06 16:22:52 +02:00
console.warn(
`${capitalizedResource} doesn't have a fallback, defaulting to stock.`,
)
2025-01-30 21:56:07 +02:00
fetchFunc = () => Promise.resolve(null)
}
}
result.dataUsed = await fetchFunc()
}
return result
}
2026-01-06 16:22:52 +02:00
const { style: instanceStyleName, palette: instancePaletteName } =
window.vuex.useInstanceStore()
2025-01-30 21:56:07 +02:00
let {
theme: instanceThemeV2Name,
themesIndex,
stylesIndex,
2026-01-06 16:22:52 +02:00
palettesIndex,
} = window.vuex.useInstanceStore()
2025-01-30 21:56:07 +02:00
const {
style: userStyleName,
styleCustomData: userStyleCustomData,
palette: userPaletteName,
2026-01-06 16:22:52 +02:00
paletteCustomData: userPaletteCustomData,
2025-01-30 21:56:07 +02:00
} = window.vuex.state.config
let {
theme: userThemeV2Name,
customTheme: userThemeV2Snapshot,
2026-01-06 16:22:52 +02:00
customThemeSource: userThemeV2Source,
2025-01-30 21:56:07 +02:00
} = window.vuex.state.config
let majorVersionUsed
console.debug(
2026-01-06 16:22:52 +02:00
`User V3 palette: ${userPaletteName}, style: ${userStyleName} , custom: ${!!userStyleCustomData}`,
2025-01-30 21:56:07 +02:00
)
console.debug(
2026-01-06 16:22:52 +02:00
`User V2 name: ${userThemeV2Name}, source: ${!!userThemeV2Source}, snapshot: ${!!userThemeV2Snapshot}`,
2025-01-30 21:56:07 +02:00
)
2026-01-06 16:22:52 +02:00
console.debug(
`Instance V3 palette: ${instancePaletteName}, style: ${instanceStyleName}`,
)
2025-01-30 21:56:07 +02:00
console.debug('Instance V2 theme: ' + instanceThemeV2Name)
2026-01-06 16:22:52 +02:00
if (
userPaletteName ||
userPaletteCustomData ||
userStyleName ||
userStyleCustomData ||
// User V2 overrides instance V3
((instancePaletteName || instanceStyleName) &&
instanceThemeV2Name == null &&
userThemeV2Name == null)
2025-01-30 21:56:07 +02:00
) {
// Palette and/or style overrides V2 themes
instanceThemeV2Name = null
userThemeV2Name = null
userThemeV2Source = null
userThemeV2Snapshot = null
majorVersionUsed = 'v3'
} else if (
2026-01-06 16:22:52 +02:00
userThemeV2Name ||
userThemeV2Snapshot ||
userThemeV2Source ||
instanceThemeV2Name
2025-01-30 21:56:07 +02:00
) {
majorVersionUsed = 'v2'
} else {
// if all fails fallback to v3
majorVersionUsed = 'v3'
}
if (majorVersionUsed === 'v3') {
const result = await Promise.all([
this.fetchPalettesIndex(),
2026-01-06 16:22:52 +02:00
this.fetchStylesIndex(),
2025-01-30 21:56:07 +02:00
])
palettesIndex = result[0]
stylesIndex = result[1]
} else {
// Promise.all just to be uniform with v3
2026-01-06 16:22:52 +02:00
const result = await Promise.all([this.fetchThemesIndex()])
2025-01-30 21:56:07 +02:00
themesIndex = result[0]
}
this.themeVersion = majorVersionUsed
console.debug('Version used', majorVersionUsed)
if (majorVersionUsed === 'v3') {
this.themeDataUsed = null
this.themeNameUsed = null
const style = await getData(
'style',
stylesIndex,
userStyleCustomData,
2026-01-06 16:22:52 +02:00
userStyleName || instanceStyleName,
2025-01-30 21:56:07 +02:00
)
this.styleNameUsed = style.nameUsed
this.styleDataUsed = style.dataUsed
let firstStylePaletteName = null
2026-01-06 16:22:52 +02:00
style.dataUsed
?.filter((x) => x.component === '@palette')
.map((x) => {
2025-01-30 21:56:07 +02:00
const cleanDirectives = Object.fromEntries(
2026-01-06 16:22:52 +02:00
Object.entries(x.directives).filter(([k]) => k),
2025-01-30 21:56:07 +02:00
)
return { name: x.variant, ...cleanDirectives }
})
2026-01-06 16:22:52 +02:00
.forEach((palette) => {
2025-01-30 21:56:07 +02:00
const key = 'style.' + palette.name.toLowerCase().replace(/ /g, '_')
if (!firstStylePaletteName) firstStylePaletteName = key
palettesIndex[key] = () => Promise.resolve(palette)
})
const palette = await getData(
'palette',
palettesIndex,
userPaletteCustomData,
2026-01-06 16:22:52 +02:00
this.useStylePalette
? firstStylePaletteName
: userPaletteName || instancePaletteName,
2025-01-30 21:56:07 +02:00
)
if (this.useStylePalette) {
2026-01-06 16:22:52 +02:00
window.vuex.commit('setOption', {
name: 'palette',
value: firstStylePaletteName,
})
2025-01-30 21:56:07 +02:00
}
this.paletteNameUsed = palette.nameUsed
this.paletteDataUsed = palette.dataUsed
if (this.paletteDataUsed) {
2026-01-06 16:22:52 +02:00
this.paletteDataUsed.link =
this.paletteDataUsed.link || this.paletteDataUsed.accent
this.paletteDataUsed.accent =
this.paletteDataUsed.accent || this.paletteDataUsed.link
2025-01-30 21:56:07 +02:00
}
if (Array.isArray(this.paletteDataUsed)) {
const [
name,
bg,
fg,
text,
link,
cRed = '#FF0000',
cGreen = '#00FF00',
cBlue = '#0000FF',
2026-01-06 16:22:52 +02:00
cOrange = '#E3FF00',
2025-01-30 21:56:07 +02:00
] = palette.dataUsed
this.paletteDataUsed = {
name,
bg,
fg,
text,
link,
accent: link,
cRed,
cBlue,
cGreen,
2026-01-06 16:22:52 +02:00
cOrange,
2025-01-30 21:56:07 +02:00
}
}
console.debug('Palette data used', palette.dataUsed)
} else {
this.styleNameUsed = null
this.styleDataUsed = null
this.paletteNameUsed = null
this.paletteDataUsed = null
const theme = await getData(
'theme',
themesIndex,
userThemeV2Source || userThemeV2Snapshot,
2026-01-06 16:22:52 +02:00
userThemeV2Name || instanceThemeV2Name,
2025-01-30 21:56:07 +02:00
)
this.themeNameUsed = theme.nameUsed
this.themeDataUsed = theme.dataUsed
}
},
2026-01-06 16:22:52 +02:00
async setThemeApplied() {
2025-01-30 21:56:07 +02:00
this.themeApplied = true
},
2026-01-06 16:22:52 +02:00
async applyTheme({ recompile = false } = {}) {
const { forceThemeRecompilation, themeDebug, theme3hacks } =
window.vuex.state.config
2025-01-30 21:56:07 +02:00
this.themeChangeInProgress = true
2025-04-13 21:16:09 +02:00
// If we're not forced to recompile try using
2025-01-30 21:56:07 +02:00
// cache (tryLoadCache return true if load successful)
const forceRecompile = forceThemeRecompilation || recompile
2025-04-13 21:16:09 +02:00
await this.getThemeData()
2026-01-06 16:22:52 +02:00
if (!forceRecompile && !themeDebug && (await tryLoadCache())) {
2025-01-30 21:56:07 +02:00
this.themeChangeInProgress = false
return this.setThemeApplied()
}
window.splashUpdate('splash.theme')
try {
const paletteIss = (() => {
if (!this.paletteDataUsed) return null
const result = {
component: 'Root',
2026-01-06 16:22:52 +02:00
directives: {},
2025-01-30 21:56:07 +02:00
}
2026-01-06 16:22:52 +02:00
Object.entries(this.paletteDataUsed)
2025-01-30 21:56:07 +02:00
.filter(([k]) => k !== 'name')
.forEach(([k, v]) => {
let issRootDirectiveName
switch (k) {
case 'background':
issRootDirectiveName = 'bg'
break
case 'foreground':
issRootDirectiveName = 'fg'
break
default:
issRootDirectiveName = k
}
result.directives['--' + issRootDirectiveName] = 'color | ' + v
})
return result
})()
2026-01-06 16:22:52 +02:00
const theme2ruleset =
this.themeDataUsed &&
convertTheme2To3(normalizeThemeData(this.themeDataUsed))
2025-01-30 21:56:07 +02:00
const hacks = []
Object.entries(theme3hacks).forEach(([key, value]) => {
switch (key) {
case 'fonts': {
Object.entries(theme3hacks.fonts).forEach(([fontKey, font]) => {
if (!font?.family) return
switch (fontKey) {
case 'interface':
hacks.push({
component: 'Root',
directives: {
2026-01-06 16:22:52 +02:00
'--font': 'generic | ' + font.family,
},
2025-01-30 21:56:07 +02:00
})
break
case 'input':
hacks.push({
component: 'Input',
directives: {
2026-01-06 16:22:52 +02:00
'--font': 'generic | ' + font.family,
},
2025-01-30 21:56:07 +02:00
})
break
case 'post':
hacks.push({
component: 'RichContent',
directives: {
2026-01-06 16:22:52 +02:00
'--font': 'generic | ' + font.family,
},
2025-01-30 21:56:07 +02:00
})
break
case 'monospace':
hacks.push({
component: 'Root',
directives: {
2026-01-06 16:22:52 +02:00
'--monoFont': 'generic | ' + font.family,
},
2025-01-30 21:56:07 +02:00
})
break
}
})
break
}
case 'underlay': {
if (value !== 'none') {
const newRule = {
component: 'Underlay',
2026-01-06 16:22:52 +02:00
directives: {},
2025-01-30 21:56:07 +02:00
}
if (value === 'opaque') {
newRule.directives.opacity = 1
newRule.directives.background = '--wallpaper'
}
if (value === 'transparent') {
newRule.directives.opacity = 0
}
hacks.push(newRule)
}
break
}
}
})
const rulesetArray = [
theme2ruleset,
this.styleDataUsed,
paletteIss,
2026-01-06 16:22:52 +02:00
hacks,
].filter((x) => x)
2025-01-30 21:56:07 +02:00
return applyTheme(
rulesetArray.flat(),
() => this.setThemeApplied(),
() => {
this.themeChangeInProgress = false
},
2026-01-06 16:22:52 +02:00
themeDebug,
2025-01-30 21:56:07 +02:00
)
} catch (e) {
window.splashError(e)
}
2026-01-06 16:22:52 +02:00
},
},
2023-04-05 21:06:37 -06:00
})
2025-01-30 21:56:07 +02:00
export const normalizeThemeData = (input) => {
let themeData, themeSource
if (input.themeFileVerison === 1) {
// this might not be even used at all, some leftover of unimplemented code in V2 editor
return generatePreset(input).theme
} else if (
2026-01-06 16:20:14 +02:00
Object.hasOwn(input, '_pleroma_theme_version') ||
2026-01-06 16:22:52 +02:00
Object.hasOwn(input, 'source') ||
Object.hasOwn(input, 'theme')
2025-01-30 21:56:07 +02:00
) {
// We got passed a full theme file
themeData = input.theme
themeSource = input.source
} else if (
2026-01-06 16:20:14 +02:00
Object.hasOwn(input, 'themeEngineVersion') ||
2026-01-06 16:22:52 +02:00
Object.hasOwn(input, 'colors')
2025-01-30 21:56:07 +02:00
) {
// We got passed a source/snapshot
themeData = input
themeSource = input
}
// New theme presets don't have 'theme' property, they use 'source'
let out // shout, shout let it all out
if (themeSource && themeSource.themeEngineVersion === CURRENT_VERSION) {
// There are some themes in wild that have completely broken source
out = { ...(themeData || {}), ...themeSource }
} else {
out = themeData
}
// generatePreset here basically creates/updates "snapshot",
// while also fixing the 2.2 -> 2.3 colors/shadows/etc
return generatePreset(out).theme
}