diff --git a/src/components/color_input/color_input.scss b/src/components/color_input/color_input.scss index 3128381f5..19c88a690 100644 --- a/src/components/color_input/color_input.scss +++ b/src/components/color_input/color_input.scss @@ -5,10 +5,6 @@ flex: 1 1 auto; } - .opt { - margin-right: 0.5em; - } - &-field.input { display: inline-flex; flex: 0 0 0; diff --git a/src/components/component_preview/component_preview.vue b/src/components/component_preview/component_preview.vue index 6795d393a..fc8b3f4fb 100644 --- a/src/components/component_preview/component_preview.vue +++ b/src/components/component_preview/component_preview.vue @@ -103,7 +103,6 @@ > { const moveDnValid = computed(() => { return props.selectedId < props.modelValue.length - 1 }) - const moveDn = () => { const newModel = [...props.modelValue] const movable = newModel.splice(props.selectedId.value, 1)[0] diff --git a/src/components/settings_modal/tabs/style_tab/style_tab.js b/src/components/settings_modal/tabs/style_tab/style_tab.js index 2605e8d8b..c5ac8d06c 100644 --- a/src/components/settings_modal/tabs/style_tab/style_tab.js +++ b/src/components/settings_modal/tabs/style_tab/style_tab.js @@ -1,6 +1,5 @@ -import { ref, reactive, computed, watch, provide } from 'vue' -import { useStore } from 'vuex' -import { get, set, unset } from 'lodash' +import { ref, reactive, computed, watch } from 'vue' +import { get, set } from 'lodash' import Select from 'src/components/select/select.vue' import SelectMotion from 'src/components/select/select_motion.vue' @@ -16,14 +15,12 @@ import Tooltip from 'src/components/tooltip/tooltip.vue' import ContrastRatio from 'src/components/contrast_ratio/contrast_ratio.vue' import Preview from '../theme_tab/theme_preview.vue' -import VirtualDirectivesTab from './virtual_directives_tab.vue' - import { init, findColor } from 'src/services/theme_data/theme_data_3.service.js' import { getCssRules, getScopedVersion } from 'src/services/theme_data/css_utils.js' -import { serialize } from 'src/services/theme_data/iss_serializer.js' +import { serializeShadow, serialize } from 'src/services/theme_data/iss_serializer.js' import { deserializeShadow, deserialize } from 'src/services/theme_data/iss_deserializer.js' import { rgb2hex, @@ -73,12 +70,10 @@ export default { PaletteEditor, OpacityInput, ContrastRatio, - Preview, - VirtualDirectivesTab + Preview }, - setup (props, context) { + setup () { const exports = {} - const store = useStore() // All rules that are made by editor const allEditedRules = reactive({}) @@ -102,7 +97,7 @@ export default { // ## Palette stuff const palettes = reactive([ { - name: 'default', + name: 'dark', bg: '#121a24', fg: '#182230', text: '#b9b9ba', @@ -152,7 +147,6 @@ export default { }) exports.selectedPaletteId = selectedPaletteId exports.selectedPalette = selectedPalette - provide('selectedPalette', selectedPalette) exports.getNewPalette = () => ({ name: 'new palette', @@ -192,20 +186,19 @@ export default { const componentKeys = [...componentsMap.keys()] exports.componentKeys = componentKeys - // Component list and selection + // selection basis const selectedComponentKey = ref(componentsMap.keys().next().value) exports.selectedComponentKey = selectedComponentKey - const selectedComponent = computed(() => componentsMap.get(selectedComponentKey.value)) const selectedComponentName = computed(() => selectedComponent.value.name) - - // Selection basis exports.selectedComponentVariants = computed(() => { return Object.keys({ normal: null, ...(selectedComponent.value.variants || {}) }) }) + const selectedComponentStatesAll = computed(() => { + return Object.keys({ normal: null, ...(selectedComponent.value.states || {}) }) + }) exports.selectedComponentStates = computed(() => { - const all = Object.keys({ normal: null, ...(selectedComponent.value.states || {}) }) - return all.filter(x => x !== 'normal') + return selectedComponentStatesAll.value.filter(x => x !== 'normal') }) // selection @@ -221,17 +214,6 @@ export default { } } - // Reset variant and state on component change - const updateSelectedComponent = () => { - selectedVariant.value = 'normal' - selectedState.clear() - } - - watch( - selectedComponentName, - updateSelectedComponent - ) - // ### Rules stuff aka meat and potatoes // The native structure of separate rules and the child -> parent // relation isn't very convenient for editor, we replace the array @@ -288,13 +270,13 @@ export default { return root }) - // Checking whether component can support some "directives" which + // Checkging whether component can support some "directives" which // are actually virtual subcomponents, i.e. Text, Link etc exports.componentHas = (subComponent) => { return !!selectedComponent.value.validInnerComponents?.find(x => x === subComponent) } - // Path for lodash's get and set + // Path is path for lodash's get and set const getPath = (component, directive) => { const pathSuffix = component ? `._children.${component}.normal.normal` : '' const path = `${selectedComponentName.value}.${selectedVariant.value}.${normalizeStates([...selectedState])}${pathSuffix}.directives.${directive}` @@ -314,7 +296,7 @@ export default { ) set(allEditedRules, getPath(component, directive), fallback ?? defaultValue) } else { - unset(allEditedRules, getPath(component, directive)) + set(allEditedRules, getPath(component, directive), null) } } }) @@ -331,14 +313,14 @@ export default { usedRule = get(fallback, path) } + if (directive === 'shadow') { + console.log('EDITED', usedRule) + console.log('PP', postProcess(usedRule)) + } return postProcess(usedRule) }, set (value) { - if (value) { - set(allEditedRules, getPath(component, directive), value) - } else { - unset(allEditedRules, getPath(component, directive)) - } + set(allEditedRules, getPath(component, directive), value) } }) @@ -396,7 +378,6 @@ export default { return null }) } - provide('normalizeShadows', normalizeShadows) // Shadow is partially edited outside the ShadowControl // for better space utilization @@ -447,6 +428,57 @@ export default { } return styles.join('; ') }) + // Apart from "hover" we can't really show how component looks like in + // certain states, so we have to fake them. + const simulatePseudoSelectors = css => css + .replace(selectedComponent.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') + exports.previewClass = computed(() => { + const selectors = [] + if (!!selectedComponent.value.variants?.normal || selectedVariant.value !== 'normal') { + selectors.push(selectedComponent.value.variants[selectedVariant.value]) + } + if (selectedState.size > 0) { + selectedState.forEach(state => { + const original = selectedComponent.value.states[state] + selectors.push(simulatePseudoSelectors(original)) + }) + } + return selectors.map(x => x.substring(1)).join('') + }) + const previewRules = reactive([]) + exports.previewRules = previewRules + exports.previewCss = computed(() => { + try { + const scoped = getCssRules(previewRules).map(simulatePseudoSelectors) + return scoped.join('\n') + } catch (e) { + console.error('Invalid ruleset', e) + return null + } + }) + + const applicablePreviewRules = computed(() => { + return previewRules.filter(rule => { + const filterable = rule.parent ? rule.parent : rule + const variantMatches = filterable.variant === selectedVariant.value + const stateMatches = filterable.state.filter(x => x !== 'normal').every(x => selectedState.has(x)) + return variantMatches && stateMatches + }) + }) + const previewColors = computed(() => ({ + text: applicablePreviewRules.value.find(r => r.component === 'Text')?.virtualDirectives['--text'], + link: applicablePreviewRules.value.find(r => r.component === 'Link')?.virtualDirectives['--link'], + border: applicablePreviewRules.value.find(r => r.component === 'Border')?.virtualDirectives['--border'], + icon: applicablePreviewRules.value.find(r => r.component === 'Icon')?.virtualDirectives['--icon'], + background: applicablePreviewRules.value.find(r => r.parent == null)?.dynamicVars.stacked + })) + exports.previewColors = previewColors const editorFriendlyToOriginal = computed(() => { const resultRules = [] @@ -482,13 +514,64 @@ export default { }) } - componentsMap.values().forEach(({ name }) => { - convert(name, allEditedRules[name]) - }) + convert(selectedComponentName.value, allEditedRules[selectedComponentName.value]) return resultRules }) + const updatePreview = () => { + try { + const { name, ...paletteData } = selectedPalette.value + // This normally would be handled by Root but since we pass something + // else we have to make do ourselves + paletteData.accent = paletteData.accent || paletteData.link + paletteData.link = paletteData.link || paletteData.accent + const rules = init({ + inputRuleset: editorFriendlyToOriginal.value, + initialStaticVars: { + ...paletteData + }, + ultimateBackgroundColor: '#000000', + rootComponentName: selectedComponentName.value, + editMode: true, + debug: true + }).eager + previewRules.splice(0, previewRules.length) + previewRules.push(...rules) + } catch (e) { + console.error('Could not compile preview theme', e) + } + } + + const updateSelectedComponent = () => { + selectedVariant.value = 'normal' + selectedState.clear() + updatePreview() + } + updateSelectedComponent() + + // export and import + watch( + allEditedRules, + updatePreview + ) + + watch( + palettes, + updatePreview + ) + + watch( + selectedPalette, + updatePreview + ) + + watch( + selectedComponentName, + updateSelectedComponent + ) + + // ## Variables const allCustomVirtualDirectives = [...componentsMap.values()] .map(c => { return c @@ -507,29 +590,124 @@ export default { value: valVal.trim() } }) - - const virtualDirectives = ref(allCustomVirtualDirectives) + const virtualDirectives = reactive(allCustomVirtualDirectives) exports.virtualDirectives = virtualDirectives - exports.updateVirtualDirectives = (value) => { - virtualDirectives.value = value + + exports.onVirtualDirectivesUpdate = (e) => { + virtualDirectives.splice(0, virtualDirectives.length) + virtualDirectives.push(...e) } + const selectedVirtualDirectiveId = ref(0) + exports.selectedVirtualDirectiveId = selectedVirtualDirectiveId + + const selectedVirtualDirective = computed({ + get () { + return virtualDirectives[selectedVirtualDirectiveId.value] + }, + set (value) { + virtualDirectives[selectedVirtualDirectiveId.value].value = value + } + }) + exports.selectedVirtualDirective = selectedVirtualDirective + + exports.selectedVirtualDirectiveValType = computed({ + get () { + return virtualDirectives[selectedVirtualDirectiveId.value].valType + }, + set (value) { + const newValType = value + let newValue + switch (value) { + case 'shadow': + newValue = '0 0 0 #000000 / 1' + break + case 'color': + newValue = '#000000' + break + default: + newValue = 'none' + } + const newName = virtualDirectives[selectedVirtualDirectiveId.value].name + virtualDirectives[selectedVirtualDirectiveId.value] = { + name: newName, + value: newValue, + valType: newValType + } + } + }) + + const draftVirtualDirectiveValid = ref(true) + const draftVirtualDirective = ref({}) + exports.draftVirtualDirective = draftVirtualDirective + + watch( + selectedVirtualDirective, + (directive) => { + switch (directive.valType) { + case 'shadow': { + if (Array.isArray(directive.value)) { + draftVirtualDirective.value = normalizeShadows(directive.value) + } else { + const splitShadow = directive.value.split(/,/g).map(x => x.trim()) + draftVirtualDirective.value = normalizeShadows(splitShadow) + } + break + } + case 'color': + draftVirtualDirective.value = directive.value + break + default: + draftVirtualDirective.value = directive.value + break + } + }, + { immediate: true } + ) + + watch( + draftVirtualDirective, + (directive) => { + try { + switch (selectedVirtualDirective.value.valType) { + case 'shadow': { + virtualDirectives[selectedVirtualDirectiveId.value].value = + directive.map(x => serializeShadow(x)).join(', ') + break + } + default: + virtualDirectives[selectedVirtualDirectiveId.value].value = directive + } + draftVirtualDirectiveValid.value = true + } catch (e) { + console.error('Invalid virtual directive value', e) + draftVirtualDirectiveValid.value = false + } + }, + { immediate: true } + ) + const virtualDirectivesOut = computed(() => { return [ 'Root {', - ...virtualDirectives.value.map(vd => ` --${vd.name}: ${vd.valType} | ${vd.value};`), + ...virtualDirectives.map(vd => ` --${vd.name}: ${vd.valType} | ${vd.value};`), '}' ].join('\n') }) + exports.getNewVirtualDirective = () => ({ + name: 'newDirective', + valType: 'generic', + value: 'foobar' + }) + exports.computeColor = (color) => { - const computedColor = findColor(color, { dynamicVars: dynamicVars.value, staticVars: selectedPalette.value }) + const computedColor = findColor(color, { dynamicVars: {}, staticVars: selectedPalette.value }) if (computedColor) { return rgb2hex(computedColor) } return null } - provide('computeColor', exports.computeColor) exports.contrast = computed(() => { return getContrast( @@ -538,24 +716,48 @@ export default { ) }) - const paletteRule = computed(() => { - const { name, ...rest } = selectedPalette.value - return { - component: 'Root', - directives: Object - .entries(rest) - .filter(([k, v]) => v) - .map(([k, v]) => ['--' + k, v]) - .reduce((acc, [k, v]) => ({ ...acc, [k]: `color | ${v}` }), {}) - } - }) + const overallPreviewRules = ref() + exports.overallPreviewRules = overallPreviewRules + exports.updateOverallPreview = () => { + try { + // This normally would be handled by Root but since we pass something + // else we have to make do ourselves - const virtualDirectivesRule = computed(() => ({ - component: 'Root', - directives: Object.fromEntries( - virtualDirectives.value.map(vd => [`--${vd.name}`, `${vd.valType} | ${vd.value}`]) - ) - })) + const { name, ...rest } = selectedPalette.value + const paletteRule = { + component: 'Root', + directives: Object + .entries(rest) + .map(([k, v]) => ['--' + k, v]) + .reduce((acc, [k, v]) => ({ ...acc, [k]: `color | ${v}` }), {}) + } + + const virtualDirectivesRule = { + component: 'Root', + directives: Object.fromEntries( + virtualDirectives.map(vd => [`--${vd.name}`, `${vd.valType} | ${vd.value}`]) + ) + } + + const rules = init({ + inputRuleset: [ + paletteRule, + virtualDirectivesRule, + ...editorFriendlyToOriginal.value + ], + ultimateBackgroundColor: '#000000', + liteMode: true, + debug: true + }).eager + + overallPreviewRules.value = getScopedVersion( + getCssRules(rules), + '#edited-style-preview' + ).join('\n') + } catch (e) { + console.error('Could not compile preview theme', e) + } + } // ## Export and Import const styleExporter = newExporter({ @@ -580,13 +782,14 @@ export default { exports.author.value = metaIn.author exports.website.value = metaIn.website + virtualDirectives.splice(0, virtualDirectives.length) const newVirtualDirectives = Object .entries(rootComponent.directives) .map(([name, value]) => { const [valType, valVal] = value.split('|').map(x => x.trim()) return { name: name.substring(2), valType, value: valVal } }) - virtualDirectives.value = newVirtualDirectives + virtualDirectives.push(...newVirtualDirectives) onPalettesUpdate(palettesIn.map(x => ({ name: x.variant, ...x.directives }))) @@ -598,8 +801,6 @@ export default { allEditedRules ) }) - - exports.updateOverallPreview() } }) @@ -620,133 +821,6 @@ export default { styleImporter.importData() } - const exportRules = computed(() => [ - paletteRule.value, - virtualDirectivesRule.value, - ...editorFriendlyToOriginal.value - ]) - - exports.applyStyle = () => { - store.dispatch('setStyleCustom', exportRules.value) - } - - const overallPreviewRules = ref([]) - exports.overallPreviewRules = overallPreviewRules - - const overallPreviewCssRules = computed(() => getScopedVersion( - getCssRules(overallPreviewRules.value), - '#edited-style-preview' - ).join('\n')) - exports.overallPreviewCssRules = overallPreviewCssRules - - const updateOverallPreview = () => { - try { - overallPreviewRules.value = init({ - inputRuleset: exportRules.value, - ultimateBackgroundColor: '#000000', - liteMode: true, - debug: true - }).eager - } catch (e) { - console.error('Could not compile preview theme', e) - return null - } - } - // - // Apart from "hover" we can't really show how component looks like in - // certain states, so we have to fake them. - const simulatePseudoSelectors = (css, prefix) => css - .replace(prefix, '.component-preview .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 = computed(() => { - const filtered = overallPreviewRules.value.filter(r => { - const componentMatch = r.component === selectedComponentName.value - const parentComponentMatch = r.parent?.component === selectedComponentName.value - if (!componentMatch && !parentComponentMatch) return false - const rule = parentComponentMatch ? r.parent : r - if (rule.component !== selectedComponentName.value) return false - if (rule.variant !== selectedVariant.value) return false - const ruleState = new Set(rule.state.filter(x => x !== 'normal')) - const differenceA = [...ruleState].filter(x => !selectedState.has(x)) - const differenceB = [...selectedState].filter(x => !ruleState.has(x)) - return (differenceA.length + differenceB.length) === 0 - }) - const sorted = [...filtered] - .filter(x => x.component === selectedComponentName.value) - .sort((a, b) => { - const aSelectorLength = a.selector.split(/ /g).length - const bSelectorLength = b.selector.split(/ /g).length - return aSelectorLength - bSelectorLength - }) - - const prefix = sorted[0].selector - - return filtered.filter(x => x.selector.startsWith(prefix)) - }) - - exports.previewClass = computed(() => { - const selectors = [] - if (!!selectedComponent.value.variants?.normal || selectedVariant.value !== 'normal') { - selectors.push(selectedComponent.value.variants[selectedVariant.value]) - } - if (selectedState.size > 0) { - selectedState.forEach(state => { - const original = selectedComponent.value.states[state] - console.log('ORIG', original) - selectors.push(simulatePseudoSelectors(original)) - }) - } - return selectors.map(x => x.substring(1)).join('') - }) - - exports.previewCss = computed(() => { - try { - const prefix = previewRules.value[0].selector - const scoped = getCssRules(previewRules.value).map(x => simulatePseudoSelectors(x, prefix)) - return scoped.join('\n') - } catch (e) { - console.error('Invalid ruleset', e) - return null - } - }) - - const dynamicVars = computed(() => { - return previewRules.value[0].dynamicVars - }) - - const previewColors = computed(() => { - const stacked = dynamicVars.value.stacked - const background = typeof stacked === 'string' ? stacked : rgb2hex(stacked) - return { - text: previewRules.value.find(r => r.component === 'Text')?.virtualDirectives['--text'], - link: previewRules.value.find(r => r.component === 'Link')?.virtualDirectives['--link'], - border: previewRules.value.find(r => r.component === 'Border')?.virtualDirectives['--border'], - icon: previewRules.value.find(r => r.component === 'Icon')?.virtualDirectives['--icon'], - background - } - }) - exports.previewColors = previewColors - exports.updateOverallPreview = updateOverallPreview - - updateOverallPreview() - - watch( - [ - allEditedRules, - palettes, - selectedPalette, - selectedState, - selectedVariant - ], - updateOverallPreview - ) - return exports } } diff --git a/src/components/settings_modal/tabs/style_tab/style_tab.vue b/src/components/settings_modal/tabs/style_tab/style_tab.vue index ccba2c7dc..5ca274c35 100644 --- a/src/components/settings_modal/tabs/style_tab/style_tab.vue +++ b/src/components/settings_modal/tabs/style_tab/style_tab.vue @@ -7,7 +7,7 @@ @@ -41,7 +41,7 @@