logo

pleroma-fe

My custom branche(s) on git.pleroma.social/pleroma/pleroma-fe git clone https://anongit.hacktivis.me/git/pleroma-fe.git/
commit: f0957bdb4ff1fed060696ac953679cef241e92be
parent 07a48315a14bee16a3b6a1822502301180803e60
Author: Henry Jameson <me@hjkos.com>
Date:   Tue,  1 Oct 2024 00:42:33 +0300

palettes that actually work

Diffstat:

Msrc/components/button.style.js8+++++++-
Msrc/components/palette_editor/palette_editor.vue88++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------
Msrc/components/settings_modal/tabs/appearance_tab.js98+++++++++++++++++++++++++++++++++++++++++++++++++++----------------------------
Asrc/components/settings_modal/tabs/appearance_tab.scss76++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/components/settings_modal/tabs/appearance_tab.vue102+++++++++++++++++++++++++++++++++++--------------------------------------------
Msrc/components/settings_modal/tabs/style_tab/style_tab.vue6+++---
Msrc/components/settings_modal/tabs/theme_tab/theme_tab.js34++++------------------------------
Msrc/i18n/en.json37++++++++++++++++++++-----------------
Msrc/modules/interface.js50+++++++++++++++++++++++++++++++++++++++++++++++---
Msrc/services/style_setter/style_setter.js26+++++++++++++++++++++-----
Astatic/palettes/index.json32++++++++++++++++++++++++++++++++
Mstatic/styles.json6------
12 files changed, 393 insertions(+), 170 deletions(-)

