Compare commits
15 commits
104bc8c86e
...
f8b39faae7
Author | SHA1 | Date | |
---|---|---|---|
|
f8b39faae7 | ||
|
4d472e1d4b | ||
|
4f66731723 | ||
|
f0e5b0be1e | ||
|
f75ea738ca | ||
|
97c058ebda | ||
|
cfe52185f7 | ||
|
756ea63b67 | ||
|
8725de3e91 | ||
|
a6863248bb | ||
|
da2c016ab4 | ||
|
7e684ea3ff | ||
|
0f2bd39db8 | ||
|
c58ed1036f | ||
|
d31da2c300 |
14 changed files with 475 additions and 310 deletions
|
@ -35,11 +35,11 @@ export default {
|
|||
{
|
||||
component: 'Root',
|
||||
directives: {
|
||||
'--defaultButtonHoverGlow': 'shadow | 0 0 4 --text / 0.5',
|
||||
'--defaultButtonFocusGlow': 'shadow | 0 0 4 4 --link / 0.5',
|
||||
'--defaultButtonShadow': 'shadow | 0 0 2 #000000',
|
||||
'--defaultButtonBevel': 'shadow | $borderSide(#FFFFFF top 0.2 2), $borderSide(#000000 bottom 0.2 2)',
|
||||
'--pressedButtonBevel': 'shadow | $borderSide(#FFFFFF bottom 0.2 2), $borderSide(#000000 top 0.2 2)'
|
||||
'--buttonDefaultHoverGlow': 'shadow | 0 0 4 --text / 0.5',
|
||||
'--buttonDefaultFocusGlow': 'shadow | 0 0 4 4 --link / 0.5',
|
||||
'--buttonDefaultShadow': 'shadow | 0 0 2 #000000',
|
||||
'--buttonDefaultBevel': 'shadow | $borderSide(#FFFFFF top 0.2 2), $borderSide(#000000 bottom 0.2 2)',
|
||||
'--buttonPressedBevel': 'shadow | $borderSide(#FFFFFF bottom 0.2 2), $borderSide(#000000 top 0.2 2)'
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -47,53 +47,53 @@ export default {
|
|||
// like within it
|
||||
directives: {
|
||||
background: '--fg',
|
||||
shadow: ['--defaultButtonShadow', '--defaultButtonBevel'],
|
||||
shadow: ['--buttonDefaultShadow', '--buttonDefaultBevel'],
|
||||
roundness: 3
|
||||
}
|
||||
},
|
||||
{
|
||||
state: ['hover'],
|
||||
directives: {
|
||||
shadow: ['--defaultButtonHoverGlow', '--defaultButtonBevel']
|
||||
shadow: ['--buttonDefaultHoverGlow', '--buttonDefaultBevel']
|
||||
}
|
||||
},
|
||||
{
|
||||
state: ['focused'],
|
||||
directives: {
|
||||
shadow: ['--defaultButtonFocusGlow', '--defaultButtonBevel']
|
||||
shadow: ['--buttonDefaultFocusGlow', '--buttonDefaultBevel']
|
||||
}
|
||||
},
|
||||
{
|
||||
state: ['pressed'],
|
||||
directives: {
|
||||
shadow: ['--defaultButtonShadow', '--pressedButtonBevel']
|
||||
shadow: ['--buttonDefaultShadow', '--buttonPressedBevel']
|
||||
}
|
||||
},
|
||||
{
|
||||
state: ['pressed', 'hover'],
|
||||
directives: {
|
||||
shadow: ['--pressedButtonBevel', '--defaultButtonHoverGlow']
|
||||
shadow: ['--buttonPressedBevel', '--buttonDefaultHoverGlow']
|
||||
}
|
||||
},
|
||||
{
|
||||
state: ['toggled'],
|
||||
directives: {
|
||||
background: '--inheritedBackground,-14.2',
|
||||
shadow: ['--defaultButtonShadow', '--pressedButtonBevel']
|
||||
shadow: ['--buttonDefaultShadow', '--buttonPressedBevel']
|
||||
}
|
||||
},
|
||||
{
|
||||
state: ['toggled', 'hover'],
|
||||
directives: {
|
||||
background: '--inheritedBackground,-14.2',
|
||||
shadow: ['--defaultButtonHoverGlow', '--pressedButtonBevel']
|
||||
shadow: ['--buttonDefaultHoverGlow', '--buttonPressedBevel']
|
||||
}
|
||||
},
|
||||
{
|
||||
state: ['disabled'],
|
||||
directives: {
|
||||
background: '$blend(--inheritedBackground 0.25 --parent)',
|
||||
shadow: ['--defaultButtonBevel']
|
||||
shadow: ['--buttonDefaultBevel']
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
|
@ -111,6 +111,8 @@ export default {
|
|||
"x-num x-slide . "
|
||||
"options options options";
|
||||
grid-gap: 0.5em;
|
||||
max-width: 25em;
|
||||
max-height: 25em;
|
||||
|
||||
.header {
|
||||
grid-area: header;
|
||||
|
|
|
@ -107,7 +107,7 @@ const updatePalette = (paletteKey, value) => {
|
|||
grid-template-columns: repeat(4, 1fr);
|
||||
grid-template-rows: repeat(3, 1fr) auto;
|
||||
grid-gap: 0.5em;
|
||||
align-items: space-between;
|
||||
align-items: baseline;
|
||||
|
||||
.palette-import-button {
|
||||
grid-column: 1 / span 2;
|
||||
|
|
|
@ -82,9 +82,7 @@ const moveDn = () => {
|
|||
}
|
||||
|
||||
const add = () => {
|
||||
const newModel = [...props.modelValue]
|
||||
newModel.push(props.getAddValue())
|
||||
console.log(newModel)
|
||||
const newModel = [...props.modelValue, props.getAddValue()]
|
||||
|
||||
emit('update:modelValue', newModel)
|
||||
emit('update:selectedId', Math.max(newModel.length - 1, 0))
|
||||
|
|
|
@ -17,12 +17,16 @@ import ContrastRatio from 'src/components/contrast_ratio/contrast_ratio.vue'
|
|||
import { init } from 'src/services/theme_data/theme_data_3.service.js'
|
||||
import { getCssRules } from 'src/services/theme_data/css_utils.js'
|
||||
import { serialize } from 'src/services/theme_data/iss_serializer.js'
|
||||
import { deserialize } from 'src/services/theme_data/iss_deserializer.js'
|
||||
import { parseShadow /* , deserialize */ } from 'src/services/theme_data/iss_deserializer.js'
|
||||
import {
|
||||
// rgb2hex,
|
||||
hex2rgb,
|
||||
getContrastRatio
|
||||
} from 'src/services/color_convert/color_convert.js'
|
||||
import {
|
||||
// newImporter,
|
||||
newExporter
|
||||
} from 'src/services/export_import/export_import.js'
|
||||
|
||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
import { faFloppyDisk, faFolderOpen, faFile } from '@fortawesome/free-solid-svg-icons'
|
||||
|
@ -56,23 +60,40 @@ export default {
|
|||
ContrastRatio
|
||||
},
|
||||
setup () {
|
||||
// ### Meta stuff
|
||||
// All rules that are made by editor
|
||||
const allEditedRules = reactive({})
|
||||
|
||||
// ## Meta stuff
|
||||
const name = ref('')
|
||||
const author = ref('')
|
||||
const license = ref('')
|
||||
const website = ref('')
|
||||
|
||||
const metaOut = computed(() => {
|
||||
return `@meta {
|
||||
name: ${name.value};
|
||||
author: ${author.value};
|
||||
license: ${license.value};
|
||||
website: ${website.value};
|
||||
}`
|
||||
return [
|
||||
'@meta {',
|
||||
` name: ${name.value};`,
|
||||
` author: ${author.value};`,
|
||||
` license: ${license.value};`,
|
||||
` website: ${website.value};`,
|
||||
'}'
|
||||
].join('\n')
|
||||
})
|
||||
|
||||
// ### Palette stuff
|
||||
// ## Palette stuff
|
||||
const palettes = reactive([
|
||||
{
|
||||
name: 'dark',
|
||||
bg: '#121a24',
|
||||
fg: '#182230',
|
||||
text: '#b9b9ba',
|
||||
link: '#d8a070',
|
||||
accent: '#d8a070',
|
||||
cRed: '#FF0000',
|
||||
cBlue: '#0095ff',
|
||||
cGreen: '#0fa00f',
|
||||
cOrange: '#ffa500'
|
||||
},
|
||||
{
|
||||
name: 'light',
|
||||
bg: '#f2f6f9',
|
||||
|
@ -85,44 +106,17 @@ export default {
|
|||
cGreen: '#0fa00f',
|
||||
cOrange: '#ffa500',
|
||||
border: '#d8e6f9'
|
||||
},
|
||||
{
|
||||
name: 'dark',
|
||||
bg: '#121a24',
|
||||
fg: '#182230',
|
||||
text: '#b9b9ba',
|
||||
link: '#d8a070',
|
||||
accent: '#d8a070',
|
||||
cRed: '#FF0000',
|
||||
cBlue: '#0095ff',
|
||||
cGreen: '#0fa00f',
|
||||
cOrange: '#ffa500'
|
||||
}
|
||||
])
|
||||
|
||||
const palettesOut = computed(() => {
|
||||
console.log('WORK DAMN', palettes)
|
||||
return palettes.map(({ name, ...palette }) => {
|
||||
const entries = Object
|
||||
.entries(palette)
|
||||
.map(([slot, data]) => ` ${slot}: ${data};`)
|
||||
.join('\n')
|
||||
|
||||
return `@palette.${name} {\n${entries}\n}`
|
||||
}).join('\n\n')
|
||||
})
|
||||
|
||||
const editedPalette = ref(0)
|
||||
const palette = computed({
|
||||
const selectedPaletteId = ref(0)
|
||||
const selectedPalette = computed({
|
||||
get () {
|
||||
console.log(palettes, editedPalette.value)
|
||||
return palettes[editedPalette.value]
|
||||
return palettes[selectedPaletteId.value]
|
||||
},
|
||||
set (newPalette) {
|
||||
palettes[editedPalette.value] = newPalette
|
||||
palettes[selectedPaletteId.value] = newPalette
|
||||
}
|
||||
})
|
||||
|
||||
const getNewPalette = () => ({
|
||||
name: 'new palette',
|
||||
bg: '#121a24',
|
||||
|
@ -136,34 +130,21 @@ export default {
|
|||
cOrange: '#ffa500'
|
||||
})
|
||||
|
||||
// ### I18n stuff
|
||||
// The paths in i18n are getting ridicously long, this effectively shortens them
|
||||
const getI18nPath = (componentName) => `settings.style.themes3.editor.components.${componentName}`
|
||||
// vue i18n doesn't seem to have (working) mechanic to have a fallback so we have to
|
||||
// make do ourselves
|
||||
const fallbackI18n = (translated, fallback) => {
|
||||
if (translated.startsWith('settings.style.themes3')) {
|
||||
return fallback
|
||||
}
|
||||
return translated
|
||||
}
|
||||
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}`
|
||||
}
|
||||
const palettesOut = computed(() => {
|
||||
return palettes.map(({ name, ...palette }) => {
|
||||
const entries = Object
|
||||
.entries(palette)
|
||||
.map(([slot, data]) => ` ${slot}: ${data};`)
|
||||
.join('\n')
|
||||
|
||||
// ### Initialization stuff
|
||||
return `@palette.${name} {\n${entries}\n}`
|
||||
}).join('\n\n')
|
||||
})
|
||||
|
||||
// ## Components stuff
|
||||
// Getting existing components
|
||||
const componentsContext = require.context('src', true, /\.style.js(on)?$/)
|
||||
const componentKeysAll = componentsContext.keys()
|
||||
|
||||
const componentsMap = new Map(
|
||||
componentKeysAll
|
||||
.map(
|
||||
|
@ -172,23 +153,23 @@ export default {
|
|||
)
|
||||
const componentKeys = [...componentsMap.keys()]
|
||||
|
||||
// Initializing selected component and its computed descendants
|
||||
// selection basis
|
||||
const selectedComponentKey = ref(componentsMap.keys().next().value)
|
||||
const selectedComponent = computed(() => componentsMap.get(selectedComponentKey.value))
|
||||
const selectedComponentName = computed(() => selectedComponent.value.name)
|
||||
|
||||
const selectedVariant = ref('normal')
|
||||
const selectedComponentVariantsAll = computed(() => {
|
||||
const selectedComponentVariants = computed(() => {
|
||||
return Object.keys({ normal: null, ...(selectedComponent.value.variants || {}) })
|
||||
})
|
||||
|
||||
const selectedState = reactive(new Set())
|
||||
const selectedComponentStatesAll = computed(() => {
|
||||
return Object.keys({ normal: null, ...(selectedComponent.value.states || {}) })
|
||||
})
|
||||
const selectedComponentStates = computed(() => {
|
||||
return selectedComponentStatesAll.value.filter(x => x !== 'normal')
|
||||
})
|
||||
|
||||
// selection
|
||||
const selectedVariant = ref('normal')
|
||||
const selectedState = reactive(new Set())
|
||||
const updateSelectedStates = (state, v) => {
|
||||
if (v) {
|
||||
selectedState.add(state)
|
||||
|
@ -197,56 +178,6 @@ export default {
|
|||
}
|
||||
}
|
||||
|
||||
// ### Preview stuff
|
||||
const editorHintStyle = computed(() => {
|
||||
const editorHint = selectedComponent.value.editor
|
||||
const styles = []
|
||||
if (editorHint && Object.keys(editorHint).length > 0) {
|
||||
if (editorHint.aspect != null) {
|
||||
styles.push(`aspect-ratio: ${editorHint.aspect} !important;`)
|
||||
}
|
||||
if (editorHint.border != null) {
|
||||
styles.push(`border-width: ${editorHint.border}px !important;`)
|
||||
}
|
||||
}
|
||||
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')
|
||||
|
||||
const previewRules = reactive([])
|
||||
const 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 previewCss = computed(() => {
|
||||
try {
|
||||
const scoped = getCssRules(previewRules).map(simulatePseudoSelectors)
|
||||
return scoped.join('\n')
|
||||
} catch (e) {
|
||||
console.error('Invalid ruleset', e)
|
||||
return null
|
||||
}
|
||||
})
|
||||
|
||||
// ### 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
|
||||
|
@ -297,9 +228,6 @@ export default {
|
|||
return root
|
||||
})
|
||||
|
||||
// All rules that are made by editor
|
||||
const allEditedRules = reactive({})
|
||||
|
||||
// Checkging whether component can support some "directives" which
|
||||
// are actually virtual subcomponents, i.e. Text, Link etc
|
||||
const componentHas = (subComponent) => {
|
||||
|
@ -331,7 +259,7 @@ export default {
|
|||
}
|
||||
})
|
||||
|
||||
const getEditedElement = (component, directive) => computed({
|
||||
const getEditedElement = (component, directive, postProcess = x => x) => computed({
|
||||
get () {
|
||||
let usedRule
|
||||
const fallback = editorFriendlyFallbackStructure.value
|
||||
|
@ -343,7 +271,11 @@ export default {
|
|||
usedRule = get(fallback, path)
|
||||
}
|
||||
|
||||
return usedRule
|
||||
if (directive === 'shadow') {
|
||||
console.log('EDITED', usedRule)
|
||||
console.log('PP', postProcess(usedRule))
|
||||
}
|
||||
return postProcess(usedRule)
|
||||
},
|
||||
set (value) {
|
||||
set(allEditedRules, getPath(component, directive), value)
|
||||
|
@ -352,21 +284,71 @@ export default {
|
|||
|
||||
// All the editable stuff for the component
|
||||
const editedBackgroundColor = getEditedElement(null, 'background')
|
||||
const isBackgroundColorPresent = isElementPresent(null, 'background', '#FFFFFF')
|
||||
const editedOpacity = getEditedElement(null, 'opacity')
|
||||
const isOpacityPresent = isElementPresent(null, 'opacity', 1)
|
||||
const editedTextColor = getEditedElement('Text', 'textColor')
|
||||
const isTextColorPresent = isElementPresent('Text', 'textColor', '#000000')
|
||||
const editedTextAuto = getEditedElement('Text', 'textAuto')
|
||||
const isTextAutoPresent = isElementPresent('Text', 'textAuto', '#000000')
|
||||
const editedLinkColor = getEditedElement('Link', 'textColor')
|
||||
const isLinkColorPresent = isElementPresent('Link', 'textColor', '#000080')
|
||||
const editedIconColor = getEditedElement('Icon', 'textColor')
|
||||
const editedShadow = getEditedElement(null, 'shadow')
|
||||
const isIconColorPresent = isElementPresent('Icon', 'textColor', '#909090')
|
||||
// TODO this is VERY primitive right now, need to make it
|
||||
// support variables, fallbacks etc.
|
||||
const getContrast = (bg, text) => {
|
||||
try {
|
||||
const bgRgb = hex2rgb(bg)
|
||||
const textRgb = hex2rgb(text)
|
||||
|
||||
const ratio = getContrastRatio(bgRgb, textRgb)
|
||||
return {
|
||||
// TODO this ideally should be part of <ContractRatio />
|
||||
ratio,
|
||||
text: ratio.toPrecision(3) + ':1',
|
||||
// AA level, AAA level
|
||||
aa: ratio >= 4.5,
|
||||
aaa: ratio >= 7,
|
||||
// same but for 18pt+ texts
|
||||
laa: ratio >= 3,
|
||||
laaa: ratio >= 4.5
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('Failure computing contrast', e)
|
||||
return { error: e }
|
||||
}
|
||||
}
|
||||
|
||||
const normalizeShadows = (shadows) => {
|
||||
console.log('NORMALIZE')
|
||||
return shadows?.map(shadow => {
|
||||
if (typeof shadow === 'object') {
|
||||
return shadow
|
||||
}
|
||||
if (typeof shadow === 'string') {
|
||||
return parseShadow(shadow)
|
||||
}
|
||||
return null
|
||||
})
|
||||
}
|
||||
|
||||
// Shadow is partially edited outside the ShadowControl
|
||||
// for better space utilization
|
||||
const editedShadow = getEditedElement(null, 'shadow', normalizeShadows)
|
||||
const editedSubShadowId = ref(null)
|
||||
const editedSubShadow = computed(() => {
|
||||
if (editedShadow.value == null || editedSubShadowId.value == null) return null
|
||||
return editedShadow.value[editedSubShadowId.value]
|
||||
})
|
||||
|
||||
const isShadowPresent = isElementPresent(null, 'shadow', [])
|
||||
const onSubShadow = (id) => {
|
||||
if (id != null) {
|
||||
editedSubShadowId.value = id
|
||||
} else {
|
||||
editedSubShadow.value = null
|
||||
}
|
||||
}
|
||||
const updateSubShadow = (axis, value) => {
|
||||
if (!editedSubShadow.value || editedSubShadowId.value == null) return
|
||||
const newEditedShadow = [...editedShadow.value]
|
||||
|
@ -378,26 +360,58 @@ export default {
|
|||
|
||||
editedShadow.value = newEditedShadow
|
||||
}
|
||||
|
||||
const onSubShadow = (id) => {
|
||||
if (id != null) {
|
||||
editedSubShadowId.value = id
|
||||
} else {
|
||||
editedSubShadow.value = null
|
||||
}
|
||||
const isShadowTabOpen = ref(false)
|
||||
const onTabSwitch = (tab) => {
|
||||
isShadowTabOpen.value = tab === 'shadow'
|
||||
}
|
||||
|
||||
// Whether specific directives present in the edited rule or not
|
||||
// Somewhat serves double-duty as it creates/removes the directive
|
||||
// when set
|
||||
const isBackgroundColorPresent = isElementPresent(null, 'background', '#FFFFFF')
|
||||
const isOpacityPresent = isElementPresent(null, 'opacity', 1)
|
||||
const isTextColorPresent = isElementPresent('Text', 'textColor', '#000000')
|
||||
const isTextAutoPresent = isElementPresent('Text', 'textAuto', '#000000')
|
||||
const isLinkColorPresent = isElementPresent('Link', 'textColor', '#000080')
|
||||
const isIconColorPresent = isElementPresent('Icon', 'textColor', '#909090')
|
||||
const isShadowPresent = isElementPresent(null, 'shadow', [])
|
||||
|
||||
// component preview
|
||||
const editorHintStyle = computed(() => {
|
||||
const editorHint = selectedComponent.value.editor
|
||||
const styles = []
|
||||
if (editorHint && Object.keys(editorHint).length > 0) {
|
||||
if (editorHint.aspect != null) {
|
||||
styles.push(`aspect-ratio: ${editorHint.aspect} !important;`)
|
||||
}
|
||||
if (editorHint.border != null) {
|
||||
styles.push(`border-width: ${editorHint.border}px !important;`)
|
||||
}
|
||||
}
|
||||
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')
|
||||
const 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([])
|
||||
const previewCss = computed(() => {
|
||||
try {
|
||||
const scoped = getCssRules(previewRules).map(simulatePseudoSelectors)
|
||||
return scoped.join('\n')
|
||||
} catch (e) {
|
||||
console.error('Invalid ruleset', e)
|
||||
return null
|
||||
}
|
||||
})
|
||||
const editorFriendlyToOriginal = computed(() => {
|
||||
const resultRules = []
|
||||
|
||||
|
@ -439,8 +453,11 @@ export default {
|
|||
|
||||
const updatePreview = () => {
|
||||
try {
|
||||
const { name, ...paletteData } = palette.value
|
||||
console.log('WORK', paletteData)
|
||||
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: {
|
||||
|
@ -465,6 +482,7 @@ export default {
|
|||
}
|
||||
updateSelectedComponent()
|
||||
|
||||
// export and import
|
||||
watch(
|
||||
allEditedRules,
|
||||
updatePreview
|
||||
|
@ -476,7 +494,7 @@ export default {
|
|||
)
|
||||
|
||||
watch(
|
||||
editedPalette,
|
||||
selectedPalette,
|
||||
updatePreview
|
||||
)
|
||||
|
||||
|
@ -485,97 +503,139 @@ export default {
|
|||
updateSelectedComponent
|
||||
)
|
||||
|
||||
// TODO this is VERY primitive right now, need to make it
|
||||
// support variables, fallbacks etc.
|
||||
const getContrast = (bg, text) => {
|
||||
try {
|
||||
const bgRgb = hex2rgb(bg)
|
||||
const textRgb = hex2rgb(text)
|
||||
|
||||
const ratio = getContrastRatio(bgRgb, textRgb)
|
||||
// ## Variables
|
||||
const allCustomVirtualDirectives = [...componentsMap.values()]
|
||||
.map(c => {
|
||||
return c
|
||||
.defaultRules
|
||||
.filter(c => c.component === 'Root')
|
||||
.map(x => Object.entries(x.directives))
|
||||
.flat()
|
||||
})
|
||||
.filter(x => x)
|
||||
.flat()
|
||||
.map(([name, value]) => {
|
||||
const [valType, valVal] = value.split('|')
|
||||
return {
|
||||
// TODO this ideally should be part of <ContractRatio />
|
||||
ratio,
|
||||
text: ratio.toPrecision(3) + ':1',
|
||||
// AA level, AAA level
|
||||
aa: ratio >= 4.5,
|
||||
aaa: ratio >= 7,
|
||||
// same but for 18pt+ texts
|
||||
laa: ratio >= 3,
|
||||
laaa: ratio >= 4.5
|
||||
name: name.substring(2),
|
||||
valType: valType.trim(),
|
||||
value: valVal.trim()
|
||||
}
|
||||
})
|
||||
const virtualDirectives = reactive(allCustomVirtualDirectives)
|
||||
const selectedVirtualDirectiveId = ref(0)
|
||||
const selectedVirtualDirective = computed(() => virtualDirectives[selectedVirtualDirectiveId.value])
|
||||
const selectedVirtualDirectiveParsed = computed({
|
||||
get () {
|
||||
switch (selectedVirtualDirective.value.valType) {
|
||||
case 'shadow': {
|
||||
const directiveValue = selectedVirtualDirective.value.value
|
||||
if (Array.isArray(directiveValue)) {
|
||||
return normalizeShadows(directiveValue)
|
||||
} else {
|
||||
const splitShadow = directiveValue.split(/,/g).map(x => x.trim())
|
||||
return normalizeShadows(splitShadow)
|
||||
}
|
||||
}
|
||||
default:
|
||||
return null
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('Failure computing contrast', e)
|
||||
return { error: e }
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const isShadowTabOpen = ref(false)
|
||||
const onTabSwitch = (tab) => {
|
||||
isShadowTabOpen.value = tab === 'shadow'
|
||||
}
|
||||
const getNewDirective = () => ({
|
||||
name: 'newDirective',
|
||||
valType: 'generic',
|
||||
value: 'foobar'
|
||||
})
|
||||
|
||||
const exportStyle = () => {
|
||||
console.log('ORIG', toValue(editorFriendlyToOriginal.value))
|
||||
console.log('SERI', serialize(editorFriendlyToOriginal.value))
|
||||
|
||||
const result = [
|
||||
// ## Export and Import
|
||||
const styleExporter = newExporter({
|
||||
filename: name.value || 'pleroma_theme',
|
||||
mime: 'text/plain',
|
||||
extension: 'piss',
|
||||
getExportedObject: () => exportStyleData.value
|
||||
})
|
||||
const exportStyleData = computed(() => {
|
||||
return [
|
||||
metaOut.value,
|
||||
palettesOut.value,
|
||||
serialize(editorFriendlyToOriginal.value)
|
||||
].join('\n\n')
|
||||
|
||||
console.log('RESULT', result)
|
||||
console.log('DESERI', deserialize(result))
|
||||
})
|
||||
const exportStyle = () => {
|
||||
styleExporter.exportData()
|
||||
}
|
||||
|
||||
return {
|
||||
// ## Meta
|
||||
name,
|
||||
author,
|
||||
license,
|
||||
website,
|
||||
palette,
|
||||
|
||||
// ## Palette
|
||||
palettes,
|
||||
editedPalette,
|
||||
selectedPalette,
|
||||
selectedPaletteId,
|
||||
getNewPalette,
|
||||
|
||||
// ## Components
|
||||
componentKeys,
|
||||
componentsMap,
|
||||
|
||||
// selection basis
|
||||
selectedComponent,
|
||||
selectedComponentName,
|
||||
selectedComponentKey,
|
||||
selectedComponentVariantsAll,
|
||||
selectedComponentVariants,
|
||||
selectedComponentStates,
|
||||
|
||||
// selection
|
||||
selectedVariant,
|
||||
selectedState,
|
||||
updateSelectedStates,
|
||||
|
||||
// component directives
|
||||
componentHas,
|
||||
|
||||
// component colors
|
||||
editedBackgroundColor,
|
||||
isBackgroundColorPresent,
|
||||
editedOpacity,
|
||||
isOpacityPresent,
|
||||
editedTextColor,
|
||||
isTextColorPresent,
|
||||
editedTextAuto,
|
||||
isTextAutoPresent,
|
||||
editedLinkColor,
|
||||
isLinkColorPresent,
|
||||
editedIconColor,
|
||||
isIconColorPresent,
|
||||
getContrast,
|
||||
|
||||
// component shadow
|
||||
editedShadow,
|
||||
editedSubShadow,
|
||||
isShadowPresent,
|
||||
onSubShadow,
|
||||
updateSubShadow,
|
||||
getContrast,
|
||||
isBackgroundColorPresent,
|
||||
isOpacityPresent,
|
||||
isTextColorPresent,
|
||||
isTextAutoPresent,
|
||||
isLinkColorPresent,
|
||||
isIconColorPresent,
|
||||
isShadowPresent,
|
||||
previewCss,
|
||||
previewClass,
|
||||
editorHintStyle,
|
||||
getFriendlyNamePath,
|
||||
fallbackI18n,
|
||||
getVariantPath,
|
||||
getStatePath,
|
||||
componentHas,
|
||||
isShadowTabOpen,
|
||||
onTabSwitch,
|
||||
|
||||
// component preview
|
||||
editorHintStyle,
|
||||
previewCss,
|
||||
previewClass,
|
||||
|
||||
// ## Variables
|
||||
virtualDirectives,
|
||||
selectedVirtualDirective,
|
||||
selectedVirtualDirectiveId,
|
||||
selectedVirtualDirectiveParsed,
|
||||
getNewDirective,
|
||||
|
||||
// ## Export and Import
|
||||
exportStyle
|
||||
}
|
||||
}
|
||||
|
|
|
@ -87,48 +87,72 @@
|
|||
}
|
||||
}
|
||||
|
||||
.palette-editor {
|
||||
.list-editor {
|
||||
display: grid;
|
||||
grid-template-areas:
|
||||
"label editor"
|
||||
"selector editor"
|
||||
"motion editor";
|
||||
grid-template-columns: auto 1fr;
|
||||
"movement editor";
|
||||
grid-template-columns: 10em 1fr;
|
||||
grid-template-rows: auto 1fr auto;
|
||||
grid-gap: 0.5em;
|
||||
|
||||
.palette-editor-edit {
|
||||
.list-edit-area {
|
||||
grid-area: editor;
|
||||
}
|
||||
|
||||
.palette-selector {
|
||||
.list-select {
|
||||
grid-area: selector;
|
||||
margin: 0;
|
||||
|
||||
&-label {
|
||||
font-weight: bold;
|
||||
grid-area: label;
|
||||
margin: 0;
|
||||
align-self: baseline;
|
||||
}
|
||||
}
|
||||
|
||||
.palette-list {
|
||||
grid-area: selector;
|
||||
margin: 0;
|
||||
|
||||
&-movement {
|
||||
grid-area: motion;
|
||||
grid-area: movement;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.palettes-editor {
|
||||
.list-edit-area {
|
||||
align-self: baseline;
|
||||
}
|
||||
}
|
||||
|
||||
.variables-editor {
|
||||
.variable-selector {
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr auto 10em;
|
||||
grid-template-rows: subgrid;
|
||||
align-items: baseline;
|
||||
grid-gap: 0 0.5em;
|
||||
}
|
||||
|
||||
.list-edit-area {
|
||||
display: grid;
|
||||
grid-template-rows: subgrid;
|
||||
}
|
||||
|
||||
.shadow-control {
|
||||
grid-row: 2 / span 2;
|
||||
}
|
||||
}
|
||||
|
||||
.component-editor {
|
||||
display: grid;
|
||||
grid-template-columns: 6fr 3fr 4fr;
|
||||
grid-template-rows: auto auto 1fr;
|
||||
grid-gap: 0.5em;
|
||||
grid-template-areas:
|
||||
"component component variant"
|
||||
"state state state"
|
||||
"preview settings settings";
|
||||
"component component variant"
|
||||
"state state state"
|
||||
"preview settings settings";
|
||||
|
||||
.component-selector {
|
||||
grid-area: component;
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
<template>
|
||||
<div class="StyleTab">
|
||||
<div class="setting-item heading">
|
||||
<!-- TODO: This needs to go -->
|
||||
<h2>{{ $t('settings.style.themes3.editor.title') }}</h2>
|
||||
<button
|
||||
class="btn button-default"
|
||||
|
@ -71,12 +72,12 @@
|
|||
:key="'component-' + key"
|
||||
:value="key"
|
||||
>
|
||||
{{ fallbackI18n($t(getFriendlyNamePath(componentsMap.get(key).name)), componentsMap.get(key).name) }}
|
||||
{{ componentsMap.get(key).name }}
|
||||
</option>
|
||||
</Select>
|
||||
</div>
|
||||
<div
|
||||
v-if="selectedComponentVariantsAll.length > 1"
|
||||
v-if="selectedComponentVariants.length > 1"
|
||||
class="variant-selector"
|
||||
>
|
||||
<label for="variant-selector">
|
||||
|
@ -86,11 +87,11 @@
|
|||
v-model="selectedVariant"
|
||||
>
|
||||
<option
|
||||
v-for="variant in selectedComponentVariantsAll"
|
||||
v-for="variant in selectedComponentVariants"
|
||||
:key="'component-variant-' + variant"
|
||||
:value="variant"
|
||||
>
|
||||
{{ fallbackI18n($t(getVariantPath(selectedComponentName, variant)), variant) }}
|
||||
{{ variant }}
|
||||
</option>
|
||||
</Select>
|
||||
</div>
|
||||
|
@ -112,7 +113,7 @@
|
|||
:value="selectedState.has(state)"
|
||||
@update:modelValue="(v) => updateSelectedStates(state, v)"
|
||||
>
|
||||
{{ fallbackI18n($t(getStatePath(selectedComponentName, state)), state) }}
|
||||
{{ state }}
|
||||
</Checkbox>
|
||||
</li>
|
||||
</ul>
|
||||
|
@ -249,6 +250,7 @@
|
|||
v-model="editedShadow"
|
||||
:disabled="!isShadowPresent"
|
||||
:no-preview="true"
|
||||
:compact="true"
|
||||
:separate-inset="shadowSelected === 'avatar' || shadowSelected === 'avatarStatus'"
|
||||
@subShadowSelected="onSubShadow"
|
||||
/>
|
||||
|
@ -258,10 +260,10 @@
|
|||
<div
|
||||
key="palette"
|
||||
:label="$t('settings.style.themes3.editor.palette_tab')"
|
||||
class="setting-item palette-editor"
|
||||
class="setting-item list-editor palette-editor"
|
||||
>
|
||||
<label
|
||||
class="palette-selector-label"
|
||||
class="list-select-label"
|
||||
for="palette-selector"
|
||||
>
|
||||
{{ $t('settings.style.themes3.palette.label') }}
|
||||
|
@ -269,9 +271,9 @@
|
|||
</label>
|
||||
<Select
|
||||
id="palette-selector"
|
||||
v-model="editedPalette"
|
||||
class="palette-list"
|
||||
size="9"
|
||||
v-model="selectedPaletteId"
|
||||
class="list-select"
|
||||
size="4"
|
||||
>
|
||||
<option
|
||||
v-for="(p, index) in palettes"
|
||||
|
@ -282,17 +284,87 @@
|
|||
</option>
|
||||
</Select>
|
||||
<SelectMotion
|
||||
class="palette-list-movement"
|
||||
class="list-select-movement"
|
||||
v-model="palettes"
|
||||
:selected-id="editedPalette"
|
||||
:get-add-value="getNewPalette"
|
||||
@update:selectedId="e => editedPalette = e"
|
||||
:selected-id="selectedPaletteId"
|
||||
@update:selectedId="e => selectedPaletteId = e"
|
||||
/>
|
||||
<PaletteEditor
|
||||
class="palette-editor-edit"
|
||||
v-model="palette"
|
||||
class="list-edit-area"
|
||||
v-model="selectedPalette"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
key="variables"
|
||||
:label="$t('settings.style.themes3.editor.variables_tab')"
|
||||
class="setting-item list-editor variables-editor"
|
||||
>
|
||||
<label
|
||||
class="list-select-label"
|
||||
for="variables-selector"
|
||||
>
|
||||
{{ $t('settings.style.themes3.variables.label') }}
|
||||
{{ ' ' }}
|
||||
</label>
|
||||
<Select
|
||||
id="variables-selector"
|
||||
v-model="selectedVirtualDirectiveId"
|
||||
class="list-select"
|
||||
size="9"
|
||||
>
|
||||
<option
|
||||
v-for="(p, index) in virtualDirectives"
|
||||
:key="p.name"
|
||||
:value="index"
|
||||
>
|
||||
{{ p.name }}
|
||||
</option>
|
||||
</Select>
|
||||
<SelectMotion
|
||||
class="list-select-movement"
|
||||
v-model="virtualDirectives"
|
||||
:selected-id="selectedVirtualDirectiveId"
|
||||
:get-add-value="getNewVirtualDirective"
|
||||
@update:selectedId="e => selectedVirtualDirectiveId = e"
|
||||
/>
|
||||
<div class="list-edit-area">
|
||||
<div class="variable-selector">
|
||||
<label
|
||||
class="variable-name-label"
|
||||
for="variables-selector"
|
||||
>
|
||||
{{ $t('settings.style.themes3.variables.name_label') }}
|
||||
{{ ' ' }}
|
||||
</label>
|
||||
<input
|
||||
class="input"
|
||||
v-model="selectedVirtualDirective.name"
|
||||
>
|
||||
<label
|
||||
class="variable-type-label"
|
||||
for="variables-selector"
|
||||
>
|
||||
{{ $t('settings.style.themes3.variables.type_label') }}
|
||||
{{ ' ' }}
|
||||
</label>
|
||||
<Select
|
||||
v-model="selectedVirtualDirective.valType"
|
||||
>
|
||||
<option value='shadow'>
|
||||
{{ $t('settings.style.themes3.variables.type_label') }}
|
||||
shadow</option>
|
||||
<option value='shadow'>color</option>
|
||||
<option value='shadow'>generic</option>
|
||||
</Select>
|
||||
</div>
|
||||
<ShadowControl
|
||||
v-if="selectedVirtualDirective.valType === 'shadow'"
|
||||
v-model="selectedVirtualDirectiveParsed"
|
||||
:compact="true"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</tab-switcher>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -41,14 +41,17 @@ const toModel = (input) => {
|
|||
|
||||
export default {
|
||||
props: [
|
||||
'modelValue', 'fallback', 'separateInset', 'noPreview', 'disabled'
|
||||
'modelValue',
|
||||
'fallback',
|
||||
'separateInset',
|
||||
'noPreview',
|
||||
'disabled',
|
||||
'compact'
|
||||
],
|
||||
emits: ['update:modelValue', 'subShadowSelected'],
|
||||
data () {
|
||||
return {
|
||||
selectedId: 0,
|
||||
// TODO there are some bugs regarding display of array (it's not getting updated when deleting for some reason)
|
||||
cValue: (this.modelValue ?? this.fallback ?? []).map(toModel)
|
||||
selectedId: 0
|
||||
}
|
||||
},
|
||||
components: {
|
||||
|
@ -61,6 +64,14 @@ export default {
|
|||
ComponentPreview
|
||||
},
|
||||
computed: {
|
||||
cValue: {
|
||||
get () {
|
||||
return (this.modelValue ?? this.fallback ?? []).map(toModel)
|
||||
},
|
||||
set (newVal) {
|
||||
this.$emit('update:modelValue', newVal)
|
||||
}
|
||||
},
|
||||
selectedType: {
|
||||
get () {
|
||||
return typeof this.selected
|
||||
|
@ -115,9 +126,6 @@ export default {
|
|||
}
|
||||
},
|
||||
watch: {
|
||||
modelValue (value) {
|
||||
if (!value) this.cValue = (this.modelValue ?? this.fallback ?? []).map(toModel)
|
||||
},
|
||||
selected (value) {
|
||||
this.$emit('subShadowSelected', this.selectedId)
|
||||
}
|
||||
|
|
|
@ -1,12 +1,31 @@
|
|||
.settings-modal .settings-modal-panel .shadow-control {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
.ShadowControl {
|
||||
display: grid;
|
||||
grid-template-columns: 10em 1fr 1fr;
|
||||
grid-template-rows: 1fr;
|
||||
grid-template-areas: "selector preview tweak";
|
||||
grid-gap: 0.5em;
|
||||
justify-content: stretch;
|
||||
grid-gap: 0.25em;
|
||||
margin-bottom: 1em;
|
||||
width: 100%;
|
||||
|
||||
&.-compact {
|
||||
grid-template-columns: 10em 1fr;
|
||||
grid-template-rows: auto auto;
|
||||
grid-template-areas:
|
||||
"selector preview"
|
||||
"tweak tweak";
|
||||
|
||||
&.-no-preview {
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-rows: 10em 1fr;
|
||||
grid-template-areas:
|
||||
"selector"
|
||||
"tweak";
|
||||
}
|
||||
}
|
||||
|
||||
.shadow-switcher {
|
||||
grid-area: selector;
|
||||
order: 1;
|
||||
flex: 1 0 6em;
|
||||
min-width: 6em;
|
||||
|
@ -20,6 +39,7 @@
|
|||
}
|
||||
|
||||
.shadow-tweak {
|
||||
grid-area: tweak;
|
||||
order: 3;
|
||||
flex: 2 0 10em;
|
||||
min-width: 10em;
|
||||
|
@ -65,6 +85,10 @@
|
|||
}
|
||||
|
||||
&.-no-preview {
|
||||
grid-template-columns: 10em 1fr;
|
||||
grid-template-rows: 1fr;
|
||||
grid-template-areas: "selector tweak";
|
||||
|
||||
.shadow-tweak {
|
||||
order: 0;
|
||||
flex: 2 0 8em;
|
||||
|
@ -87,11 +111,11 @@
|
|||
}
|
||||
|
||||
.shadow-preview {
|
||||
order: 2;
|
||||
flex: 3 3 15em;
|
||||
grid-area: preview;
|
||||
min-width: 10em;
|
||||
margin-left: 0.125em;
|
||||
align-self: start;
|
||||
justify-self: center;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<div
|
||||
class="label shadow-control"
|
||||
:class="{ disabled: disabled || !present, '-no-preview': noPreview }"
|
||||
class="ShadowControl label shadow-control"
|
||||
:class="{ disabled: disabled || !present, '-no-preview': noPreview, '-compact': compact }"
|
||||
>
|
||||
<ComponentPreview
|
||||
v-if="!noPreview"
|
||||
|
@ -16,7 +16,7 @@
|
|||
id="shadow-list"
|
||||
v-model="selectedId"
|
||||
class="shadow-list"
|
||||
size="10"
|
||||
size="4"
|
||||
:disabled="disabled || shadowsAreNull"
|
||||
>
|
||||
<option
|
||||
|
|
|
@ -14,14 +14,14 @@ export default {
|
|||
{
|
||||
directives: {
|
||||
background: '--fg',
|
||||
shadow: ['--defaultButtonShadow', '--defaultButtonBevel'],
|
||||
shadow: ['--buttonDefaultShadow', '--buttonDefaultBevel'],
|
||||
roundness: 3
|
||||
}
|
||||
},
|
||||
{
|
||||
state: ['hover'],
|
||||
directives: {
|
||||
shadow: ['--defaultButtonHoverGlow', '--defaultButtonBevel']
|
||||
shadow: ['--buttonDefaultHoverGlow', '--buttonDefaultBevel']
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -33,14 +33,14 @@ export default {
|
|||
{
|
||||
state: ['hover', 'active'],
|
||||
directives: {
|
||||
shadow: ['--defaultButtonShadow', '--defaultButtonBevel']
|
||||
shadow: ['--buttonDefaultShadow', '--buttonDefaultBevel']
|
||||
}
|
||||
},
|
||||
{
|
||||
state: ['disabled'],
|
||||
directives: {
|
||||
background: '$blend(--inheritedBackground 0.25 --parent)',
|
||||
shadow: ['--defaultButtonBevel']
|
||||
shadow: ['--buttonDefaultBevel']
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
|
@ -757,7 +757,7 @@
|
|||
"themes3": {
|
||||
"define": "Override",
|
||||
"palette": {
|
||||
"label": "Palette",
|
||||
"label": "Palettes",
|
||||
"import": "Import",
|
||||
"export": "Export",
|
||||
"bg": "Panel background",
|
||||
|
@ -774,6 +774,11 @@
|
|||
"extra3": "Extra 3",
|
||||
"v2_unsupported": "Older v2 themes don't support palettes. Switch to v3 theme to make use of palettes"
|
||||
},
|
||||
"variables": {
|
||||
"label": "Variables",
|
||||
"name_label": "Name:",
|
||||
"type_label": "Type:"
|
||||
},
|
||||
"editor": {
|
||||
"title": "Style",
|
||||
"new_style": "New",
|
||||
|
@ -801,45 +806,7 @@
|
|||
},
|
||||
"component_tab": "Components style",
|
||||
"palette_tab": "Color presets",
|
||||
"components": {
|
||||
"normal": {
|
||||
"state": "Normal",
|
||||
"variant": "Default"
|
||||
},
|
||||
"Alert": {
|
||||
"friendlyName": "Alert",
|
||||
"variants": {
|
||||
"error": "Error",
|
||||
"warning": "Warning",
|
||||
"success": "Success"
|
||||
}
|
||||
},
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
"variables_tab": "Variables (Advanced)"
|
||||
},
|
||||
"hacks": {
|
||||
"underlay_overrides": "Change underlay",
|
||||
|
|
|
@ -627,7 +627,10 @@ export const normalizeThemeData = (input) => {
|
|||
// We got passed a full theme file
|
||||
themeData = input.theme
|
||||
themeSource = input.source
|
||||
} else if (Object.prototype.hasOwnProperty.call(input, 'themeEngineVersion')) {
|
||||
} else if (
|
||||
Object.prototype.hasOwnProperty.call(input, 'themeEngineVersion') ||
|
||||
Object.prototype.hasOwnProperty.call(input, 'bg')
|
||||
) {
|
||||
// We got passed a source/snapshot
|
||||
themeData = input
|
||||
themeSource = input
|
||||
|
|
|
@ -2,15 +2,22 @@ import utf8 from 'utf8'
|
|||
|
||||
export const newExporter = ({
|
||||
filename = 'data',
|
||||
mime = 'application/json',
|
||||
extension = '.json',
|
||||
getExportedObject
|
||||
}) => ({
|
||||
exportData () {
|
||||
const stringified = utf8.encode(JSON.stringify(getExportedObject(), null, 2)) // Pretty-print and indent with 2 spaces
|
||||
let stringified
|
||||
if (mime === 'application/json') {
|
||||
stringified = utf8.encode(JSON.stringify(getExportedObject(), null, 2)) // Pretty-print and indent with 2 spaces
|
||||
} else {
|
||||
stringified = utf8.encode(getExportedObject()) // Pretty-print and indent with 2 spaces
|
||||
}
|
||||
|
||||
// Create an invisible link with a data url and simulate a click
|
||||
const e = document.createElement('a')
|
||||
e.setAttribute('download', `${filename}.json`)
|
||||
e.setAttribute('href', 'data:application/json;base64,' + window.btoa(stringified))
|
||||
e.setAttribute('download', `${filename}.${extension}`)
|
||||
e.setAttribute('href', `data:${mime};base64, ${window.btoa(stringified)}`)
|
||||
e.style.display = 'none'
|
||||
|
||||
document.body.appendChild(e)
|
||||
|
|
Loading…
Add table
Reference in a new issue