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:
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 }