Compare commits

...

15 commits

Author SHA1 Message Date
Henry Jameson
f8b39faae7 Merge branch 'themes3-grand-finale-maybe' into shigusegubu-themes3 2024-10-07 12:25:29 +03:00
Henry Jameson
4d472e1d4b fix my phone 2024-10-07 12:21:36 +03:00
Henry Jameson
4f66731723 lookin' good 2024-10-07 02:16:24 +03:00
Henry Jameson
f0e5b0be1e cleanup 2024-10-07 01:33:31 +03:00
Henry Jameson
f75ea738ca better cValue logic in shadow-control 2024-10-07 01:31:22 +03:00
Henry Jameson
97c058ebda neater looks for shadow-related things 2024-10-07 01:30:53 +03:00
Henry Jameson
cfe52185f7 neat-looking variables tab (sans shadow editor) 2024-10-07 00:57:54 +03:00
Henry Jameson
756ea63b67 variables stuff seem to be at least somewhat working 2024-10-06 20:21:13 +03:00
Henry Jameson
8725de3e91 got it working again 2024-10-06 18:56:45 +03:00
Henry Jameson
a6863248bb variables + consistency in code 2024-10-06 15:19:30 +03:00
Henry Jameson
da2c016ab4 better vars nomenclature 2024-10-06 15:19:30 +03:00
Henry Jameson
7e684ea3ff proper exporter 2024-10-06 03:32:08 +03:00
Henry Jameson
0f2bd39db8 cleanup 2024-10-06 03:31:59 +03:00
Henry Jameson
c58ed1036f remove i18n stuff.
first of all - it's too much work for me AND for translators
second of all - providing support would be a living hell nightmare
trying to understand what component it is in users's language that isn't english
2024-10-06 03:30:52 +03:00
Henry Jameson
d31da2c300 theme v3 config.json fields 2024-10-06 02:48:21 +03:00
14 changed files with 475 additions and 310 deletions

View file

@ -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']
}
},
{

View file

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

View file

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

View file

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

View file

@ -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()
}
} catch (e) {
console.warn('Failure computing contrast', e)
return { error: e }
})
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
}
}
})
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
}
}

View file

@ -87,39 +87,63 @@
}
}
.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;

View file

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

View file

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

View file

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

View file

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

View file

@ -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']
}
},
{

View file

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

View file

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

View file

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