appearance_tab.js (13136B)
- import BooleanSetting from '../helpers/boolean_setting.vue'
- import ChoiceSetting from '../helpers/choice_setting.vue'
- import IntegerSetting from '../helpers/integer_setting.vue'
- import FloatSetting from '../helpers/float_setting.vue'
- import UnitSetting, { defaultHorizontalUnits } from '../helpers/unit_setting.vue'
- import PaletteEditor from 'src/components/palette_editor/palette_editor.vue'
- import FontControl from 'src/components/font_control/font_control.vue'
- import { normalizeThemeData } from 'src/modules/interface'
- import { newImporter } from 'src/services/export_import/export_import.js'
- import { convertTheme2To3 } from 'src/services/theme_data/theme2_to_theme3.js'
- import { init } from 'src/services/theme_data/theme_data_3.service.js'
- import {
- getCssRules,
- getScopedVersion
- } from 'src/services/theme_data/css_utils.js'
- import { deserialize } from 'src/services/theme_data/iss_deserializer.js'
- import SharedComputedObject from '../helpers/shared_computed_object.js'
- import ProfileSettingIndicator from '../helpers/profile_setting_indicator.vue'
- import { library } from '@fortawesome/fontawesome-svg-core'
- import {
- faGlobe
- } from '@fortawesome/free-solid-svg-icons'
- import Preview from './theme_tab/theme_preview.vue'
- // helper for debugging
- // eslint-disable-next-line no-unused-vars
- const toValue = (x) => JSON.parse(JSON.stringify(x === undefined ? 'null' : x))
- library.add(
- faGlobe
- )
- const AppearanceTab = {
- data () {
- return {
- availableThemesV3: [],
- availableThemesV2: [],
- bundledPalettes: [],
- compilationCache: {},
- fileImporter: newImporter({
- accept: '.json, .iss',
- validator: this.importValidator,
- onImport: this.onImport,
- parser: this.importParser,
- onImportFailure: this.onImportFailure
- }),
- palettesKeys: [
- 'bg',
- 'fg',
- 'link',
- 'text',
- 'cRed',
- 'cGreen',
- 'cBlue',
- 'cOrange'
- ],
- userPalette: {},
- intersectionObserver: null,
- thirdColumnModeOptions: ['none', 'notifications', 'postform'].map(mode => ({
- key: mode,
- value: mode,
- label: this.$t(`settings.third_column_mode_${mode}`)
- })),
- forcedRoundnessOptions: ['disabled', 'sharp', 'nonsharp', 'round'].map((mode, i) => ({
- key: mode,
- value: i - 1,
- label: this.$t(`settings.style.themes3.hacks.forced_roundness_mode_${mode}`)
- })),
- underlayOverrideModes: ['none', 'opaque', 'transparent'].map((mode, i) => ({
- key: mode,
- value: mode,
- label: this.$t(`settings.style.themes3.hacks.underlay_override_mode_${mode}`)
- }))
- }
- },
- components: {
- BooleanSetting,
- ChoiceSetting,
- IntegerSetting,
- FloatSetting,
- UnitSetting,
- ProfileSettingIndicator,
- FontControl,
- Preview,
- PaletteEditor
- },
- mounted () {
- this.$store.dispatch('getThemeData')
- const updateIndex = (resource) => {
- const capitalizedResource = resource[0].toUpperCase() + resource.slice(1)
- const currentIndex = this.$store.state.instance[`${resource}sIndex`]
- let promise
- if (currentIndex) {
- promise = Promise.resolve(currentIndex)
- } else {
- promise = this.$store.dispatch(`fetch${capitalizedResource}sIndex`)
- }
- return promise.then(index => {
- return Object
- .entries(index)
- .map(([k, func]) => [k, func()])
- })
- }
- updateIndex('style').then(styles => {
- styles.forEach(([key, stylePromise]) => stylePromise.then(data => {
- const meta = data.find(x => x.component === '@meta')
- this.availableThemesV3.push({ key, data, name: meta.directives.name, version: 'v3' })
- }))
- })
- updateIndex('theme').then(themes => {
- themes.forEach(([key, themePromise]) => themePromise.then(data => {
- if (!data) {
- console.warn(`Theme with key ${key} is empty or malformed`)
- } else if (Array.isArray(data)) {
- console.warn(`Theme with key ${key} is a v1 theme and should be moved to static/palettes/index.json`)
- } else if (!data.source && !data.theme) {
- console.warn(`Theme with key ${key} is malformed`)
- } else {
- this.availableThemesV2.push({ key, data, name: data.name, version: 'v2' })
- }
- }))
- })
- this.userPalette = this.$store.state.interface.paletteDataUsed || {}
- updateIndex('palette').then(bundledPalettes => {
- bundledPalettes.forEach(([key, palettePromise]) => palettePromise.then(v => {
- let palette
- if (Array.isArray(v)) {
- const [
- name,
- bg,
- fg,
- text,
- link,
- cRed = '#FF0000',
- cGreen = '#00FF00',
- cBlue = '#0000FF',
- cOrange = '#E3FF00'
- ] = v
- palette = { key, name, bg, fg, text, link, cRed, cBlue, cGreen, cOrange }
- } else {
- palette = { key, ...v }
- }
- if (!palette.key.startsWith('style.')) {
- this.bundledPalettes.push(palette)
- }
- }))
- })
- if (window.IntersectionObserver) {
- this.intersectionObserver = new IntersectionObserver((entries, observer) => {
- entries.forEach(({ target, isIntersecting }) => {
- if (!isIntersecting) return
- const theme = this.availableStyles.find(x => x.key === target.dataset.themeKey)
- this.$nextTick(() => {
- if (theme) theme.ready = true
- })
- observer.unobserve(target)
- })
- }, {
- root: this.$refs.themeList
- })
- }
- },
- updated () {
- this.$nextTick(() => {
- this.$refs.themeList.querySelectorAll('.theme-preview').forEach(node => {
- this.intersectionObserver.observe(node)
- })
- })
- },
- watch: {
- paletteDataUsed () {
- this.userPalette = this.paletteDataUsed || {}
- }
- },
- computed: {
- switchInProgress () {
- return this.$store.state.interface.themeChangeInProgress
- },
- paletteDataUsed () {
- return this.$store.state.interface.paletteDataUsed
- },
- availableStyles () {
- return [
- ...this.availableThemesV3,
- ...this.availableThemesV2
- ]
- },
- availablePalettes () {
- return [
- ...this.bundledPalettes,
- ...this.stylePalettes
- ]
- },
- stylePalettes () {
- const ruleset = this.$store.state.interface.styleDataUsed || []
- if (!ruleset && ruleset.length === 0) return
- const meta = ruleset.find(x => x.component === '@meta')
- const result = ruleset.filter(x => x.component.startsWith('@palette'))
- .map(x => {
- const { variant, directives } = x
- const {
- bg,
- fg,
- text,
- link,
- accent,
- cRed,
- cBlue,
- cGreen,
- cOrange,
- wallpaper
- } = directives
- const result = {
- name: `${meta.directives.name || this.$t('settings.style.themes3.palette.imported')}: ${variant}`,
- key: `style.${variant.toLowerCase().replace(/ /g, '_')}`,
- bg,
- fg,
- text,
- link,
- accent,
- cRed,
- cBlue,
- cGreen,
- cOrange,
- wallpaper
- }
- return Object.fromEntries(Object.entries(result).filter(([k, v]) => v))
- })
- return result
- },
- noIntersectionObserver () {
- return !window.IntersectionObserver
- },
- horizontalUnits () {
- return defaultHorizontalUnits
- },
- fontsOverride () {
- return this.$store.getters.mergedConfig.fontsOverride
- },
- columns () {
- const mode = this.$store.getters.mergedConfig.thirdColumnMode
- const notif = mode === 'none' ? [] : ['notifs']
- if (this.$store.getters.mergedConfig.sidebarRight || mode === 'postform') {
- return [...notif, 'content', 'sidebar']
- } else {
- return ['sidebar', 'content', ...notif]
- }
- },
- instanceWallpaperUsed () {
- return this.$store.state.instance.background &&
- !this.$store.state.users.currentUser.background_image
- },
- language: {
- get: function () { return this.$store.getters.mergedConfig.interfaceLanguage },
- set: function (val) {
- this.$store.dispatch('setOption', { name: 'interfaceLanguage', value: val })
- }
- },
- customThemeVersion () {
- const { themeVersion } = this.$store.state.interface
- return themeVersion
- },
- isCustomThemeUsed () {
- const { customTheme, customThemeSource } = this.mergedConfig
- return customTheme != null || customThemeSource != null
- },
- isCustomStyleUsed (name) {
- const { styleCustomData } = this.mergedConfig
- return styleCustomData != null
- },
- ...SharedComputedObject()
- },
- methods: {
- updateFont (key, value) {
- this.$store.dispatch('setOption', {
- name: 'theme3hacks',
- value: {
- ...this.mergedConfig.theme3hacks,
- fonts: {
- ...this.mergedConfig.theme3hacks.fonts,
- [key]: value
- }
- }
- })
- },
- importFile () {
- this.fileImporter.importData()
- },
- importParser (file, filename) {
- if (filename.endsWith('.json')) {
- return JSON.parse(file)
- } else if (filename.endsWith('.iss')) {
- return deserialize(file)
- }
- },
- onImport (parsed, filename) {
- if (filename.endsWith('.json')) {
- this.$store.dispatch('setThemeCustom', parsed.source || parsed.theme)
- } else if (filename.endsWith('.iss')) {
- this.$store.dispatch('setStyleCustom', parsed)
- }
- },
- onImportFailure (result) {
- console.error('Failure importing theme:', result)
- this.$store.dispatch('pushGlobalNotice', { messageKey: 'settings.invalid_theme_imported', level: 'error' })
- },
- importValidator (parsed, filename) {
- if (filename.endsWith('.json')) {
- const version = parsed._pleroma_theme_version
- return version >= 1 || version <= 2
- } else if (filename.endsWith('.iss')) {
- if (!Array.isArray(parsed)) return false
- if (parsed.length < 1) return false
- if (parsed.find(x => x.component === '@meta') == null) return false
- return true
- }
- },
- isThemeActive (key) {
- return key === (this.mergedConfig.theme || this.$store.state.instance.theme)
- },
- isStyleActive (key) {
- return key === (this.mergedConfig.style || this.$store.state.instance.style)
- },
- isPaletteActive (key) {
- return key === (this.mergedConfig.palette || this.$store.state.instance.palette)
- },
- setStyle (name) {
- this.$store.dispatch('setStyle', name)
- },
- setTheme (name) {
- this.$store.dispatch('setTheme', name)
- },
- setPalette (name, data) {
- this.$store.dispatch('setPalette', name)
- this.userPalette = data
- },
- setPaletteCustom (data) {
- this.$store.dispatch('setPaletteCustom', data)
- this.userPalette = data
- },
- resetTheming (name) {
- this.$store.dispatch('setStyle', 'stock')
- },
- previewTheme (key, version, input) {
- let theme3
- if (this.compilationCache[key]) {
- theme3 = this.compilationCache[key]
- } else if (input) {
- if (version === 'v2') {
- const style = normalizeThemeData(input)
- const theme2 = convertTheme2To3(style)
- theme3 = init({
- inputRuleset: theme2,
- ultimateBackgroundColor: '#000000',
- liteMode: true,
- debug: true,
- onlyNormalState: true
- })
- } else if (version === 'v3') {
- const palette = input.find(x => x.component === '@palette')
- let paletteRule
- if (palette) {
- const { directives } = palette
- directives.link = directives.link || directives.accent
- directives.accent = directives.accent || directives.link
- paletteRule = {
- component: 'Root',
- directives: Object.fromEntries(
- Object
- .entries(directives)
- .filter(([k, v]) => k && k !== 'name')
- .map(([k, v]) => ['--' + k, 'color | ' + v])
- )
- }
- } else {
- paletteRule = null
- }
- theme3 = init({
- inputRuleset: [...input, paletteRule].filter(x => x),
- ultimateBackgroundColor: '#000000',
- liteMode: true,
- debug: true,
- onlyNormalState: true
- })
- }
- } else {
- theme3 = init({
- inputRuleset: [],
- ultimateBackgroundColor: '#000000',
- liteMode: true,
- debug: true,
- onlyNormalState: true
- })
- }
- if (!this.compilationCache[key]) {
- this.compilationCache[key] = theme3
- }
- return getScopedVersion(
- getCssRules(theme3.eager),
- '#theme-preview-' + key
- ).join('\n')
- }
- }
- }
- export default AppearanceTab