From d31da2c3009c49eb8f55ff03ad622f0be4f4bf6c Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Sun, 6 Oct 2024 02:48:21 +0300 Subject: [PATCH 01/14] theme v3 config.json fields --- static/config.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/static/config.json b/static/config.json index 46a4857d1..8a78284a3 100644 --- a/static/config.json +++ b/static/config.json @@ -25,5 +25,7 @@ "sidebarRight": false, "subjectLineBehavior": "email", "theme": null, + "style": null, + "palette": null, "webPushNotifications": false } From c58ed1036fd3bdbfb13f5dbc92e887acc97c5e64 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Sun, 6 Oct 2024 03:30:52 +0300 Subject: [PATCH 02/14] remove i18n stuff. first of all - it's too much work for me AND for translators second of all - providing support would be a living hell nightmare trying to understand what component it is in users's language that isn't english --- .../tabs/style_tab/style_tab.js | 27 ------------ .../tabs/style_tab/style_tab.vue | 6 +-- src/i18n/en.json | 41 +------------------ 3 files changed, 4 insertions(+), 70 deletions(-) diff --git a/src/components/settings_modal/tabs/style_tab/style_tab.js b/src/components/settings_modal/tabs/style_tab/style_tab.js index fb42e193e..3d13ed2ff 100644 --- a/src/components/settings_modal/tabs/style_tab/style_tab.js +++ b/src/components/settings_modal/tabs/style_tab/style_tab.js @@ -136,29 +136,6 @@ export default { cOrange: '#ffa500' }) - // ### I18n stuff - // The paths in i18n are getting ridicously long, this effectively shortens them - const getI18nPath = (componentName) => `settings.style.themes3.editor.components.${componentName}` - // vue i18n doesn't seem to have (working) mechanic to have a fallback so we have to - // make do ourselves - const fallbackI18n = (translated, fallback) => { - if (translated.startsWith('settings.style.themes3')) { - return fallback - } - return translated - } - const getFriendlyNamePath = (componentName) => getI18nPath(componentName) + '.friendlyName' - const getVariantPath = (componentName, variant) => { - return variant === 'normal' - ? 'settings.style.themes3.editor.components.normal.variant' - : `${getI18nPath(componentName)}.variants.${variant}` - } - const getStatePath = (componentName, state) => { - return state === 'normal' - ? 'settings.style.themes3.editor.components.normal.state' - : `${getI18nPath(componentName)}.states.${state}` - } - // ### Initialization stuff // Getting existing components const componentsContext = require.context('src', true, /\.style.js(on)?$/) @@ -569,10 +546,6 @@ export default { previewCss, previewClass, editorHintStyle, - getFriendlyNamePath, - fallbackI18n, - getVariantPath, - getStatePath, componentHas, isShadowTabOpen, onTabSwitch, diff --git a/src/components/settings_modal/tabs/style_tab/style_tab.vue b/src/components/settings_modal/tabs/style_tab/style_tab.vue index 704cdc1bc..dff57742a 100644 --- a/src/components/settings_modal/tabs/style_tab/style_tab.vue +++ b/src/components/settings_modal/tabs/style_tab/style_tab.vue @@ -71,7 +71,7 @@ :key="'component-' + key" :value="key" > - {{ fallbackI18n($t(getFriendlyNamePath(componentsMap.get(key).name)), componentsMap.get(key).name) }} + {{ componentsMap.get(key).name }} @@ -90,7 +90,7 @@ :key="'component-variant-' + variant" :value="variant" > - {{ fallbackI18n($t(getVariantPath(selectedComponentName, variant)), variant) }} + {{ variant }} @@ -112,7 +112,7 @@ :value="selectedState.has(state)" @update:modelValue="(v) => updateSelectedStates(state, v)" > - {{ fallbackI18n($t(getStatePath(selectedComponentName, state)), state) }} + {{ state }} diff --git a/src/i18n/en.json b/src/i18n/en.json index b7ee0984c..3694a3908 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -800,46 +800,7 @@ "no-auto": "Disabled" }, "component_tab": "Components style", - "palette_tab": "Color presets", - "components": { - "normal": { - "state": "Normal", - "variant": "Default" - }, - "Alert": { - "friendlyName": "Alert", - "variants": { - "error": "Error", - "warning": "Warning", - "success": "Success" - } - }, - "Button": { - "friendlyName": "Button", - "variants": { - "danger": "Dangerous" - }, - "states": { - "toggled": "Toggled", - "pressed": "Pressed", - "hover": "Hovered", - "focused": "Has focus", - "disabled": "Disabled" - } - }, - "Input": { - "friendlyName": "Input fields", - "variants": { - "checkbox": "Checkbox", - "radio": "Radio" - }, - "states": { - "hover": "Hovered", - "focus": "Focused", - "disabled": "Disabled" - } - } - } + "palette_tab": "Color presets" }, "hacks": { "underlay_overrides": "Change underlay", From 0f2bd39db8feb317f9b9295597bad6566661ad79 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Sun, 6 Oct 2024 03:31:59 +0300 Subject: [PATCH 03/14] cleanup --- .../settings_modal/tabs/style_tab/style_tab.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/components/settings_modal/tabs/style_tab/style_tab.js b/src/components/settings_modal/tabs/style_tab/style_tab.js index 3d13ed2ff..9c51c8eab 100644 --- a/src/components/settings_modal/tabs/style_tab/style_tab.js +++ b/src/components/settings_modal/tabs/style_tab/style_tab.js @@ -63,12 +63,14 @@ export default { const website = ref('') const metaOut = computed(() => { - return `@meta { - name: ${name.value}; - author: ${author.value}; - license: ${license.value}; - website: ${website.value}; -}` + return [ + '@meta {', + ` name: ${name.value};`, + ` author: ${author.value};`, + ` license: ${license.value};`, + ` website: ${website.value};`, + '}' + ].join('\n') }) // ### Palette stuff @@ -101,7 +103,6 @@ export default { ]) const palettesOut = computed(() => { - console.log('WORK DAMN', palettes) return palettes.map(({ name, ...palette }) => { const entries = Object .entries(palette) @@ -417,7 +418,6 @@ export default { const updatePreview = () => { try { const { name, ...paletteData } = palette.value - console.log('WORK', paletteData) const rules = init({ inputRuleset: editorFriendlyToOriginal.value, initialStaticVars: { From 7e684ea3ff67803e4dd419f9db561fc52fd9893f Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Sun, 6 Oct 2024 03:32:08 +0300 Subject: [PATCH 04/14] proper exporter --- .../tabs/style_tab/style_tab.js | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/src/components/settings_modal/tabs/style_tab/style_tab.js b/src/components/settings_modal/tabs/style_tab/style_tab.js index 9c51c8eab..2a29744db 100644 --- a/src/components/settings_modal/tabs/style_tab/style_tab.js +++ b/src/components/settings_modal/tabs/style_tab/style_tab.js @@ -17,12 +17,16 @@ import ContrastRatio from 'src/components/contrast_ratio/contrast_ratio.vue' import { init } from 'src/services/theme_data/theme_data_3.service.js' import { getCssRules } from 'src/services/theme_data/css_utils.js' import { serialize } from 'src/services/theme_data/iss_serializer.js' -import { deserialize } from 'src/services/theme_data/iss_deserializer.js' +// import { deserialize } from 'src/services/theme_data/iss_deserializer.js' import { // rgb2hex, hex2rgb, getContrastRatio } from 'src/services/color_convert/color_convert.js' +import { + // newImporter, + newExporter +} from 'src/services/export_import/export_import.js' import { library } from '@fortawesome/fontawesome-svg-core' import { faFloppyDisk, faFolderOpen, faFile } from '@fortawesome/free-solid-svg-icons' @@ -492,18 +496,19 @@ export default { isShadowTabOpen.value = tab === 'shadow' } - const exportStyle = () => { - console.log('ORIG', toValue(editorFriendlyToOriginal.value)) - console.log('SERI', serialize(editorFriendlyToOriginal.value)) - - const result = [ + const styleExporter = newExporter({ + filename: 'pleroma.palette.json', + getExportedObject: () => exportStyleData + }) + const exportStyleData = computed(() => { + return [ metaOut.value, palettesOut.value, serialize(editorFriendlyToOriginal.value) ].join('\n\n') - - console.log('RESULT', result) - console.log('DESERI', deserialize(result)) + }) + const exportStyle = () => { + styleExporter.exportData() } return { From da2c016ab42f2cfbc8926e816fb842ce2293d1a0 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Sun, 6 Oct 2024 14:10:19 +0300 Subject: [PATCH 05/14] better vars nomenclature --- src/components/button.style.js | 26 ++++++++++++------------ src/components/input.style.js | 14 ++++++------- src/components/tab_switcher/tab.style.js | 8 ++++---- 3 files changed, 24 insertions(+), 24 deletions(-) diff --git a/src/components/button.style.js b/src/components/button.style.js index 95ef3e403..248da8bbd 100644 --- a/src/components/button.style.js +++ b/src/components/button.style.js @@ -35,11 +35,11 @@ export default { { component: 'Root', directives: { - '--defaultButtonHoverGlow': 'shadow | 0 0 4 --text / 0.5', - '--defaultButtonFocusGlow': 'shadow | 0 0 4 4 --link / 0.5', - '--defaultButtonShadow': 'shadow | 0 0 2 #000000', - '--defaultButtonBevel': 'shadow | $borderSide(#FFFFFF top 0.2 2), $borderSide(#000000 bottom 0.2 2)', - '--pressedButtonBevel': 'shadow | $borderSide(#FFFFFF bottom 0.2 2), $borderSide(#000000 top 0.2 2)' + '--buttonDefaultHoverGlow': 'shadow | 0 0 4 --text / 0.5', + '--buttonDefaultFocusGlow': 'shadow | 0 0 4 4 --link / 0.5', + '--buttonDefaultShadow': 'shadow | 0 0 2 #000000', + '--buttonDefaultBevel': 'shadow | $borderSide(#FFFFFF top 0.2 2), $borderSide(#000000 bottom 0.2 2)', + '--buttonPressedBevel': 'shadow | $borderSide(#FFFFFF bottom 0.2 2), $borderSide(#000000 top 0.2 2)' } }, { @@ -47,53 +47,53 @@ export default { // like within it directives: { background: '--fg', - shadow: ['--defaultButtonShadow', '--defaultButtonBevel'], + shadow: ['--buttonDefaultShadow', '--buttonDefaultBevel'], roundness: 3 } }, { state: ['hover'], directives: { - shadow: ['--defaultButtonHoverGlow', '--defaultButtonBevel'] + shadow: ['--buttonDefaultHoverGlow', '--buttonDefaultBevel'] } }, { state: ['focused'], directives: { - shadow: ['--defaultButtonFocusGlow', '--defaultButtonBevel'] + shadow: ['--buttonDefaultFocusGlow', '--buttonDefaultBevel'] } }, { state: ['pressed'], directives: { - shadow: ['--defaultButtonShadow', '--pressedButtonBevel'] + shadow: ['--buttonDefaultShadow', '--buttonPressedBevel'] } }, { state: ['pressed', 'hover'], directives: { - shadow: ['--pressedButtonBevel', '--defaultButtonHoverGlow'] + shadow: ['--buttonPressedBevel', '--buttonDefaultHoverGlow'] } }, { state: ['toggled'], directives: { background: '--inheritedBackground,-14.2', - shadow: ['--defaultButtonShadow', '--pressedButtonBevel'] + shadow: ['--buttonDefaultShadow', '--buttonPressedBevel'] } }, { state: ['toggled', 'hover'], directives: { background: '--inheritedBackground,-14.2', - shadow: ['--defaultButtonHoverGlow', '--pressedButtonBevel'] + shadow: ['--buttonDefaultHoverGlow', '--buttonPressedBevel'] } }, { state: ['disabled'], directives: { background: '$blend(--inheritedBackground 0.25 --parent)', - shadow: ['--defaultButtonBevel'] + shadow: ['--buttonDefaultBevel'] } }, { diff --git a/src/components/input.style.js b/src/components/input.style.js index c60ac1e4e..087de1e14 100644 --- a/src/components/input.style.js +++ b/src/components/input.style.js @@ -18,9 +18,9 @@ export default { { component: 'Root', directives: { - '--defaultInputBevel': 'shadow | $borderSide(#FFFFFF bottom 0.2), $borderSide(#000000 top 0.2)', - '--defaultInputHoverGlow': 'shadow | 0 0 4 --text / 0.5', - '--defaultInputFocusGlow': 'shadow | 0 0 4 4 --link / 0.5' + '--inputDefaultBevel': 'shadow | $borderSide(#FFFFFF bottom 0.2), $borderSide(#000000 top 0.2)', + '--inputDefaultHoverGlow': 'shadow | 0 0 4 --text / 0.5', + '--inputDefaultFocusGlow': 'shadow | 0 0 4 4 --link / 0.5' } }, { @@ -41,25 +41,25 @@ export default { spread: 0, color: '#000000', alpha: 1 - }, '--defaultInputBevel'] + }, '--inputDefaultBevel'] } }, { state: ['hover'], directives: { - shadow: ['--defaultInputHoverGlow', '--defaultInputBevel'] + shadow: ['--inputDefaultHoverGlow', '--inputDefaultBevel'] } }, { state: ['focused'], directives: { - shadow: ['--defaultInputFocusGlow', '--defaultInputBevel'] + shadow: ['--inputDefaultFocusGlow', '--inputDefaultBevel'] } }, { state: ['focused', 'hover'], directives: { - shadow: ['--defaultInputFocusGlow', '--defaultInputHoverGlow', '--defaultInputBevel'] + shadow: ['--inputDefaultFocusGlow', '--inputDefaultHoverGlow', '--inputDefaultBevel'] } }, { diff --git a/src/components/tab_switcher/tab.style.js b/src/components/tab_switcher/tab.style.js index 217d42ebc..814d33047 100644 --- a/src/components/tab_switcher/tab.style.js +++ b/src/components/tab_switcher/tab.style.js @@ -14,14 +14,14 @@ export default { { directives: { background: '--fg', - shadow: ['--defaultButtonShadow', '--defaultButtonBevel'], + shadow: ['--buttonDefaultShadow', '--buttonDefaultBevel'], roundness: 3 } }, { state: ['hover'], directives: { - shadow: ['--defaultButtonHoverGlow', '--defaultButtonBevel'] + shadow: ['--buttonDefaultHoverGlow', '--buttonDefaultBevel'] } }, { @@ -33,14 +33,14 @@ export default { { state: ['hover', 'active'], directives: { - shadow: ['--defaultButtonShadow', '--defaultButtonBevel'] + shadow: ['--buttonDefaultShadow', '--buttonDefaultBevel'] } }, { state: ['disabled'], directives: { background: '$blend(--inheritedBackground 0.25 --parent)', - shadow: ['--defaultButtonBevel'] + shadow: ['--buttonDefaultBevel'] } }, { From a6863248bbffac9d8efddb00903ec631113df33f Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Sun, 6 Oct 2024 15:00:52 +0300 Subject: [PATCH 06/14] variables + consistency in code --- .../tabs/style_tab/style_tab.js | 412 ++++++++++-------- .../tabs/style_tab/style_tab.vue | 4 +- 2 files changed, 228 insertions(+), 188 deletions(-) diff --git a/src/components/settings_modal/tabs/style_tab/style_tab.js b/src/components/settings_modal/tabs/style_tab/style_tab.js index 2a29744db..3b5dba94f 100644 --- a/src/components/settings_modal/tabs/style_tab/style_tab.js +++ b/src/components/settings_modal/tabs/style_tab/style_tab.js @@ -60,7 +60,7 @@ export default { ContrastRatio }, setup () { - // ### Meta stuff + // ## Meta stuff const name = ref('') const author = ref('') const license = ref('') @@ -77,7 +77,7 @@ export default { ].join('\n') }) - // ### Palette stuff + // ## Palette stuff const palettes = reactive([ { name: 'light', @@ -105,29 +105,15 @@ export default { cOrange: '#ffa500' } ]) - - const palettesOut = computed(() => { - return palettes.map(({ name, ...palette }) => { - const entries = Object - .entries(palette) - .map(([slot, data]) => ` ${slot}: ${data};`) - .join('\n') - - return `@palette.${name} {\n${entries}\n}` - }).join('\n\n') - }) - - const editedPalette = ref(0) - const palette = computed({ + const selectedPaletteId = ref(0) + const selectedPalette = computed({ get () { - console.log(palettes, editedPalette.value) - return palettes[editedPalette.value] + return palettes[selectedPaletteId.value] }, set (newPalette) { - palettes[editedPalette.value] = newPalette + palettes[selectedPaletteId.value] = newPalette } }) - const getNewPalette = () => ({ name: 'new palette', bg: '#121a24', @@ -141,11 +127,21 @@ export default { cOrange: '#ffa500' }) - // ### Initialization stuff + const palettesOut = computed(() => { + return palettes.map(({ name, ...palette }) => { + const entries = Object + .entries(palette) + .map(([slot, data]) => ` ${slot}: ${data};`) + .join('\n') + + return `@palette.${name} {\n${entries}\n}` + }).join('\n\n') + }) + + // ## Components stuff // Getting existing components const componentsContext = require.context('src', true, /\.style.js(on)?$/) const componentKeysAll = componentsContext.keys() - const componentsMap = new Map( componentKeysAll .map( @@ -154,23 +150,23 @@ export default { ) const componentKeys = [...componentsMap.keys()] - // Initializing selected component and its computed descendants + // selection basis const selectedComponentKey = ref(componentsMap.keys().next().value) const selectedComponent = computed(() => componentsMap.get(selectedComponentKey.value)) const selectedComponentName = computed(() => selectedComponent.value.name) - - const selectedVariant = ref('normal') - const selectedComponentVariantsAll = computed(() => { + const selectedComponentVariants = computed(() => { return Object.keys({ normal: null, ...(selectedComponent.value.variants || {}) }) }) - - const selectedState = reactive(new Set()) const selectedComponentStatesAll = computed(() => { return Object.keys({ normal: null, ...(selectedComponent.value.states || {}) }) }) const selectedComponentStates = computed(() => { return selectedComponentStatesAll.value.filter(x => x !== 'normal') }) + + // selection + const selectedVariant = ref('normal') + const selectedState = reactive(new Set()) const updateSelectedStates = (state, v) => { if (v) { selectedState.add(state) @@ -179,56 +175,6 @@ export default { } } - // ### Preview stuff - const editorHintStyle = computed(() => { - const editorHint = selectedComponent.value.editor - const styles = [] - if (editorHint && Object.keys(editorHint).length > 0) { - if (editorHint.aspect != null) { - styles.push(`aspect-ratio: ${editorHint.aspect} !important;`) - } - if (editorHint.border != null) { - styles.push(`border-width: ${editorHint.border}px !important;`) - } - } - return styles.join('; ') - }) - - // Apart from "hover" we can't really show how component looks like in - // certain states, so we have to fake them. - const simulatePseudoSelectors = css => css - .replace(selectedComponent.value.selector, '.ComponentPreview .preview-block') - .replace(':active', '.preview-active') - .replace(':hover', '.preview-hover') - .replace(':active', '.preview-active') - .replace(':focus', '.preview-focus') - .replace(':focus-within', '.preview-focus-within') - .replace(':disabled', '.preview-disabled') - - const previewRules = reactive([]) - const previewClass = computed(() => { - const selectors = [] - if (!!selectedComponent.value.variants?.normal || selectedVariant.value !== 'normal') { - selectors.push(selectedComponent.value.variants[selectedVariant.value]) - } - if (selectedState.size > 0) { - selectedState.forEach(state => { - const original = selectedComponent.value.states[state] - selectors.push(simulatePseudoSelectors(original)) - }) - } - return selectors.map(x => x.substring(1)).join('') - }) - const previewCss = computed(() => { - try { - const scoped = getCssRules(previewRules).map(simulatePseudoSelectors) - return scoped.join('\n') - } catch (e) { - console.error('Invalid ruleset', e) - return null - } - }) - // ### Rules stuff aka meat and potatoes // The native structure of separate rules and the child -> parent // relation isn't very convenient for editor, we replace the array @@ -334,21 +280,58 @@ export default { // All the editable stuff for the component const editedBackgroundColor = getEditedElement(null, 'background') + const isBackgroundColorPresent = isElementPresent(null, 'background', '#FFFFFF') const editedOpacity = getEditedElement(null, 'opacity') + const isOpacityPresent = isElementPresent(null, 'opacity', 1) const editedTextColor = getEditedElement('Text', 'textColor') + const isTextColorPresent = isElementPresent('Text', 'textColor', '#000000') const editedTextAuto = getEditedElement('Text', 'textAuto') + const isTextAutoPresent = isElementPresent('Text', 'textAuto', '#000000') const editedLinkColor = getEditedElement('Link', 'textColor') + const isLinkColorPresent = isElementPresent('Link', 'textColor', '#000080') const editedIconColor = getEditedElement('Icon', 'textColor') - const editedShadow = getEditedElement(null, 'shadow') + const isIconColorPresent = isElementPresent('Icon', 'textColor', '#909090') + // TODO this is VERY primitive right now, need to make it + // support variables, fallbacks etc. + const getContrast = (bg, text) => { + try { + const bgRgb = hex2rgb(bg) + const textRgb = hex2rgb(text) + + const ratio = getContrastRatio(bgRgb, textRgb) + return { + // TODO this ideally should be part of + ratio, + text: ratio.toPrecision(3) + ':1', + // AA level, AAA level + aa: ratio >= 4.5, + aaa: ratio >= 7, + // same but for 18pt+ texts + laa: ratio >= 3, + laaa: ratio >= 4.5 + } + } catch (e) { + console.warn('Failure computing contrast', e) + return { error: e } + } + } // Shadow is partially edited outside the ShadowControl // for better space utilization + const editedShadow = getEditedElement(null, 'shadow') const editedSubShadowId = ref(null) const editedSubShadow = computed(() => { if (editedShadow.value == null || editedSubShadowId.value == null) return null return editedShadow.value[editedSubShadowId.value] }) - + const isShadowPresent = isElementPresent(null, 'shadow', []) + const onSubShadow = (id) => { + if (id != null) { + editedSubShadowId.value = id + } else { + editedSubShadow.value = null + } + } const updateSubShadow = (axis, value) => { if (!editedSubShadow.value || editedSubShadowId.value == null) return const newEditedShadow = [...editedShadow.value] @@ -360,26 +343,105 @@ export default { editedShadow.value = newEditedShadow } - - const onSubShadow = (id) => { - if (id != null) { - editedSubShadowId.value = id - } else { - editedSubShadow.value = null - } + const isShadowTabOpen = ref(false) + const onTabSwitch = (tab) => { + isShadowTabOpen.value = tab === 'shadow' } - // Whether specific directives present in the edited rule or not - // Somewhat serves double-duty as it creates/removes the directive - // when set - const isBackgroundColorPresent = isElementPresent(null, 'background', '#FFFFFF') - const isOpacityPresent = isElementPresent(null, 'opacity', 1) - const isTextColorPresent = isElementPresent('Text', 'textColor', '#000000') - const isTextAutoPresent = isElementPresent('Text', 'textAuto', '#000000') - const isLinkColorPresent = isElementPresent('Link', 'textColor', '#000080') - const isIconColorPresent = isElementPresent('Icon', 'textColor', '#909090') - const isShadowPresent = isElementPresent(null, 'shadow', []) + // component preview + const editorHintStyle = computed(() => { + const editorHint = selectedComponent.value.editor + const styles = [] + if (editorHint && Object.keys(editorHint).length > 0) { + if (editorHint.aspect != null) { + styles.push(`aspect-ratio: ${editorHint.aspect} !important;`) + } + if (editorHint.border != null) { + styles.push(`border-width: ${editorHint.border}px !important;`) + } + } + return styles.join('; ') + }) + // Apart from "hover" we can't really show how component looks like in + // certain states, so we have to fake them. + const simulatePseudoSelectors = css => css + .replace(selectedComponent.value.selector, '.ComponentPreview .preview-block') + .replace(':active', '.preview-active') + .replace(':hover', '.preview-hover') + .replace(':active', '.preview-active') + .replace(':focus', '.preview-focus') + .replace(':focus-within', '.preview-focus-within') + .replace(':disabled', '.preview-disabled') + const previewClass = computed(() => { + const selectors = [] + if (!!selectedComponent.value.variants?.normal || selectedVariant.value !== 'normal') { + selectors.push(selectedComponent.value.variants[selectedVariant.value]) + } + if (selectedState.size > 0) { + selectedState.forEach(state => { + const original = selectedComponent.value.states[state] + selectors.push(simulatePseudoSelectors(original)) + }) + } + return selectors.map(x => x.substring(1)).join('') + }) + const previewRules = reactive([]) + const previewCss = computed(() => { + try { + const scoped = getCssRules(previewRules).map(simulatePseudoSelectors) + return scoped.join('\n') + } catch (e) { + console.error('Invalid ruleset', e) + return null + } + }) + const updatePreview = () => { + try { + const { name, ...paletteData } = selectedPalette.value + const rules = init({ + inputRuleset: editorFriendlyToOriginal.value, + initialStaticVars: { + ...paletteData + }, + ultimateBackgroundColor: '#000000', + rootComponentName: selectedComponentName.value, + editMode: true, + debug: true + }).eager + previewRules.splice(0, previewRules.length) + previewRules.push(...rules) + } catch (e) { + console.error('Could not compile preview theme', e) + } + } + const updateSelectedComponent = () => { + selectedVariant.value = 'normal' + selectedState.clear() + updatePreview() + } + updateSelectedComponent() + watch( + allEditedRules, + updatePreview + ) + + watch( + palettes, + updatePreview + ) + + watch( + selectedPalette, + updatePreview + ) + + watch( + selectedComponentName, + updateSelectedComponent + ) + + // export and import const editorFriendlyToOriginal = computed(() => { const resultRules = [] @@ -419,83 +481,36 @@ export default { return resultRules }) - const updatePreview = () => { - try { - const { name, ...paletteData } = palette.value - const rules = init({ - inputRuleset: editorFriendlyToOriginal.value, - initialStaticVars: { - ...paletteData - }, - ultimateBackgroundColor: '#000000', - rootComponentName: selectedComponentName.value, - editMode: true, - debug: true - }).eager - previewRules.splice(0, previewRules.length) - previewRules.push(...rules) - } catch (e) { - console.error('Could not compile preview theme', e) - } - } - - const updateSelectedComponent = () => { - selectedVariant.value = 'normal' - selectedState.clear() - updatePreview() - } - updateSelectedComponent() - - watch( - allEditedRules, - updatePreview - ) - - watch( - palettes, - updatePreview - ) - - watch( - editedPalette, - updatePreview - ) - - watch( - selectedComponentName, - updateSelectedComponent - ) - - // TODO this is VERY primitive right now, need to make it - // support variables, fallbacks etc. - const getContrast = (bg, text) => { - try { - const bgRgb = hex2rgb(bg) - const textRgb = hex2rgb(text) - - const ratio = getContrastRatio(bgRgb, textRgb) + // ## Variables + const allCustomVirtualDirectives = [...componentsMap.values()] + .map(c => { + console.log(c.defaultRules.filter(c => c.component === 'Root')) + return c + .defaultRules + .filter(c => c.component === 'Root') + .map(x => Object.entries(x.directives)) + .flat() + }) + .filter(x => x) + .flat() + .map(([name, value]) => { + const [valType, valVal] = value.split('|') return { - // TODO this ideally should be part of - ratio, - text: ratio.toPrecision(3) + ':1', - // AA level, AAA level - aa: ratio >= 4.5, - aaa: ratio >= 7, - // same but for 18pt+ texts - laa: ratio >= 3, - laaa: ratio >= 4.5 + name: name.substring(2), + valType: valType.trim(), + value: valVal.trim() } - } catch (e) { - console.warn('Failure computing contrast', e) - return { error: e } - } - } - - const isShadowTabOpen = ref(false) - const onTabSwitch = (tab) => { - isShadowTabOpen.value = tab === 'shadow' - } + }) + const virtualDirectives = reactive(allCustomVirtualDirectives) + const selectedVirtualDirectiveId = ref(0) + const selectedVirtualDirective = computed(() => virtualDirectives[selectedVirtualDirectiveId.value]) + const getNewDirective = () => ({ + name: 'newDirective', + valType: 'generic', + value: 'foobar' + }) + // ## Export and Import const styleExporter = newExporter({ filename: 'pleroma.palette.json', getExportedObject: () => exportStyleData @@ -512,48 +527,73 @@ export default { } return { + // ## Meta name, author, license, website, - palette, + + // ## Palette palettes, - editedPalette, + selectedPalette, + selectedPaletteId, getNewPalette, + + // ## Components componentKeys, componentsMap, + + // selection basis selectedComponent, selectedComponentName, selectedComponentKey, - selectedComponentVariantsAll, + selectedComponentVariants, selectedComponentStates, + + // selection selectedVariant, selectedState, updateSelectedStates, + + // component directives + componentHas, + + // component colors editedBackgroundColor, + isBackgroundColorPresent, editedOpacity, + isOpacityPresent, editedTextColor, + isTextColorPresent, editedTextAuto, + isTextAutoPresent, editedLinkColor, + isLinkColorPresent, editedIconColor, + isIconColorPresent, + getContrast, + + // component shadow editedShadow, editedSubShadow, + isShadowPresent, onSubShadow, updateSubShadow, - getContrast, - isBackgroundColorPresent, - isOpacityPresent, - isTextColorPresent, - isTextAutoPresent, - isLinkColorPresent, - isIconColorPresent, - isShadowPresent, - previewCss, - previewClass, - editorHintStyle, - componentHas, isShadowTabOpen, onTabSwitch, + + // component preview + editorHintStyle, + previewCss, + previewClass, + + // ## Variables + virtualDirectives, + selectedVirtualDirective, + selectedVirtualDirectiveId, + getNewDirective, + + // ## Export and Import exportStyle } } diff --git a/src/components/settings_modal/tabs/style_tab/style_tab.vue b/src/components/settings_modal/tabs/style_tab/style_tab.vue index dff57742a..326db5cce 100644 --- a/src/components/settings_modal/tabs/style_tab/style_tab.vue +++ b/src/components/settings_modal/tabs/style_tab/style_tab.vue @@ -76,7 +76,7 @@
diff --git a/src/services/export_import/export_import.js b/src/services/export_import/export_import.js index e1ac6a425..b8f84351b 100644 --- a/src/services/export_import/export_import.js +++ b/src/services/export_import/export_import.js @@ -2,15 +2,22 @@ import utf8 from 'utf8' export const newExporter = ({ filename = 'data', + mime = 'application/json', + extension = '.json', getExportedObject }) => ({ exportData () { - const stringified = utf8.encode(JSON.stringify(getExportedObject(), null, 2)) // Pretty-print and indent with 2 spaces + let stringified + if (mime === 'application/json') { + stringified = utf8.encode(JSON.stringify(getExportedObject(), null, 2)) // Pretty-print and indent with 2 spaces + } else { + stringified = utf8.encode(getExportedObject()) // Pretty-print and indent with 2 spaces + } // Create an invisible link with a data url and simulate a click const e = document.createElement('a') - e.setAttribute('download', `${filename}.json`) - e.setAttribute('href', 'data:application/json;base64,' + window.btoa(stringified)) + e.setAttribute('download', `${filename}.${extension}`) + e.setAttribute('href', `data:${mime};base64, ${window.btoa(stringified)}`) e.style.display = 'none' document.body.appendChild(e) From 756ea63b6709e3daf35d738b0a1f3a5e9439c4d9 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Sun, 6 Oct 2024 20:21:13 +0300 Subject: [PATCH 08/14] variables stuff seem to be at least somewhat working --- .../palette_editor/palette_editor.vue | 2 +- .../tabs/style_tab/style_tab.js | 36 +++++++++++++++---- .../tabs/style_tab/style_tab.scss | 8 ++--- .../tabs/style_tab/style_tab.vue | 9 +++-- .../shadow_control/shadow_control.js | 4 +++ src/i18n/en.json | 6 +++- 6 files changed, 51 insertions(+), 14 deletions(-) diff --git a/src/components/palette_editor/palette_editor.vue b/src/components/palette_editor/palette_editor.vue index cebe73ee1..16148262c 100644 --- a/src/components/palette_editor/palette_editor.vue +++ b/src/components/palette_editor/palette_editor.vue @@ -107,7 +107,7 @@ const updatePalette = (paletteKey, value) => { grid-template-columns: repeat(4, 1fr); grid-template-rows: repeat(3, 1fr) auto; grid-gap: 0.5em; - align-items: space-between; + align-items: baseline; .palette-import-button { grid-column: 1 / span 2; diff --git a/src/components/settings_modal/tabs/style_tab/style_tab.js b/src/components/settings_modal/tabs/style_tab/style_tab.js index 4e9a39060..c75e6b6b4 100644 --- a/src/components/settings_modal/tabs/style_tab/style_tab.js +++ b/src/components/settings_modal/tabs/style_tab/style_tab.js @@ -17,7 +17,7 @@ import ContrastRatio from 'src/components/contrast_ratio/contrast_ratio.vue' import { init } from 'src/services/theme_data/theme_data_3.service.js' import { getCssRules } from 'src/services/theme_data/css_utils.js' import { serialize } from 'src/services/theme_data/iss_serializer.js' -// import { deserialize } from 'src/services/theme_data/iss_deserializer.js' +import { parseShadow /* , deserialize */ } from 'src/services/theme_data/iss_deserializer.js' import { // rgb2hex, hex2rgb, @@ -259,7 +259,7 @@ export default { } }) - const getEditedElement = (component, directive) => computed({ + const getEditedElement = (component, directive, postProcess = x => x) => computed({ get () { let usedRule const fallback = editorFriendlyFallbackStructure.value @@ -271,7 +271,11 @@ export default { usedRule = get(fallback, path) } - return usedRule + if (directive === 'shadow') { + console.log('EDITED', usedRule) + console.log('PP', postProcess(usedRule)) + } + return postProcess(usedRule) }, set (value) { set(allEditedRules, getPath(component, directive), value) @@ -316,9 +320,22 @@ export default { } } + const normalizeShadows = (shadows) => { + console.log('NORMALIZE') + return shadows?.map(shadow => { + if (typeof shadow === 'object') { + return shadow + } + if (typeof shadow === 'string') { + return parseShadow(shadow) + } + return null + }) + } + // Shadow is partially edited outside the ShadowControl // for better space utilization - const editedShadow = getEditedElement(null, 'shadow') + const editedShadow = getEditedElement(null, 'shadow', normalizeShadows) const editedSubShadowId = ref(null) const editedSubShadow = computed(() => { if (editedShadow.value == null || editedSubShadowId.value == null) return null @@ -511,8 +528,15 @@ export default { const selectedVirtualDirectiveParsed = computed({ get () { switch (selectedVirtualDirective.value.valType) { - case 'shadow': - return {} + case 'shadow': { + const directiveValue = selectedVirtualDirective.value.value + if (Array.isArray(directiveValue)) { + return normalizeShadows(directiveValue) + } else { + const splitShadow = directiveValue.split(/,/g).map(x => x.trim()) + return normalizeShadows(splitShadow) + } + } default: return null } diff --git a/src/components/settings_modal/tabs/style_tab/style_tab.scss b/src/components/settings_modal/tabs/style_tab/style_tab.scss index 0f8540f9a..0384f7a48 100644 --- a/src/components/settings_modal/tabs/style_tab/style_tab.scss +++ b/src/components/settings_modal/tabs/style_tab/style_tab.scss @@ -93,7 +93,7 @@ "label editor" "selector editor" "movement editor"; - grid-template-columns: auto 1fr; + grid-template-columns: 10em 1fr; grid-template-rows: auto 1fr auto; grid-gap: 0.5em; @@ -124,9 +124,9 @@ grid-template-rows: auto auto 1fr; grid-gap: 0.5em; grid-template-areas: - "component component variant" - "state state state" - "preview settings settings"; + "component component variant" + "state state state" + "preview settings settings"; .component-selector { grid-area: component; diff --git a/src/components/settings_modal/tabs/style_tab/style_tab.vue b/src/components/settings_modal/tabs/style_tab/style_tab.vue index 8aaa67130..430b7c614 100644 --- a/src/components/settings_modal/tabs/style_tab/style_tab.vue +++ b/src/components/settings_modal/tabs/style_tab/style_tab.vue @@ -245,6 +245,7 @@ > {{ $t('settings.style.themes3.editor.include_in_rule') }} + {{ editedShadow }}
+ - {{ selectedVirtualDirective.valType }}
diff --git a/src/components/shadow_control/shadow_control.js b/src/components/shadow_control/shadow_control.js index fc227b5be..3fe1aa299 100644 --- a/src/components/shadow_control/shadow_control.js +++ b/src/components/shadow_control/shadow_control.js @@ -45,6 +45,7 @@ export default { ], emits: ['update:modelValue', 'subShadowSelected'], data () { + console.log('MODEL VALUE', this.modelValue, this.fallback) return { selectedId: 0, // TODO there are some bugs regarding display of array (it's not getting updated when deleting for some reason) @@ -60,6 +61,9 @@ export default { Popover, ComponentPreview }, + beforeUpdate () { + this.cValue = (this.modelValue ?? this.fallback ?? []).map(toModel) + }, computed: { selectedType: { get () { diff --git a/src/i18n/en.json b/src/i18n/en.json index 3694a3908..bad4ddb03 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -774,6 +774,9 @@ "extra3": "Extra 3", "v2_unsupported": "Older v2 themes don't support palettes. Switch to v3 theme to make use of palettes" }, + "variables": { + "label": "Variables" + }, "editor": { "title": "Style", "new_style": "New", @@ -800,7 +803,8 @@ "no-auto": "Disabled" }, "component_tab": "Components style", - "palette_tab": "Color presets" + "palette_tab": "Color presets", + "variables_tab": "Variables (Advanced)" }, "hacks": { "underlay_overrides": "Change underlay", From cfe52185f74684ebb599754ebb5b888302498231 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Mon, 7 Oct 2024 00:57:54 +0300 Subject: [PATCH 09/14] neat-looking variables tab (sans shadow editor) --- .../tabs/style_tab/style_tab.scss | 16 ++++ .../tabs/style_tab/style_tab.vue | 90 ++++++++++++------- src/i18n/en.json | 4 +- 3 files changed, 75 insertions(+), 35 deletions(-) diff --git a/src/components/settings_modal/tabs/style_tab/style_tab.scss b/src/components/settings_modal/tabs/style_tab/style_tab.scss index 0384f7a48..69349f785 100644 --- a/src/components/settings_modal/tabs/style_tab/style_tab.scss +++ b/src/components/settings_modal/tabs/style_tab/style_tab.scss @@ -109,6 +109,7 @@ font-weight: bold; grid-area: label; margin: 0; + align-self: baseline; } &-movement { @@ -118,6 +119,21 @@ } } + .variables-editor { + .variable-selector { + display: grid; + grid-template-columns: auto 1fr auto 10em; + grid-template-rows: subgrid; + align-items: baseline; + grid-gap: 0 0.5em; + } + + .list-edit-area { + display: grid; + grid-template-rows: subgrid; + } + } + .component-editor { display: grid; grid-template-columns: 6fr 3fr 4fr; diff --git a/src/components/settings_modal/tabs/style_tab/style_tab.vue b/src/components/settings_modal/tabs/style_tab/style_tab.vue index 430b7c614..9b5a4f9e6 100644 --- a/src/components/settings_modal/tabs/style_tab/style_tab.vue +++ b/src/components/settings_modal/tabs/style_tab/style_tab.vue @@ -259,7 +259,7 @@
diff --git a/src/components/shadow_control/shadow_control.js b/src/components/shadow_control/shadow_control.js index 3fe1aa299..8eab5c917 100644 --- a/src/components/shadow_control/shadow_control.js +++ b/src/components/shadow_control/shadow_control.js @@ -41,7 +41,12 @@ const toModel = (input) => { export default { props: [ - 'modelValue', 'fallback', 'separateInset', 'noPreview', 'disabled' + 'modelValue', + 'fallback', + 'separateInset', + 'noPreview', + 'disabled', + 'compact' ], emits: ['update:modelValue', 'subShadowSelected'], data () { diff --git a/src/components/shadow_control/shadow_control.scss b/src/components/shadow_control/shadow_control.scss index de4159c11..b0cf70148 100644 --- a/src/components/shadow_control/shadow_control.scss +++ b/src/components/shadow_control/shadow_control.scss @@ -1,12 +1,32 @@ -.settings-modal .settings-modal-panel .shadow-control { - display: flex; - flex-wrap: wrap; +.ShadowControl { + display: grid; + grid-template-columns: 10em 1fr 1fr; + grid-template-rows: 1fr; + grid-template-areas: "selector preview tweak"; + grid-gap: 0.5em; justify-content: stretch; - grid-gap: 0.25em; margin-bottom: 1em; width: 100%; + &.-compact { + grid-template-columns: 1fr; + grid-template-rows: 10em auto auto; + grid-template-areas: + "selector" + "preview" + "tweak"; + + &.-no-preview { + grid-template-columns: 1fr; + grid-template-rows: 10em 1fr; + grid-template-areas: + "selector" + "tweak"; + } + } + .shadow-switcher { + grid-area: selector; order: 1; flex: 1 0 6em; min-width: 6em; @@ -20,6 +40,7 @@ } .shadow-tweak { + grid-area: tweak; order: 3; flex: 2 0 10em; min-width: 10em; @@ -65,6 +86,10 @@ } &.-no-preview { + grid-template-columns: 10em 1fr; + grid-template-rows: 1fr; + grid-template-areas: "selector tweak"; + .shadow-tweak { order: 0; flex: 2 0 8em; @@ -87,8 +112,7 @@ } .shadow-preview { - order: 2; - flex: 3 3 15em; + grid-area: preview; min-width: 10em; margin-left: 0.125em; align-self: start; diff --git a/src/components/shadow_control/shadow_control.vue b/src/components/shadow_control/shadow_control.vue index 29adfff4a..4f0906c70 100644 --- a/src/components/shadow_control/shadow_control.vue +++ b/src/components/shadow_control/shadow_control.vue @@ -1,7 +1,7 @@