aboutsummaryrefslogtreecommitdiff
path: root/src/modules/interface.js
diff options
context:
space:
mode:
Diffstat (limited to 'src/modules/interface.js')
-rw-r--r--src/modules/interface.js235
1 files changed, 234 insertions, 1 deletions
diff --git a/src/modules/interface.js b/src/modules/interface.js
index f8d62d87..57bfe0c6 100644
--- a/src/modules/interface.js
+++ b/src/modules/interface.js
@@ -1,4 +1,13 @@
+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'
+
const defaultState = {
+ localFonts: null,
+ themeApplied: false,
+ temporaryChangesTimeoutId: null, // used for temporary options that revert after a timeout
+ temporaryChangesConfirm: () => {}, // used for applying temporary options
+ temporaryChangesRevert: () => {}, // used for reverting temporary options
settingsModalState: 'hidden',
settingsModalLoadedUser: false,
settingsModalLoadedAdmin: false,
@@ -13,7 +22,8 @@ const defaultState = {
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'
},
layoutType: 'normal',
globalNotices: [],
@@ -35,6 +45,20 @@ const interfaceMod = {
state.settings.currentSaveStateNotice = { error: true, errorData: error }
}
},
+ setTemporaryChanges (state, { timeoutId, confirm, revert }) {
+ state.temporaryChangesTimeoutId = timeoutId
+ state.temporaryChangesConfirm = confirm
+ state.temporaryChangesRevert = revert
+ },
+ clearTemporaryChanges (state) {
+ clearTimeout(state.temporaryChangesTimeoutId)
+ state.temporaryChangesTimeoutId = null
+ state.temporaryChangesConfirm = () => {}
+ state.temporaryChangesRevert = () => {}
+ },
+ setThemeApplied (state) {
+ state.themeApplied = true
+ },
setNotificationPermission (state, permission) {
state.notificationPermission = permission
},
@@ -86,6 +110,10 @@ const interfaceMod = {
},
setLastTimeline (state, value) {
state.lastTimeline = value
+ },
+ setFontsList (state, value) {
+ // Set is used here so that we filter out duplicate fonts (possibly same font but with different weight)
+ state.localFonts = [...(new Set(value.map(font => font.family))).values()]
}
},
actions: {
@@ -160,10 +188,215 @@ const interfaceMod = {
commit('setLayoutType', wideLayout ? 'wide' : normalOrMobile)
}
},
+ queryLocalFonts ({ commit, dispatch, state }) {
+ if (state.localFonts !== null) return
+ commit('setFontsList', [])
+ if (!state.browserSupport.localFonts) {
+ return
+ }
+ window
+ .queryLocalFonts()
+ .then((fonts) => {
+ commit('setFontsList', fonts)
+ })
+ .catch((e) => {
+ dispatch('pushGlobalNotice', {
+ messageKey: 'settings.style.themes3.font.font_list_unavailable',
+ messageArgs: {
+ error: e
+ },
+ level: 'error'
+ })
+ })
+ },
setLastTimeline ({ commit }, value) {
commit('setLastTimeline', value)
+ },
+ setTheme ({ commit, rootState }, { themeName, themeData, recompile, saveData } = {}) {
+ const {
+ theme: instanceThemeName
+ } = rootState.instance
+
+ const {
+ theme: userThemeName,
+ customTheme: userThemeSnapshot,
+ customThemeSource: userThemeSource,
+ forceThemeRecompilation,
+ themeDebug,
+ theme3hacks
+ } = rootState.config
+
+ const actualThemeName = userThemeName || instanceThemeName
+
+ const forceRecompile = forceThemeRecompilation || recompile
+
+ let promise = null
+
+ if (themeData) {
+ promise = Promise.resolve(normalizeThemeData(themeData))
+ } else if (themeName) {
+ promise = getPreset(themeName).then(themeData => normalizeThemeData(themeData))
+ } else if (userThemeSource || userThemeSnapshot) {
+ promise = Promise.resolve(normalizeThemeData({
+ _pleroma_theme_version: 2,
+ theme: userThemeSnapshot,
+ source: userThemeSource
+ }))
+ } else if (actualThemeName && actualThemeName !== 'custom') {
+ promise = getPreset(actualThemeName).then(themeData => {
+ const realThemeData = normalizeThemeData(themeData)
+ if (actualThemeName === instanceThemeName) {
+ // This sole line is the reason why this whole block is above the recompilation check
+ commit('setInstanceOption', { name: 'themeData', value: { theme: realThemeData } })
+ }
+ return realThemeData
+ })
+ } else {
+ throw new Error('Cannot load any theme!')
+ }
+
+ // If we're not not forced to recompile try using
+ // cache (tryLoadCache return true if load successful)
+ if (!forceRecompile && !themeDebug && tryLoadCache()) {
+ commit('setThemeApplied')
+ return
+ }
+
+ promise
+ .then(realThemeData => {
+ const theme2ruleset = convertTheme2To3(realThemeData)
+
+ if (saveData) {
+ commit('setOption', { name: 'theme', value: themeName || actualThemeName })
+ commit('setOption', { name: 'customTheme', value: realThemeData })
+ commit('setOption', { name: 'customThemeSource', value: realThemeData })
+ }
+ const hacks = []
+
+ Object.entries(theme3hacks).forEach(([key, value]) => {
+ switch (key) {
+ case 'fonts': {
+ Object.entries(theme3hacks.fonts).forEach(([fontKey, font]) => {
+ if (!font?.family) return
+ switch (fontKey) {
+ case 'interface':
+ hacks.push({
+ component: 'Root',
+ directives: {
+ '--font': 'generic | ' + font.family
+ }
+ })
+ break
+ case 'input':
+ hacks.push({
+ component: 'Input',
+ directives: {
+ '--font': 'generic | ' + font.family
+ }
+ })
+ break
+ case 'post':
+ hacks.push({
+ component: 'RichContent',
+ directives: {
+ '--font': 'generic | ' + font.family
+ }
+ })
+ break
+ case 'monospace':
+ hacks.push({
+ component: 'Root',
+ directives: {
+ '--monoFont': 'generic | ' + font.family
+ }
+ })
+ break
+ }
+ })
+ break
+ }
+ case 'underlay': {
+ if (value !== 'none') {
+ const newRule = {
+ component: 'Underlay',
+ directives: {}
+ }
+ if (value === 'opaque') {
+ newRule.directives.opacity = 1
+ newRule.directives.background = '--wallpaper'
+ }
+ if (value === 'transparent') {
+ newRule.directives.opacity = 0
+ }
+ hacks.push(newRule)
+ }
+ break
+ }
+ }
+ })
+
+ const ruleset = [
+ ...theme2ruleset,
+ ...hacks
+ ]
+
+ applyTheme(
+ ruleset,
+ () => commit('setThemeApplied'),
+ themeDebug
+ )
+ })
+
+ return promise
}
}
}
export default interfaceMod
+
+export const normalizeThemeData = (input) => {
+ if (Array.isArray(input)) {
+ const themeData = { colors: {} }
+ themeData.colors.bg = input[1]
+ themeData.colors.fg = input[2]
+ themeData.colors.text = input[3]
+ themeData.colors.link = input[4]
+ themeData.colors.cRed = input[5]
+ themeData.colors.cGreen = input[6]
+ themeData.colors.cBlue = input[7]
+ themeData.colors.cOrange = input[8]
+ return generatePreset(themeData).theme
+ }
+
+ 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 (
+ Object.prototype.hasOwnProperty.call(input, '_pleroma_theme_version') ||
+ Object.prototype.hasOwnProperty.call(input, 'source') ||
+ Object.prototype.hasOwnProperty.call(input, 'theme')
+ ) {
+ // We got passed a full theme file
+ themeData = input.theme
+ themeSource = input.source
+ } else if (Object.prototype.hasOwnProperty.call(input, 'themeEngineVersion')) {
+ // 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
+}