diff --git a/src/components/button.style.js b/src/components/button.style.js @@ -63,12 +63,18 @@ export default { } }, { - state: ['hover', 'pressed'], + state: ['pressed', 'hover'], directives: { shadow: ['--defaultButtonHoverGlow', '--pressedButtonBevel'] } }, { + state: ['pressed', 'hover', 'focused'], + directives: { + shadow: ['--pressedButtonBevel'] + } + }, + { state: ['toggled'], directives: { background: '--inheritedBackground,-14.2', diff --git a/src/components/palette_editor/palette_editor.vue b/src/components/palette_editor/palette_editor.vue @@ -1,21 +1,73 @@ <template> <div class="PaletteEditor"> - <ColorInput - v-for="key in paletteKeys" - :key="key" - :model-value="props.modelValue[key]" - :fallback="fallback(key)" - :label="$t('settings.style.themes3.editor.palette.' + key)" - @update:modelValue="value => updatePalette(key, value)" - /> + <div class="colors"> + <ColorInput + v-for="key in paletteKeys" + :key="key" + :model-value="props.modelValue[key]" + :fallback="fallback(key)" + :label="$t('settings.style.themes3.palette.' + key)" + @update:modelValue="value => updatePalette(key, value)" + /> + </div> + <div class="controls"> + <button + class="btn button-default" + @click="importPalette" + > + <FAIcon icon="file-import"/> + {{ $t('settings.style.themes3.palette.import') }} + </button> + <button + class="btn button-default" + @click="exportPalette" + > + <FAIcon icon="file-export"/> + {{ $t('settings.style.themes3.palette.export') }} + </button> + </div> </div> </template> <script setup> import ColorInput from 'src/components/color_input/color_input.vue' +import { + // newImporter, + newExporter +} from 'src/services/export_import/export_import.js' + +import { library } from '@fortawesome/fontawesome-svg-core' +import { + faFileImport, + faFileExport +} from '@fortawesome/free-solid-svg-icons' + +library.add( + faFileImport, + faFileExport +) const props = defineProps(['modelValue']) const emit = defineEmits(['update:modelValue']) +const paletteExporter = newExporter({ + filename: 'pleroma.palette.json', + getExportedObject: () => props.modelValue +}) +/* + const themeImporter = newImporter({ + validator: importValidator, + onImport, + onImportFailure, + }) +*/ + +const exportPalette = () => { + paletteExporter.exportData() +} + +const importPalette = () => { + // TODO +} const paletteKeys = [ 'bg', @@ -40,7 +92,7 @@ const fallback = (key) => { return props.modelValue.accent } if (key.startsWith('extra')) { - return '#000000' + return '#008080' } } @@ -54,10 +106,18 @@ const updatePalette = (paletteKey, value) => { <style lang="scss"> .PaletteEditor { - display: grid; - justify-content: space-around; - grid-template-columns: repeat(4, min-content); - grid-template-rows: repeat(auto-fit, min-content); - grid-gap: 0.5em; + .colors { + display: grid; + justify-content: space-around; + grid-template-columns: repeat(4, min-content); + grid-template-rows: repeat(auto-fit, min-content); + grid-gap: 0.5em; + } + + .controls { + display: grid; + grid-template-columns: 1fr 1fr; + grid-gap: 0.5em; + } } </style> diff --git a/src/components/settings_modal/tabs/appearance_tab.js b/src/components/settings_modal/tabs/appearance_tab.js @@ -9,7 +9,7 @@ import FontControl from 'src/components/font_control/font_control.vue' import { normalizeThemeData } from 'src/modules/interface' import { - getThemes + getThemeResources } from 'src/services/style_setter/style_setter.js' import { convertTheme2To3 } from 'src/services/theme_data/theme2_to_theme3.js' import { init } from 'src/services/theme_data/theme_data_3.service.js' @@ -35,6 +35,17 @@ const AppearanceTab = { data () { return { availableStyles: [], + availablePalettes: [], + palettesKeys: [ + 'background', + 'foreground', + 'link', + 'text', + 'cRed', + 'cBlue', + 'cGreen', + 'cOrange' + ], intersectionObserver: null, thirdColumnModeOptions: ['none', 'notifications', 'postform'].map(mode => ({ key: mode, @@ -64,29 +75,36 @@ const AppearanceTab = { Preview }, mounted () { - getThemes() - .then((promises) => { - return Promise.all( - Object.entries(promises) - .map(([k, v]) => v.then(res => [k, res])) - ) + getThemeResources('/static/styles.json') + .then((themes) => { + this.availableStyles = Object + .entries(themes) + .map(([key, data]) => ({ key, data, name: data.name || data[0], version: 'v2' })) }) - .then(themes => themes.reduce((acc, [k, v]) => { - if (v) { - return [ - ...acc, - { - name: v.name || v[0], - key: k, - data: v - } - ] - } else { - return acc - } - }, [])) - .then((themesComplete) => { - this.availableStyles = themesComplete + + getThemeResources('/static/palettes/index.json') + .then((palettes) => { + const result = {} + console.log(palettes) + Object.entries(palettes).forEach(([k, v]) => { + if (Array.isArray(v)) { + const [ + name, + background, + foreground, + text, + link, + cRed = '#FF0000', + cBlue = '#0000FF', + cGreen = '#00FF00', + cOrange = '#E3FF00' + ] = v + result[k] = { name, background, foreground, text, link, cRed, cBlue, cGreen, cOrange } + } else { + result[k] = v + } + }) + this.availablePalettes = result }) if (window.IntersectionObserver) { @@ -171,18 +189,30 @@ const AppearanceTab = { setTheme (name) { this.$store.dispatch('setTheme', { themeName: name, saveData: true, recompile: true }) }, + setPalette (name) { + this.$store.dispatch('setPalette', { paletteData: name }) + }, previewTheme (key, input) { - const style = normalizeThemeData(input) - const x = 2 - if (x === 1) return - const theme2 = convertTheme2To3(style) - const theme3 = init({ - inputRuleset: theme2, - ultimateBackgroundColor: '#000000', - liteMode: true, - debug: true, - onlyNormalState: true - }) + let theme3 + if (input) { + const style = normalizeThemeData(input) + const theme2 = convertTheme2To3(style) + theme3 = init({ + inputRuleset: theme2, + ultimateBackgroundColor: '#000000', + liteMode: true, + debug: true, + onlyNormalState: true + }) + } else { + theme3 = init({ + inputRuleset: [], + ultimateBackgroundColor: '#000000', + liteMode: true, + debug: true, + onlyNormalState: true + }) + } return getScopedVersion( getCssRules(theme3.eager), diff --git a/src/components/settings_modal/tabs/appearance_tab.scss b/src/components/settings_modal/tabs/appearance_tab.scss @@ -0,0 +1,76 @@ +.appearance-tab { + .palette, + .theme-notice { + padding: 0.5em; + margin: 1em; + } + + .palettes { + display: grid; + grid-template-columns: 1fr 1fr; + grid-gap: 0.5em; + } + + .palette-entry { + display: flex; + align-items: center; + + > label { + flex: 1 0 auto; + } + + .palette-square { + flex: 0 0 auto; + display: inline-block; + min-width: 1em; + min-height: 1em; + } + } + + .column-settings { + display: flex; + justify-content: space-evenly; + flex-wrap: wrap; + } + + .column-settings .size-label { + display: block; + margin-bottom: 0.5em; + margin-top: 0.5em; + } + + .theme-list { + list-style: none; + display: flex; + flex-wrap: wrap; + margin: -0.5em 0; + height: 25em; + overflow-x: hidden; + overflow-y: auto; + scrollbar-gutter: stable; + border-radius: var(--roundness); + border: 1px solid var(--border); + padding: 0; + + .theme-preview { + font-size: 1rem; // fix for firefox + width: 19rem; + display: flex; + flex-direction: column; + align-items: center; + margin: 0.5em; + + &.placeholder { + opacity: 0.2; + } + + .theme-preview-container { + pointer-events: none; + zoom: 0.5; + border: none; + border-radius: var(--roundness); + text-align: left; + } + } + } +} diff --git a/src/components/settings_modal/tabs/appearance_tab.vue b/src/components/settings_modal/tabs/appearance_tab.vue @@ -7,12 +7,32 @@ ref="themeList" > <button + class="button-default theme-preview" + data-theme-key="stock" + @click="setTheme(null)" + > + <!-- eslint-disable vue/no-v-text-v-html-on-component --> + <component + :is="'style'" + v-html="previewTheme('stock')" + /> + <!-- eslint-enable vue/no-v-text-v-html-on-component --> + <preview /> + <h4 class="theme-name"> + {{ $t('settings.style.stock_theme_used') }} + <span class="alert neutral version">v3</span> + </h4> + </button> + <button v-if="isCustomThemeUsed" disabled class="button-default theme-preview" > <preview /> - <h4 class="theme-name">{{ $t('settings.style.custom_theme_used') }}</h4> + <h4 class="theme-name"> + {{ $t('settings.style.custom_theme_used') }} + <span class="alert neutral version">v2</span> + </h4> </button> <button v-for="style in availableStyles" @@ -30,9 +50,31 @@ /> <!-- eslint-enable vue/no-v-text-v-html-on-component --> <preview :class="{ placeholder: ready }" :id="'theme-preview-' + style.key"/> - <h4 class="theme-name">{{ style.name }}</h4> + <h4 class="theme-name"> + {{ style.name }} + <span class="alert neutral version">{{ style.version }}</span> + </h4> </button> </ul> + <h3>{{ $t('settings.style.themes3.palette.label') }}</h3> + <div class="palettes"> + <button + v-for="p in availablePalettes" + class="btn button-default palette-entry" + :key="p.name" + @click="() => setPalette(p)" + > + <label> + {{ p.name }} + </label> + <span + v-for="c in palettesKeys" + :key="c" + class="palette-square" + :style="{ backgroundColor: p[c], border: '1px solid ' + (p[c] ?? 'var(--text)') }" + /> + </button> + </div> </div> <div class="alert neutral theme-notice"> {{ $t("settings.style.appearance_tab_note") }} @@ -256,58 +298,4 @@ <script src="./appearance_tab.js"></script> -<style lang="scss"> -.appearance-tab { - .theme-notice { - padding: 0.5em; - margin: 1em; - } - - .column-settings { - display: flex; - justify-content: space-evenly; - flex-wrap: wrap; - } - - .column-settings .size-label { - display: block; - margin-bottom: 0.5em; - margin-top: 0.5em; - } - - .theme-list { - list-style: none; - display: flex; - flex-wrap: wrap; - margin: -0.5em 0; - height: 25em; - overflow-x: hidden; - overflow-y: auto; - scrollbar-gutter: stable; - border-radius: var(--roundness); - border: 1px solid var(--border); - padding: 0; - - .theme-preview { - font-size: 1rem; // fix for firefox - width: 19rem; - display: flex; - flex-direction: column; - align-items: center; - margin: 0.5em; - - &.placeholder { - opacity: 0.2; - } - - .theme-preview-container { - pointer-events: none; - zoom: 0.5; - border: none; - border-radius: var(--roundness); - text-align: left; - } - } - } -} -</style> +<style lang="scss" src="./appearance_tab.scss"></style> diff --git a/src/components/settings_modal/tabs/style_tab/style_tab.vue b/src/components/settings_modal/tabs/style_tab/style_tab.vue @@ -54,7 +54,7 @@ <div class="setting-item palette-editor"> <div class="label"> <label for="palette-selector"> - {{ $t('settings.style.themes3.editor.palette.label') }} + {{ $t('settings.style.themes3.palette.label') }} {{ ' ' }} </label> <Select @@ -62,10 +62,10 @@ id="palette-selector" > <option key="dark" value="dark"> - {{ $t('settings.style.themes3.editor.palette.dark') }} + {{ $t('settings.style.themes3.palette.dark') }} </option> <option key="light" value="light"> - {{ $t('settings.style.themes3.editor.palette.light') }} + {{ $t('settings.style.themes3.palette.light') }} </option> </Select> </div> diff --git a/src/components/settings_modal/tabs/theme_tab/theme_tab.js b/src/components/settings_modal/tabs/theme_tab/theme_tab.js @@ -5,7 +5,7 @@ import { relativeLuminance } from 'src/services/color_convert/color_convert.js' import { - getThemes + getThemeResources } from 'src/services/style_setter/style_setter.js' import { newImporter, @@ -125,25 +125,9 @@ export default { created () { const self = this - getThemes() - .then((promises) => { - return Promise.all( - Object.entries(promises) - .map(([k, v]) => v.then(res => [k, res])) - ) - }) - .then(themes => themes.reduce((acc, [k, v]) => { - if (v) { - return { - ...acc, - [k]: v - } - } else { - return acc - } - }, {})) + getThemeResources('/static/styles.json') .then((themesComplete) => { - self.availableStyles = themesComplete + self.availableStyles = Object.values(themesComplete) }) }, mounted () { @@ -412,9 +396,6 @@ export default { forceUseSource = false ) { this.dismissWarning() - if (!source && !theme) { - throw new Error('Can\'t load theme: empty') - } const version = (origin === 'localStorage' && !theme.colors) ? 'l1' : fileVersion @@ -494,14 +475,7 @@ export default { customTheme: theme, customThemeSource: source } = this.$store.getters.mergedConfig - if (!theme && !source) { - // Anon user or never touched themes - this.loadTheme( - this.$store.state.instance.themeData, - 'defaults', - confirmLoadSource - ) - } else { + if (theme || source) { this.loadTheme( { theme, diff --git a/src/i18n/en.json b/src/i18n/en.json @@ -748,11 +748,31 @@ "more_settings": "More settings", "style": { "custom_theme_used": "(Custom theme)", + "stock_theme_used": "(Stock theme)", "themes2_outdated": "Editor for Themes V2 is being phased out and will eventually be replaced with a new one that takes advantage of new Themes V3 engine. It should still work but experience might be degraded and inconsistent.", "appearance_tab_note": "Changes on this tab do not affect the theme used, so exported theme will be different from what seen in the UI", "update_preview": "Update preview", "themes3": { "define": "Override", + "palette": { + "label": "Palette", + "import": "Import", + "export": "Export", + "dark": "Dark mode", + "light": "Light mode", + "bg": "Panel background", + "fg": "Buttons etc.", + "text": "Text", + "link": "Links", + "accent": "Accent color", + "cRed": "Red color", + "cBlue": "Blue color", + "cGreen": "Green color", + "cOrange": "Orange color", + "extra1": "Extra 1", + "extra2": "Extra 2", + "extra3": "Extra 3" + }, "editor": { "title": "Style", "new_style": "New", @@ -778,23 +798,6 @@ "preserve": "Keep color", "no-auto": "Disabled" }, - "palette": { - "label": "Palette", - "dark": "Dark mode", - "light": "Light mode", - "bg": "Panel background", - "fg": "Buttons etc.", - "text": "Text", - "link": "Links", - "accent": "Accent color", - "cRed": "Red color", - "cBlue": "Blue color", - "cGreen": "Green color", - "cOrange": "Orange color", - "extra1": "Extra 1", - "extra2": "Extra 2", - "extra3": "Extra 3" - }, "components": { "normal": { "state": "Normal", diff --git a/src/modules/interface.js b/src/modules/interface.js @@ -212,6 +212,11 @@ const interfaceMod = { setLastTimeline ({ commit }, value) { commit('setLastTimeline', value) }, + setPalette ({ dispatch, commit }, { paletteData }) { + console.log('PAL', paletteData) + commit('setOption', { name: 'userPalette', value: paletteData }) + dispatch('setTheme', { themeName: null, recompile: true }) + }, setTheme ({ commit, rootState }, { themeName, themeData, recompile, saveData } = {}) { const { theme: instanceThemeName @@ -221,21 +226,52 @@ const interfaceMod = { theme: userThemeName, customTheme: userThemeSnapshot, customThemeSource: userThemeSource, + userPalette, forceThemeRecompilation, themeDebug, theme3hacks } = rootState.config + const userPaletteIss = (() => { + if (!userPalette) return null + const result = { + component: 'Root', + directives: {} + } + + Object + .entries(userPalette) + .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 + })() const actualThemeName = userThemeName || instanceThemeName const forceRecompile = forceThemeRecompilation || recompile let promise = null + console.log('TEST', actualThemeName, themeData) + if (themeData) { promise = Promise.resolve(normalizeThemeData(themeData)) } else if (themeName) { promise = getPreset(themeName).then(themeData => normalizeThemeData(themeData)) + } else if (themeName === null) { + promise = Promise.resolve(null) } else if (userThemeSource || userThemeSnapshot) { promise = Promise.resolve(normalizeThemeData({ _pleroma_theme_version: 2, @@ -264,13 +300,14 @@ const interfaceMod = { promise .then(realThemeData => { - const theme2ruleset = convertTheme2To3(realThemeData) + const theme2ruleset = realThemeData ? convertTheme2To3(realThemeData) : null 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]) => { @@ -336,9 +373,15 @@ const interfaceMod = { }) const ruleset = [ - ...theme2ruleset, ...hacks ] + if (!theme2ruleset && userPaletteIss) { + ruleset.unshift(userPaletteIss) + } + + if (theme2ruleset) { + ruleset.unshift(...theme2ruleset) + } applyTheme( ruleset, @@ -355,6 +398,7 @@ const interfaceMod = { export default interfaceMod export const normalizeThemeData = (input) => { + console.log(input) if (Array.isArray(input)) { const themeData = { colors: {} } themeData.colors.bg = input[1] @@ -365,7 +409,7 @@ export const normalizeThemeData = (input) => { themeData.colors.cGreen = input[6] themeData.colors.cBlue = input[7] themeData.colors.cOrange = input[8] - return generatePreset(themeData).theme + return generatePreset(themeData).source || generatePreset(themeData).theme } let themeData, themeSource diff --git a/src/services/style_setter/style_setter.js b/src/services/style_setter/style_setter.js @@ -256,13 +256,13 @@ export const applyConfig = (input) => { body.classList.remove('hidden') } -export const getThemes = () => { +export const getThemeResources = (url) => { const cache = 'no-store' - return window.fetch('/static/styles.json', { cache }) + return window.fetch(url, { cache }) .then((data) => data.json()) - .then((themes) => { - return Object.entries(themes).map(([k, v]) => { + .then((resources) => { + return Object.entries(resources).map(([k, v]) => { let promise = null if (typeof v === 'object') { promise = Promise.resolve(v) @@ -284,10 +284,26 @@ export const getThemes = () => { return acc }, {}) }) + .then((promises) => { + return Promise.all( + Object.entries(promises) + .map(([k, v]) => v.then(res => [k, res])) + ) + }) + .then(themes => themes.reduce((acc, [k, v]) => { + if (v) { + return { + ...acc, + [k]: v + } + } else { + return acc + } + }, {})) } export const getPreset = (val) => { - return getThemes() + return getThemeResources('/static/styles.json') .then((themes) => themes[val] ? themes[val] : themes['pleroma-dark']) .then((theme) => { const isV1 = Array.isArray(theme) diff --git a/static/palettes/index.json b/static/palettes/index.json @@ -0,0 +1,32 @@ +{ + "pleroma-light": [ "Pleroma Light", "#f2f4f6", "#dbe0e8", "#304055", "#f86f0f", "#d31014", "#0fa00f", "#0095ff", "#ffa500" ], + "pleroma-dark": [ "Pleroma Dark", "#121a24", "#182230", "#b9b9ba", "#d8a070", "#d31014", "#0fa00f", "#0095ff", "#ffa500" ], + "classic-dark": { + "name": "Classic Dark", + "background": "#161c20", + "foreground": "#282e32", + "text": "#b9b9b9", + "link": "#baaa9c", + "cRed": "#d31014", + "cBlue": "#0fa00f", + "cGreen": "#0095ff", + "cOrange": "#ffa500" + }, + "pleroma-amoled": [ "Pleroma Dark AMOLED", "#000000", "#111111", "#b0b0b1", "#d8a070", "#aa0000", "#0fa00f", "#0095ff", "#d59500"], + "tomorrow-night": { + "name": "Tomorrow Night", + "background": "#1d1f21", + "foreground": "#373b41", + "link": "#81a2be", + "text": "#c5c8c6", + "cRed": "#cc6666", + "cBlue": "#8abeb7", + "cGreen": "#b5bd68", + "cOrange": "#de935f", + "_cYellow": "#f0c674", + "_cPurple": "#b294bb" + }, + "bird": [ "Bird", "#f8fafd", "#e6ecf0", "#14171a", "#0084b8", "#e0245e", "#17bf63", "#1b95e0", "#fab81e"], + "ir-black": [ "Ir Black", "#000000", "#242422", "#b5b3aa", "#ff6c60", "#FF6C60", "#A8FF60", "#96CBFE", "#FFFFB6" ], + "monokai": [ "Monokai", "#272822", "#383830", "#f8f8f2", "#f92672", "#F92672", "#a6e22e", "#66d9ef", "#f4bf75" ] +} diff --git a/static/styles.json b/static/styles.json @@ -1,12 +1,6 @@ { "pleroma-dark": "/static/themes/pleroma-dark.json", "pleroma-light": "/static/themes/pleroma-light.json", - "pleroma-amoled": [ "Pleroma Dark AMOLED", "#000000", "#111111", "#b0b0b1", "#d8a070", "#aa0000", "#0fa00f", "#0095ff", "#d59500"], - "classic-dark": [ "Classic Dark", "#161c20", "#282e32", "#b9b9b9", "#baaa9c", "#d31014", "#0fa00f", "#0095ff", "#ffa500" ], - "bird": [ "Bird", "#f8fafd", "#e6ecf0", "#14171a", "#0084b8", "#e0245e", "#17bf63", "#1b95e0", "#fab81e"], - "ir-black": [ "Ir Black", "#000000", "#242422", "#b5b3aa", "#ff6c60", "#FF6C60", "#A8FF60", "#96CBFE", "#FFFFB6" ], - "monokai": [ "Monokai", "#272822", "#383830", "#f8f8f2", "#f92672", "#F92672", "#a6e22e", "#66d9ef", "#f4bf75" ], - "redmond-xx": "/static/themes/redmond-xx.json", "redmond-xx-se": "/static/themes/redmond-xx-se.json", "redmond-xxi": "/static/themes/redmond-xxi.json",