some initial drafts of component editor
This commit is contained in:
parent
d5549ac1ee
commit
e1d3ebc943
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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 }
|
||||
|
|
Loading…
Reference in a new issue