some initial drafts of component editor

This commit is contained in:
Henry Jameson 2024-09-24 21:32:13 +03:00
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>
@ -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>