some initial work on theme editor

This commit is contained in:
Henry Jameson 2024-09-24 03:07:27 +03:00
parent a8092de638
commit 144d426864
9 changed files with 283 additions and 8 deletions

View file

@ -10,9 +10,13 @@ export default {
ProfileSettingIndicator
},
props: {
modelValue: {
type: String,
default: null
},
path: {
type: [String, Array],
required: true
required: false
},
disabled: {
type: Boolean,
@ -68,7 +72,7 @@ export default {
}
},
created () {
if (this.realDraftMode && this.realSource !== 'admin') {
if (this.realDraftMode && (this.realSource !== 'admin' || this.path == null)) {
this.draft = this.state
}
},
@ -76,14 +80,14 @@ export default {
draft: {
// TODO allow passing shared draft object?
get () {
if (this.realSource === 'admin') {
if (this.realSource === 'admin' || this.path == null) {
return get(this.$store.state.adminSettings.draft, this.canonPath)
} else {
return this.localDraft
}
},
set (value) {
if (this.realSource === 'admin') {
if (this.realSource === 'admin' || this.path == null) {
this.$store.commit('updateAdminDraft', { path: this.canonPath, value })
} else {
this.localDraft = value
@ -91,6 +95,9 @@ export default {
}
},
state () {
if (this.path == null) {
return this.modelValue
}
const value = get(this.configSource, this.canonPath)
if (value === undefined) {
return this.defaultState
@ -145,6 +152,9 @@ export default {
return this.backendDescription?.suggestions
},
shouldBeDisabled () {
if (this.path == null) {
return this.disabled
}
const parentValue = this.parentPath !== undefined ? get(this.configSource, this.parentPath) : null
return this.disabled || (parentValue !== null ? (this.parentInvert ? parentValue : !parentValue) : false)
},
@ -159,6 +169,9 @@ export default {
}
},
configSink () {
if (this.path == null) {
return (k, v) => this.$emit('modelValue:update', v)
}
switch (this.realSource) {
case 'profile':
return (k, v) => this.$store.dispatch('setProfileOption', { name: k, value: v })
@ -184,6 +197,7 @@ export default {
return this.realSource === 'profile'
},
isChanged () {
if (this.path == null) return false
switch (this.realSource) {
case 'profile':
case 'admin':
@ -193,9 +207,11 @@ export default {
}
},
canonPath () {
if (this.path == null) return null
return Array.isArray(this.path) ? this.path : this.path.split('.')
},
isDirty () {
if (this.path == null) return false
if (this.realSource === 'admin' && this.canonPath.length > 3) {
return false // should not show draft buttons for "grouped" values
} else {

View file

@ -15,6 +15,7 @@
</template>
<slot v-else />
</label>
{{ ' ' }}
<input
:id="path"
class="input string-input"

View file

@ -10,6 +10,10 @@
list-style-type: none;
padding-left: 2em;
.btn:not(.dropdown-button) {
padding: 0 2em;
}
li {
margin-bottom: 0.5em;
}
@ -54,10 +58,6 @@
.btn {
min-height: 2em;
}
.btn:not(.dropdown-button) {
padding: 0 2em;
}
}
}

View file

@ -10,6 +10,7 @@ import GeneralTab from './tabs/general_tab.vue'
import AppearanceTab from './tabs/appearance_tab.vue'
import VersionTab from './tabs/version_tab.vue'
import ThemeTab from './tabs/theme_tab/theme_tab.vue'
import StyleTab from './tabs/style_tab/style_tab.vue'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
@ -17,6 +18,7 @@ import {
faUser,
faFilter,
faPaintBrush,
faPalette,
faBell,
faDownload,
faEyeSlash,
@ -29,6 +31,7 @@ library.add(
faUser,
faFilter,
faPaintBrush,
faPalette,
faBell,
faDownload,
faEyeSlash,
@ -48,6 +51,7 @@ const SettingsModalContent = {
ProfileTab,
GeneralTab,
AppearanceTab,
StyleTab,
VersionTab,
ThemeTab
},

View file

@ -20,6 +20,13 @@
>
<AppearanceTab />
</div>
<div
:label="$t('settings.style.themes3.editor.title')"
icon="palette"
data-tab-name="style"
>
<StyleTab />
</div>
<div
:label="$t('settings.theme')"
icon="paint-brush"

View file

@ -0,0 +1,40 @@
// import {
// rgb2hex,
// hex2rgb,
// getContrastRatioLayers,
// relativeLuminance
// } from 'src/services/color_convert/color_convert.js'
// import {
// getThemes
// } from 'src/services/style_setter/style_setter.js'
// import {
// newImporter,
// newExporter
// } from 'src/services/export_import/export_import.js'
// import { convertTheme2To3 } from 'src/services/theme_data/theme2_to_theme3.js'
// import { init } from 'src/services/theme_data/theme_data_3.service.js'
// import {
// getCssRules,
// getScopedVersion
// } from 'src/services/theme_data/css_utils.js'
// import ColorInput from 'src/components/color_input/color_input.vue'
// import RangeInput from 'src/components/range_input/range_input.vue'
// import OpacityInput from 'src/components/opacity_input/opacity_input.vue'
// import ShadowControl from 'src/components/shadow_control/shadow_control.vue'
// import FontControl from 'src/components/font_control/font_control.vue'
// import ContrastRatio from 'src/components/contrast_ratio/contrast_ratio.vue'
// import TabSwitcher from 'src/components/tab_switcher/tab_switcher.jsx'
// import Checkbox from 'src/components/checkbox/checkbox.vue'
/* eslint-disable no-unused-vars */
import Select from 'src/components/select/select.vue'
import Preview from './theme_preview.vue'
import { defineOptions, ref } from 'vue'
const componentsContext = require.context('src', true, /\.style.js(on)?$/)
const componentNames = componentsContext.keys()
const componentName = ref(componentNames[0])

View file

@ -0,0 +1,59 @@
.StyleTab {
.setting-item {
padding-bottom: 0;
.btn {
padding: 0 0.5em;
}
&:not(:first-child) {
margin-top: 0.5em;
}
&:not(:last-child) {
margin-bottom: 0.5em;
}
&.heading {
display: grid;
align-items: baseline;
grid-template-columns: 1fr auto auto auto;
grid-gap: 0.5em;
h2 {
flex: 1 0 auto;
}
}
&.metadata {
display: flex;
.setting-item {
flex: 2 0 auto;
}
li {
text-align: right;
}
}
}
.component-editor {
display: grid;
grid-template-columns: 10em, 1fr, 2fr;
grid-template-rows: auto 1fr;
grid-template-areas:
"variant" "preview" "controls",
"state" "preview" "controls";
.state-selector {
grid-area: state;
}
.variant-selector {
grid-area: variant;
display: flex;
flex-direction: column;
}
}
}

View file

@ -0,0 +1,138 @@
<script setup>
import { ref, computed } from 'vue'
import Select from 'src/components/select/select.vue'
import Checkbox from 'src/components/checkbox/checkbox.vue'
import StringSetting from '../../helpers/string_setting.vue'
// import Preview from '../theme_tab/theme_preview.vue'
import { library } from '@fortawesome/fontawesome-svg-core'
import { faFloppyDisk, faFolderOpen, faFile } from '@fortawesome/free-solid-svg-icons'
library.add(
faFile,
faFloppyDisk,
faFolderOpen
)
const name = ref('')
const author = ref('')
const license = ref('')
const website = ref('')
// Getting existing components
const componentsContext = require.context('src', true, /\.style.js(on)?$/)
const componentKeys = componentsContext.keys()
const componentsMap = new Map(
componentKeys
.map(
key => [key, componentsContext(key).default]
)
)
// const componentValues = componentsMap.values()
// Initializing selected component and its computed descendants
const selectedComponentKey = ref(componentKeys[0])
const selectedComponentValue = computed(() => componentsMap.get(selectedComponentKey.value))
// const selectedComponentName = computed(() => selectedComponent.value.name)
const selectedComponentVariants = computed(() => {
return new Set([...(Object.keys(selectedComponentValue.value.variants || {})), 'normal'])
})
const selectedComponentStates = computed(() => {
return new Set([...(Object.keys(selectedComponentValue.value.states || {})), 'normal'])
})
</script>
<template>
<div class="StyleTab">
<div class="setting-item heading">
<h2>{{ $t('settings.style.themes3.editor.title') }}</h2>
<button
class="btn button-default"
@click="clearTheme"
>
<FAIcon icon="file" />
{{ $t('settings.style.themes3.editor.new_style') }}
</button>
<button
class="btn button-default"
@click="importStyle"
>
<FAIcon icon="folder-open" />
{{ $t('settings.style.themes3.editor.load_style') }}
</button>
<button
class="btn button-default"
@click="exportTheme"
>
<FAIcon icon="floppy-disk" />
{{ $t('settings.style.themes3.editor.save_style') }}
</button>
</div>
<div class="setting-item metadata">
<ul class="setting-list">
<li>
<StringSetting v-model="name">
{{ $t('settings.style.themes3.editor.style_name') }}
</StringSetting>
</li>
<li>
<StringSetting v-model="author">
{{ $t('settings.style.themes3.editor.style_author') }}
</StringSetting>
</li>
<li>
<StringSetting v-model="license">
{{ $t('settings.style.themes3.editor.style_license') }}
</StringSetting>
</li>
<li>
<StringSetting v-model="website">
{{ $t('settings.style.themes3.editor.style_website') }}
</StringSetting>
</li>
</ul>
</div>
<div class="setting-item">
<Select v-model="selectedComponentKey">
<option
v-for="key in componentKeys"
:key="'component-' + key"
:value="key"
>
{{ componentsMap.get(key).name }}
</option>
</Select>
<div class="component-editor">
<Select
v-model="selectedComponentVariant"
class="variant-selector"
>
<option
v-for="variant in selectedComponentVariants"
:key="'component-variant-' + variant"
>
{{ variant }}
</option>
</Select>
<ul class="state-selector">
<li
v-for="state in selectedComponentStates"
:key="'component-variant-' + state"
>
<Checkbox
v-model="selectedComponentStates"
>
{{ state }}
</Checkbox>
</li>
</ul>
</div>
</div>
</div>
</template>
<style src="./style_tab.scss" lang="scss"></style>

View file

@ -753,6 +753,16 @@
"update_preview": "Update preview",
"themes3": {
"define": "Override",
"editor": {
"title": "Style",
"new_style": "New",
"load_style": "Open",
"save_style": "Save",
"style_name": "Stylesheet name",
"style_author": "Made by",
"style_license": "License",
"style_website": "Website"
},
"hacks": {
"underlay_overrides": "Change underlay",
"underlay_override_mode_none": "Theme default",