some initial work on theme editor
This commit is contained in:
parent
a8092de638
commit
144d426864
|
@ -10,9 +10,13 @@ export default {
|
||||||
ProfileSettingIndicator
|
ProfileSettingIndicator
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
|
modelValue: {
|
||||||
|
type: String,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
path: {
|
path: {
|
||||||
type: [String, Array],
|
type: [String, Array],
|
||||||
required: true
|
required: false
|
||||||
},
|
},
|
||||||
disabled: {
|
disabled: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
|
@ -68,7 +72,7 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created () {
|
created () {
|
||||||
if (this.realDraftMode && this.realSource !== 'admin') {
|
if (this.realDraftMode && (this.realSource !== 'admin' || this.path == null)) {
|
||||||
this.draft = this.state
|
this.draft = this.state
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -76,14 +80,14 @@ export default {
|
||||||
draft: {
|
draft: {
|
||||||
// TODO allow passing shared draft object?
|
// TODO allow passing shared draft object?
|
||||||
get () {
|
get () {
|
||||||
if (this.realSource === 'admin') {
|
if (this.realSource === 'admin' || this.path == null) {
|
||||||
return get(this.$store.state.adminSettings.draft, this.canonPath)
|
return get(this.$store.state.adminSettings.draft, this.canonPath)
|
||||||
} else {
|
} else {
|
||||||
return this.localDraft
|
return this.localDraft
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
set (value) {
|
set (value) {
|
||||||
if (this.realSource === 'admin') {
|
if (this.realSource === 'admin' || this.path == null) {
|
||||||
this.$store.commit('updateAdminDraft', { path: this.canonPath, value })
|
this.$store.commit('updateAdminDraft', { path: this.canonPath, value })
|
||||||
} else {
|
} else {
|
||||||
this.localDraft = value
|
this.localDraft = value
|
||||||
|
@ -91,6 +95,9 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
state () {
|
state () {
|
||||||
|
if (this.path == null) {
|
||||||
|
return this.modelValue
|
||||||
|
}
|
||||||
const value = get(this.configSource, this.canonPath)
|
const value = get(this.configSource, this.canonPath)
|
||||||
if (value === undefined) {
|
if (value === undefined) {
|
||||||
return this.defaultState
|
return this.defaultState
|
||||||
|
@ -145,6 +152,9 @@ export default {
|
||||||
return this.backendDescription?.suggestions
|
return this.backendDescription?.suggestions
|
||||||
},
|
},
|
||||||
shouldBeDisabled () {
|
shouldBeDisabled () {
|
||||||
|
if (this.path == null) {
|
||||||
|
return this.disabled
|
||||||
|
}
|
||||||
const parentValue = this.parentPath !== undefined ? get(this.configSource, this.parentPath) : null
|
const parentValue = this.parentPath !== undefined ? get(this.configSource, this.parentPath) : null
|
||||||
return this.disabled || (parentValue !== null ? (this.parentInvert ? parentValue : !parentValue) : false)
|
return this.disabled || (parentValue !== null ? (this.parentInvert ? parentValue : !parentValue) : false)
|
||||||
},
|
},
|
||||||
|
@ -159,6 +169,9 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
configSink () {
|
configSink () {
|
||||||
|
if (this.path == null) {
|
||||||
|
return (k, v) => this.$emit('modelValue:update', v)
|
||||||
|
}
|
||||||
switch (this.realSource) {
|
switch (this.realSource) {
|
||||||
case 'profile':
|
case 'profile':
|
||||||
return (k, v) => this.$store.dispatch('setProfileOption', { name: k, value: v })
|
return (k, v) => this.$store.dispatch('setProfileOption', { name: k, value: v })
|
||||||
|
@ -184,6 +197,7 @@ export default {
|
||||||
return this.realSource === 'profile'
|
return this.realSource === 'profile'
|
||||||
},
|
},
|
||||||
isChanged () {
|
isChanged () {
|
||||||
|
if (this.path == null) return false
|
||||||
switch (this.realSource) {
|
switch (this.realSource) {
|
||||||
case 'profile':
|
case 'profile':
|
||||||
case 'admin':
|
case 'admin':
|
||||||
|
@ -193,9 +207,11 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
canonPath () {
|
canonPath () {
|
||||||
|
if (this.path == null) return null
|
||||||
return Array.isArray(this.path) ? this.path : this.path.split('.')
|
return Array.isArray(this.path) ? this.path : this.path.split('.')
|
||||||
},
|
},
|
||||||
isDirty () {
|
isDirty () {
|
||||||
|
if (this.path == null) return false
|
||||||
if (this.realSource === 'admin' && this.canonPath.length > 3) {
|
if (this.realSource === 'admin' && this.canonPath.length > 3) {
|
||||||
return false // should not show draft buttons for "grouped" values
|
return false // should not show draft buttons for "grouped" values
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
</template>
|
</template>
|
||||||
<slot v-else />
|
<slot v-else />
|
||||||
</label>
|
</label>
|
||||||
|
{{ ' ' }}
|
||||||
<input
|
<input
|
||||||
:id="path"
|
:id="path"
|
||||||
class="input string-input"
|
class="input string-input"
|
||||||
|
|
|
@ -10,6 +10,10 @@
|
||||||
list-style-type: none;
|
list-style-type: none;
|
||||||
padding-left: 2em;
|
padding-left: 2em;
|
||||||
|
|
||||||
|
.btn:not(.dropdown-button) {
|
||||||
|
padding: 0 2em;
|
||||||
|
}
|
||||||
|
|
||||||
li {
|
li {
|
||||||
margin-bottom: 0.5em;
|
margin-bottom: 0.5em;
|
||||||
}
|
}
|
||||||
|
@ -54,10 +58,6 @@
|
||||||
.btn {
|
.btn {
|
||||||
min-height: 2em;
|
min-height: 2em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn:not(.dropdown-button) {
|
|
||||||
padding: 0 2em;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@ import GeneralTab from './tabs/general_tab.vue'
|
||||||
import AppearanceTab from './tabs/appearance_tab.vue'
|
import AppearanceTab from './tabs/appearance_tab.vue'
|
||||||
import VersionTab from './tabs/version_tab.vue'
|
import VersionTab from './tabs/version_tab.vue'
|
||||||
import ThemeTab from './tabs/theme_tab/theme_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 { library } from '@fortawesome/fontawesome-svg-core'
|
||||||
import {
|
import {
|
||||||
|
@ -17,6 +18,7 @@ import {
|
||||||
faUser,
|
faUser,
|
||||||
faFilter,
|
faFilter,
|
||||||
faPaintBrush,
|
faPaintBrush,
|
||||||
|
faPalette,
|
||||||
faBell,
|
faBell,
|
||||||
faDownload,
|
faDownload,
|
||||||
faEyeSlash,
|
faEyeSlash,
|
||||||
|
@ -29,6 +31,7 @@ library.add(
|
||||||
faUser,
|
faUser,
|
||||||
faFilter,
|
faFilter,
|
||||||
faPaintBrush,
|
faPaintBrush,
|
||||||
|
faPalette,
|
||||||
faBell,
|
faBell,
|
||||||
faDownload,
|
faDownload,
|
||||||
faEyeSlash,
|
faEyeSlash,
|
||||||
|
@ -48,6 +51,7 @@ const SettingsModalContent = {
|
||||||
ProfileTab,
|
ProfileTab,
|
||||||
GeneralTab,
|
GeneralTab,
|
||||||
AppearanceTab,
|
AppearanceTab,
|
||||||
|
StyleTab,
|
||||||
VersionTab,
|
VersionTab,
|
||||||
ThemeTab
|
ThemeTab
|
||||||
},
|
},
|
||||||
|
|
|
@ -20,6 +20,13 @@
|
||||||
>
|
>
|
||||||
<AppearanceTab />
|
<AppearanceTab />
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
:label="$t('settings.style.themes3.editor.title')"
|
||||||
|
icon="palette"
|
||||||
|
data-tab-name="style"
|
||||||
|
>
|
||||||
|
<StyleTab />
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
:label="$t('settings.theme')"
|
:label="$t('settings.theme')"
|
||||||
icon="paint-brush"
|
icon="paint-brush"
|
||||||
|
|
40
src/components/settings_modal/tabs/style_tab/style_tab.js
Normal file
40
src/components/settings_modal/tabs/style_tab/style_tab.js
Normal 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])
|
59
src/components/settings_modal/tabs/style_tab/style_tab.scss
Normal file
59
src/components/settings_modal/tabs/style_tab/style_tab.scss
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
138
src/components/settings_modal/tabs/style_tab/style_tab.vue
Normal file
138
src/components/settings_modal/tabs/style_tab/style_tab.vue
Normal 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>
|
|
@ -753,6 +753,16 @@
|
||||||
"update_preview": "Update preview",
|
"update_preview": "Update preview",
|
||||||
"themes3": {
|
"themes3": {
|
||||||
"define": "Override",
|
"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": {
|
"hacks": {
|
||||||
"underlay_overrides": "Change underlay",
|
"underlay_overrides": "Change underlay",
|
||||||
"underlay_override_mode_none": "Theme default",
|
"underlay_override_mode_none": "Theme default",
|
||||||
|
|
Loading…
Reference in a new issue