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: e1d3ebc943f12bd7928878982405307c29d1b32b
parent d5549ac1ee045cf1b628c06a5456de25f66d6c84
Author: Henry Jameson <me@hjkos.com>
Date:   Tue, 24 Sep 2024 21:32:13 +0300

some initial drafts of component editor

Diffstat:

Msrc/components/component_preview/component_preview.vue9+++++++--
Msrc/components/settings_modal/tabs/style_tab/style_tab.js217+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------
Msrc/components/settings_modal/tabs/style_tab/style_tab.scss53+++++++++++++++++++++++++++++++++++++++++++++++------
Msrc/components/settings_modal/tabs/style_tab/style_tab.vue124+++++++++++++++++++++++++++++++++++++++++--------------------------------------
Msrc/i18n/en.json38+++++++++++++++++++++++++++++++++++++-
Msrc/services/theme_data/theme_data_3.service.js26++++++++++++++++----------
6 files changed, 352 insertions(+), 115 deletions(-)

diff --git a/src/components/component_preview/component_preview.vue b/src/components/component_preview/component_preview.vue @@ -33,8 +33,11 @@ > <div class="preview-block" + :class="previewClass" :style="previewStyle" - /> + > + TEST + </div> </div> <input v-show="shadowControl" @@ -59,7 +62,6 @@ <Checkbox id="lightGrid" v-model="lightGrid" - :disabled="shadow == null" name="lightGrid" class="input-light-grid" > @@ -163,6 +165,9 @@ } .preview-block { + display: flex; + justify-content: center; + align-items: center; width: 33%; height: 33%; border-radius: var(--roundness); diff --git a/src/components/settings_modal/tabs/style_tab/style_tab.js b/src/components/settings_modal/tabs/style_tab/style_tab.js @@ -1,40 +1,183 @@ -// import { -// rgb2hex, -// hex2rgb, -// getContrastRatioLayers, -// relativeLuminance -// } from 'src/services/color_convert/color_convert.js' - -// import { -// getThemes -// } from 'src/services/style_setter/style_setter.js' - -// import { -// newImporter, -// newExporter -// } 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 ColorInput from 'src/components/color_input/color_input.vue' -// import RangeInput from 'src/components/range_input/range_input.vue' -// import OpacityInput from 'src/components/opacity_input/opacity_input.vue' -// import ShadowControl from 'src/components/shadow_control/shadow_control.vue' -// import FontControl from 'src/components/font_control/font_control.vue' -// import ContrastRatio from 'src/components/contrast_ratio/contrast_ratio.vue' -// import TabSwitcher from 'src/components/tab_switcher/tab_switcher.jsx' -// import Checkbox from 'src/components/checkbox/checkbox.vue' -/* eslint-disable no-unused-vars */ +import { ref, reactive, computed, watch } from 'vue' import Select from 'src/components/select/select.vue' -import Preview from './theme_preview.vue' +import Checkbox from 'src/components/checkbox/checkbox.vue' +import ComponentPreview from 'src/components/component_preview/component_preview.vue' +import StringSetting from '../../helpers/string_setting.vue' -import { defineOptions, ref } from 'vue' -const componentsContext = require.context('src', true, /\.style.js(on)?$/) -const componentNames = componentsContext.keys() -const componentName = ref(componentNames[0]) +import { init } from 'src/services/theme_data/theme_data_3.service.js' +import { getCssRules } from 'src/services/theme_data/css_utils.js' + +import { library } from '@fortawesome/fontawesome-svg-core' +import { faFloppyDisk, faFolderOpen, faFile } from '@fortawesome/free-solid-svg-icons' + +library.add( + faFile, + faFloppyDisk, + faFolderOpen +) + +export default { + components: { + Select, + Checkbox, + StringSetting, + ComponentPreview + }, + setup () { + const name = ref('') + const author = ref('') + const license = ref('') + const website = ref('') + + const palette = { + // These are here just to establish order, + // themes should override those + bg: '#121a24', + fg: '#182230', + text: '#b9b9ba', + link: '#d8a070', + accent: '#d8a070', + cRed: '#FF0000', + cBlue: '#0095ff', + cGreen: '#0fa00f', + cOrange: '#ffa500' + } + + const getI18nPath = (componentName) => `settings.style.themes3.editor.components.${componentName}` + + 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}` + } + + // Getting existing components + const componentsContext = require.context('src', true, /\.style.js(on)?$/) + const componentKeysRaw = componentsContext.keys() + + const componentsMap = new Map( + componentKeysRaw + .map( + key => [key, componentsContext(key).default] + ).filter(([key, component]) => !component.virtual) + ) + const componentKeys = [...componentsMap.keys()] + + // const componentValues = componentsMap.values() + + // Initializing selected component and its computed descendants + const selectedComponentKey = ref(componentKeys[0]) + const selectedComponentValue = computed(() => componentsMap.get(selectedComponentKey.value)) + + const selectedComponentName = computed(() => selectedComponentValue.value.name) + const selectedComponentVariants = computed(() => { + return new Set(['normal', ...(Object.keys(selectedComponentValue.value.variants || {}))]) + }) + const selectedComponentStates = computed(() => { + return new Set([...(Object.keys(selectedComponentValue.value.states || {}).filter(x => x !== 'normal'))]) + }) + + const selectedVariant = ref('normal') + const selectedStates = reactive(new Set()) + + const updateSelectedStates = (state, v) => { + if (v) { + selectedStates.add(state) + } else { + selectedStates.delete(state) + } + } + + const simulatePseudoSelectors = css => css + .replace(selectedComponentValue.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 = [] + selectors.push(selectedComponentValue.value.variants[selectedVariant.value]) + if (selectedStates.size > 0) { + selectedStates.forEach(state => { + const original = selectedComponentValue.value.states[state] + selectors.push(simulatePseudoSelectors(original)) + }) + } + console.log(selectors) + return selectors.map(x => x.substring(1)).join('') + }) + + const previewCss = computed(() => { + console.log(previewRules) + const scoped = getCssRules(previewRules).map(simulatePseudoSelectors) + return scoped.join('\n') + }) + + const updateSelectedComponent = () => { + selectedVariant.value = 'normal' + selectedStates.clear() + + previewRules.splice(0, previewRules.length) + previewRules.push(...init({ + inputRuleset: [{ + component: selectedComponentName.value + }], + initialStaticVars: { + ...palette + }, + ultimateBackgroundColor: '#000000', + rootComponentName: selectedComponentName.value, + editMode: true, + debug: true + }).eager) + } + + updateSelectedComponent() + + watch( + selectedComponentName, + updateSelectedComponent + ) + + return { + name, + author, + license, + website, + componentKeys, + componentsMap, + + selectedComponentValue, + selectedComponentName, + selectedComponentKey, + selectedComponentVariants, + selectedComponentStates, + updateSelectedStates, + selectedVariant, + selectedStates, + getFriendlyNamePath, + getStatePath, + getVariantPath, + previewCss, + previewClass, + fallbackI18n (translated, fallback) { + if (translated.startsWith('settings.style.themes3')) { + return fallback + } + return translated + } + } + } +} diff --git a/src/components/settings_modal/tabs/style_tab/style_tab.scss b/src/components/settings_modal/tabs/style_tab/style_tab.scss @@ -40,11 +40,37 @@ .component-editor { display: grid; - grid-template-columns: 10em, 1fr, 2fr; - grid-template-rows: auto 1fr; + grid-template-columns: 10em 2fr 3fr; + grid-template-rows: auto auto 1fr; + grid-gap: 0.5em; grid-template-areas: - "variant" "preview" "controls", - "state" "preview" "controls"; + "component-label component ." + "variant preview settings" + "state preview settings"; + + .compopnent-selector { + grid-area: component; + align-self: center; + } + + .component-selector-label { + grid-area: component-label; + align-self: center; + text-align: right; + font-weight: bold; + } + + .state-selector, + .variant-selector { + display: grid; + grid-template-rows: auto auto; + grid-auto-flow: rows; + grid-gap: 0.5em; + + > label { + font-weight: bold; + } + } .state-selector { grid-area: state; @@ -52,8 +78,23 @@ .variant-selector { grid-area: variant; - display: flex; - flex-direction: column; + } + + .state-selector-list { + display: grid; + list-style: none; + grid-template-rows: auto; + grid-gap: 0.5em; + padding: 0; + margin: 0; + } + + .preview-container { + grid-area: preview; + } + + .component-settings { + grid-area: settings; } } } diff --git a/src/components/settings_modal/tabs/style_tab/style_tab.vue b/src/components/settings_modal/tabs/style_tab/style_tab.vue @@ -1,49 +1,4 @@ -<script setup> -import { ref, computed } from 'vue' - -import Select from 'src/components/select/select.vue' -import Checkbox from 'src/components/checkbox/checkbox.vue' -import StringSetting from '../../helpers/string_setting.vue' -// import Preview from '../theme_tab/theme_preview.vue' - -import { library } from '@fortawesome/fontawesome-svg-core' -import { faFloppyDisk, faFolderOpen, faFile } from '@fortawesome/free-solid-svg-icons' - -library.add( - faFile, - faFloppyDisk, - faFolderOpen -) - -const name = ref('') -const author = ref('') -const license = ref('') -const website = ref('') - -// Getting existing components -const componentsContext = require.context('src', true, /\.style.js(on)?$/) -const componentKeys = componentsContext.keys() - -const componentsMap = new Map( - componentKeys - .map( - key => [key, componentsContext(key).default] - ) -) -// const componentValues = componentsMap.values() - -// Initializing selected component and its computed descendants -const selectedComponentKey = ref(componentKeys[0]) -const selectedComponentValue = computed(() => componentsMap.get(selectedComponentKey.value)) - -// const selectedComponentName = computed(() => selectedComponent.value.name) -const selectedComponentVariants = computed(() => { - return new Set([...(Object.keys(selectedComponentValue.value.variants || {})), 'normal']) -}) -const selectedComponentStates = computed(() => { - return new Set([...(Object.keys(selectedComponentValue.value.states || {})), 'normal']) -}) - +<script src="./style_tab.js"> </script> <template> @@ -53,21 +8,21 @@ const selectedComponentStates = computed(() => { <button class="btn button-default" @click="clearTheme" - > + > <FAIcon icon="file" /> {{ $t('settings.style.themes3.editor.new_style') }} </button> <button class="btn button-default" @click="importStyle" - > + > <FAIcon icon="folder-open" /> {{ $t('settings.style.themes3.editor.load_style') }} </button> <button class="btn button-default" @click="exportTheme" - > + > <FAIcon icon="floppy-disk" /> {{ $t('settings.style.themes3.editor.save_style') }} </button> @@ -96,40 +51,91 @@ const selectedComponentStates = computed(() => { </li> </ul> </div> - <div class="setting-item"> - <Select v-model="selectedComponentKey"> + <div class="setting-item component-editor"> + <label + for="component-selector" + class="component-selector-label" + > + {{ $t('settings.style.themes3.editor.component_selector') }} + {{ ' ' }} + </label> + <Select + v-model="selectedComponentKey" + id="component-selector" + class="component-selector" + > <option v-for="key in componentKeys" :key="'component-' + key" :value="key" > - {{ componentsMap.get(key).name }} + {{ fallbackI18n($t(getFriendlyNamePath(componentsMap.get(key).name)), componentsMap.get(key).name) }} </option> </Select> - <div class="component-editor"> + <label + for="component-selector" + class="component-selector-label" + > + {{ $t('settings.style.themes3.editor.component_selector') }} + </label> + <div class="variant-selector"> + <label for="variant-selector"> + {{ $t('settings.style.themes3.editor.variant_selector') }} + </label> <Select - v-model="selectedComponentVariant" - class="variant-selector" + v-if="selectedComponentVariants.size > 1" + v-model="selectedVariant" > <option v-for="variant in selectedComponentVariants" + :value="variant" :key="'component-variant-' + variant" > - {{ variant }} + {{ fallbackI18n($t(getVariantPath(selectedComponentName, variant)), variant) }} </option> </Select> - <ul class="state-selector"> + <p v-else> + {{ $t('settings.style.themes3.editor.only_variant') }} + </p> + </div> + <div class="state-selector"> + <label for="variant-selector"> + {{ $t('settings.style.themes3.editor.states_selector') }} + </label> + <ul + v-if="selectedComponentStates.size > 0" + class="state-selector-list" + > <li v-for="state in selectedComponentStates" :key="'component-variant-' + state" > <Checkbox - v-model="selectedComponentStates" + :value="selectedStates.has(state)" + @update:modelValue="(v) => updateSelectedStates(state, v)" > - {{ state }} + {{ fallbackI18n($t(getStatePath(selectedComponentName, state)), state) }} </Checkbox> </li> </ul> + <p v-else> + {{ $t('settings.style.themes3.editor.only_state') }} + </p> + </div> + <div class="preview-container"> + <!-- eslint-disable vue/no-v-html vue/no-v-text-v-html-on-component --> + <component + :is="'style'" + v-html="previewCss" + /> + <!-- eslint-enable vue/no-v-html vue/no-v-text-v-html-on-component --> + <ComponentPreview + class="component-preview" + :previewClass="previewClass" + @update:shadow="({ axis, value }) => updateProperty(axis, value)" + /> + </div> + <div class="component-setting"> </div> </div> </div> diff --git a/src/i18n/en.json b/src/i18n/en.json @@ -761,7 +761,43 @@ "style_name": "Stylesheet name", "style_author": "Made by", "style_license": "License", - "style_website": "Website" + "style_website": "Website", + "component_selector": "Component", + "variant_selector": "Variant", + "states_selector": "States", + "only_variant": "Component doesn't have any variants", + "only_state": "Component only has default state", + "components": { + "normal": { + "normal": "Normal state", + "variant": "Default variant" + }, + "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" + } + } + } }, "hacks": { "underlay_overrides": "Change underlay", diff --git a/src/services/theme_data/theme_data_3.service.js b/src/services/theme_data/theme_data_3.service.js @@ -172,11 +172,13 @@ export const init = ({ ultimateBackgroundColor, debug = false, liteMode = false, + editMode = false, onlyNormalState = false, - rootComponentName = 'Root' + rootComponentName = 'Root', + initialStaticVars = {} }) => { if (!inputRuleset) throw new Error('Ruleset is null or undefined!') - const staticVars = {} + const staticVars = { ...initialStaticVars } const stacked = {} const computed = {} @@ -402,7 +404,7 @@ export const init = ({ case 'color': { const color = findColor(value[0], { dynamicVars, staticVars }) dynamicVars[k] = color - if (combination.component === 'Root') { + if (combination.component === rootComponentName) { staticVars[k.substring(2)] = color } break @@ -410,14 +412,14 @@ export const init = ({ case 'shadow': { const shadow = value dynamicVars[k] = shadow - if (combination.component === 'Root') { + if (combination.component === rootComponentName) { staticVars[k.substring(2)] = shadow } break } case 'generic': { dynamicVars[k] = value - if (combination.component === 'Root') { + if (combination.component === rootComponentName) { staticVars[k.substring(2)] = value } break @@ -443,11 +445,15 @@ export const init = ({ variants: originalVariants = {} } = component - const validInnerComponents = ( - liteMode - ? (component.validInnerComponentsLite || component.validInnerComponents) - : component.validInnerComponents - ) || [] + let validInnerComponents + if (editMode) { + const temp = (component.validInnerComponentsLite || component.validInnerComponents || []) + validInnerComponents = temp.filter(c => virtualComponents.has(c)) + } else if (editMode) { + validInnerComponents = (component.validInnerComponentsLite || component.validInnerComponents || []) + } else { + validInnerComponents = component.validInnerComponents || [] + } // Normalizing states and variants to always include "normal" const states = { normal: '', ...originalStates }