some initial drafts of component editor

This commit is contained in:
Henry Jameson 2024-09-24 21:32:13 +03:00
parent d5549ac1ee
commit e1d3ebc943
6 changed files with 352 additions and 115 deletions

View file

@ -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);

View file

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

View file

@ -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;
}
}
}

View file

@ -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>
@ -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>

View file

@ -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",

View file

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