Merge remote-tracking branch 'origin/develop' into fix-develop-issues
This commit is contained in:
commit
e8d7d341f0
1
changelog.d/appearance-tab.change
Normal file
1
changelog.d/appearance-tab.change
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Reorganized Settings modal to move out visual stuff into Appearance tab
|
1
changelog.d/emoji-scale.add
Normal file
1
changelog.d/emoji-scale.add
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Ability to change size of emoji
|
1
changelog.d/firefox-redmon.fix
Normal file
1
changelog.d/firefox-redmon.fix
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Bug with firefox and redmond themes
|
1
changelog.d/theme-selector.add
Normal file
1
changelog.d/theme-selector.add
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Theme selector with visual previews of the theme
|
1
changelog.d/ui-scale.add
Normal file
1
changelog.d/ui-scale.add
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Ability to resize UI (and certain components) scale independent of browser/text scale
|
1
changelog.d/user-overrides.add
Normal file
1
changelog.d/user-overrides.add
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Ability to override certain aspects of UI style independent of theme used (UI roundness, fonts, underlay)
|
0
preview.style.js
Normal file
0
preview.style.js
Normal file
48
src/App.scss
48
src/App.scss
|
@ -4,7 +4,8 @@
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--status-margin: 0.75em;
|
--status-margin: 0.75em;
|
||||||
--navbar-height: 3.5rem;
|
--navbar-height: var(--navbarSize, 3.5rem);
|
||||||
|
--panel-header-height: var(--panelHeaderSize, 3.2rem);
|
||||||
--post-line-height: 1.4;
|
--post-line-height: 1.4;
|
||||||
// Z-Index stuff
|
// Z-Index stuff
|
||||||
--ZI_media_modal: 9000;
|
--ZI_media_modal: 9000;
|
||||||
|
@ -19,7 +20,9 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
html {
|
html {
|
||||||
font-size: var(--font-size, 14px);
|
font-size: var(--textSize, 14px);
|
||||||
|
--navbar-height: var(--navbarSize, 3.5rem);
|
||||||
|
--emoji-size: var(--emojiSize, 32px);
|
||||||
// overflow-x: clip causes my browser's tab to crash with SIGILL lul
|
// overflow-x: clip causes my browser's tab to crash with SIGILL lul
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -155,6 +158,7 @@ nav {
|
||||||
box-shadow: var(--shadow);
|
box-shadow: var(--shadow);
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
height: var(--navbar-height);
|
height: var(--navbar-height);
|
||||||
|
font-size: calc(var(--navbar-height) / 3.5);
|
||||||
position: fixed;
|
position: fixed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -206,7 +210,7 @@ nav {
|
||||||
.app-layout {
|
.app-layout {
|
||||||
--miniColumn: 25rem;
|
--miniColumn: 25rem;
|
||||||
--maxiColumn: 45rem;
|
--maxiColumn: 45rem;
|
||||||
--columnGap: 1em;
|
--columnGap: 1rem;
|
||||||
--effectiveSidebarColumnWidth: minmax(var(--miniColumn), var(--sidebarColumnWidth, var(--miniColumn)));
|
--effectiveSidebarColumnWidth: minmax(var(--miniColumn), var(--sidebarColumnWidth, var(--miniColumn)));
|
||||||
--effectiveNotifsColumnWidth: minmax(var(--miniColumn), var(--notifsColumnWidth, var(--miniColumn)));
|
--effectiveNotifsColumnWidth: minmax(var(--miniColumn), var(--notifsColumnWidth, var(--miniColumn)));
|
||||||
--effectiveContentColumnWidth: minmax(var(--miniColumn), var(--contentColumnWidth, var(--maxiColumn)));
|
--effectiveContentColumnWidth: minmax(var(--miniColumn), var(--contentColumnWidth, var(--maxiColumn)));
|
||||||
|
@ -370,7 +374,6 @@ nav {
|
||||||
user-select: none;
|
user-select: none;
|
||||||
color: var(--text);
|
color: var(--text);
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: var(--roundness);
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
background-color: var(--background);
|
background-color: var(--background);
|
||||||
box-shadow: var(--shadow);
|
box-shadow: var(--shadow);
|
||||||
|
@ -506,7 +509,6 @@ textarea {
|
||||||
--_padding: 0.5em;
|
--_padding: 0.5em;
|
||||||
|
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: var(--roundness);
|
|
||||||
background-color: var(--background);
|
background-color: var(--background);
|
||||||
color: var(--text);
|
color: var(--text);
|
||||||
box-shadow: var(--shadow);
|
box-shadow: var(--shadow);
|
||||||
|
@ -612,6 +614,17 @@ textarea {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.input,
|
||||||
|
.button-default {
|
||||||
|
--_roundness-left: var(--roundness);
|
||||||
|
--_roundness-right: var(--roundness);
|
||||||
|
|
||||||
|
border-top-left-radius: var(--_roundness-left);
|
||||||
|
border-bottom-left-radius: var(--_roundness-left);
|
||||||
|
border-top-right-radius: var(--_roundness-right);
|
||||||
|
border-bottom-right-radius: var(--_roundness-right);
|
||||||
|
}
|
||||||
|
|
||||||
// Textareas should have stock line-height + vertical padding instead of huge line-height
|
// Textareas should have stock line-height + vertical padding instead of huge line-height
|
||||||
textarea.input {
|
textarea.input {
|
||||||
padding: var(--_padding);
|
padding: var(--_padding);
|
||||||
|
@ -657,22 +670,23 @@ option {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
|
|
||||||
button,
|
> *,
|
||||||
.button-dropdown {
|
> * .button-default {
|
||||||
|
--_roundness-left: 0;
|
||||||
|
--_roundness-right: 0;
|
||||||
|
|
||||||
position: relative;
|
position: relative;
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
|
}
|
||||||
|
|
||||||
&:not(:last-child),
|
> *:first-child,
|
||||||
&:not(:last-child) .button-default {
|
> *:first-child .button-default {
|
||||||
border-top-right-radius: 0;
|
--_roundness-left: var(--roundness);
|
||||||
border-bottom-right-radius: 0;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
&:not(:first-child),
|
> *:last-child,
|
||||||
&:not(:first-child) .button-default {
|
> *:last-child .button-default {
|
||||||
border-top-left-radius: 0;
|
--_roundness-right: var(--roundness);
|
||||||
border-bottom-left-radius: 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,8 +13,7 @@ import VBodyScrollLock from 'src/directives/body_scroll_lock'
|
||||||
import { windowWidth, windowHeight } from '../services/window_utils/window_utils'
|
import { windowWidth, windowHeight } from '../services/window_utils/window_utils'
|
||||||
import { getOrCreateApp, getClientToken } from '../services/new_api/oauth.js'
|
import { getOrCreateApp, getClientToken } from '../services/new_api/oauth.js'
|
||||||
import backendInteractorService from '../services/backend_interactor_service/backend_interactor_service.js'
|
import backendInteractorService from '../services/backend_interactor_service/backend_interactor_service.js'
|
||||||
import { CURRENT_VERSION } from '../services/theme_data/theme_data.service.js'
|
import { applyConfig } from '../services/style_setter/style_setter.js'
|
||||||
import { applyTheme, applyConfig, tryLoadCache } from '../services/style_setter/style_setter.js'
|
|
||||||
import FaviconService from '../services/favicon_service/favicon_service.js'
|
import FaviconService from '../services/favicon_service/favicon_service.js'
|
||||||
import { initServiceWorker, updateFocus } from '../services/sw/sw.js'
|
import { initServiceWorker, updateFocus } from '../services/sw/sw.js'
|
||||||
|
|
||||||
|
@ -160,8 +159,6 @@ const setSettings = async ({ apiConfig, staticConfig, store }) => {
|
||||||
copyInstanceOption('showFeaturesPanel')
|
copyInstanceOption('showFeaturesPanel')
|
||||||
copyInstanceOption('hideSitename')
|
copyInstanceOption('hideSitename')
|
||||||
copyInstanceOption('sidebarRight')
|
copyInstanceOption('sidebarRight')
|
||||||
|
|
||||||
return store.dispatch('setTheme', config.theme)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const getTOS = async ({ store }) => {
|
const getTOS = async ({ store }) => {
|
||||||
|
@ -352,27 +349,7 @@ const afterStoreSetup = async ({ store, i18n }) => {
|
||||||
store.dispatch('setInstanceOption', { name: 'server', value: server })
|
store.dispatch('setInstanceOption', { name: 'server', value: server })
|
||||||
|
|
||||||
await setConfig({ store })
|
await setConfig({ store })
|
||||||
|
await store.dispatch('setTheme')
|
||||||
const { customTheme, customThemeSource, forceThemeRecompilation } = store.state.config
|
|
||||||
const { theme } = store.state.instance
|
|
||||||
const customThemePresent = customThemeSource || customTheme
|
|
||||||
|
|
||||||
if (!forceThemeRecompilation && tryLoadCache()) {
|
|
||||||
store.commit('setThemeApplied')
|
|
||||||
} else {
|
|
||||||
if (customThemePresent) {
|
|
||||||
if (customThemeSource && customThemeSource.themeEngineVersion === CURRENT_VERSION) {
|
|
||||||
applyTheme(customThemeSource)
|
|
||||||
} else {
|
|
||||||
applyTheme(customTheme)
|
|
||||||
}
|
|
||||||
store.commit('setThemeApplied')
|
|
||||||
} else if (theme) {
|
|
||||||
// do nothing, it will load asynchronously
|
|
||||||
} else {
|
|
||||||
console.error('Failed to load any theme!')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
applyConfig(store.state.config)
|
applyConfig(store.state.config)
|
||||||
|
|
||||||
|
|
|
@ -120,6 +120,7 @@ const EmojiPicker = {
|
||||||
groupRefs: {},
|
groupRefs: {},
|
||||||
emojiRefs: {},
|
emojiRefs: {},
|
||||||
filteredEmojiGroups: [],
|
filteredEmojiGroups: [],
|
||||||
|
emojiSize: 0,
|
||||||
width: 0
|
width: 0
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -130,6 +131,23 @@ const EmojiPicker = {
|
||||||
Popover
|
Popover
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
updateEmojiSize () {
|
||||||
|
const css = window.getComputedStyle(this.$refs.popover.$el)
|
||||||
|
const emojiSize = css.getPropertyValue('--emojiSize')
|
||||||
|
const emojiSizeUnit = emojiSize.replace(/[0-9,.]+/, '')
|
||||||
|
const emojiSizeValue = Number(emojiSize.replace(/[^0-9,.]+/, ''))
|
||||||
|
const fontSize = css.getPropertyValue('font-size').replace(/[^0-9,.]+/, '')
|
||||||
|
|
||||||
|
let emojiSizeReal
|
||||||
|
if (emojiSizeUnit.endsWith('em')) {
|
||||||
|
emojiSizeReal = emojiSizeValue * fontSize
|
||||||
|
} else {
|
||||||
|
emojiSizeReal = emojiSizeValue
|
||||||
|
}
|
||||||
|
|
||||||
|
const fullEmojiSize = emojiSizeReal + (2 * 0.2 * fontSize)
|
||||||
|
this.emojiSize = fullEmojiSize
|
||||||
|
},
|
||||||
showPicker () {
|
showPicker () {
|
||||||
this.$refs.popover.showPopover()
|
this.$refs.popover.showPopover()
|
||||||
this.onShowing()
|
this.onShowing()
|
||||||
|
@ -224,6 +242,7 @@ const EmojiPicker = {
|
||||||
},
|
},
|
||||||
onShowing () {
|
onShowing () {
|
||||||
const oldContentLoaded = this.contentLoaded
|
const oldContentLoaded = this.contentLoaded
|
||||||
|
this.updateEmojiSize()
|
||||||
this.recalculateItemPerRow()
|
this.recalculateItemPerRow()
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
this.$refs.search.focus()
|
this.$refs.search.focus()
|
||||||
|
@ -266,16 +285,20 @@ const EmojiPicker = {
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
minItemSize () {
|
minItemSize () {
|
||||||
return this.emojiHeight
|
return this.emojiSize
|
||||||
|
},
|
||||||
|
// used to watch it
|
||||||
|
fontSize () {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.updateEmojiSize()
|
||||||
|
})
|
||||||
|
return this.$store.getters.mergedConfig.fontSize
|
||||||
},
|
},
|
||||||
emojiHeight () {
|
emojiHeight () {
|
||||||
return 32 + 4
|
return this.emojiSize
|
||||||
},
|
|
||||||
emojiWidth () {
|
|
||||||
return 32 + 4
|
|
||||||
},
|
},
|
||||||
itemPerRow () {
|
itemPerRow () {
|
||||||
return this.width ? Math.floor(this.width / this.emojiWidth - 1) : 6
|
return this.width ? Math.floor(this.width / this.emojiSize) : 6
|
||||||
},
|
},
|
||||||
activeGroupView () {
|
activeGroupView () {
|
||||||
return this.showingStickers ? '' : this.activeGroup
|
return this.showingStickers ? '' : this.activeGroup
|
||||||
|
|
|
@ -1,9 +1,6 @@
|
||||||
$emoji-picker-header-height: 36px;
|
|
||||||
$emoji-picker-header-picture-width: 32px;
|
|
||||||
$emoji-picker-header-picture-height: 32px;
|
|
||||||
$emoji-picker-emoji-size: 32px;
|
|
||||||
|
|
||||||
.emoji-picker {
|
.emoji-picker {
|
||||||
|
--__emoji-picker-header: 2.2em;
|
||||||
|
|
||||||
width: 25em;
|
width: 25em;
|
||||||
max-width: calc(100vw - 20px); // popover gives 10px margin from window edge
|
max-width: calc(100vw - 20px); // popover gives 10px margin from window edge
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -13,24 +10,26 @@ $emoji-picker-emoji-size: 32px;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
width: $emoji-picker-header-picture-width;
|
width: var(--__emoji-picker-header);
|
||||||
max-width: $emoji-picker-header-picture-width;
|
max-width: var(--__emoji-picker-header);
|
||||||
height: $emoji-picker-header-picture-height;
|
height: var(--__emoji-picker-header);
|
||||||
max-height: $emoji-picker-header-picture-height;
|
max-height: var(--__emoji-picker-header);
|
||||||
|
|
||||||
.still-image {
|
.still-image {
|
||||||
max-width: 100%;
|
width: var(--__emoji-picker-header);
|
||||||
max-height: 100%;
|
max-width: var(--__emoji-picker-header);
|
||||||
height: 100%;
|
height: var(--__emoji-picker-header);
|
||||||
width: 100%;
|
max-height: var(--__emoji-picker-header);
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
|
|
||||||
|
--_still_image-label-scale: 0.5;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.keep-open,
|
.keep-open,
|
||||||
.too-many-emoji,
|
.too-many-emoji,
|
||||||
.hide-custom-emoji {
|
.hide-custom-emoji {
|
||||||
padding: 7px;
|
padding: 0.5em;
|
||||||
line-height: normal;
|
line-height: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,13 +43,13 @@ $emoji-picker-emoji-size: 32px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.keep-open-label {
|
.keep-open-label {
|
||||||
padding: 0 7px;
|
padding: 0 0.5em;
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
.heading {
|
.heading {
|
||||||
display: flex;
|
display: flex;
|
||||||
padding: 10px 7px 5px;
|
padding: 0.7em 0.5em 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
|
@ -65,13 +64,14 @@ $emoji-picker-emoji-size: 32px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-flow: row nowrap;
|
flex-flow: row nowrap;
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
|
overflow-y: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.additional-tabs {
|
.additional-tabs {
|
||||||
display: flex;
|
display: flex;
|
||||||
border-left: 1px solid;
|
border-left: 1px solid;
|
||||||
border-left-color: var(--border);
|
border-left-color: var(--border);
|
||||||
padding-left: 7px;
|
padding-left: 0.5em;
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -80,25 +80,29 @@ $emoji-picker-emoji-size: 32px;
|
||||||
flex-basis: auto;
|
flex-basis: auto;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-content: center;
|
align-content: center;
|
||||||
|
scrollbar-width: thin;
|
||||||
|
|
||||||
&-item {
|
&-item {
|
||||||
padding: 0 7px;
|
padding: 0 0.5em;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 1.85em;
|
width: var(--__emoji-picker-header);
|
||||||
width: $emoji-picker-header-picture-width;
|
max-width: var(--__emoji-picker-header);
|
||||||
max-width: $emoji-picker-header-picture-width;
|
height: var(--__emoji-picker-header);
|
||||||
height: $emoji-picker-header-picture-height;
|
max-height: var(--__emoji-picker-header);
|
||||||
max-height: $emoji-picker-header-picture-height;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
|
.svg-inline--fa {
|
||||||
|
font-size: 1.85em;
|
||||||
|
}
|
||||||
|
|
||||||
&.disabled {
|
&.disabled {
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.toggled {
|
&.toggled {
|
||||||
border-bottom: 4px solid;
|
border-bottom: 0.2em solid;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -125,7 +129,7 @@ $emoji-picker-emoji-size: 32px;
|
||||||
|
|
||||||
.emoji {
|
.emoji {
|
||||||
&-search {
|
&-search {
|
||||||
padding: 5px;
|
padding: 0.3em;
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
|
|
||||||
input {
|
input {
|
||||||
|
@ -139,6 +143,7 @@ $emoji-picker-emoji-size: 32px;
|
||||||
flex: 1 1 1px;
|
flex: 1 1 1px;
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
|
scrollbar-gutter: stable both-edges;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
mask:
|
mask:
|
||||||
linear-gradient(to top, white 0, transparent 100%) bottom no-repeat,
|
linear-gradient(to top, white 0, transparent 100%) bottom no-repeat,
|
||||||
|
@ -165,13 +170,13 @@ $emoji-picker-emoji-size: 32px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
padding-left: 5px;
|
|
||||||
justify-content: left;
|
justify-content: left;
|
||||||
|
|
||||||
&-title {
|
&-title {
|
||||||
font-size: 0.85em;
|
font-size: 0.85em;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
padding-left: 0.3em;
|
||||||
|
|
||||||
&.disabled {
|
&.disabled {
|
||||||
display: none;
|
display: none;
|
||||||
|
@ -180,24 +185,28 @@ $emoji-picker-emoji-size: 32px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&-item {
|
&-item {
|
||||||
width: $emoji-picker-emoji-size;
|
width: var(--emoji-size);
|
||||||
height: $emoji-picker-emoji-size;
|
height: var(--emoji-size);
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
display: flex;
|
display: flex;
|
||||||
line-height: $emoji-picker-emoji-size;
|
line-height: var(--emoji-size);
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
margin: 4px;
|
margin: 0.2em;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
.emoji-picker-emoji.-custom {
|
.emoji-picker-emoji.-custom {
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
max-width: 100%;
|
width: var(--emoji-size);
|
||||||
max-height: 100%;
|
max-width: var(--emoji-size);
|
||||||
|
height: var(--emoji-size);
|
||||||
|
max-height: var(--emoji-size);
|
||||||
|
|
||||||
|
--_still_image-label-scale: 0.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
.emoji-picker-emoji.-unicode {
|
.emoji-picker-emoji.-unicode {
|
||||||
font-size: 24px;
|
font-size: 1.6em;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -79,7 +79,7 @@
|
||||||
margin-top: 0.25em;
|
margin-top: 0.25em;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
|
||||||
--emoji-size: calc(1.25em * var(--emojiReactionsScale, 1));
|
--emoji-size: calc(var(--emojiSize, 1.25em) * var(--emojiReactionsScale, 1));
|
||||||
|
|
||||||
.emoji-reaction-container {
|
.emoji-reaction-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
|
@ -1,63 +1,59 @@
|
||||||
import { set } from 'lodash'
|
|
||||||
import Select from '../select/select.vue'
|
import Select from '../select/select.vue'
|
||||||
|
import Checkbox from 'src/components/checkbox/checkbox.vue'
|
||||||
|
import Popover from 'src/components/popover/popover.vue'
|
||||||
|
|
||||||
|
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||||
|
import {
|
||||||
|
faExclamationTriangle,
|
||||||
|
faKeyboard,
|
||||||
|
faFont
|
||||||
|
} from '@fortawesome/free-solid-svg-icons'
|
||||||
|
|
||||||
|
library.add(
|
||||||
|
faExclamationTriangle,
|
||||||
|
faKeyboard,
|
||||||
|
faFont
|
||||||
|
)
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
Select
|
Select,
|
||||||
|
Checkbox,
|
||||||
|
Popover
|
||||||
},
|
},
|
||||||
props: [
|
props: [
|
||||||
'name', 'label', 'modelValue', 'fallback', 'options', 'no-inherit'
|
'name', 'label', 'modelValue', 'fallback', 'options', 'no-inherit'
|
||||||
],
|
],
|
||||||
|
mounted () {
|
||||||
|
this.$store.dispatch('queryLocalFonts')
|
||||||
|
},
|
||||||
emits: ['update:modelValue'],
|
emits: ['update:modelValue'],
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
lValue: this.modelValue,
|
manualEntry: false,
|
||||||
availableOptions: [
|
availableOptions: [
|
||||||
this.noInherit ? '' : 'inherit',
|
this.noInherit ? '' : 'inherit',
|
||||||
'custom',
|
|
||||||
...(this.options || []),
|
|
||||||
'serif',
|
'serif',
|
||||||
|
'sans-serif',
|
||||||
'monospace',
|
'monospace',
|
||||||
'sans-serif'
|
...(this.options || [])
|
||||||
].filter(_ => _)
|
].filter(_ => _)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
beforeUpdate () {
|
methods: {
|
||||||
this.lValue = this.modelValue
|
toggleManualEntry () {
|
||||||
|
this.manualEntry = !this.manualEntry
|
||||||
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
present () {
|
present () {
|
||||||
return typeof this.lValue !== 'undefined'
|
return typeof this.modelValue !== 'undefined'
|
||||||
},
|
},
|
||||||
dValue () {
|
localFontsList () {
|
||||||
return this.lValue || this.fallback || {}
|
return this.$store.state.interface.localFonts
|
||||||
},
|
},
|
||||||
family: {
|
localFontsSize () {
|
||||||
get () {
|
return this.$store.state.interface.localFonts?.length
|
||||||
return this.dValue.family
|
|
||||||
},
|
|
||||||
set (v) {
|
|
||||||
set(this.lValue, 'family', v)
|
|
||||||
this.$emit('update:modelValue', this.lValue)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
isCustom () {
|
|
||||||
return this.preset === 'custom'
|
|
||||||
},
|
|
||||||
preset: {
|
|
||||||
get () {
|
|
||||||
if (this.family === 'serif' ||
|
|
||||||
this.family === 'sans-serif' ||
|
|
||||||
this.family === 'monospace' ||
|
|
||||||
this.family === 'inherit') {
|
|
||||||
return this.family
|
|
||||||
} else {
|
|
||||||
return 'custom'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
set (v) {
|
|
||||||
this.family = v === 'custom' ? '' : v
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="font-control style-control"
|
class="font-control"
|
||||||
:class="{ custom: isCustom }"
|
:class="{ custom: isCustom }"
|
||||||
>
|
>
|
||||||
<label
|
<label
|
||||||
|
@ -10,43 +10,121 @@
|
||||||
>
|
>
|
||||||
{{ label }}
|
{{ label }}
|
||||||
</label>
|
</label>
|
||||||
<input
|
{{ ' ' }}
|
||||||
|
<Checkbox
|
||||||
v-if="typeof fallback !== 'undefined'"
|
v-if="typeof fallback !== 'undefined'"
|
||||||
:id="name + '-o'"
|
:id="name + '-o'"
|
||||||
:aria-labelledby="name + '-label'"
|
:modelValue="present"
|
||||||
class="input -checkbox opt exlcude-disabled visible-for-screenreader-only"
|
|
||||||
type="checkbox"
|
|
||||||
:checked="present"
|
|
||||||
@change="$emit('update:modelValue', typeof modelValue === 'undefined' ? fallback : undefined)"
|
@change="$emit('update:modelValue', typeof modelValue === 'undefined' ? fallback : undefined)"
|
||||||
>
|
>
|
||||||
<label
|
{{ $t('settings.style.themes3.define') }}
|
||||||
v-if="typeof fallback !== 'undefined'"
|
</Checkbox>
|
||||||
class="opt-l"
|
<p v-if="modelValue?.family">
|
||||||
:for="name + '-o'"
|
<label
|
||||||
:aria-hidden="true"
|
v-if="manualEntry"
|
||||||
/>
|
:id="name + '-label'"
|
||||||
{{ ' ' }}
|
:for="preset === 'custom' ? name : name + '-font-switcher'"
|
||||||
<Select
|
class="label"
|
||||||
:id="name + '-font-switcher'"
|
|
||||||
v-model="preset"
|
|
||||||
:disabled="!present"
|
|
||||||
class="font-switcher"
|
|
||||||
>
|
|
||||||
<option
|
|
||||||
v-for="option in availableOptions"
|
|
||||||
:key="option"
|
|
||||||
:value="option"
|
|
||||||
>
|
>
|
||||||
{{ option === 'custom' ? $t('settings.style.fonts.custom') : option }}
|
<i18n-t
|
||||||
</option>
|
keypath="settings.style.themes3.font.entry"
|
||||||
</Select>
|
tag="span"
|
||||||
<input
|
>
|
||||||
v-if="isCustom"
|
<template #fontFamily>
|
||||||
:id="name"
|
<code>font-family</code>
|
||||||
v-model="family"
|
</template>
|
||||||
class="input custom-font"
|
</i18n-t>
|
||||||
type="text"
|
</label>
|
||||||
>
|
<label
|
||||||
|
v-else
|
||||||
|
:id="name + '-label'"
|
||||||
|
:for="preset === 'custom' ? name : name + '-font-switcher'"
|
||||||
|
class="label"
|
||||||
|
>
|
||||||
|
{{ $t('settings.style.themes3.font.select') }}
|
||||||
|
</label>
|
||||||
|
{{ ' ' }}
|
||||||
|
<span
|
||||||
|
v-if="manualEntry"
|
||||||
|
class="btn-group"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="btn button-default"
|
||||||
|
@click="toggleManualEntry"
|
||||||
|
:title="$t('settings.style.themes3.font.lookup_local_fonts')"
|
||||||
|
>
|
||||||
|
<FAIcon
|
||||||
|
fixed-width
|
||||||
|
icon="font"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
<input
|
||||||
|
:id="name"
|
||||||
|
:model-value="modelValue.family"
|
||||||
|
class="input custom-font"
|
||||||
|
type="text"
|
||||||
|
@update:modelValue="$emit('update:modelValue', { ...(modelValue || {}), family: $event.target.value })"
|
||||||
|
>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
v-else
|
||||||
|
class="btn-group"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="btn button-default"
|
||||||
|
@click="toggleManualEntry"
|
||||||
|
:title="$t('settings.style.themes3.font.enter_manually')"
|
||||||
|
>
|
||||||
|
<FAIcon
|
||||||
|
fixed-width
|
||||||
|
icon="keyboard"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
<Select
|
||||||
|
:id="name + '-local-font-switcher'"
|
||||||
|
:model-value="modelValue?.family"
|
||||||
|
class="custom-font"
|
||||||
|
@update:modelValue="v => $emit('update:modelValue', { ...(modelValue || {}), family: v })"
|
||||||
|
>
|
||||||
|
<optgroup
|
||||||
|
:label="$t('settings.style.themes3.font.group-builtin')"
|
||||||
|
>
|
||||||
|
<option
|
||||||
|
v-for="option in availableOptions"
|
||||||
|
:key="option"
|
||||||
|
:value="option"
|
||||||
|
:style="{ fontFamily: option === 'inherit' ? null : option }"
|
||||||
|
>
|
||||||
|
{{ $t('settings.style.themes3.font.builtin.' + option) }}
|
||||||
|
</option>
|
||||||
|
</optgroup>
|
||||||
|
<optgroup
|
||||||
|
v-if="localFontsSize > 0"
|
||||||
|
:label="$t('settings.style.themes3.font.group-local')"
|
||||||
|
>
|
||||||
|
<option
|
||||||
|
v-for="option in localFontsList"
|
||||||
|
:key="option"
|
||||||
|
:value="option"
|
||||||
|
:style="{ fontFamily: option }"
|
||||||
|
>
|
||||||
|
{{ option }}
|
||||||
|
</option>
|
||||||
|
</optgroup>
|
||||||
|
<optgroup
|
||||||
|
v-else
|
||||||
|
:label="$t('settings.style.themes3.font.group-local')"
|
||||||
|
>
|
||||||
|
<option disabled>
|
||||||
|
{{ $t('settings.style.themes3.font.local-unavailable1') }}
|
||||||
|
</option>
|
||||||
|
<option disabled>
|
||||||
|
{{ $t('settings.style.themes3.font.local-unavailable2') }}
|
||||||
|
</option>
|
||||||
|
</optgroup>
|
||||||
|
</Select>
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -54,21 +132,15 @@
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.font-control {
|
.font-control {
|
||||||
input.custom-font {
|
.custom-font {
|
||||||
min-width: 10em;
|
min-width: 20em;
|
||||||
}
|
max-width: 20em;
|
||||||
|
|
||||||
&.custom {
|
|
||||||
/* TODO Should make proper joiners... */
|
|
||||||
.font-switcher {
|
|
||||||
border-top-right-radius: 0;
|
|
||||||
border-bottom-right-radius: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.custom-font {
|
|
||||||
border-top-left-radius: 0;
|
|
||||||
border-bottom-left-radius: 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.invalid-tooltip {
|
||||||
|
margin: 0.5em 1em;
|
||||||
|
min-width: 10em;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -129,7 +129,7 @@
|
||||||
.mobile-nav {
|
.mobile-nav {
|
||||||
display: grid;
|
display: grid;
|
||||||
line-height: var(--navbar-height);
|
line-height: var(--navbar-height);
|
||||||
grid-template-rows: 50px;
|
grid-template-rows: var(--navbar-height);
|
||||||
grid-template-columns: 2fr auto;
|
grid-template-columns: 2fr auto;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
@ -190,8 +190,8 @@
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
z-index: calc(var(--ZI_navbar) + 100);
|
z-index: calc(var(--ZI_navbar) + 100);
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 50px;
|
height: 3.5em;
|
||||||
line-height: 50px;
|
line-height: 3.5em;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
box-shadow: var(--shadow);
|
box-shadow: var(--shadow);
|
||||||
|
|
||||||
|
@ -214,7 +214,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.mobile-notifications {
|
.mobile-notifications {
|
||||||
margin-top: 50px;
|
margin-top: 3.5em;
|
||||||
width: 100vw;
|
width: 100vw;
|
||||||
height: calc(100vh - var(--navbar-height));
|
height: calc(100vh - var(--navbar-height));
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
|
|
|
@ -49,6 +49,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
&.toggled {
|
&.toggled {
|
||||||
|
margin-bottom: -4px;
|
||||||
border-bottom: 4px solid;
|
border-bottom: 4px solid;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,8 @@ export default {
|
||||||
'RichContent',
|
'RichContent',
|
||||||
'Input',
|
'Input',
|
||||||
'Avatar',
|
'Avatar',
|
||||||
'Attachment'
|
'Attachment',
|
||||||
|
'PollGraph'
|
||||||
],
|
],
|
||||||
defaultRules: []
|
defaultRules: []
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,16 @@ export default {
|
||||||
'Tab',
|
'Tab',
|
||||||
'ListItem'
|
'ListItem'
|
||||||
],
|
],
|
||||||
|
validInnerComponentsLite: [
|
||||||
|
'Text',
|
||||||
|
'Link',
|
||||||
|
'Icon',
|
||||||
|
'Border',
|
||||||
|
'Button',
|
||||||
|
'Input',
|
||||||
|
'PanelHeader',
|
||||||
|
'Alert'
|
||||||
|
],
|
||||||
defaultRules: [
|
defaultRules: [
|
||||||
{
|
{
|
||||||
directives: {
|
directives: {
|
||||||
|
|
|
@ -12,6 +12,11 @@ export default {
|
||||||
'Alert',
|
'Alert',
|
||||||
'Button' // mobile post button
|
'Button' // mobile post button
|
||||||
],
|
],
|
||||||
|
validInnerComponentsLite: [
|
||||||
|
'Underlay',
|
||||||
|
'Scrollbar',
|
||||||
|
'ScrollbarElement'
|
||||||
|
],
|
||||||
defaultRules: [
|
defaultRules: [
|
||||||
{
|
{
|
||||||
directives: {
|
directives: {
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
</template>
|
</template>
|
||||||
<slot v-else />
|
<slot v-else />
|
||||||
</label>
|
</label>
|
||||||
|
{{ ' ' }}
|
||||||
<input
|
<input
|
||||||
:id="path"
|
:id="path"
|
||||||
class="input number-input"
|
class="input number-input"
|
||||||
|
|
|
@ -48,6 +48,10 @@ export default {
|
||||||
draftMode: {
|
draftMode: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: undefined
|
default: undefined
|
||||||
|
},
|
||||||
|
timedApplyMode: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
inject: {
|
inject: {
|
||||||
|
@ -161,7 +165,11 @@ export default {
|
||||||
case 'admin':
|
case 'admin':
|
||||||
return (k, v) => this.$store.dispatch('pushAdminSetting', { path: k, value: v })
|
return (k, v) => this.$store.dispatch('pushAdminSetting', { path: k, value: v })
|
||||||
default:
|
default:
|
||||||
return (k, v) => this.$store.dispatch('setOption', { name: k, value: v })
|
if (this.timedApplyMode) {
|
||||||
|
return (k, v) => this.$store.dispatch('setOptionTemporarily', { name: k, value: v })
|
||||||
|
} else {
|
||||||
|
return (k, v) => this.$store.dispatch('setOption', { name: k, value: v })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
defaultState () {
|
defaultState () {
|
||||||
|
|
|
@ -21,15 +21,23 @@ export default {
|
||||||
unitSet: {
|
unitSet: {
|
||||||
type: String,
|
type: String,
|
||||||
default: 'none'
|
default: 'none'
|
||||||
|
},
|
||||||
|
step: {
|
||||||
|
type: Number,
|
||||||
|
default: 1
|
||||||
|
},
|
||||||
|
resetDefault: {
|
||||||
|
type: Object,
|
||||||
|
default: null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...Setting.computed,
|
...Setting.computed,
|
||||||
stateUnit () {
|
stateUnit () {
|
||||||
return this.state.replace(/\d+/, '')
|
return typeof this.state === 'string' ? this.state.replace(/[0-9,.]+/, '') : ''
|
||||||
},
|
},
|
||||||
stateValue () {
|
stateValue () {
|
||||||
return this.state.replace(/\D+/, '')
|
return typeof this.state === 'string' ? this.state.replace(/[^0-9,.]+/, '') : ''
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
@ -39,10 +47,18 @@ export default {
|
||||||
return this.$t(['settings', 'units', this.unitSet, value].join('.'))
|
return this.$t(['settings', 'units', this.unitSet, value].join('.'))
|
||||||
},
|
},
|
||||||
updateValue (e) {
|
updateValue (e) {
|
||||||
this.configSink(this.path, parseInt(e.target.value) + this.stateUnit)
|
this.configSink(this.path, parseFloat(e.target.value) + this.stateUnit)
|
||||||
},
|
},
|
||||||
updateUnit (e) {
|
updateUnit (e) {
|
||||||
this.configSink(this.path, this.stateValue + e.target.value)
|
let value = this.stateValue
|
||||||
|
const newUnit = e.target.value
|
||||||
|
if (this.resetDefault) {
|
||||||
|
const replaceValue = this.resetDefault[newUnit]
|
||||||
|
if (replaceValue != null) {
|
||||||
|
value = replaceValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.configSink(this.path, value + newUnit)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,11 +9,12 @@
|
||||||
>
|
>
|
||||||
<slot />
|
<slot />
|
||||||
</label>
|
</label>
|
||||||
|
{{ ' ' }}
|
||||||
<input
|
<input
|
||||||
:id="path"
|
:id="path"
|
||||||
class="input number-input"
|
class="input number-input"
|
||||||
type="number"
|
type="number"
|
||||||
step="1"
|
:step="step"
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
:min="min || 0"
|
:min="min || 0"
|
||||||
:value="stateValue"
|
:value="stateValue"
|
||||||
|
|
|
@ -4,6 +4,7 @@ import AsyncComponentError from 'src/components/async_component_error/async_comp
|
||||||
import getResettableAsyncComponent from 'src/services/resettable_async_component.js'
|
import getResettableAsyncComponent from 'src/services/resettable_async_component.js'
|
||||||
import Popover from '../popover/popover.vue'
|
import Popover from '../popover/popover.vue'
|
||||||
import Checkbox from 'src/components/checkbox/checkbox.vue'
|
import Checkbox from 'src/components/checkbox/checkbox.vue'
|
||||||
|
import ConfirmModal from 'src/components/confirm_modal/confirm_modal.vue'
|
||||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||||
import { cloneDeep, isEqual } from 'lodash'
|
import { cloneDeep, isEqual } from 'lodash'
|
||||||
import {
|
import {
|
||||||
|
@ -53,6 +54,7 @@ const SettingsModal = {
|
||||||
Modal,
|
Modal,
|
||||||
Popover,
|
Popover,
|
||||||
Checkbox,
|
Checkbox,
|
||||||
|
ConfirmModal,
|
||||||
SettingsModalUserContent: getResettableAsyncComponent(
|
SettingsModalUserContent: getResettableAsyncComponent(
|
||||||
() => import('./settings_modal_user_content.vue'),
|
() => import('./settings_modal_user_content.vue'),
|
||||||
{
|
{
|
||||||
|
@ -165,6 +167,7 @@ const SettingsModal = {
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
currentSaveStateNotice () {
|
currentSaveStateNotice () {
|
||||||
|
console.log(this.$store.state.interface.settings.currentSaveStateNotice)
|
||||||
return this.$store.state.interface.settings.currentSaveStateNotice
|
return this.$store.state.interface.settings.currentSaveStateNotice
|
||||||
},
|
},
|
||||||
modalActivated () {
|
modalActivated () {
|
||||||
|
|
|
@ -147,6 +147,18 @@
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<teleport to="#modal">
|
||||||
|
<ConfirmModal
|
||||||
|
v-if="$store.state.interface.temporaryChangesTimeoutId"
|
||||||
|
:title="$t('settings.confirm_new_setting')"
|
||||||
|
:cancel-text="$t('settings.revert')"
|
||||||
|
:confirm-text="$t('settings.confirm')"
|
||||||
|
@cancelled="$store.state.interface.temporaryChangesRevert"
|
||||||
|
@accepted="$store.state.interface.temporaryChangesConfirm"
|
||||||
|
>
|
||||||
|
{{ $t('settings.confirm_new_question') }}
|
||||||
|
</ConfirmModal>
|
||||||
|
</teleport>
|
||||||
</Modal>
|
</Modal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@ import FilteringTab from './tabs/filtering_tab.vue'
|
||||||
import SecurityTab from './tabs/security_tab/security_tab.vue'
|
import SecurityTab from './tabs/security_tab/security_tab.vue'
|
||||||
import ProfileTab from './tabs/profile_tab.vue'
|
import ProfileTab from './tabs/profile_tab.vue'
|
||||||
import GeneralTab from './tabs/general_tab.vue'
|
import GeneralTab from './tabs/general_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'
|
||||||
|
|
||||||
|
@ -19,7 +20,8 @@ import {
|
||||||
faBell,
|
faBell,
|
||||||
faDownload,
|
faDownload,
|
||||||
faEyeSlash,
|
faEyeSlash,
|
||||||
faInfo
|
faInfo,
|
||||||
|
faWindowRestore
|
||||||
} from '@fortawesome/free-solid-svg-icons'
|
} from '@fortawesome/free-solid-svg-icons'
|
||||||
|
|
||||||
library.add(
|
library.add(
|
||||||
|
@ -30,7 +32,8 @@ library.add(
|
||||||
faBell,
|
faBell,
|
||||||
faDownload,
|
faDownload,
|
||||||
faEyeSlash,
|
faEyeSlash,
|
||||||
faInfo
|
faInfo,
|
||||||
|
faWindowRestore
|
||||||
)
|
)
|
||||||
|
|
||||||
const SettingsModalContent = {
|
const SettingsModalContent = {
|
||||||
|
@ -44,6 +47,7 @@ const SettingsModalContent = {
|
||||||
SecurityTab,
|
SecurityTab,
|
||||||
ProfileTab,
|
ProfileTab,
|
||||||
GeneralTab,
|
GeneralTab,
|
||||||
|
AppearanceTab,
|
||||||
VersionTab,
|
VersionTab,
|
||||||
ThemeTab
|
ThemeTab
|
||||||
},
|
},
|
||||||
|
|
|
@ -13,6 +13,20 @@
|
||||||
>
|
>
|
||||||
<GeneralTab />
|
<GeneralTab />
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
:label="$t('settings.appearance')"
|
||||||
|
icon="window-restore"
|
||||||
|
data-tab-name="appearance"
|
||||||
|
>
|
||||||
|
<AppearanceTab />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
:label="$t('settings.theme')"
|
||||||
|
icon="paint-brush"
|
||||||
|
data-tab-name="theme"
|
||||||
|
>
|
||||||
|
<ThemeTab />
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="isLoggedIn"
|
v-if="isLoggedIn"
|
||||||
:label="$t('settings.profile_tab')"
|
:label="$t('settings.profile_tab')"
|
||||||
|
@ -21,6 +35,14 @@
|
||||||
>
|
>
|
||||||
<ProfileTab />
|
<ProfileTab />
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="isLoggedIn"
|
||||||
|
:label="$t('settings.notifications')"
|
||||||
|
icon="bell"
|
||||||
|
data-tab-name="notifications"
|
||||||
|
>
|
||||||
|
<NotificationsTab />
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="isLoggedIn"
|
v-if="isLoggedIn"
|
||||||
:label="$t('settings.security_tab')"
|
:label="$t('settings.security_tab')"
|
||||||
|
@ -36,20 +58,14 @@
|
||||||
>
|
>
|
||||||
<FilteringTab />
|
<FilteringTab />
|
||||||
</div>
|
</div>
|
||||||
<div
|
|
||||||
:label="$t('settings.theme')"
|
|
||||||
icon="paint-brush"
|
|
||||||
data-tab-name="theme"
|
|
||||||
>
|
|
||||||
<ThemeTab />
|
|
||||||
</div>
|
|
||||||
<div
|
<div
|
||||||
v-if="isLoggedIn"
|
v-if="isLoggedIn"
|
||||||
:label="$t('settings.notifications')"
|
:label="$t('settings.mutes_and_blocks')"
|
||||||
icon="bell"
|
:fullHeight="true"
|
||||||
data-tab-name="notifications"
|
icon="eye-slash"
|
||||||
|
data-tab-name="mutesAndBlocks"
|
||||||
>
|
>
|
||||||
<NotificationsTab />
|
<MutesAndBlocksTab />
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="isLoggedIn"
|
v-if="isLoggedIn"
|
||||||
|
@ -59,15 +75,6 @@
|
||||||
>
|
>
|
||||||
<DataImportExportTab />
|
<DataImportExportTab />
|
||||||
</div>
|
</div>
|
||||||
<div
|
|
||||||
v-if="isLoggedIn"
|
|
||||||
:label="$t('settings.mutes_and_blocks')"
|
|
||||||
:fullHeight="true"
|
|
||||||
icon="eye-slash"
|
|
||||||
data-tab-name="mutesAndBlocks"
|
|
||||||
>
|
|
||||||
<MutesAndBlocksTab />
|
|
||||||
</div>
|
|
||||||
<div
|
<div
|
||||||
:label="$t('settings.version.title')"
|
:label="$t('settings.version.title')"
|
||||||
icon="info"
|
icon="info"
|
||||||
|
|
195
src/components/settings_modal/tabs/appearance_tab.js
Normal file
195
src/components/settings_modal/tabs/appearance_tab.js
Normal file
|
@ -0,0 +1,195 @@
|
||||||
|
import BooleanSetting from '../helpers/boolean_setting.vue'
|
||||||
|
import ChoiceSetting from '../helpers/choice_setting.vue'
|
||||||
|
import IntegerSetting from '../helpers/integer_setting.vue'
|
||||||
|
import FloatSetting from '../helpers/float_setting.vue'
|
||||||
|
import UnitSetting, { defaultHorizontalUnits } from '../helpers/unit_setting.vue'
|
||||||
|
|
||||||
|
import FontControl from 'src/components/font_control/font_control.vue'
|
||||||
|
|
||||||
|
import { normalizeThemeData } from 'src/modules/interface'
|
||||||
|
|
||||||
|
import {
|
||||||
|
getThemes
|
||||||
|
} from 'src/services/style_setter/style_setter.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 SharedComputedObject from '../helpers/shared_computed_object.js'
|
||||||
|
import ProfileSettingIndicator from '../helpers/profile_setting_indicator.vue'
|
||||||
|
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||||
|
import {
|
||||||
|
faGlobe
|
||||||
|
} from '@fortawesome/free-solid-svg-icons'
|
||||||
|
|
||||||
|
import Preview from './theme_tab/preview.vue'
|
||||||
|
|
||||||
|
library.add(
|
||||||
|
faGlobe
|
||||||
|
)
|
||||||
|
|
||||||
|
const AppearanceTab = {
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
availableStyles: [],
|
||||||
|
intersectionObserver: null,
|
||||||
|
thirdColumnModeOptions: ['none', 'notifications', 'postform'].map(mode => ({
|
||||||
|
key: mode,
|
||||||
|
value: mode,
|
||||||
|
label: this.$t(`settings.third_column_mode_${mode}`)
|
||||||
|
})),
|
||||||
|
forcedRoundnessOptions: ['disabled', 'sharp', 'nonsharp', 'round'].map((mode, i) => ({
|
||||||
|
key: mode,
|
||||||
|
value: i - 1,
|
||||||
|
label: this.$t(`settings.style.themes3.hacks.forced_roundness_mode_${mode}`)
|
||||||
|
})),
|
||||||
|
underlayOverrideModes: ['none', 'opaque', 'transparent'].map((mode, i) => ({
|
||||||
|
key: mode,
|
||||||
|
value: mode,
|
||||||
|
label: this.$t(`settings.style.themes3.hacks.underlay_override_mode_${mode}`)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
BooleanSetting,
|
||||||
|
ChoiceSetting,
|
||||||
|
IntegerSetting,
|
||||||
|
FloatSetting,
|
||||||
|
UnitSetting,
|
||||||
|
ProfileSettingIndicator,
|
||||||
|
FontControl,
|
||||||
|
Preview
|
||||||
|
},
|
||||||
|
mounted () {
|
||||||
|
getThemes()
|
||||||
|
.then((promises) => {
|
||||||
|
return Promise.all(
|
||||||
|
Object.entries(promises)
|
||||||
|
.map(([k, v]) => v.then(res => [k, res]))
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.then(themes => themes.reduce((acc, [k, v]) => {
|
||||||
|
if (v) {
|
||||||
|
return [
|
||||||
|
...acc,
|
||||||
|
{
|
||||||
|
name: v.name || v[0],
|
||||||
|
key: k,
|
||||||
|
data: v
|
||||||
|
}
|
||||||
|
]
|
||||||
|
} else {
|
||||||
|
return acc
|
||||||
|
}
|
||||||
|
}, []))
|
||||||
|
.then((themesComplete) => {
|
||||||
|
this.availableStyles = themesComplete
|
||||||
|
})
|
||||||
|
|
||||||
|
if (window.IntersectionObserver) {
|
||||||
|
this.intersectionObserver = new IntersectionObserver((entries, observer) => {
|
||||||
|
entries.forEach(({ target, isIntersecting }) => {
|
||||||
|
if (!isIntersecting) return
|
||||||
|
const theme = this.availableStyles.find(x => x.key === target.dataset.themeKey)
|
||||||
|
this.$nextTick(() => {
|
||||||
|
if (theme) theme.ready = true
|
||||||
|
})
|
||||||
|
observer.unobserve(target)
|
||||||
|
})
|
||||||
|
}, {
|
||||||
|
root: this.$refs.themeList
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
updated () {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.$refs.themeList.querySelectorAll('.theme-preview').forEach(node => {
|
||||||
|
this.intersectionObserver.observe(node)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
noIntersectionObserver () {
|
||||||
|
return !window.IntersectionObserver
|
||||||
|
},
|
||||||
|
horizontalUnits () {
|
||||||
|
return defaultHorizontalUnits
|
||||||
|
},
|
||||||
|
fontsOverride () {
|
||||||
|
return this.$store.getters.mergedConfig.fontsOverride
|
||||||
|
},
|
||||||
|
columns () {
|
||||||
|
const mode = this.$store.getters.mergedConfig.thirdColumnMode
|
||||||
|
|
||||||
|
const notif = mode === 'none' ? [] : ['notifs']
|
||||||
|
|
||||||
|
if (this.$store.getters.mergedConfig.sidebarRight || mode === 'postform') {
|
||||||
|
return [...notif, 'content', 'sidebar']
|
||||||
|
} else {
|
||||||
|
return ['sidebar', 'content', ...notif]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
instanceSpecificPanelPresent () { return this.$store.state.instance.showInstanceSpecificPanel },
|
||||||
|
instanceWallpaperUsed () {
|
||||||
|
return this.$store.state.instance.background &&
|
||||||
|
!this.$store.state.users.currentUser.background_image
|
||||||
|
},
|
||||||
|
instanceShoutboxPresent () { return this.$store.state.instance.shoutAvailable },
|
||||||
|
language: {
|
||||||
|
get: function () { return this.$store.getters.mergedConfig.interfaceLanguage },
|
||||||
|
set: function (val) {
|
||||||
|
this.$store.dispatch('setOption', { name: 'interfaceLanguage', value: val })
|
||||||
|
}
|
||||||
|
},
|
||||||
|
isCustomThemeUsed () {
|
||||||
|
const { theme } = this.mergedConfig
|
||||||
|
return theme === 'custom' || theme === null
|
||||||
|
},
|
||||||
|
...SharedComputedObject()
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
updateFont (key, value) {
|
||||||
|
console.log(key, value)
|
||||||
|
this.$store.dispatch('setOption', {
|
||||||
|
name: 'theme3hacks',
|
||||||
|
value: {
|
||||||
|
...this.mergedConfig.theme3hacks,
|
||||||
|
fonts: {
|
||||||
|
...this.mergedConfig.theme3hacks.fonts,
|
||||||
|
[key]: value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
isThemeActive (key) {
|
||||||
|
const { theme } = this.mergedConfig
|
||||||
|
return key === theme
|
||||||
|
},
|
||||||
|
setTheme (name) {
|
||||||
|
this.$store.dispatch('setTheme', { themeName: name, saveData: true, recompile: true })
|
||||||
|
},
|
||||||
|
previewTheme (key, input) {
|
||||||
|
const style = normalizeThemeData(input)
|
||||||
|
const x = 2
|
||||||
|
if (x === 1) return
|
||||||
|
const theme2 = convertTheme2To3(style)
|
||||||
|
const theme3 = init({
|
||||||
|
inputRuleset: theme2,
|
||||||
|
ultimateBackgroundColor: '#000000',
|
||||||
|
liteMode: true,
|
||||||
|
debug: true,
|
||||||
|
onlyNormalState: true
|
||||||
|
})
|
||||||
|
|
||||||
|
return getScopedVersion(
|
||||||
|
getCssRules(theme3.eager),
|
||||||
|
'#theme-preview-' + key
|
||||||
|
).join('\n')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AppearanceTab
|
313
src/components/settings_modal/tabs/appearance_tab.vue
Normal file
313
src/components/settings_modal/tabs/appearance_tab.vue
Normal file
|
@ -0,0 +1,313 @@
|
||||||
|
<template>
|
||||||
|
<div class="appearance-tab" :label="$t('settings.general')">
|
||||||
|
<div class="setting-item">
|
||||||
|
<h2>{{ $t('settings.theme') }}</h2>
|
||||||
|
<ul
|
||||||
|
class="theme-list"
|
||||||
|
ref="themeList"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
v-if="isCustomThemeUsed"
|
||||||
|
disabled
|
||||||
|
class="button-default theme-preview"
|
||||||
|
>
|
||||||
|
<preview />
|
||||||
|
<h4 class="theme-name">{{ $t('settings.style.custom_theme_used') }}</h4>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
v-for="style in availableStyles"
|
||||||
|
:data-theme-key="style.key"
|
||||||
|
:key="style.key"
|
||||||
|
class="button-default theme-preview"
|
||||||
|
:class="{ toggled: isThemeActive(style.key) }"
|
||||||
|
@click="setTheme(style.key)"
|
||||||
|
>
|
||||||
|
<!-- eslint-disable vue/no-v-text-v-html-on-component -->
|
||||||
|
<component
|
||||||
|
:is="'style'"
|
||||||
|
v-if="style.ready || noIntersectionObserver"
|
||||||
|
v-html="previewTheme(style.key, style.data)"
|
||||||
|
/>
|
||||||
|
<!-- eslint-enable vue/no-v-text-v-html-on-component -->
|
||||||
|
<preview :class="{ placeholder: ready }" :id="'theme-preview-' + style.key"/>
|
||||||
|
<h4 class="theme-name">{{ style.name }}</h4>
|
||||||
|
</button>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="alert neutral theme-notice">
|
||||||
|
{{ $t("settings.style.appearance_tab_note") }}
|
||||||
|
</div>
|
||||||
|
<div class="setting-item">
|
||||||
|
<h2>{{ $t('settings.scale_and_layout') }}</h2>
|
||||||
|
<ul class="setting-list">
|
||||||
|
<li>
|
||||||
|
<UnitSetting
|
||||||
|
path="textSize"
|
||||||
|
step="0.1"
|
||||||
|
:units="['px', 'rem']"
|
||||||
|
:reset-default="{ 'px': 14, 'rem': 1 }"
|
||||||
|
timed-apply-mode
|
||||||
|
>
|
||||||
|
{{ $t('settings.text_size') }}
|
||||||
|
</UnitSetting>
|
||||||
|
<div>
|
||||||
|
<small>
|
||||||
|
<i18n-t
|
||||||
|
scope="global"
|
||||||
|
keypath="settings.text_size_tip"
|
||||||
|
tag="span"
|
||||||
|
>
|
||||||
|
<code>px</code>
|
||||||
|
<code>rem</code>
|
||||||
|
</i18n-t>
|
||||||
|
<br/>
|
||||||
|
<i18n-t
|
||||||
|
scope="global"
|
||||||
|
keypath="settings.text_size_tip2"
|
||||||
|
tag="span"
|
||||||
|
>
|
||||||
|
<code>14px</code>
|
||||||
|
</i18n-t>
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<h3>{{ $t('settings.style.interface_font_user_override') }}</h3>
|
||||||
|
<ul class="setting-list">
|
||||||
|
<li>
|
||||||
|
<FontControl
|
||||||
|
:model-value="mergedConfig.theme3hacks.fonts.interface"
|
||||||
|
name="ui"
|
||||||
|
:label="$t('settings.style.fonts.components.interface')"
|
||||||
|
:fallback="{ family: 'sans-serif' }"
|
||||||
|
no-inherit="1"
|
||||||
|
@update:modelValue="v => updateFont('interface', v)"
|
||||||
|
/>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<FontControl
|
||||||
|
v-if="expertLevel > 0"
|
||||||
|
:model-value="mergedConfig.theme3hacks.fonts.input"
|
||||||
|
name="input"
|
||||||
|
:fallback="{ family: 'inherit' }"
|
||||||
|
:label="$t('settings.style.fonts.components.input')"
|
||||||
|
@update:modelValue="v => updateFont('input', v)"
|
||||||
|
/>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<FontControl
|
||||||
|
v-if="expertLevel > 0"
|
||||||
|
:model-value="mergedConfig.theme3hacks.fonts.post"
|
||||||
|
name="post"
|
||||||
|
:fallback="{ family: 'inherit' }"
|
||||||
|
:label="$t('settings.style.fonts.components.post')"
|
||||||
|
@update:modelValue="v => updateFont('post', v)"
|
||||||
|
/>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<FontControl
|
||||||
|
v-if="expertLevel > 0"
|
||||||
|
:model-value="mergedConfig.theme3hacks.fonts.monospace"
|
||||||
|
name="postCode"
|
||||||
|
:fallback="{ family: 'monospace' }"
|
||||||
|
:label="$t('settings.style.fonts.components.monospace')"
|
||||||
|
@update:modelValue="v => updateFont('monospace', v)"
|
||||||
|
/>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<UnitSetting
|
||||||
|
path="emojiSize"
|
||||||
|
step="0.1"
|
||||||
|
:units="['px', 'rem']"
|
||||||
|
:reset-default="{ 'px': 32, 'rem': 2.2 }"
|
||||||
|
>
|
||||||
|
{{ $t('settings.emoji_size') }}
|
||||||
|
</UnitSetting>
|
||||||
|
<ul
|
||||||
|
class="setting-list suboptions"
|
||||||
|
>
|
||||||
|
<li>
|
||||||
|
<FloatSetting
|
||||||
|
v-if="user"
|
||||||
|
path="emojiReactionsScale"
|
||||||
|
expert="1"
|
||||||
|
>
|
||||||
|
{{ $t('settings.emoji_reactions_scale') }}
|
||||||
|
</FloatSetting>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<UnitSetting
|
||||||
|
path="navbarSize"
|
||||||
|
step="0.1"
|
||||||
|
:units="['px', 'rem']"
|
||||||
|
:reset-default="{ 'px': 55, 'rem': 3.5 }"
|
||||||
|
>
|
||||||
|
{{ $t('settings.navbar_size') }}
|
||||||
|
</UnitSetting>
|
||||||
|
</li>
|
||||||
|
<h3>{{ $t('settings.columns') }}</h3>
|
||||||
|
<li>
|
||||||
|
<UnitSetting
|
||||||
|
path="panelHeaderSize"
|
||||||
|
step="0.1"
|
||||||
|
:units="['px', 'rem']"
|
||||||
|
:reset-default="{ 'px': 52, 'rem': 3.2 }"
|
||||||
|
timed-apply-mode
|
||||||
|
>
|
||||||
|
{{ $t('settings.panel_header_size') }}
|
||||||
|
</UnitSetting>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<BooleanSetting path="sidebarRight">
|
||||||
|
{{ $t('settings.right_sidebar') }}
|
||||||
|
</BooleanSetting>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<BooleanSetting path="navbarColumnStretch">
|
||||||
|
{{ $t('settings.navbar_column_stretch') }}
|
||||||
|
</BooleanSetting>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<ChoiceSetting
|
||||||
|
v-if="user"
|
||||||
|
id="thirdColumnMode"
|
||||||
|
path="thirdColumnMode"
|
||||||
|
:options="thirdColumnModeOptions"
|
||||||
|
>
|
||||||
|
{{ $t('settings.third_column_mode') }}
|
||||||
|
</ChoiceSetting>
|
||||||
|
</li>
|
||||||
|
<li v-if="expertLevel > 0">
|
||||||
|
{{ $t('settings.column_sizes') }}
|
||||||
|
<div class="column-settings">
|
||||||
|
<UnitSetting
|
||||||
|
v-for="column in columns"
|
||||||
|
:key="column"
|
||||||
|
:path="column + 'ColumnWidth'"
|
||||||
|
:units="horizontalUnits"
|
||||||
|
expert="1"
|
||||||
|
>
|
||||||
|
{{ $t('settings.column_sizes_' + column) }}
|
||||||
|
</UnitSetting>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<BooleanSetting path="disableStickyHeaders">
|
||||||
|
{{ $t('settings.disable_sticky_headers') }}
|
||||||
|
</BooleanSetting>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<BooleanSetting path="showScrollbars">
|
||||||
|
{{ $t('settings.show_scrollbars') }}
|
||||||
|
</BooleanSetting>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="setting-item">
|
||||||
|
<h2>{{ $t('settings.visual_tweaks') }}</h2>
|
||||||
|
<ul class="setting-list">
|
||||||
|
<li>
|
||||||
|
<ChoiceSetting
|
||||||
|
id="forcedRoundness"
|
||||||
|
path="forcedRoundness"
|
||||||
|
:options="forcedRoundnessOptions"
|
||||||
|
>
|
||||||
|
{{ $t('settings.style.themes3.hacks.force_interface_roundness') }}
|
||||||
|
</ChoiceSetting>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<ChoiceSetting
|
||||||
|
id="underlayOverride"
|
||||||
|
path="theme3hacks.underlay"
|
||||||
|
:options="underlayOverrideModes"
|
||||||
|
>
|
||||||
|
{{ $t('settings.style.themes3.hacks.underlay_overrides') }}
|
||||||
|
</ChoiceSetting>
|
||||||
|
</li>
|
||||||
|
<li v-if="instanceWallpaperUsed">
|
||||||
|
<BooleanSetting path="hideInstanceWallpaper">
|
||||||
|
{{ $t('settings.hide_wallpaper') }}
|
||||||
|
</BooleanSetting>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<BooleanSetting
|
||||||
|
path="forceThemeRecompilation"
|
||||||
|
:expert="1"
|
||||||
|
>
|
||||||
|
{{ $t('settings.force_theme_recompilation_debug') }}
|
||||||
|
</BooleanSetting>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<BooleanSetting
|
||||||
|
path="themeDebug"
|
||||||
|
:expert="1"
|
||||||
|
>
|
||||||
|
{{ $t('settings.theme_debug') }}
|
||||||
|
</BooleanSetting>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script src="./appearance_tab.js"></script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.appearance-tab {
|
||||||
|
.theme-notice {
|
||||||
|
padding: 0.5em;
|
||||||
|
margin: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.column-settings {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-evenly;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.column-settings .size-label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 0.5em;
|
||||||
|
margin-top: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-list {
|
||||||
|
list-style: none;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
margin: -0.5em 0;
|
||||||
|
height: 25em;
|
||||||
|
overflow-x: hidden;
|
||||||
|
overflow-y: auto;
|
||||||
|
scrollbar-gutter: stable;
|
||||||
|
border-radius: var(--roundness);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
.theme-preview {
|
||||||
|
font-size: 1rem; // fix for firefox
|
||||||
|
width: 19rem;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
margin: 0.5em;
|
||||||
|
|
||||||
|
&.placeholder {
|
||||||
|
opacity: 0.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-container {
|
||||||
|
pointer-events: none;
|
||||||
|
zoom: 0.5;
|
||||||
|
border: none;
|
||||||
|
border-radius: var(--roundness);
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -49,24 +49,6 @@
|
||||||
{{ $t('settings.mute_sensitive_posts') }}
|
{{ $t('settings.mute_sensitive_posts') }}
|
||||||
</BooleanSetting>
|
</BooleanSetting>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
|
||||||
<BooleanSetting path="hideMutedFederationRestrictions">
|
|
||||||
{{ $t('settings.hide_muted_federation_restrictions') }}
|
|
||||||
</BooleanSetting>
|
|
||||||
<ul
|
|
||||||
class="setting-list suboptions"
|
|
||||||
:class="[{disabled: !streaming}]"
|
|
||||||
>
|
|
||||||
<li
|
|
||||||
v-for="item in muteFederationRestrictionsLevels"
|
|
||||||
:key="'mute_' + item + '_federation_restriction'"
|
|
||||||
>
|
|
||||||
<BooleanSetting :path="'muteFederationRestrictions.' + item">
|
|
||||||
{{ $t('settings.mute_' + item + '_federation_restriction') }}
|
|
||||||
</BooleanSetting>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
<li>
|
<li>
|
||||||
<BooleanSetting path="hidePostStats">
|
<BooleanSetting path="hidePostStats">
|
||||||
{{ $t('settings.hide_post_stats') }}
|
{{ $t('settings.hide_post_stats') }}
|
||||||
|
|
|
@ -3,7 +3,7 @@ import ChoiceSetting from '../helpers/choice_setting.vue'
|
||||||
import ScopeSelector from 'src/components/scope_selector/scope_selector.vue'
|
import ScopeSelector from 'src/components/scope_selector/scope_selector.vue'
|
||||||
import IntegerSetting from '../helpers/integer_setting.vue'
|
import IntegerSetting from '../helpers/integer_setting.vue'
|
||||||
import FloatSetting from '../helpers/float_setting.vue'
|
import FloatSetting from '../helpers/float_setting.vue'
|
||||||
import UnitSetting, { defaultHorizontalUnits } from '../helpers/unit_setting.vue'
|
import UnitSetting from '../helpers/unit_setting.vue'
|
||||||
import InterfaceLanguageSwitcher from 'src/components/interface_language_switcher/interface_language_switcher.vue'
|
import InterfaceLanguageSwitcher from 'src/components/interface_language_switcher/interface_language_switcher.vue'
|
||||||
|
|
||||||
import SharedComputedObject from '../helpers/shared_computed_object.js'
|
import SharedComputedObject from '../helpers/shared_computed_object.js'
|
||||||
|
@ -40,11 +40,6 @@ const GeneralTab = {
|
||||||
value: mode,
|
value: mode,
|
||||||
label: this.$t(`settings.mention_link_display_${mode}`)
|
label: this.$t(`settings.mention_link_display_${mode}`)
|
||||||
})),
|
})),
|
||||||
thirdColumnModeOptions: ['none', 'notifications', 'postform'].map(mode => ({
|
|
||||||
key: mode,
|
|
||||||
value: mode,
|
|
||||||
label: this.$t(`settings.third_column_mode_${mode}`)
|
|
||||||
})),
|
|
||||||
userPopoverAvatarActionOptions: ['close', 'zoom', 'open'].map(mode => ({
|
userPopoverAvatarActionOptions: ['close', 'zoom', 'open'].map(mode => ({
|
||||||
key: mode,
|
key: mode,
|
||||||
value: mode,
|
value: mode,
|
||||||
|
@ -70,9 +65,6 @@ const GeneralTab = {
|
||||||
ProfileSettingIndicator
|
ProfileSettingIndicator
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
horizontalUnits () {
|
|
||||||
return defaultHorizontalUnits
|
|
||||||
},
|
|
||||||
postFormats () {
|
postFormats () {
|
||||||
return this.$store.state.instance.postFormats || []
|
return this.$store.state.instance.postFormats || []
|
||||||
},
|
},
|
||||||
|
@ -83,29 +75,6 @@ const GeneralTab = {
|
||||||
label: this.$t(`post_status.content_type["${format}"]`)
|
label: this.$t(`post_status.content_type["${format}"]`)
|
||||||
}))
|
}))
|
||||||
},
|
},
|
||||||
columns () {
|
|
||||||
const mode = this.$store.getters.mergedConfig.thirdColumnMode
|
|
||||||
|
|
||||||
const notif = mode === 'none' ? [] : ['notifs']
|
|
||||||
|
|
||||||
if (this.$store.getters.mergedConfig.sidebarRight || mode === 'postform') {
|
|
||||||
return [...notif, 'content', 'sidebar']
|
|
||||||
} else {
|
|
||||||
return ['sidebar', 'content', ...notif]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
instanceSpecificPanelPresent () { return this.$store.state.instance.showInstanceSpecificPanel },
|
|
||||||
instanceWallpaperUsed () {
|
|
||||||
return this.$store.state.instance.background &&
|
|
||||||
!this.$store.state.users.currentUser.background_image
|
|
||||||
},
|
|
||||||
instanceShoutboxPresent () { return this.$store.state.instance.shoutAvailable },
|
|
||||||
language: {
|
|
||||||
get: function () { return this.$store.getters.mergedConfig.interfaceLanguage },
|
|
||||||
set: function (val) {
|
|
||||||
this.$store.dispatch('setOption', { name: 'interfaceLanguage', value: val })
|
|
||||||
}
|
|
||||||
},
|
|
||||||
...SharedComputedObject()
|
...SharedComputedObject()
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|
|
@ -15,11 +15,6 @@
|
||||||
{{ $t('settings.hide_isp') }}
|
{{ $t('settings.hide_isp') }}
|
||||||
</BooleanSetting>
|
</BooleanSetting>
|
||||||
</li>
|
</li>
|
||||||
<li v-if="instanceWallpaperUsed">
|
|
||||||
<BooleanSetting path="hideInstanceWallpaper">
|
|
||||||
{{ $t('settings.hide_wallpaper') }}
|
|
||||||
</BooleanSetting>
|
|
||||||
</li>
|
|
||||||
<li>
|
<li>
|
||||||
<BooleanSetting path="stopGifs">
|
<BooleanSetting path="stopGifs">
|
||||||
{{ $t('settings.stop_gifs') }}
|
{{ $t('settings.stop_gifs') }}
|
||||||
|
@ -98,53 +93,6 @@
|
||||||
{{ $t('settings.hide_shoutbox') }}
|
{{ $t('settings.hide_shoutbox') }}
|
||||||
</BooleanSetting>
|
</BooleanSetting>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
|
||||||
<h3>{{ $t('settings.columns') }}</h3>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<BooleanSetting path="disableStickyHeaders">
|
|
||||||
{{ $t('settings.disable_sticky_headers') }}
|
|
||||||
</BooleanSetting>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<BooleanSetting path="showScrollbars">
|
|
||||||
{{ $t('settings.show_scrollbars') }}
|
|
||||||
</BooleanSetting>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<BooleanSetting path="sidebarRight">
|
|
||||||
{{ $t('settings.right_sidebar') }}
|
|
||||||
</BooleanSetting>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<BooleanSetting path="navbarColumnStretch">
|
|
||||||
{{ $t('settings.navbar_column_stretch') }}
|
|
||||||
</BooleanSetting>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<ChoiceSetting
|
|
||||||
v-if="user"
|
|
||||||
id="thirdColumnMode"
|
|
||||||
path="thirdColumnMode"
|
|
||||||
:options="thirdColumnModeOptions"
|
|
||||||
>
|
|
||||||
{{ $t('settings.third_column_mode') }}
|
|
||||||
</ChoiceSetting>
|
|
||||||
</li>
|
|
||||||
<li v-if="expertLevel > 0">
|
|
||||||
{{ $t('settings.column_sizes') }}
|
|
||||||
<div class="column-settings">
|
|
||||||
<UnitSetting
|
|
||||||
v-for="column in columns"
|
|
||||||
:key="column"
|
|
||||||
:path="column + 'ColumnWidth'"
|
|
||||||
:units="horizontalUnits"
|
|
||||||
expert="1"
|
|
||||||
>
|
|
||||||
{{ $t('settings.column_sizes_' + column) }}
|
|
||||||
</UnitSetting>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
<li class="select-multiple">
|
<li class="select-multiple">
|
||||||
<span class="label">{{ $t('settings.confirm_dialogs') }}</span>
|
<span class="label">{{ $t('settings.confirm_dialogs') }}</span>
|
||||||
<ul class="option-list">
|
<ul class="option-list">
|
||||||
|
@ -200,14 +148,6 @@
|
||||||
<div class="setting-item">
|
<div class="setting-item">
|
||||||
<h2>{{ $t('settings.post_look_feel') }}</h2>
|
<h2>{{ $t('settings.post_look_feel') }}</h2>
|
||||||
<ul class="setting-list">
|
<ul class="setting-list">
|
||||||
<li>
|
|
||||||
<BooleanSetting
|
|
||||||
path="forceThemeRecompilation"
|
|
||||||
:expert="1"
|
|
||||||
>
|
|
||||||
{{ $t('settings.force_theme_recompilation_debug') }}
|
|
||||||
</BooleanSetting>
|
|
||||||
</li>
|
|
||||||
<li>
|
<li>
|
||||||
<ChoiceSetting
|
<ChoiceSetting
|
||||||
id="conversationDisplay"
|
id="conversationDisplay"
|
||||||
|
@ -277,15 +217,6 @@
|
||||||
{{ $t('settings.no_rich_text_description') }}
|
{{ $t('settings.no_rich_text_description') }}
|
||||||
</BooleanSetting>
|
</BooleanSetting>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
|
||||||
<FloatSetting
|
|
||||||
v-if="user"
|
|
||||||
path="emojiReactionsScale"
|
|
||||||
expert="1"
|
|
||||||
>
|
|
||||||
{{ $t('settings.emoji_reactions_scale') }}
|
|
||||||
</FloatSetting>
|
|
||||||
</li>
|
|
||||||
<h3>{{ $t('settings.attachments') }}</h3>
|
<h3>{{ $t('settings.attachments') }}</h3>
|
||||||
<li>
|
<li>
|
||||||
<BooleanSetting
|
<BooleanSetting
|
||||||
|
@ -528,17 +459,3 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script src="./general_tab.js"></script>
|
<script src="./general_tab.js"></script>
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
.column-settings {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-evenly;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.column-settings .size-label {
|
|
||||||
display: block;
|
|
||||||
margin-bottom: 0.5em;
|
|
||||||
margin-top: 0.5em;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
|
@ -99,15 +99,9 @@
|
||||||
>
|
>
|
||||||
|
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
<span class="checkbox">
|
<Checkbox>
|
||||||
<input
|
{{ $t('settings.style.preview.checkbox') }}
|
||||||
id="preview_checkbox"
|
</Checkbox>
|
||||||
checked="very yes"
|
|
||||||
type="checkbox"
|
|
||||||
class="input"
|
|
||||||
>
|
|
||||||
<label for="preview_checkbox">{{ $t('settings.style.preview.checkbox') }}</label>
|
|
||||||
</span>
|
|
||||||
<button class="btn button-default">
|
<button class="btn button-default">
|
||||||
{{ $t('settings.style.preview.button') }}
|
{{ $t('settings.style.preview.button') }}
|
||||||
</button>
|
</button>
|
||||||
|
@ -118,6 +112,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import Checkbox from 'src/components/checkbox/checkbox.vue'
|
||||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||||
import {
|
import {
|
||||||
faTimes,
|
faTimes,
|
||||||
|
@ -133,12 +128,116 @@ library.add(
|
||||||
faReply
|
faReply
|
||||||
)
|
)
|
||||||
|
|
||||||
export default {}
|
export default {
|
||||||
|
components: {
|
||||||
|
Checkbox
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.preview-container {
|
.preview-container {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
border-top: 1px dashed;
|
||||||
|
border-bottom: 1px dashed;
|
||||||
|
border-color: var(--border);
|
||||||
|
margin: 1em 0;
|
||||||
|
padding: 1em;
|
||||||
|
background-color: var(--wallpaper);
|
||||||
|
background-image: var(--body-background-image);
|
||||||
|
background-size: cover;
|
||||||
|
background-position: 50% 50%;
|
||||||
|
|
||||||
|
.theme-preview-content {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dummy {
|
||||||
|
.post {
|
||||||
|
font-family: var(--postFont);
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
.content {
|
||||||
|
flex: 1;
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
margin-bottom: 0.25em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icons {
|
||||||
|
margin-top: 0.5em;
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
i {
|
||||||
|
margin-right: 1em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.after-post {
|
||||||
|
margin-top: 1em;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar,
|
||||||
|
.avatar-alt {
|
||||||
|
background:
|
||||||
|
linear-gradient(
|
||||||
|
135deg,
|
||||||
|
#b8e1fc 0%,
|
||||||
|
#a9d2f3 10%,
|
||||||
|
#90bae4 25%,
|
||||||
|
#90bcea 37%,
|
||||||
|
#90bff0 50%,
|
||||||
|
#6ba8e5 51%,
|
||||||
|
#a2daf5 83%,
|
||||||
|
#bdf3fd 100%
|
||||||
|
);
|
||||||
|
color: black;
|
||||||
|
font-family: sans-serif;
|
||||||
|
text-align: center;
|
||||||
|
margin-right: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-alt {
|
||||||
|
flex: 0 auto;
|
||||||
|
margin-left: 28px;
|
||||||
|
font-size: 12px;
|
||||||
|
min-width: 20px;
|
||||||
|
min-height: 20px;
|
||||||
|
line-height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar {
|
||||||
|
flex: 0 auto;
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 48px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
|
||||||
|
.checkbox {
|
||||||
|
margin-right: 1em;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.separator {
|
||||||
|
margin: 1em;
|
||||||
|
border-bottom: 1px solid;
|
||||||
|
border-color: var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
min-width: 3em;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.underlay-preview {
|
.underlay-preview {
|
||||||
|
@ -148,4 +247,4 @@ export default {}
|
||||||
left: 10px;
|
left: 10px;
|
||||||
right: 10px;
|
right: 10px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
import {
|
import {
|
||||||
rgb2hex,
|
rgb2hex,
|
||||||
hex2rgb,
|
hex2rgb,
|
||||||
getContrastRatioLayers
|
getContrastRatioLayers,
|
||||||
|
relativeLuminance
|
||||||
} from 'src/services/color_convert/color_convert.js'
|
} from 'src/services/color_convert/color_convert.js'
|
||||||
import {
|
import {
|
||||||
getThemes
|
getThemes
|
||||||
|
@ -23,10 +24,17 @@ import {
|
||||||
generateShadows,
|
generateShadows,
|
||||||
generateRadii,
|
generateRadii,
|
||||||
generateFonts,
|
generateFonts,
|
||||||
composePreset,
|
|
||||||
shadows2to3,
|
shadows2to3,
|
||||||
colors2to3
|
colors2to3
|
||||||
} from 'src/services/theme_data/theme_data.service.js'
|
} from 'src/services/theme_data/theme_data.service.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 ColorInput from 'src/components/color_input/color_input.vue'
|
||||||
import RangeInput from 'src/components/range_input/range_input.vue'
|
import RangeInput from 'src/components/range_input/range_input.vue'
|
||||||
import OpacityInput from 'src/components/opacity_input/opacity_input.vue'
|
import OpacityInput from 'src/components/opacity_input/opacity_input.vue'
|
||||||
|
@ -62,6 +70,7 @@ const colorConvert = (color) => {
|
||||||
export default {
|
export default {
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
|
themeV3Preview: [],
|
||||||
themeImporter: newImporter({
|
themeImporter: newImporter({
|
||||||
validator: this.importValidator,
|
validator: this.importValidator,
|
||||||
onImport: this.onImport,
|
onImport: this.onImport,
|
||||||
|
@ -78,10 +87,7 @@ export default {
|
||||||
tempImportFile: undefined,
|
tempImportFile: undefined,
|
||||||
engineVersion: 0,
|
engineVersion: 0,
|
||||||
|
|
||||||
previewShadows: {},
|
previewTheme: {},
|
||||||
previewColors: {},
|
|
||||||
previewRadii: {},
|
|
||||||
previewFonts: {},
|
|
||||||
|
|
||||||
shadowsInvalid: true,
|
shadowsInvalid: true,
|
||||||
colorsInvalid: true,
|
colorsInvalid: true,
|
||||||
|
@ -232,13 +238,6 @@ export default {
|
||||||
chatMessage: this.chatMessageRadiusLocal
|
chatMessage: this.chatMessageRadiusLocal
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
preview () {
|
|
||||||
return composePreset(this.previewColors, this.previewRadii, this.previewShadows, this.previewFonts)
|
|
||||||
},
|
|
||||||
previewTheme () {
|
|
||||||
if (!this.preview.theme.colors) return { colors: {}, opacity: {}, radii: {}, shadows: {}, fonts: {} }
|
|
||||||
return this.preview.theme
|
|
||||||
},
|
|
||||||
// This needs optimization maybe
|
// This needs optimization maybe
|
||||||
previewContrast () {
|
previewContrast () {
|
||||||
try {
|
try {
|
||||||
|
@ -306,14 +305,6 @@ export default {
|
||||||
return {}
|
return {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
previewRules () {
|
|
||||||
if (!this.preview.rules) return ''
|
|
||||||
return [
|
|
||||||
...Object.values(this.preview.rules),
|
|
||||||
'color: var(--text)',
|
|
||||||
'font-family: var(--interfaceFont, sans-serif)'
|
|
||||||
].join(';')
|
|
||||||
},
|
|
||||||
shadowsAvailable () {
|
shadowsAvailable () {
|
||||||
return Object.keys(DEFAULT_SHADOWS).sort()
|
return Object.keys(DEFAULT_SHADOWS).sort()
|
||||||
},
|
},
|
||||||
|
@ -511,17 +502,14 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
setCustomTheme () {
|
setCustomTheme () {
|
||||||
this.$store.dispatch('setOption', {
|
this.$store.dispatch('setThemeV2', {
|
||||||
name: 'customTheme',
|
customTheme: {
|
||||||
value: {
|
ignore: true,
|
||||||
themeFileVersion: this.selectedVersion,
|
themeFileVersion: this.selectedVersion,
|
||||||
themeEngineVersion: CURRENT_VERSION,
|
themeEngineVersion: CURRENT_VERSION,
|
||||||
...this.previewTheme
|
...this.previewTheme
|
||||||
}
|
},
|
||||||
})
|
customThemeSource: {
|
||||||
this.$store.dispatch('setOption', {
|
|
||||||
name: 'customThemeSource',
|
|
||||||
value: {
|
|
||||||
themeFileVersion: this.selectedVersion,
|
themeFileVersion: this.selectedVersion,
|
||||||
themeEngineVersion: CURRENT_VERSION,
|
themeEngineVersion: CURRENT_VERSION,
|
||||||
shadows: this.shadowsLocal,
|
shadows: this.shadowsLocal,
|
||||||
|
@ -532,16 +520,24 @@ export default {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
updatePreviewColorsAndShadows () {
|
updatePreviewColors () {
|
||||||
this.previewColors = generateColors({
|
const result = generateColors({
|
||||||
opacity: this.currentOpacity,
|
opacity: this.currentOpacity,
|
||||||
colors: this.currentColors
|
colors: this.currentColors
|
||||||
})
|
})
|
||||||
this.previewShadows = generateShadows(
|
this.previewTheme.colors = result.theme.colors
|
||||||
{ shadows: this.shadowsLocal, opacity: this.previewTheme.opacity, themeEngineVersion: this.engineVersion },
|
this.previewTheme.opacity = result.theme.opacity
|
||||||
this.previewColors.theme.colors,
|
},
|
||||||
this.previewColors.mod
|
updatePreviewShadows () {
|
||||||
)
|
this.previewTheme.shadows = generateShadows(
|
||||||
|
{
|
||||||
|
shadows: this.shadowsLocal,
|
||||||
|
opacity: this.previewTheme.opacity,
|
||||||
|
themeEngineVersion: this.engineVersion
|
||||||
|
},
|
||||||
|
this.previewTheme.colors,
|
||||||
|
relativeLuminance(this.previewTheme.colors.bg) < 0.5 ? 1 : -1
|
||||||
|
).theme.shadows
|
||||||
},
|
},
|
||||||
importTheme () { this.themeImporter.importData() },
|
importTheme () { this.themeImporter.importData() },
|
||||||
exportTheme () { this.themeExporter.exportData() },
|
exportTheme () { this.themeExporter.exportData() },
|
||||||
|
@ -610,7 +606,7 @@ export default {
|
||||||
normalizeLocalState (theme, version = 0, source, forceSource = false) {
|
normalizeLocalState (theme, version = 0, source, forceSource = false) {
|
||||||
let input
|
let input
|
||||||
if (typeof source !== 'undefined') {
|
if (typeof source !== 'undefined') {
|
||||||
if (forceSource || source.themeEngineVersion === CURRENT_VERSION) {
|
if (forceSource || source?.themeEngineVersion === CURRENT_VERSION) {
|
||||||
input = source
|
input = source
|
||||||
version = source.themeEngineVersion
|
version = source.themeEngineVersion
|
||||||
} else {
|
} else {
|
||||||
|
@ -692,6 +688,8 @@ export default {
|
||||||
} else {
|
} else {
|
||||||
this.shadowsLocal = shadows
|
this.shadowsLocal = shadows
|
||||||
}
|
}
|
||||||
|
this.updatePreviewColors()
|
||||||
|
this.updatePreviewShadows()
|
||||||
this.shadowSelected = this.shadowsAvailable[0]
|
this.shadowSelected = this.shadowsAvailable[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -699,12 +697,25 @@ export default {
|
||||||
this.clearFonts()
|
this.clearFonts()
|
||||||
this.fontsLocal = fonts
|
this.fontsLocal = fonts
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
updateTheme3Preview () {
|
||||||
|
const theme2 = convertTheme2To3(this.previewTheme)
|
||||||
|
const theme3 = init({
|
||||||
|
inputRuleset: theme2,
|
||||||
|
ultimateBackgroundColor: '#000000',
|
||||||
|
liteMode: true
|
||||||
|
})
|
||||||
|
|
||||||
|
this.themeV3Preview = getScopedVersion(
|
||||||
|
getCssRules(theme3.eager),
|
||||||
|
'#theme-preview'
|
||||||
|
).join('\n')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
currentRadii () {
|
currentRadii () {
|
||||||
try {
|
try {
|
||||||
this.previewRadii = generateRadii({ radii: this.currentRadii })
|
this.previewTheme.radii = generateRadii({ radii: this.currentRadii }).theme.radii
|
||||||
this.radiiInvalid = false
|
this.radiiInvalid = false
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.radiiInvalid = true
|
this.radiiInvalid = true
|
||||||
|
@ -713,9 +724,8 @@ export default {
|
||||||
},
|
},
|
||||||
shadowsLocal: {
|
shadowsLocal: {
|
||||||
handler () {
|
handler () {
|
||||||
if (Object.getOwnPropertyNames(this.previewColors).length === 1) return
|
|
||||||
try {
|
try {
|
||||||
this.updatePreviewColorsAndShadows()
|
this.updatePreviewShadows()
|
||||||
this.shadowsInvalid = false
|
this.shadowsInvalid = false
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.shadowsInvalid = true
|
this.shadowsInvalid = true
|
||||||
|
@ -727,7 +737,7 @@ export default {
|
||||||
fontsLocal: {
|
fontsLocal: {
|
||||||
handler () {
|
handler () {
|
||||||
try {
|
try {
|
||||||
this.previewFonts = generateFonts({ fonts: this.fontsLocal })
|
this.previewTheme.fonts = generateFonts({ fonts: this.fontsLocal }).theme.fonts
|
||||||
this.fontsInvalid = false
|
this.fontsInvalid = false
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.fontsInvalid = true
|
this.fontsInvalid = true
|
||||||
|
@ -738,18 +748,16 @@ export default {
|
||||||
},
|
},
|
||||||
currentColors () {
|
currentColors () {
|
||||||
try {
|
try {
|
||||||
this.updatePreviewColorsAndShadows()
|
this.updatePreviewColors()
|
||||||
this.colorsInvalid = false
|
this.colorsInvalid = false
|
||||||
this.shadowsInvalid = false
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.colorsInvalid = true
|
this.colorsInvalid = true
|
||||||
this.shadowsInvalid = true
|
|
||||||
console.warn(e)
|
console.warn(e)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
currentOpacity () {
|
currentOpacity () {
|
||||||
try {
|
try {
|
||||||
this.updatePreviewColorsAndShadows()
|
this.updatePreviewColors()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn(e)
|
console.warn(e)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,9 @@
|
||||||
.theme-tab {
|
.theme-tab {
|
||||||
|
.deprecation-warning {
|
||||||
|
padding: 0.5em;
|
||||||
|
margin: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
padding-bottom: 2em;
|
padding-bottom: 2em;
|
||||||
|
|
||||||
.preset-switcher {
|
.preset-switcher {
|
||||||
|
@ -10,6 +15,10 @@
|
||||||
margin-right: 0.25em;
|
margin-right: 0.25em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.btn-group .btn {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.style-control {
|
.style-control {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: baseline;
|
align-items: baseline;
|
||||||
|
@ -157,107 +166,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.preview-container {
|
|
||||||
border-top: 1px dashed;
|
|
||||||
border-bottom: 1px dashed;
|
|
||||||
border-color: var(--border);
|
|
||||||
margin: 1em 0;
|
|
||||||
padding: 1em;
|
|
||||||
background-color: var(--wallpaper);
|
|
||||||
background-image: var(--body-background-image);
|
|
||||||
background-size: cover;
|
|
||||||
background-position: 50% 50%;
|
|
||||||
|
|
||||||
.dummy {
|
|
||||||
.post {
|
|
||||||
font-family: var(--postFont);
|
|
||||||
display: flex;
|
|
||||||
|
|
||||||
.content {
|
|
||||||
flex: 1;
|
|
||||||
|
|
||||||
h4 {
|
|
||||||
margin-bottom: 0.25em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.icons {
|
|
||||||
margin-top: 0.5em;
|
|
||||||
display: flex;
|
|
||||||
|
|
||||||
i {
|
|
||||||
margin-right: 1em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.after-post {
|
|
||||||
margin-top: 1em;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.avatar,
|
|
||||||
.avatar-alt {
|
|
||||||
background:
|
|
||||||
linear-gradient(
|
|
||||||
135deg,
|
|
||||||
#b8e1fc 0%,
|
|
||||||
#a9d2f3 10%,
|
|
||||||
#90bae4 25%,
|
|
||||||
#90bcea 37%,
|
|
||||||
#90bff0 50%,
|
|
||||||
#6ba8e5 51%,
|
|
||||||
#a2daf5 83%,
|
|
||||||
#bdf3fd 100%
|
|
||||||
);
|
|
||||||
color: black;
|
|
||||||
font-family: sans-serif;
|
|
||||||
text-align: center;
|
|
||||||
margin-right: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.avatar-alt {
|
|
||||||
flex: 0 auto;
|
|
||||||
margin-left: 28px;
|
|
||||||
font-size: 12px;
|
|
||||||
min-width: 20px;
|
|
||||||
min-height: 20px;
|
|
||||||
line-height: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.avatar {
|
|
||||||
flex: 0 auto;
|
|
||||||
width: 48px;
|
|
||||||
height: 48px;
|
|
||||||
font-size: 14px;
|
|
||||||
line-height: 48px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.actions {
|
|
||||||
display: flex;
|
|
||||||
align-items: baseline;
|
|
||||||
|
|
||||||
.checkbox {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: baseline;
|
|
||||||
margin-right: 1em;
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.separator {
|
|
||||||
margin: 1em;
|
|
||||||
border-bottom: 1px solid;
|
|
||||||
border-color: var(--border);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn {
|
|
||||||
min-width: 3em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.radius-item {
|
.radius-item {
|
||||||
flex-basis: auto;
|
flex-basis: auto;
|
||||||
}
|
}
|
||||||
|
@ -310,10 +218,6 @@
|
||||||
max-width: 50em;
|
max-width: 50em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.theme-preview-content {
|
|
||||||
padding: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.theme-warning {
|
.theme-warning {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: baseline;
|
align-items: baseline;
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="theme-tab">
|
<div class="theme-tab">
|
||||||
|
<div class="alert warning deprecation-warning">
|
||||||
|
{{ $t("settings.style.themes2_outdated") }}
|
||||||
|
</div>
|
||||||
<div class="presets-container">
|
<div class="presets-container">
|
||||||
<div class="save-load">
|
<div class="save-load">
|
||||||
<div
|
<div
|
||||||
|
@ -120,7 +123,19 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<preview :style="previewRules" />
|
<!-- eslint-disable vue/no-v-text-v-html-on-component -->
|
||||||
|
<component :is="'style'" v-html="themeV3Preview"/>
|
||||||
|
<!-- eslint-enable vue/no-v-text-v-html-on-component -->
|
||||||
|
<preview id="theme-preview"/>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<button
|
||||||
|
class="btn button-default"
|
||||||
|
@click="updateTheme3Preview"
|
||||||
|
>
|
||||||
|
{{ $t("settings.style.update_preview") }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<keep-alive>
|
<keep-alive>
|
||||||
<tab-switcher key="style-tweak">
|
<tab-switcher key="style-tweak">
|
||||||
|
@ -156,7 +171,7 @@
|
||||||
<OpacityInput
|
<OpacityInput
|
||||||
v-model="bgOpacityLocal"
|
v-model="bgOpacityLocal"
|
||||||
name="bgOpacity"
|
name="bgOpacity"
|
||||||
:fallback="previewTheme.opacity.bg"
|
:fallback="previewTheme.opacity?.bg"
|
||||||
/>
|
/>
|
||||||
<ColorInput
|
<ColorInput
|
||||||
v-model="textColorLocal"
|
v-model="textColorLocal"
|
||||||
|
@ -167,14 +182,14 @@
|
||||||
<ColorInput
|
<ColorInput
|
||||||
v-model="accentColorLocal"
|
v-model="accentColorLocal"
|
||||||
name="accentColor"
|
name="accentColor"
|
||||||
:fallback="previewTheme.colors.link"
|
:fallback="previewTheme.colors?.link"
|
||||||
:label="$t('settings.accent')"
|
:label="$t('settings.accent')"
|
||||||
:show-optional-tickbox="typeof linkColorLocal !== 'undefined'"
|
:show-optional-tickbox="typeof linkColorLocal !== 'undefined'"
|
||||||
/>
|
/>
|
||||||
<ColorInput
|
<ColorInput
|
||||||
v-model="linkColorLocal"
|
v-model="linkColorLocal"
|
||||||
name="linkColor"
|
name="linkColor"
|
||||||
:fallback="previewTheme.colors.accent"
|
:fallback="previewTheme.colors?.accent"
|
||||||
:label="$t('settings.links')"
|
:label="$t('settings.links')"
|
||||||
:show-optional-tickbox="typeof accentColorLocal !== 'undefined'"
|
:show-optional-tickbox="typeof accentColorLocal !== 'undefined'"
|
||||||
/>
|
/>
|
||||||
|
@ -190,13 +205,13 @@
|
||||||
v-model="fgTextColorLocal"
|
v-model="fgTextColorLocal"
|
||||||
name="fgTextColor"
|
name="fgTextColor"
|
||||||
:label="$t('settings.text')"
|
:label="$t('settings.text')"
|
||||||
:fallback="previewTheme.colors.fgText"
|
:fallback="previewTheme.colors?.fgText"
|
||||||
/>
|
/>
|
||||||
<ColorInput
|
<ColorInput
|
||||||
v-model="fgLinkColorLocal"
|
v-model="fgLinkColorLocal"
|
||||||
name="fgLinkColor"
|
name="fgLinkColor"
|
||||||
:label="$t('settings.links')"
|
:label="$t('settings.links')"
|
||||||
:fallback="previewTheme.colors.fgLink"
|
:fallback="previewTheme.colors?.fgLink"
|
||||||
/>
|
/>
|
||||||
<p>{{ $t('settings.style.common_colors.foreground_hint') }}</p>
|
<p>{{ $t('settings.style.common_colors.foreground_hint') }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
@ -256,14 +271,14 @@
|
||||||
<ColorInput
|
<ColorInput
|
||||||
v-model="postLinkColorLocal"
|
v-model="postLinkColorLocal"
|
||||||
name="postLinkColor"
|
name="postLinkColor"
|
||||||
:fallback="previewTheme.colors.accent"
|
:fallback="previewTheme.colors?.accent"
|
||||||
:label="$t('settings.links')"
|
:label="$t('settings.links')"
|
||||||
/>
|
/>
|
||||||
<ContrastRatio :contrast="previewContrast.postLink" />
|
<ContrastRatio :contrast="previewContrast.postLink" />
|
||||||
<ColorInput
|
<ColorInput
|
||||||
v-model="postGreentextColorLocal"
|
v-model="postGreentextColorLocal"
|
||||||
name="postGreentextColor"
|
name="postGreentextColor"
|
||||||
:fallback="previewTheme.colors.cGreen"
|
:fallback="previewTheme.colors?.cGreen"
|
||||||
:label="$t('settings.greentext')"
|
:label="$t('settings.greentext')"
|
||||||
/>
|
/>
|
||||||
<ContrastRatio :contrast="previewContrast.postGreentext" />
|
<ContrastRatio :contrast="previewContrast.postGreentext" />
|
||||||
|
@ -272,13 +287,13 @@
|
||||||
v-model="alertErrorColorLocal"
|
v-model="alertErrorColorLocal"
|
||||||
name="alertError"
|
name="alertError"
|
||||||
:label="$t('settings.style.advanced_colors.alert_error')"
|
:label="$t('settings.style.advanced_colors.alert_error')"
|
||||||
:fallback="previewTheme.colors.alertError"
|
:fallback="previewTheme.colors?.alertError"
|
||||||
/>
|
/>
|
||||||
<ColorInput
|
<ColorInput
|
||||||
v-model="alertErrorTextColorLocal"
|
v-model="alertErrorTextColorLocal"
|
||||||
name="alertErrorText"
|
name="alertErrorText"
|
||||||
:label="$t('settings.text')"
|
:label="$t('settings.text')"
|
||||||
:fallback="previewTheme.colors.alertErrorText"
|
:fallback="previewTheme.colors?.alertErrorText"
|
||||||
/>
|
/>
|
||||||
<ContrastRatio
|
<ContrastRatio
|
||||||
:contrast="previewContrast.alertErrorText"
|
:contrast="previewContrast.alertErrorText"
|
||||||
|
@ -288,13 +303,13 @@
|
||||||
v-model="alertWarningColorLocal"
|
v-model="alertWarningColorLocal"
|
||||||
name="alertWarning"
|
name="alertWarning"
|
||||||
:label="$t('settings.style.advanced_colors.alert_warning')"
|
:label="$t('settings.style.advanced_colors.alert_warning')"
|
||||||
:fallback="previewTheme.colors.alertWarning"
|
:fallback="previewTheme.colors?.alertWarning"
|
||||||
/>
|
/>
|
||||||
<ColorInput
|
<ColorInput
|
||||||
v-model="alertWarningTextColorLocal"
|
v-model="alertWarningTextColorLocal"
|
||||||
name="alertWarningText"
|
name="alertWarningText"
|
||||||
:label="$t('settings.text')"
|
:label="$t('settings.text')"
|
||||||
:fallback="previewTheme.colors.alertWarningText"
|
:fallback="previewTheme.colors?.alertWarningText"
|
||||||
/>
|
/>
|
||||||
<ContrastRatio
|
<ContrastRatio
|
||||||
:contrast="previewContrast.alertWarningText"
|
:contrast="previewContrast.alertWarningText"
|
||||||
|
@ -304,13 +319,13 @@
|
||||||
v-model="alertNeutralColorLocal"
|
v-model="alertNeutralColorLocal"
|
||||||
name="alertNeutral"
|
name="alertNeutral"
|
||||||
:label="$t('settings.style.advanced_colors.alert_neutral')"
|
:label="$t('settings.style.advanced_colors.alert_neutral')"
|
||||||
:fallback="previewTheme.colors.alertNeutral"
|
:fallback="previewTheme.colors?.alertNeutral"
|
||||||
/>
|
/>
|
||||||
<ColorInput
|
<ColorInput
|
||||||
v-model="alertNeutralTextColorLocal"
|
v-model="alertNeutralTextColorLocal"
|
||||||
name="alertNeutralText"
|
name="alertNeutralText"
|
||||||
:label="$t('settings.text')"
|
:label="$t('settings.text')"
|
||||||
:fallback="previewTheme.colors.alertNeutralText"
|
:fallback="previewTheme.colors?.alertNeutralText"
|
||||||
/>
|
/>
|
||||||
<ContrastRatio
|
<ContrastRatio
|
||||||
:contrast="previewContrast.alertNeutralText"
|
:contrast="previewContrast.alertNeutralText"
|
||||||
|
@ -319,7 +334,7 @@
|
||||||
<OpacityInput
|
<OpacityInput
|
||||||
v-model="alertOpacityLocal"
|
v-model="alertOpacityLocal"
|
||||||
name="alertOpacity"
|
name="alertOpacity"
|
||||||
:fallback="previewTheme.opacity.alert"
|
:fallback="previewTheme.opacity?.alert"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="color-item">
|
<div class="color-item">
|
||||||
|
@ -328,13 +343,13 @@
|
||||||
v-model="badgeNotificationColorLocal"
|
v-model="badgeNotificationColorLocal"
|
||||||
name="badgeNotification"
|
name="badgeNotification"
|
||||||
:label="$t('settings.style.advanced_colors.badge_notification')"
|
:label="$t('settings.style.advanced_colors.badge_notification')"
|
||||||
:fallback="previewTheme.colors.badgeNotification"
|
:fallback="previewTheme.colors?.badgeNotification"
|
||||||
/>
|
/>
|
||||||
<ColorInput
|
<ColorInput
|
||||||
v-model="badgeNotificationTextColorLocal"
|
v-model="badgeNotificationTextColorLocal"
|
||||||
name="badgeNotificationText"
|
name="badgeNotificationText"
|
||||||
:label="$t('settings.text')"
|
:label="$t('settings.text')"
|
||||||
:fallback="previewTheme.colors.badgeNotificationText"
|
:fallback="previewTheme.colors?.badgeNotificationText"
|
||||||
/>
|
/>
|
||||||
<ContrastRatio
|
<ContrastRatio
|
||||||
:contrast="previewContrast.badgeNotificationText"
|
:contrast="previewContrast.badgeNotificationText"
|
||||||
|
@ -346,19 +361,19 @@
|
||||||
<ColorInput
|
<ColorInput
|
||||||
v-model="panelColorLocal"
|
v-model="panelColorLocal"
|
||||||
name="panelColor"
|
name="panelColor"
|
||||||
:fallback="previewTheme.colors.panel"
|
:fallback="previewTheme.colors?.panel"
|
||||||
:label="$t('settings.background')"
|
:label="$t('settings.background')"
|
||||||
/>
|
/>
|
||||||
<OpacityInput
|
<OpacityInput
|
||||||
v-model="panelOpacityLocal"
|
v-model="panelOpacityLocal"
|
||||||
name="panelOpacity"
|
name="panelOpacity"
|
||||||
:fallback="previewTheme.opacity.panel"
|
:fallback="previewTheme.opacity?.panel"
|
||||||
:disabled="panelColorLocal === 'transparent'"
|
:disabled="panelColorLocal === 'transparent'"
|
||||||
/>
|
/>
|
||||||
<ColorInput
|
<ColorInput
|
||||||
v-model="panelTextColorLocal"
|
v-model="panelTextColorLocal"
|
||||||
name="panelTextColor"
|
name="panelTextColor"
|
||||||
:fallback="previewTheme.colors.panelText"
|
:fallback="previewTheme.colors?.panelText"
|
||||||
:label="$t('settings.text')"
|
:label="$t('settings.text')"
|
||||||
/>
|
/>
|
||||||
<ContrastRatio
|
<ContrastRatio
|
||||||
|
@ -368,7 +383,7 @@
|
||||||
<ColorInput
|
<ColorInput
|
||||||
v-model="panelLinkColorLocal"
|
v-model="panelLinkColorLocal"
|
||||||
name="panelLinkColor"
|
name="panelLinkColor"
|
||||||
:fallback="previewTheme.colors.panelLink"
|
:fallback="previewTheme.colors?.panelLink"
|
||||||
:label="$t('settings.links')"
|
:label="$t('settings.links')"
|
||||||
/>
|
/>
|
||||||
<ContrastRatio
|
<ContrastRatio
|
||||||
|
@ -381,20 +396,20 @@
|
||||||
<ColorInput
|
<ColorInput
|
||||||
v-model="topBarColorLocal"
|
v-model="topBarColorLocal"
|
||||||
name="topBarColor"
|
name="topBarColor"
|
||||||
:fallback="previewTheme.colors.topBar"
|
:fallback="previewTheme.colors?.topBar"
|
||||||
:label="$t('settings.background')"
|
:label="$t('settings.background')"
|
||||||
/>
|
/>
|
||||||
<ColorInput
|
<ColorInput
|
||||||
v-model="topBarTextColorLocal"
|
v-model="topBarTextColorLocal"
|
||||||
name="topBarTextColor"
|
name="topBarTextColor"
|
||||||
:fallback="previewTheme.colors.topBarText"
|
:fallback="previewTheme.colors?.topBarText"
|
||||||
:label="$t('settings.text')"
|
:label="$t('settings.text')"
|
||||||
/>
|
/>
|
||||||
<ContrastRatio :contrast="previewContrast.topBarText" />
|
<ContrastRatio :contrast="previewContrast.topBarText" />
|
||||||
<ColorInput
|
<ColorInput
|
||||||
v-model="topBarLinkColorLocal"
|
v-model="topBarLinkColorLocal"
|
||||||
name="topBarLinkColor"
|
name="topBarLinkColor"
|
||||||
:fallback="previewTheme.colors.topBarLink"
|
:fallback="previewTheme.colors?.topBarLink"
|
||||||
:label="$t('settings.links')"
|
:label="$t('settings.links')"
|
||||||
/>
|
/>
|
||||||
<ContrastRatio :contrast="previewContrast.topBarLink" />
|
<ContrastRatio :contrast="previewContrast.topBarLink" />
|
||||||
|
@ -404,19 +419,19 @@
|
||||||
<ColorInput
|
<ColorInput
|
||||||
v-model="inputColorLocal"
|
v-model="inputColorLocal"
|
||||||
name="inputColor"
|
name="inputColor"
|
||||||
:fallback="previewTheme.colors.input"
|
:fallback="previewTheme.colors?.input"
|
||||||
:label="$t('settings.background')"
|
:label="$t('settings.background')"
|
||||||
/>
|
/>
|
||||||
<OpacityInput
|
<OpacityInput
|
||||||
v-model="inputOpacityLocal"
|
v-model="inputOpacityLocal"
|
||||||
name="inputOpacity"
|
name="inputOpacity"
|
||||||
:fallback="previewTheme.opacity.input"
|
:fallback="previewTheme.opacity?.input"
|
||||||
:disabled="inputColorLocal === 'transparent'"
|
:disabled="inputColorLocal === 'transparent'"
|
||||||
/>
|
/>
|
||||||
<ColorInput
|
<ColorInput
|
||||||
v-model="inputTextColorLocal"
|
v-model="inputTextColorLocal"
|
||||||
name="inputTextColor"
|
name="inputTextColor"
|
||||||
:fallback="previewTheme.colors.inputText"
|
:fallback="previewTheme.colors?.inputText"
|
||||||
:label="$t('settings.text')"
|
:label="$t('settings.text')"
|
||||||
/>
|
/>
|
||||||
<ContrastRatio :contrast="previewContrast.inputText" />
|
<ContrastRatio :contrast="previewContrast.inputText" />
|
||||||
|
@ -426,33 +441,33 @@
|
||||||
<ColorInput
|
<ColorInput
|
||||||
v-model="btnColorLocal"
|
v-model="btnColorLocal"
|
||||||
name="btnColor"
|
name="btnColor"
|
||||||
:fallback="previewTheme.colors.btn"
|
:fallback="previewTheme.colors?.btn"
|
||||||
:label="$t('settings.background')"
|
:label="$t('settings.background')"
|
||||||
/>
|
/>
|
||||||
<OpacityInput
|
<OpacityInput
|
||||||
v-model="btnOpacityLocal"
|
v-model="btnOpacityLocal"
|
||||||
name="btnOpacity"
|
name="btnOpacity"
|
||||||
:fallback="previewTheme.opacity.btn"
|
:fallback="previewTheme.opacity?.btn"
|
||||||
:disabled="btnColorLocal === 'transparent'"
|
:disabled="btnColorLocal === 'transparent'"
|
||||||
/>
|
/>
|
||||||
<ColorInput
|
<ColorInput
|
||||||
v-model="btnTextColorLocal"
|
v-model="btnTextColorLocal"
|
||||||
name="btnTextColor"
|
name="btnTextColor"
|
||||||
:fallback="previewTheme.colors.btnText"
|
:fallback="previewTheme.colors?.btnText"
|
||||||
:label="$t('settings.text')"
|
:label="$t('settings.text')"
|
||||||
/>
|
/>
|
||||||
<ContrastRatio :contrast="previewContrast.btnText" />
|
<ContrastRatio :contrast="previewContrast.btnText" />
|
||||||
<ColorInput
|
<ColorInput
|
||||||
v-model="btnPanelTextColorLocal"
|
v-model="btnPanelTextColorLocal"
|
||||||
name="btnPanelTextColor"
|
name="btnPanelTextColor"
|
||||||
:fallback="previewTheme.colors.btnPanelText"
|
:fallback="previewTheme.colors?.btnPanelText"
|
||||||
:label="$t('settings.style.advanced_colors.panel_header')"
|
:label="$t('settings.style.advanced_colors.panel_header')"
|
||||||
/>
|
/>
|
||||||
<ContrastRatio :contrast="previewContrast.btnPanelText" />
|
<ContrastRatio :contrast="previewContrast.btnPanelText" />
|
||||||
<ColorInput
|
<ColorInput
|
||||||
v-model="btnTopBarTextColorLocal"
|
v-model="btnTopBarTextColorLocal"
|
||||||
name="btnTopBarTextColor"
|
name="btnTopBarTextColor"
|
||||||
:fallback="previewTheme.colors.btnTopBarText"
|
:fallback="previewTheme.colors?.btnTopBarText"
|
||||||
:label="$t('settings.style.advanced_colors.top_bar')"
|
:label="$t('settings.style.advanced_colors.top_bar')"
|
||||||
/>
|
/>
|
||||||
<ContrastRatio :contrast="previewContrast.btnTopBarText" />
|
<ContrastRatio :contrast="previewContrast.btnTopBarText" />
|
||||||
|
@ -460,27 +475,27 @@
|
||||||
<ColorInput
|
<ColorInput
|
||||||
v-model="btnPressedColorLocal"
|
v-model="btnPressedColorLocal"
|
||||||
name="btnPressedColor"
|
name="btnPressedColor"
|
||||||
:fallback="previewTheme.colors.btnPressed"
|
:fallback="previewTheme.colors?.btnPressed"
|
||||||
:label="$t('settings.background')"
|
:label="$t('settings.background')"
|
||||||
/>
|
/>
|
||||||
<ColorInput
|
<ColorInput
|
||||||
v-model="btnPressedTextColorLocal"
|
v-model="btnPressedTextColorLocal"
|
||||||
name="btnPressedTextColor"
|
name="btnPressedTextColor"
|
||||||
:fallback="previewTheme.colors.btnPressedText"
|
:fallback="previewTheme.colors?.btnPressedText"
|
||||||
:label="$t('settings.text')"
|
:label="$t('settings.text')"
|
||||||
/>
|
/>
|
||||||
<ContrastRatio :contrast="previewContrast.btnPressedText" />
|
<ContrastRatio :contrast="previewContrast.btnPressedText" />
|
||||||
<ColorInput
|
<ColorInput
|
||||||
v-model="btnPressedPanelTextColorLocal"
|
v-model="btnPressedPanelTextColorLocal"
|
||||||
name="btnPressedPanelTextColor"
|
name="btnPressedPanelTextColor"
|
||||||
:fallback="previewTheme.colors.btnPressedPanelText"
|
:fallback="previewTheme.colors?.btnPressedPanelText"
|
||||||
:label="$t('settings.style.advanced_colors.panel_header')"
|
:label="$t('settings.style.advanced_colors.panel_header')"
|
||||||
/>
|
/>
|
||||||
<ContrastRatio :contrast="previewContrast.btnPressedPanelText" />
|
<ContrastRatio :contrast="previewContrast.btnPressedPanelText" />
|
||||||
<ColorInput
|
<ColorInput
|
||||||
v-model="btnPressedTopBarTextColorLocal"
|
v-model="btnPressedTopBarTextColorLocal"
|
||||||
name="btnPressedTopBarTextColor"
|
name="btnPressedTopBarTextColor"
|
||||||
:fallback="previewTheme.colors.btnPressedTopBarText"
|
:fallback="previewTheme.colors?.btnPressedTopBarText"
|
||||||
:label="$t('settings.style.advanced_colors.top_bar')"
|
:label="$t('settings.style.advanced_colors.top_bar')"
|
||||||
/>
|
/>
|
||||||
<ContrastRatio :contrast="previewContrast.btnPressedTopBarText" />
|
<ContrastRatio :contrast="previewContrast.btnPressedTopBarText" />
|
||||||
|
@ -488,52 +503,52 @@
|
||||||
<ColorInput
|
<ColorInput
|
||||||
v-model="btnDisabledColorLocal"
|
v-model="btnDisabledColorLocal"
|
||||||
name="btnDisabledColor"
|
name="btnDisabledColor"
|
||||||
:fallback="previewTheme.colors.btnDisabled"
|
:fallback="previewTheme.colors?.btnDisabled"
|
||||||
:label="$t('settings.background')"
|
:label="$t('settings.background')"
|
||||||
/>
|
/>
|
||||||
<ColorInput
|
<ColorInput
|
||||||
v-model="btnDisabledTextColorLocal"
|
v-model="btnDisabledTextColorLocal"
|
||||||
name="btnDisabledTextColor"
|
name="btnDisabledTextColor"
|
||||||
:fallback="previewTheme.colors.btnDisabledText"
|
:fallback="previewTheme.colors?.btnDisabledText"
|
||||||
:label="$t('settings.text')"
|
:label="$t('settings.text')"
|
||||||
/>
|
/>
|
||||||
<ColorInput
|
<ColorInput
|
||||||
v-model="btnDisabledPanelTextColorLocal"
|
v-model="btnDisabledPanelTextColorLocal"
|
||||||
name="btnDisabledPanelTextColor"
|
name="btnDisabledPanelTextColor"
|
||||||
:fallback="previewTheme.colors.btnDisabledPanelText"
|
:fallback="previewTheme.colors?.btnDisabledPanelText"
|
||||||
:label="$t('settings.style.advanced_colors.panel_header')"
|
:label="$t('settings.style.advanced_colors.panel_header')"
|
||||||
/>
|
/>
|
||||||
<ColorInput
|
<ColorInput
|
||||||
v-model="btnDisabledTopBarTextColorLocal"
|
v-model="btnDisabledTopBarTextColorLocal"
|
||||||
name="btnDisabledTopBarTextColor"
|
name="btnDisabledTopBarTextColor"
|
||||||
:fallback="previewTheme.colors.btnDisabledTopBarText"
|
:fallback="previewTheme.colors?.btnDisabledTopBarText"
|
||||||
:label="$t('settings.style.advanced_colors.top_bar')"
|
:label="$t('settings.style.advanced_colors.top_bar')"
|
||||||
/>
|
/>
|
||||||
<h5>{{ $t('settings.style.advanced_colors.toggled') }}</h5>
|
<h5>{{ $t('settings.style.advanced_colors.toggled') }}</h5>
|
||||||
<ColorInput
|
<ColorInput
|
||||||
v-model="btnToggledColorLocal"
|
v-model="btnToggledColorLocal"
|
||||||
name="btnToggledColor"
|
name="btnToggledColor"
|
||||||
:fallback="previewTheme.colors.btnToggled"
|
:fallback="previewTheme.colors?.btnToggled"
|
||||||
:label="$t('settings.background')"
|
:label="$t('settings.background')"
|
||||||
/>
|
/>
|
||||||
<ColorInput
|
<ColorInput
|
||||||
v-model="btnToggledTextColorLocal"
|
v-model="btnToggledTextColorLocal"
|
||||||
name="btnToggledTextColor"
|
name="btnToggledTextColor"
|
||||||
:fallback="previewTheme.colors.btnToggledText"
|
:fallback="previewTheme.colors?.btnToggledText"
|
||||||
:label="$t('settings.text')"
|
:label="$t('settings.text')"
|
||||||
/>
|
/>
|
||||||
<ContrastRatio :contrast="previewContrast.btnToggledText" />
|
<ContrastRatio :contrast="previewContrast.btnToggledText" />
|
||||||
<ColorInput
|
<ColorInput
|
||||||
v-model="btnToggledPanelTextColorLocal"
|
v-model="btnToggledPanelTextColorLocal"
|
||||||
name="btnToggledPanelTextColor"
|
name="btnToggledPanelTextColor"
|
||||||
:fallback="previewTheme.colors.btnToggledPanelText"
|
:fallback="previewTheme.colors?.btnToggledPanelText"
|
||||||
:label="$t('settings.style.advanced_colors.panel_header')"
|
:label="$t('settings.style.advanced_colors.panel_header')"
|
||||||
/>
|
/>
|
||||||
<ContrastRatio :contrast="previewContrast.btnToggledPanelText" />
|
<ContrastRatio :contrast="previewContrast.btnToggledPanelText" />
|
||||||
<ColorInput
|
<ColorInput
|
||||||
v-model="btnToggledTopBarTextColorLocal"
|
v-model="btnToggledTopBarTextColorLocal"
|
||||||
name="btnToggledTopBarTextColor"
|
name="btnToggledTopBarTextColor"
|
||||||
:fallback="previewTheme.colors.btnToggledTopBarText"
|
:fallback="previewTheme.colors?.btnToggledTopBarText"
|
||||||
:label="$t('settings.style.advanced_colors.top_bar')"
|
:label="$t('settings.style.advanced_colors.top_bar')"
|
||||||
/>
|
/>
|
||||||
<ContrastRatio :contrast="previewContrast.btnToggledTopBarText" />
|
<ContrastRatio :contrast="previewContrast.btnToggledTopBarText" />
|
||||||
|
@ -543,20 +558,20 @@
|
||||||
<ColorInput
|
<ColorInput
|
||||||
v-model="tabColorLocal"
|
v-model="tabColorLocal"
|
||||||
name="tabColor"
|
name="tabColor"
|
||||||
:fallback="previewTheme.colors.tab"
|
:fallback="previewTheme.colors?.tab"
|
||||||
:label="$t('settings.background')"
|
:label="$t('settings.background')"
|
||||||
/>
|
/>
|
||||||
<ColorInput
|
<ColorInput
|
||||||
v-model="tabTextColorLocal"
|
v-model="tabTextColorLocal"
|
||||||
name="tabTextColor"
|
name="tabTextColor"
|
||||||
:fallback="previewTheme.colors.tabText"
|
:fallback="previewTheme.colors?.tabText"
|
||||||
:label="$t('settings.text')"
|
:label="$t('settings.text')"
|
||||||
/>
|
/>
|
||||||
<ContrastRatio :contrast="previewContrast.tabText" />
|
<ContrastRatio :contrast="previewContrast.tabText" />
|
||||||
<ColorInput
|
<ColorInput
|
||||||
v-model="tabActiveTextColorLocal"
|
v-model="tabActiveTextColorLocal"
|
||||||
name="tabActiveTextColor"
|
name="tabActiveTextColor"
|
||||||
:fallback="previewTheme.colors.tabActiveText"
|
:fallback="previewTheme.colors?.tabActiveText"
|
||||||
:label="$t('settings.text')"
|
:label="$t('settings.text')"
|
||||||
/>
|
/>
|
||||||
<ContrastRatio :contrast="previewContrast.tabActiveText" />
|
<ContrastRatio :contrast="previewContrast.tabActiveText" />
|
||||||
|
@ -566,13 +581,13 @@
|
||||||
<ColorInput
|
<ColorInput
|
||||||
v-model="borderColorLocal"
|
v-model="borderColorLocal"
|
||||||
name="borderColor"
|
name="borderColor"
|
||||||
:fallback="previewTheme.colors.border"
|
:fallback="previewTheme.colors?.border"
|
||||||
:label="$t('settings.style.common.color')"
|
:label="$t('settings.style.common.color')"
|
||||||
/>
|
/>
|
||||||
<OpacityInput
|
<OpacityInput
|
||||||
v-model="borderOpacityLocal"
|
v-model="borderOpacityLocal"
|
||||||
name="borderOpacity"
|
name="borderOpacity"
|
||||||
:fallback="previewTheme.opacity.border"
|
:fallback="previewTheme.opacity?.border"
|
||||||
:disabled="borderColorLocal === 'transparent'"
|
:disabled="borderColorLocal === 'transparent'"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -581,25 +596,25 @@
|
||||||
<ColorInput
|
<ColorInput
|
||||||
v-model="faintColorLocal"
|
v-model="faintColorLocal"
|
||||||
name="faintColor"
|
name="faintColor"
|
||||||
:fallback="previewTheme.colors.faint"
|
:fallback="previewTheme.colors?.faint"
|
||||||
:label="$t('settings.text')"
|
:label="$t('settings.text')"
|
||||||
/>
|
/>
|
||||||
<ColorInput
|
<ColorInput
|
||||||
v-model="faintLinkColorLocal"
|
v-model="faintLinkColorLocal"
|
||||||
name="faintLinkColor"
|
name="faintLinkColor"
|
||||||
:fallback="previewTheme.colors.faintLink"
|
:fallback="previewTheme.colors?.faintLink"
|
||||||
:label="$t('settings.links')"
|
:label="$t('settings.links')"
|
||||||
/>
|
/>
|
||||||
<ColorInput
|
<ColorInput
|
||||||
v-model="panelFaintColorLocal"
|
v-model="panelFaintColorLocal"
|
||||||
name="panelFaintColor"
|
name="panelFaintColor"
|
||||||
:fallback="previewTheme.colors.panelFaint"
|
:fallback="previewTheme.colors?.panelFaint"
|
||||||
:label="$t('settings.style.advanced_colors.panel_header')"
|
:label="$t('settings.style.advanced_colors.panel_header')"
|
||||||
/>
|
/>
|
||||||
<OpacityInput
|
<OpacityInput
|
||||||
v-model="faintOpacityLocal"
|
v-model="faintOpacityLocal"
|
||||||
name="faintOpacity"
|
name="faintOpacity"
|
||||||
:fallback="previewTheme.opacity.faint"
|
:fallback="previewTheme.opacity?.faint"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="color-item">
|
<div class="color-item">
|
||||||
|
@ -608,12 +623,12 @@
|
||||||
v-model="underlayColorLocal"
|
v-model="underlayColorLocal"
|
||||||
name="underlay"
|
name="underlay"
|
||||||
:label="$t('settings.style.advanced_colors.underlay')"
|
:label="$t('settings.style.advanced_colors.underlay')"
|
||||||
:fallback="previewTheme.colors.underlay"
|
:fallback="previewTheme.colors?.underlay"
|
||||||
/>
|
/>
|
||||||
<OpacityInput
|
<OpacityInput
|
||||||
v-model="underlayOpacityLocal"
|
v-model="underlayOpacityLocal"
|
||||||
name="underlayOpacity"
|
name="underlayOpacity"
|
||||||
:fallback="previewTheme.opacity.underlay"
|
:fallback="previewTheme.opacity?.underlay"
|
||||||
:disabled="underlayOpacityLocal === 'transparent'"
|
:disabled="underlayOpacityLocal === 'transparent'"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -623,7 +638,7 @@
|
||||||
v-model="wallpaperColorLocal"
|
v-model="wallpaperColorLocal"
|
||||||
name="wallpaper"
|
name="wallpaper"
|
||||||
:label="$t('settings.style.advanced_colors.wallpaper')"
|
:label="$t('settings.style.advanced_colors.wallpaper')"
|
||||||
:fallback="previewTheme.colors.wallpaper"
|
:fallback="previewTheme.colors?.wallpaper"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="color-item">
|
<div class="color-item">
|
||||||
|
@ -632,13 +647,13 @@
|
||||||
v-model="pollColorLocal"
|
v-model="pollColorLocal"
|
||||||
name="poll"
|
name="poll"
|
||||||
:label="$t('settings.background')"
|
:label="$t('settings.background')"
|
||||||
:fallback="previewTheme.colors.poll"
|
:fallback="previewTheme.colors?.poll"
|
||||||
/>
|
/>
|
||||||
<ColorInput
|
<ColorInput
|
||||||
v-model="pollTextColorLocal"
|
v-model="pollTextColorLocal"
|
||||||
name="pollText"
|
name="pollText"
|
||||||
:label="$t('settings.text')"
|
:label="$t('settings.text')"
|
||||||
:fallback="previewTheme.colors.pollText"
|
:fallback="previewTheme.colors?.pollText"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="color-item">
|
<div class="color-item">
|
||||||
|
@ -647,7 +662,7 @@
|
||||||
v-model="iconColorLocal"
|
v-model="iconColorLocal"
|
||||||
name="icon"
|
name="icon"
|
||||||
:label="$t('settings.style.advanced_colors.icons')"
|
:label="$t('settings.style.advanced_colors.icons')"
|
||||||
:fallback="previewTheme.colors.icon"
|
:fallback="previewTheme.colors?.icon"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="color-item">
|
<div class="color-item">
|
||||||
|
@ -656,20 +671,20 @@
|
||||||
v-model="highlightColorLocal"
|
v-model="highlightColorLocal"
|
||||||
name="highlight"
|
name="highlight"
|
||||||
:label="$t('settings.background')"
|
:label="$t('settings.background')"
|
||||||
:fallback="previewTheme.colors.highlight"
|
:fallback="previewTheme.colors?.highlight"
|
||||||
/>
|
/>
|
||||||
<ColorInput
|
<ColorInput
|
||||||
v-model="highlightTextColorLocal"
|
v-model="highlightTextColorLocal"
|
||||||
name="highlightText"
|
name="highlightText"
|
||||||
:label="$t('settings.text')"
|
:label="$t('settings.text')"
|
||||||
:fallback="previewTheme.colors.highlightText"
|
:fallback="previewTheme.colors?.highlightText"
|
||||||
/>
|
/>
|
||||||
<ContrastRatio :contrast="previewContrast.highlightText" />
|
<ContrastRatio :contrast="previewContrast.highlightText" />
|
||||||
<ColorInput
|
<ColorInput
|
||||||
v-model="highlightLinkColorLocal"
|
v-model="highlightLinkColorLocal"
|
||||||
name="highlightLink"
|
name="highlightLink"
|
||||||
:label="$t('settings.links')"
|
:label="$t('settings.links')"
|
||||||
:fallback="previewTheme.colors.highlightLink"
|
:fallback="previewTheme.colors?.highlightLink"
|
||||||
/>
|
/>
|
||||||
<ContrastRatio :contrast="previewContrast.highlightLink" />
|
<ContrastRatio :contrast="previewContrast.highlightLink" />
|
||||||
</div>
|
</div>
|
||||||
|
@ -679,26 +694,26 @@
|
||||||
v-model="popoverColorLocal"
|
v-model="popoverColorLocal"
|
||||||
name="popover"
|
name="popover"
|
||||||
:label="$t('settings.background')"
|
:label="$t('settings.background')"
|
||||||
:fallback="previewTheme.colors.popover"
|
:fallback="previewTheme.colors?.popover"
|
||||||
/>
|
/>
|
||||||
<OpacityInput
|
<OpacityInput
|
||||||
v-model="popoverOpacityLocal"
|
v-model="popoverOpacityLocal"
|
||||||
name="popoverOpacity"
|
name="popoverOpacity"
|
||||||
:fallback="previewTheme.opacity.popover"
|
:fallback="previewTheme.opacity?.popover"
|
||||||
:disabled="popoverOpacityLocal === 'transparent'"
|
:disabled="popoverOpacityLocal === 'transparent'"
|
||||||
/>
|
/>
|
||||||
<ColorInput
|
<ColorInput
|
||||||
v-model="popoverTextColorLocal"
|
v-model="popoverTextColorLocal"
|
||||||
name="popoverText"
|
name="popoverText"
|
||||||
:label="$t('settings.text')"
|
:label="$t('settings.text')"
|
||||||
:fallback="previewTheme.colors.popoverText"
|
:fallback="previewTheme.colors?.popoverText"
|
||||||
/>
|
/>
|
||||||
<ContrastRatio :contrast="previewContrast.popoverText" />
|
<ContrastRatio :contrast="previewContrast.popoverText" />
|
||||||
<ColorInput
|
<ColorInput
|
||||||
v-model="popoverLinkColorLocal"
|
v-model="popoverLinkColorLocal"
|
||||||
name="popoverLink"
|
name="popoverLink"
|
||||||
:label="$t('settings.links')"
|
:label="$t('settings.links')"
|
||||||
:fallback="previewTheme.colors.popoverLink"
|
:fallback="previewTheme.colors?.popoverLink"
|
||||||
/>
|
/>
|
||||||
<ContrastRatio :contrast="previewContrast.popoverLink" />
|
<ContrastRatio :contrast="previewContrast.popoverLink" />
|
||||||
</div>
|
</div>
|
||||||
|
@ -708,20 +723,20 @@
|
||||||
v-model="selectedPostColorLocal"
|
v-model="selectedPostColorLocal"
|
||||||
name="selectedPost"
|
name="selectedPost"
|
||||||
:label="$t('settings.background')"
|
:label="$t('settings.background')"
|
||||||
:fallback="previewTheme.colors.selectedPost"
|
:fallback="previewTheme.colors?.selectedPost"
|
||||||
/>
|
/>
|
||||||
<ColorInput
|
<ColorInput
|
||||||
v-model="selectedPostTextColorLocal"
|
v-model="selectedPostTextColorLocal"
|
||||||
name="selectedPostText"
|
name="selectedPostText"
|
||||||
:label="$t('settings.text')"
|
:label="$t('settings.text')"
|
||||||
:fallback="previewTheme.colors.selectedPostText"
|
:fallback="previewTheme.colors?.selectedPostText"
|
||||||
/>
|
/>
|
||||||
<ContrastRatio :contrast="previewContrast.selectedPostText" />
|
<ContrastRatio :contrast="previewContrast.selectedPostText" />
|
||||||
<ColorInput
|
<ColorInput
|
||||||
v-model="selectedPostLinkColorLocal"
|
v-model="selectedPostLinkColorLocal"
|
||||||
name="selectedPostLink"
|
name="selectedPostLink"
|
||||||
:label="$t('settings.links')"
|
:label="$t('settings.links')"
|
||||||
:fallback="previewTheme.colors.selectedPostLink"
|
:fallback="previewTheme.colors?.selectedPostLink"
|
||||||
/>
|
/>
|
||||||
<ContrastRatio :contrast="previewContrast.selectedPostLink" />
|
<ContrastRatio :contrast="previewContrast.selectedPostLink" />
|
||||||
</div>
|
</div>
|
||||||
|
@ -731,20 +746,20 @@
|
||||||
v-model="selectedMenuColorLocal"
|
v-model="selectedMenuColorLocal"
|
||||||
name="selectedMenu"
|
name="selectedMenu"
|
||||||
:label="$t('settings.background')"
|
:label="$t('settings.background')"
|
||||||
:fallback="previewTheme.colors.selectedMenu"
|
:fallback="previewTheme.colors?.selectedMenu"
|
||||||
/>
|
/>
|
||||||
<ColorInput
|
<ColorInput
|
||||||
v-model="selectedMenuTextColorLocal"
|
v-model="selectedMenuTextColorLocal"
|
||||||
name="selectedMenuText"
|
name="selectedMenuText"
|
||||||
:label="$t('settings.text')"
|
:label="$t('settings.text')"
|
||||||
:fallback="previewTheme.colors.selectedMenuText"
|
:fallback="previewTheme.colors?.selectedMenuText"
|
||||||
/>
|
/>
|
||||||
<ContrastRatio :contrast="previewContrast.selectedMenuText" />
|
<ContrastRatio :contrast="previewContrast.selectedMenuText" />
|
||||||
<ColorInput
|
<ColorInput
|
||||||
v-model="selectedMenuLinkColorLocal"
|
v-model="selectedMenuLinkColorLocal"
|
||||||
name="selectedMenuLink"
|
name="selectedMenuLink"
|
||||||
:label="$t('settings.links')"
|
:label="$t('settings.links')"
|
||||||
:fallback="previewTheme.colors.selectedMenuLink"
|
:fallback="previewTheme.colors?.selectedMenuLink"
|
||||||
/>
|
/>
|
||||||
<ContrastRatio :contrast="previewContrast.selectedMenuLink" />
|
<ContrastRatio :contrast="previewContrast.selectedMenuLink" />
|
||||||
</div>
|
</div>
|
||||||
|
@ -753,57 +768,57 @@
|
||||||
<ColorInput
|
<ColorInput
|
||||||
v-model="chatBgColorLocal"
|
v-model="chatBgColorLocal"
|
||||||
name="chatBgColor"
|
name="chatBgColor"
|
||||||
:fallback="previewTheme.colors.bg"
|
:fallback="previewTheme.colors?.bg"
|
||||||
:label="$t('settings.background')"
|
:label="$t('settings.background')"
|
||||||
/>
|
/>
|
||||||
<h5>{{ $t('settings.style.advanced_colors.chat.incoming') }}</h5>
|
<h5>{{ $t('settings.style.advanced_colors.chat.incoming') }}</h5>
|
||||||
<ColorInput
|
<ColorInput
|
||||||
v-model="chatMessageIncomingBgColorLocal"
|
v-model="chatMessageIncomingBgColorLocal"
|
||||||
name="chatMessageIncomingBgColor"
|
name="chatMessageIncomingBgColor"
|
||||||
:fallback="previewTheme.colors.bg"
|
:fallback="previewTheme.colors?.bg"
|
||||||
:label="$t('settings.background')"
|
:label="$t('settings.background')"
|
||||||
/>
|
/>
|
||||||
<ColorInput
|
<ColorInput
|
||||||
v-model="chatMessageIncomingTextColorLocal"
|
v-model="chatMessageIncomingTextColorLocal"
|
||||||
name="chatMessageIncomingTextColor"
|
name="chatMessageIncomingTextColor"
|
||||||
:fallback="previewTheme.colors.text"
|
:fallback="previewTheme.colors?.text"
|
||||||
:label="$t('settings.text')"
|
:label="$t('settings.text')"
|
||||||
/>
|
/>
|
||||||
<ColorInput
|
<ColorInput
|
||||||
v-model="chatMessageIncomingLinkColorLocal"
|
v-model="chatMessageIncomingLinkColorLocal"
|
||||||
name="chatMessageIncomingLinkColor"
|
name="chatMessageIncomingLinkColor"
|
||||||
:fallback="previewTheme.colors.link"
|
:fallback="previewTheme.colors?.link"
|
||||||
:label="$t('settings.links')"
|
:label="$t('settings.links')"
|
||||||
/>
|
/>
|
||||||
<ColorInput
|
<ColorInput
|
||||||
v-model="chatMessageIncomingBorderColorLocal"
|
v-model="chatMessageIncomingBorderColorLocal"
|
||||||
name="chatMessageIncomingBorderLinkColor"
|
name="chatMessageIncomingBorderLinkColor"
|
||||||
:fallback="previewTheme.colors.fg"
|
:fallback="previewTheme.colors?.fg"
|
||||||
:label="$t('settings.style.advanced_colors.chat.border')"
|
:label="$t('settings.style.advanced_colors.chat.border')"
|
||||||
/>
|
/>
|
||||||
<h5>{{ $t('settings.style.advanced_colors.chat.outgoing') }}</h5>
|
<h5>{{ $t('settings.style.advanced_colors.chat.outgoing') }}</h5>
|
||||||
<ColorInput
|
<ColorInput
|
||||||
v-model="chatMessageOutgoingBgColorLocal"
|
v-model="chatMessageOutgoingBgColorLocal"
|
||||||
name="chatMessageOutgoingBgColor"
|
name="chatMessageOutgoingBgColor"
|
||||||
:fallback="previewTheme.colors.bg"
|
:fallback="previewTheme.colors?.bg"
|
||||||
:label="$t('settings.background')"
|
:label="$t('settings.background')"
|
||||||
/>
|
/>
|
||||||
<ColorInput
|
<ColorInput
|
||||||
v-model="chatMessageOutgoingTextColorLocal"
|
v-model="chatMessageOutgoingTextColorLocal"
|
||||||
name="chatMessageOutgoingTextColor"
|
name="chatMessageOutgoingTextColor"
|
||||||
:fallback="previewTheme.colors.text"
|
:fallback="previewTheme.colors?.text"
|
||||||
:label="$t('settings.text')"
|
:label="$t('settings.text')"
|
||||||
/>
|
/>
|
||||||
<ColorInput
|
<ColorInput
|
||||||
v-model="chatMessageOutgoingLinkColorLocal"
|
v-model="chatMessageOutgoingLinkColorLocal"
|
||||||
name="chatMessageOutgoingLinkColor"
|
name="chatMessageOutgoingLinkColor"
|
||||||
:fallback="previewTheme.colors.link"
|
:fallback="previewTheme.colors?.link"
|
||||||
:label="$t('settings.links')"
|
:label="$t('settings.links')"
|
||||||
/>
|
/>
|
||||||
<ColorInput
|
<ColorInput
|
||||||
v-model="chatMessageOutgoingBorderColorLocal"
|
v-model="chatMessageOutgoingBorderColorLocal"
|
||||||
name="chatMessageOutgoingBorderLinkColor"
|
name="chatMessageOutgoingBorderLinkColor"
|
||||||
:fallback="previewTheme.colors.bg"
|
:fallback="previewTheme.colors?.bg"
|
||||||
:label="$t('settings.style.advanced_colors.chat.border')"
|
:label="$t('settings.style.advanced_colors.chat.border')"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -826,7 +841,7 @@
|
||||||
v-model="btnRadiusLocal"
|
v-model="btnRadiusLocal"
|
||||||
name="btnRadius"
|
name="btnRadius"
|
||||||
:label="$t('settings.btnRadius')"
|
:label="$t('settings.btnRadius')"
|
||||||
:fallback="previewTheme.radii.btn"
|
:fallback="previewTheme.radii?.btn"
|
||||||
max="16"
|
max="16"
|
||||||
hard-min="0"
|
hard-min="0"
|
||||||
/>
|
/>
|
||||||
|
@ -834,7 +849,7 @@
|
||||||
v-model="inputRadiusLocal"
|
v-model="inputRadiusLocal"
|
||||||
name="inputRadius"
|
name="inputRadius"
|
||||||
:label="$t('settings.inputRadius')"
|
:label="$t('settings.inputRadius')"
|
||||||
:fallback="previewTheme.radii.input"
|
:fallback="previewTheme.radii?.input"
|
||||||
max="9"
|
max="9"
|
||||||
hard-min="0"
|
hard-min="0"
|
||||||
/>
|
/>
|
||||||
|
@ -842,7 +857,7 @@
|
||||||
v-model="checkboxRadiusLocal"
|
v-model="checkboxRadiusLocal"
|
||||||
name="checkboxRadius"
|
name="checkboxRadius"
|
||||||
:label="$t('settings.checkboxRadius')"
|
:label="$t('settings.checkboxRadius')"
|
||||||
:fallback="previewTheme.radii.checkbox"
|
:fallback="previewTheme.radii?.checkbox"
|
||||||
max="16"
|
max="16"
|
||||||
hard-min="0"
|
hard-min="0"
|
||||||
/>
|
/>
|
||||||
|
@ -850,7 +865,7 @@
|
||||||
v-model="panelRadiusLocal"
|
v-model="panelRadiusLocal"
|
||||||
name="panelRadius"
|
name="panelRadius"
|
||||||
:label="$t('settings.panelRadius')"
|
:label="$t('settings.panelRadius')"
|
||||||
:fallback="previewTheme.radii.panel"
|
:fallback="previewTheme.radii?.panel"
|
||||||
max="50"
|
max="50"
|
||||||
hard-min="0"
|
hard-min="0"
|
||||||
/>
|
/>
|
||||||
|
@ -858,7 +873,7 @@
|
||||||
v-model="avatarRadiusLocal"
|
v-model="avatarRadiusLocal"
|
||||||
name="avatarRadius"
|
name="avatarRadius"
|
||||||
:label="$t('settings.avatarRadius')"
|
:label="$t('settings.avatarRadius')"
|
||||||
:fallback="previewTheme.radii.avatar"
|
:fallback="previewTheme.radii?.avatar"
|
||||||
max="28"
|
max="28"
|
||||||
hard-min="0"
|
hard-min="0"
|
||||||
/>
|
/>
|
||||||
|
@ -866,7 +881,7 @@
|
||||||
v-model="avatarAltRadiusLocal"
|
v-model="avatarAltRadiusLocal"
|
||||||
name="avatarAltRadius"
|
name="avatarAltRadius"
|
||||||
:label="$t('settings.avatarAltRadius')"
|
:label="$t('settings.avatarAltRadius')"
|
||||||
:fallback="previewTheme.radii.avatarAlt"
|
:fallback="previewTheme.radii?.avatarAlt"
|
||||||
max="28"
|
max="28"
|
||||||
hard-min="0"
|
hard-min="0"
|
||||||
/>
|
/>
|
||||||
|
@ -874,7 +889,7 @@
|
||||||
v-model="attachmentRadiusLocal"
|
v-model="attachmentRadiusLocal"
|
||||||
name="attachmentRadius"
|
name="attachmentRadius"
|
||||||
:label="$t('settings.attachmentRadius')"
|
:label="$t('settings.attachmentRadius')"
|
||||||
:fallback="previewTheme.radii.attachment"
|
:fallback="previewTheme.radii?.attachment"
|
||||||
max="50"
|
max="50"
|
||||||
hard-min="0"
|
hard-min="0"
|
||||||
/>
|
/>
|
||||||
|
@ -882,7 +897,7 @@
|
||||||
v-model="tooltipRadiusLocal"
|
v-model="tooltipRadiusLocal"
|
||||||
name="tooltipRadius"
|
name="tooltipRadius"
|
||||||
:label="$t('settings.tooltipRadius')"
|
:label="$t('settings.tooltipRadius')"
|
||||||
:fallback="previewTheme.radii.tooltip"
|
:fallback="previewTheme.radii?.tooltip"
|
||||||
max="50"
|
max="50"
|
||||||
hard-min="0"
|
hard-min="0"
|
||||||
/>
|
/>
|
||||||
|
@ -890,7 +905,7 @@
|
||||||
v-model="chatMessageRadiusLocal"
|
v-model="chatMessageRadiusLocal"
|
||||||
name="chatMessageRadius"
|
name="chatMessageRadius"
|
||||||
:label="$t('settings.chatMessageRadius')"
|
:label="$t('settings.chatMessageRadius')"
|
||||||
:fallback="previewTheme.radii.chatMessage || 2"
|
:fallback="previewTheme.radii?.chatMessage || 2"
|
||||||
max="50"
|
max="50"
|
||||||
hard-min="0"
|
hard-min="0"
|
||||||
/>
|
/>
|
||||||
|
@ -996,26 +1011,26 @@
|
||||||
v-model="fontsLocal.interface"
|
v-model="fontsLocal.interface"
|
||||||
name="ui"
|
name="ui"
|
||||||
:label="$t('settings.style.fonts.components.interface')"
|
:label="$t('settings.style.fonts.components.interface')"
|
||||||
:fallback="previewTheme.fonts.interface"
|
:fallback="previewTheme.fonts?.interface"
|
||||||
no-inherit="1"
|
no-inherit="1"
|
||||||
/>
|
/>
|
||||||
<FontControl
|
<FontControl
|
||||||
v-model="fontsLocal.input"
|
v-model="fontsLocal.input"
|
||||||
name="input"
|
name="input"
|
||||||
:label="$t('settings.style.fonts.components.input')"
|
:label="$t('settings.style.fonts.components.input')"
|
||||||
:fallback="previewTheme.fonts.input"
|
:fallback="previewTheme.fonts?.input"
|
||||||
/>
|
/>
|
||||||
<FontControl
|
<FontControl
|
||||||
v-model="fontsLocal.post"
|
v-model="fontsLocal.post"
|
||||||
name="post"
|
name="post"
|
||||||
:label="$t('settings.style.fonts.components.post')"
|
:label="$t('settings.style.fonts.components.post')"
|
||||||
:fallback="previewTheme.fonts.post"
|
:fallback="previewTheme.fonts?.post"
|
||||||
/>
|
/>
|
||||||
<FontControl
|
<FontControl
|
||||||
v-model="fontsLocal.postCode"
|
v-model="fontsLocal.postCode"
|
||||||
name="postCode"
|
name="postCode"
|
||||||
:label="$t('settings.style.fonts.components.postCode')"
|
:label="$t('settings.style.fonts.components.postCode')"
|
||||||
:fallback="previewTheme.fonts.postCode"
|
:fallback="previewTheme.fonts?.postCode"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</tab-switcher>
|
</tab-switcher>
|
||||||
|
|
|
@ -17,6 +17,15 @@ export default {
|
||||||
'Attachment',
|
'Attachment',
|
||||||
'PollGraph'
|
'PollGraph'
|
||||||
],
|
],
|
||||||
|
validInnerComponentsLite: [
|
||||||
|
'Text',
|
||||||
|
'Link',
|
||||||
|
'Icon',
|
||||||
|
'Border',
|
||||||
|
'ButtonUnstyled',
|
||||||
|
'RichContent',
|
||||||
|
'Avatar'
|
||||||
|
],
|
||||||
defaultRules: [
|
defaultRules: [
|
||||||
{
|
{
|
||||||
directives: {
|
directives: {
|
||||||
|
|
|
@ -376,6 +376,20 @@
|
||||||
"enter_current_password_to_confirm": "Enter your current password to confirm your identity",
|
"enter_current_password_to_confirm": "Enter your current password to confirm your identity",
|
||||||
"post_look_feel": "Posts Look & Feel",
|
"post_look_feel": "Posts Look & Feel",
|
||||||
"mention_links": "Mention links",
|
"mention_links": "Mention links",
|
||||||
|
"appearance": "Appearance",
|
||||||
|
"confirm_new_setting": "Confirm new setting?",
|
||||||
|
"confirm_new_question": "Does this look ok? Setting will be reverted in 10 seconds.",
|
||||||
|
"revert": "Revert",
|
||||||
|
"confirm": "Confirm",
|
||||||
|
"text_size": "Text and interface size",
|
||||||
|
"text_size_tip": "Use {0} for absolute values, {1} will scale with browser default text size.",
|
||||||
|
"text_size_tip2": "Values other than {0} might break some things and themes",
|
||||||
|
"emoji_size": "Emoji size",
|
||||||
|
"navbar_size": "Top bar size",
|
||||||
|
"panel_header_size": "Panel header size",
|
||||||
|
"visual_tweaks": "Minor visual tweaks",
|
||||||
|
"theme_debug": "Show what background theme engine assumes when dealing with transparancy (DEBUG)",
|
||||||
|
"scale_and_layout": "Interface scale and layout",
|
||||||
"mfa": {
|
"mfa": {
|
||||||
"otp": "OTP",
|
"otp": "OTP",
|
||||||
"setup_otp": "Setup OTP",
|
"setup_otp": "Setup OTP",
|
||||||
|
@ -729,6 +743,42 @@
|
||||||
"enable_web_push_always_show_tip": "Some browsers (Chromium, Chrome) require that push messages always result in a notification, otherwise generic 'Website was updated in background' is shown, enable this to prevent this notification from showing, as Chrome seem to hide push notifications if tab is in focus. Can result in showing duplicate notifications on other browsers.",
|
"enable_web_push_always_show_tip": "Some browsers (Chromium, Chrome) require that push messages always result in a notification, otherwise generic 'Website was updated in background' is shown, enable this to prevent this notification from showing, as Chrome seem to hide push notifications if tab is in focus. Can result in showing duplicate notifications on other browsers.",
|
||||||
"more_settings": "More settings",
|
"more_settings": "More settings",
|
||||||
"style": {
|
"style": {
|
||||||
|
"custom_theme_used": "(Custom theme)",
|
||||||
|
"themes2_outdated": "Editor for Themes V2 is being phased out and will eventually be replaced with a new one that takes advantage of new Themes V3 engine. It should still work but experience might be degraded and inconsistent.",
|
||||||
|
"appearance_tab_note": "Changes on this tab do not affect the theme used, so exported theme will be different from what seen in the UI",
|
||||||
|
"update_preview": "Update preview",
|
||||||
|
"themes3": {
|
||||||
|
"define": "Override",
|
||||||
|
"hacks": {
|
||||||
|
"underlay_overrides": "Change underlay",
|
||||||
|
"underlay_override_mode_none": "Theme default",
|
||||||
|
"underlay_override_mode_opaque": "Replace with solid color",
|
||||||
|
"underlay_override_mode_transparent": "Remove entirely (might break some themes)",
|
||||||
|
"force_interface_roundness": "Override interface roundness/sharpness",
|
||||||
|
"forced_roundness_mode_disabled": "Use theme defaults",
|
||||||
|
"forced_roundness_mode_sharp": "Force sharp edges",
|
||||||
|
"forced_roundness_mode_nonsharp": "Force not-so-sharp (1px roundness) edges",
|
||||||
|
"forced_roundness_mode_round": "Force round edges"
|
||||||
|
},
|
||||||
|
"font": {
|
||||||
|
"group-builtin": "Browser default fonts",
|
||||||
|
"builtin" : {
|
||||||
|
"serif": "Serif",
|
||||||
|
"sans-serif": "Sans-serif",
|
||||||
|
"monospace": "Monospace",
|
||||||
|
"inherit": "Unchanged"
|
||||||
|
},
|
||||||
|
"group-local": "Locally installed fonts",
|
||||||
|
"local-unavailable1": "List of locally installed fonts unavailalbe",
|
||||||
|
"local-unavailable2": "Use manual entry to specify custom font",
|
||||||
|
"font_list_unavailable": "Couldn't get locally installed fonts: {error}",
|
||||||
|
"lookup_local_fonts": "Load list of fonts installed on this computer",
|
||||||
|
"enter_manually": "Enter font name family manually",
|
||||||
|
"entry": "Enter {fontFamily}",
|
||||||
|
"select": "Select font"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"interface_font_user_override": "Override theme/browser font used",
|
||||||
"switcher": {
|
"switcher": {
|
||||||
"keep_color": "Keep colors",
|
"keep_color": "Keep colors",
|
||||||
"keep_shadows": "Keep shadows",
|
"keep_shadows": "Keep shadows",
|
||||||
|
@ -852,7 +902,7 @@
|
||||||
"interface": "Interface",
|
"interface": "Interface",
|
||||||
"input": "Input fields",
|
"input": "Input fields",
|
||||||
"post": "Post text",
|
"post": "Post text",
|
||||||
"postCode": "Monospaced text in a post (rich text)"
|
"monospace": "Monospaced text"
|
||||||
},
|
},
|
||||||
"family": "Font name",
|
"family": "Font name",
|
||||||
"size": "Size (in px)",
|
"size": "Size (in px)",
|
||||||
|
|
|
@ -1,10 +1,21 @@
|
||||||
import Cookies from 'js-cookie'
|
import Cookies from 'js-cookie'
|
||||||
import { setPreset, applyTheme, applyConfig } from '../services/style_setter/style_setter.js'
|
import { applyConfig } from '../services/style_setter/style_setter.js'
|
||||||
import messages from '../i18n/messages'
|
import messages from '../i18n/messages'
|
||||||
import { set } from 'lodash'
|
import { set } from 'lodash'
|
||||||
import localeService from '../services/locale/locale.service.js'
|
import localeService from '../services/locale/locale.service.js'
|
||||||
|
|
||||||
const BACKEND_LANGUAGE_COOKIE_NAME = 'userLanguage'
|
const BACKEND_LANGUAGE_COOKIE_NAME = 'userLanguage'
|
||||||
|
const APPEARANCE_SETTINGS_KEYS = new Set([
|
||||||
|
'sidebarColumnWidth',
|
||||||
|
'contentColumnWidth',
|
||||||
|
'notifsColumnWidth',
|
||||||
|
'textSize',
|
||||||
|
'navbarSize',
|
||||||
|
'panelHeaderSize',
|
||||||
|
'forcedRoundness',
|
||||||
|
'emojiSize',
|
||||||
|
'emojiReactionsScale'
|
||||||
|
])
|
||||||
|
|
||||||
const browserLocale = (window.navigator.language || 'en').split('-')[0]
|
const browserLocale = (window.navigator.language || 'en').split('-')[0]
|
||||||
|
|
||||||
|
@ -24,11 +35,30 @@ export const multiChoiceProperties = [
|
||||||
|
|
||||||
export const defaultState = {
|
export const defaultState = {
|
||||||
expertLevel: 0, // used to track which settings to show and hide
|
expertLevel: 0, // used to track which settings to show and hide
|
||||||
colors: {},
|
|
||||||
theme: undefined,
|
// Theme stuff
|
||||||
customTheme: undefined,
|
theme: undefined, // Very old theme store, stores preset name, still in use
|
||||||
customThemeSource: undefined,
|
|
||||||
forceThemeRecompilation: false,
|
// V1
|
||||||
|
colors: {}, // VERY old theme store, just colors of V1, probably not even used anymore
|
||||||
|
|
||||||
|
// V2
|
||||||
|
customTheme: undefined, // "snapshot", previously was used as actual theme store for V2 so it's still used in case of PleromaFE downgrade event.
|
||||||
|
customThemeSource: undefined, // "source", stores original theme data
|
||||||
|
|
||||||
|
// V3
|
||||||
|
themeDebug: false, // debug mode that uses computed backgrounds instead of real ones to debug contrast functions
|
||||||
|
forceThemeRecompilation: false, // flag that forces recompilation on boot even if cache exists
|
||||||
|
theme3hacks: { // Hacks, user overrides that are independent of theme used
|
||||||
|
underlay: 'none',
|
||||||
|
fonts: {
|
||||||
|
interface: undefined,
|
||||||
|
input: undefined,
|
||||||
|
post: undefined,
|
||||||
|
monospace: undefined
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
hideISP: false,
|
hideISP: false,
|
||||||
hideInstanceWallpaper: false,
|
hideInstanceWallpaper: false,
|
||||||
hideShoutbox: false,
|
hideShoutbox: false,
|
||||||
|
@ -117,7 +147,12 @@ export const defaultState = {
|
||||||
sidebarColumnWidth: '25rem',
|
sidebarColumnWidth: '25rem',
|
||||||
contentColumnWidth: '45rem',
|
contentColumnWidth: '45rem',
|
||||||
notifsColumnWidth: '25rem',
|
notifsColumnWidth: '25rem',
|
||||||
emojiReactionsScale: 1.0,
|
emojiReactionsScale: undefined,
|
||||||
|
textSize: undefined, // instance default
|
||||||
|
emojiSize: undefined, // instance default
|
||||||
|
navbarSize: undefined, // instance default
|
||||||
|
panelHeaderSize: undefined, // instance default
|
||||||
|
forcedRoundness: undefined, // instance default
|
||||||
navbarColumnStretch: false,
|
navbarColumnStretch: false,
|
||||||
greentext: undefined, // instance default
|
greentext: undefined, // instance default
|
||||||
useAtIcon: undefined, // instance default
|
useAtIcon: undefined, // instance default
|
||||||
|
@ -175,6 +210,10 @@ const config = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mutations: {
|
mutations: {
|
||||||
|
setOptionTemporarily (state, { name, value }) {
|
||||||
|
set(state, name, value)
|
||||||
|
applyConfig(state)
|
||||||
|
},
|
||||||
setOption (state, { name, value }) {
|
setOption (state, { name, value }) {
|
||||||
set(state, name, value)
|
set(state, name, value)
|
||||||
},
|
},
|
||||||
|
@ -205,6 +244,37 @@ const config = {
|
||||||
setHighlight ({ commit, dispatch }, { user, color, type }) {
|
setHighlight ({ commit, dispatch }, { user, color, type }) {
|
||||||
commit('setHighlight', { user, color, type })
|
commit('setHighlight', { user, color, type })
|
||||||
},
|
},
|
||||||
|
setOptionTemporarily ({ commit, dispatch, state, rootState }, { name, value }) {
|
||||||
|
if (rootState.interface.temporaryChangesTimeoutId !== null) {
|
||||||
|
console.warn('Can\'t track more than one temporary change')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const oldValue = state[name]
|
||||||
|
|
||||||
|
commit('setOptionTemporarily', { name, value })
|
||||||
|
|
||||||
|
const confirm = () => {
|
||||||
|
dispatch('setOption', { name, value })
|
||||||
|
commit('clearTemporaryChanges')
|
||||||
|
}
|
||||||
|
|
||||||
|
const revert = () => {
|
||||||
|
commit('setOptionTemporarily', { name, value: oldValue })
|
||||||
|
commit('clearTemporaryChanges')
|
||||||
|
}
|
||||||
|
|
||||||
|
commit('setTemporaryChanges', {
|
||||||
|
timeoutId: setTimeout(revert, 10000),
|
||||||
|
confirm,
|
||||||
|
revert
|
||||||
|
})
|
||||||
|
},
|
||||||
|
setThemeV2 ({ commit, dispatch }, { customTheme, customThemeSource }) {
|
||||||
|
commit('setOption', { name: 'theme', value: 'custom' })
|
||||||
|
commit('setOption', { name: 'customTheme', value: customTheme })
|
||||||
|
commit('setOption', { name: 'customThemeSource', value: customThemeSource })
|
||||||
|
dispatch('setTheme', { themeData: customThemeSource, recompile: true })
|
||||||
|
},
|
||||||
setOption ({ commit, dispatch, state }, { name, value }) {
|
setOption ({ commit, dispatch, state }, { name, value }) {
|
||||||
const exceptions = new Set([
|
const exceptions = new Set([
|
||||||
'useStreamingApi'
|
'useStreamingApi'
|
||||||
|
@ -222,24 +292,26 @@ const config = {
|
||||||
dispatch('disableMastoSockets')
|
dispatch('disableMastoSockets')
|
||||||
dispatch('setOption', { name: 'useStreamingApi', value: false })
|
dispatch('setOption', { name: 'useStreamingApi', value: false })
|
||||||
})
|
})
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
commit('setOption', { name, value })
|
commit('setOption', { name, value })
|
||||||
|
if (APPEARANCE_SETTINGS_KEYS.has(name)) {
|
||||||
|
applyConfig(state)
|
||||||
|
}
|
||||||
|
if (name.startsWith('theme3hacks')) {
|
||||||
|
dispatch('setTheme', { recompile: true })
|
||||||
|
}
|
||||||
switch (name) {
|
switch (name) {
|
||||||
case 'theme':
|
case 'theme':
|
||||||
setPreset(value)
|
if (value === 'custom') break
|
||||||
|
dispatch('setTheme', { themeName: value, recompile: true, saveData: true })
|
||||||
break
|
break
|
||||||
case 'sidebarColumnWidth':
|
case 'themeDebug': {
|
||||||
case 'contentColumnWidth':
|
dispatch('setTheme', { recompile: true })
|
||||||
case 'notifsColumnWidth':
|
|
||||||
case 'emojiReactionsScale':
|
|
||||||
applyConfig(state)
|
|
||||||
break
|
|
||||||
case 'customTheme':
|
|
||||||
case 'customThemeSource':
|
|
||||||
applyTheme(value)
|
|
||||||
break
|
break
|
||||||
|
}
|
||||||
case 'interfaceLanguage':
|
case 'interfaceLanguage':
|
||||||
messages.setLanguage(this.getters.i18n, value)
|
messages.setLanguage(this.getters.i18n, value)
|
||||||
dispatch('loadUnicodeEmojiData', value)
|
dispatch('loadUnicodeEmojiData', value)
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
import { getPreset, applyTheme } from '../services/style_setter/style_setter.js'
|
|
||||||
import { CURRENT_VERSION } from '../services/theme_data/theme_data.service.js'
|
|
||||||
import apiService from '../services/api/api.service.js'
|
import apiService from '../services/api/api.service.js'
|
||||||
import { instanceDefaultProperties } from './config.js'
|
import { instanceDefaultProperties } from './config.js'
|
||||||
import { langCodeToCldrName, ensureFinalFallback } from '../i18n/languages.js'
|
import { langCodeToCldrName, ensureFinalFallback } from '../i18n/languages.js'
|
||||||
|
@ -44,7 +42,7 @@ const defaultState = {
|
||||||
registrationOpen: true,
|
registrationOpen: true,
|
||||||
server: 'http://localhost:4040/',
|
server: 'http://localhost:4040/',
|
||||||
textlimit: 5000,
|
textlimit: 5000,
|
||||||
themeData: undefined,
|
themeData: undefined, // used for theme editor v2
|
||||||
vapidPublicKey: undefined,
|
vapidPublicKey: undefined,
|
||||||
|
|
||||||
// Stuff from static/config.json
|
// Stuff from static/config.json
|
||||||
|
@ -98,6 +96,13 @@ const defaultState = {
|
||||||
sidebarRight: false,
|
sidebarRight: false,
|
||||||
subjectLineBehavior: 'email',
|
subjectLineBehavior: 'email',
|
||||||
theme: 'pleroma-dark',
|
theme: 'pleroma-dark',
|
||||||
|
emojiReactionsScale: 0.5,
|
||||||
|
textSize: '14px',
|
||||||
|
emojiSize: '2.2rem',
|
||||||
|
navbarSize: '3.5rem',
|
||||||
|
panelHeaderSize: '3.2rem',
|
||||||
|
forcedRoundness: -1,
|
||||||
|
fontsOverride: {},
|
||||||
virtualScrolling: true,
|
virtualScrolling: true,
|
||||||
sensitiveByDefault: false,
|
sensitiveByDefault: false,
|
||||||
conversationDisplay: 'linear',
|
conversationDisplay: 'linear',
|
||||||
|
@ -279,9 +284,6 @@ const instance = {
|
||||||
dispatch('initializeSocket')
|
dispatch('initializeSocket')
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
case 'theme':
|
|
||||||
dispatch('setTheme', value)
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async getStaticEmoji ({ commit }) {
|
async getStaticEmoji ({ commit }) {
|
||||||
|
@ -370,27 +372,6 @@ const instance = {
|
||||||
console.warn(e)
|
console.warn(e)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
setTheme ({ commit, rootState }, themeName) {
|
|
||||||
commit('setInstanceOption', { name: 'theme', value: themeName })
|
|
||||||
getPreset(themeName)
|
|
||||||
.then(themeData => {
|
|
||||||
commit('setInstanceOption', { name: 'themeData', value: themeData })
|
|
||||||
// No need to apply theme if there's user theme already
|
|
||||||
const { customTheme } = rootState.config
|
|
||||||
const { themeApplied } = rootState.interface
|
|
||||||
if (customTheme || themeApplied) return
|
|
||||||
|
|
||||||
// New theme presets don't have 'theme' property, they use 'source'
|
|
||||||
const themeSource = themeData.source
|
|
||||||
if (!themeData.theme || (themeSource && themeSource.themeEngineVersion === CURRENT_VERSION)) {
|
|
||||||
applyTheme(themeSource)
|
|
||||||
} else {
|
|
||||||
applyTheme(themeData.theme)
|
|
||||||
}
|
|
||||||
commit('setThemeApplied')
|
|
||||||
})
|
|
||||||
},
|
|
||||||
fetchEmoji ({ dispatch, state }) {
|
fetchEmoji ({ dispatch, state }) {
|
||||||
if (!state.customEmojiFetched) {
|
if (!state.customEmojiFetched) {
|
||||||
state.customEmojiFetched = true
|
state.customEmojiFetched = true
|
||||||
|
|
|
@ -1,5 +1,13 @@
|
||||||
|
import { getPreset, applyTheme, tryLoadCache } from '../services/style_setter/style_setter.js'
|
||||||
|
import { CURRENT_VERSION, generatePreset } from 'src/services/theme_data/theme_data.service.js'
|
||||||
|
import { convertTheme2To3 } from 'src/services/theme_data/theme2_to_theme3.js'
|
||||||
|
|
||||||
const defaultState = {
|
const defaultState = {
|
||||||
|
localFonts: null,
|
||||||
themeApplied: false,
|
themeApplied: false,
|
||||||
|
temporaryChangesTimeoutId: null, // used for temporary options that revert after a timeout
|
||||||
|
temporaryChangesConfirm: () => {}, // used for applying temporary options
|
||||||
|
temporaryChangesRevert: () => {}, // used for reverting temporary options
|
||||||
settingsModalState: 'hidden',
|
settingsModalState: 'hidden',
|
||||||
settingsModalLoadedUser: false,
|
settingsModalLoadedUser: false,
|
||||||
settingsModalLoadedAdmin: false,
|
settingsModalLoadedAdmin: false,
|
||||||
|
@ -14,7 +22,8 @@ const defaultState = {
|
||||||
cssFilter: window.CSS && window.CSS.supports && (
|
cssFilter: window.CSS && window.CSS.supports && (
|
||||||
window.CSS.supports('filter', 'drop-shadow(0 0)') ||
|
window.CSS.supports('filter', 'drop-shadow(0 0)') ||
|
||||||
window.CSS.supports('-webkit-filter', 'drop-shadow(0 0)')
|
window.CSS.supports('-webkit-filter', 'drop-shadow(0 0)')
|
||||||
)
|
),
|
||||||
|
localFonts: typeof window.queryLocalFonts === 'function'
|
||||||
},
|
},
|
||||||
layoutType: 'normal',
|
layoutType: 'normal',
|
||||||
globalNotices: [],
|
globalNotices: [],
|
||||||
|
@ -36,6 +45,17 @@ const interfaceMod = {
|
||||||
state.settings.currentSaveStateNotice = { error: true, errorData: error }
|
state.settings.currentSaveStateNotice = { error: true, errorData: error }
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
setTemporaryChanges (state, { timeoutId, confirm, revert }) {
|
||||||
|
state.temporaryChangesTimeoutId = timeoutId
|
||||||
|
state.temporaryChangesConfirm = confirm
|
||||||
|
state.temporaryChangesRevert = revert
|
||||||
|
},
|
||||||
|
clearTemporaryChanges (state) {
|
||||||
|
clearTimeout(state.temporaryChangesTimeoutId)
|
||||||
|
state.temporaryChangesTimeoutId = null
|
||||||
|
state.temporaryChangesConfirm = () => {}
|
||||||
|
state.temporaryChangesRevert = () => {}
|
||||||
|
},
|
||||||
setThemeApplied (state) {
|
setThemeApplied (state) {
|
||||||
state.themeApplied = true
|
state.themeApplied = true
|
||||||
},
|
},
|
||||||
|
@ -90,6 +110,10 @@ const interfaceMod = {
|
||||||
},
|
},
|
||||||
setLastTimeline (state, value) {
|
setLastTimeline (state, value) {
|
||||||
state.lastTimeline = value
|
state.lastTimeline = value
|
||||||
|
},
|
||||||
|
setFontsList (state, value) {
|
||||||
|
// Set is used here so that we filter out duplicate fonts (possibly same font but with different weight)
|
||||||
|
state.localFonts = [...(new Set(value.map(font => font.family))).values()]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
|
@ -164,10 +188,203 @@ const interfaceMod = {
|
||||||
commit('setLayoutType', wideLayout ? 'wide' : normalOrMobile)
|
commit('setLayoutType', wideLayout ? 'wide' : normalOrMobile)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
queryLocalFonts ({ commit, dispatch, state }) {
|
||||||
|
if (state.localFonts !== null) return
|
||||||
|
commit('setFontsList', [])
|
||||||
|
if (!state.browserSupport.localFonts) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
window
|
||||||
|
.queryLocalFonts()
|
||||||
|
.then((fonts) => {
|
||||||
|
commit('setFontsList', fonts)
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
dispatch('pushGlobalNotice', {
|
||||||
|
messageKey: 'settings.style.themes3.font.font_list_unavailable',
|
||||||
|
messageArgs: {
|
||||||
|
error: e
|
||||||
|
},
|
||||||
|
level: 'error'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
setLastTimeline ({ commit }, value) {
|
setLastTimeline ({ commit }, value) {
|
||||||
commit('setLastTimeline', value)
|
commit('setLastTimeline', value)
|
||||||
|
},
|
||||||
|
setTheme ({ commit, rootState }, { themeName, themeData, recompile, saveData } = {}) {
|
||||||
|
const {
|
||||||
|
theme: instanceThemeName
|
||||||
|
} = rootState.instance
|
||||||
|
|
||||||
|
const {
|
||||||
|
theme: userThemeName,
|
||||||
|
customTheme: userThemeSnapshot,
|
||||||
|
customThemeSource: userThemeSource,
|
||||||
|
forceThemeRecompilation,
|
||||||
|
themeDebug,
|
||||||
|
theme3hacks
|
||||||
|
} = rootState.config
|
||||||
|
|
||||||
|
const actualThemeName = userThemeName || instanceThemeName
|
||||||
|
|
||||||
|
const forceRecompile = forceThemeRecompilation || recompile
|
||||||
|
|
||||||
|
let promise = null
|
||||||
|
|
||||||
|
if (themeData) {
|
||||||
|
promise = Promise.resolve(normalizeThemeData(themeData))
|
||||||
|
} else if (themeName) {
|
||||||
|
promise = getPreset(themeName).then(themeData => normalizeThemeData(themeData))
|
||||||
|
} else if (userThemeSource || userThemeSnapshot) {
|
||||||
|
if (userThemeSource && userThemeSource.themeEngineVersion === CURRENT_VERSION) {
|
||||||
|
promise = Promise.resolve(normalizeThemeData(userThemeSource))
|
||||||
|
} else {
|
||||||
|
promise = Promise.resolve(normalizeThemeData(userThemeSnapshot))
|
||||||
|
}
|
||||||
|
} else if (actualThemeName && actualThemeName !== 'custom') {
|
||||||
|
promise = getPreset(actualThemeName).then(themeData => {
|
||||||
|
const realThemeData = normalizeThemeData(themeData)
|
||||||
|
if (actualThemeName === instanceThemeName) {
|
||||||
|
// This sole line is the reason why this whole block is above the recompilation check
|
||||||
|
commit('setInstanceOption', { name: 'themeData', value: { theme: realThemeData } })
|
||||||
|
}
|
||||||
|
return realThemeData
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
throw new Error('Cannot load any theme!')
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we're not not forced to recompile try using
|
||||||
|
// cache (tryLoadCache return true if load successful)
|
||||||
|
if (!forceRecompile && !themeDebug && tryLoadCache()) {
|
||||||
|
commit('setThemeApplied')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
promise
|
||||||
|
.then(realThemeData => {
|
||||||
|
const theme2ruleset = convertTheme2To3(realThemeData)
|
||||||
|
|
||||||
|
if (saveData) {
|
||||||
|
commit('setOption', { name: 'theme', value: themeName || actualThemeName })
|
||||||
|
commit('setOption', { name: 'customTheme', value: realThemeData })
|
||||||
|
commit('setOption', { name: 'customThemeSource', value: realThemeData })
|
||||||
|
}
|
||||||
|
const hacks = []
|
||||||
|
|
||||||
|
Object.entries(theme3hacks).forEach(([key, value]) => {
|
||||||
|
switch (key) {
|
||||||
|
case 'fonts': {
|
||||||
|
Object.entries(theme3hacks.fonts).forEach(([fontKey, font]) => {
|
||||||
|
if (!font?.family) return
|
||||||
|
switch (fontKey) {
|
||||||
|
case 'interface':
|
||||||
|
hacks.push({
|
||||||
|
component: 'Root',
|
||||||
|
directives: {
|
||||||
|
'--font': 'generic | ' + font.family
|
||||||
|
}
|
||||||
|
})
|
||||||
|
break
|
||||||
|
case 'input':
|
||||||
|
hacks.push({
|
||||||
|
component: 'Input',
|
||||||
|
directives: {
|
||||||
|
'--font': 'generic | ' + font.family
|
||||||
|
}
|
||||||
|
})
|
||||||
|
break
|
||||||
|
case 'post':
|
||||||
|
hacks.push({
|
||||||
|
component: 'RichContent',
|
||||||
|
directives: {
|
||||||
|
'--font': 'generic | ' + font.family
|
||||||
|
}
|
||||||
|
})
|
||||||
|
break
|
||||||
|
case 'monospace':
|
||||||
|
hacks.push({
|
||||||
|
component: 'Root',
|
||||||
|
directives: {
|
||||||
|
'--monoFont': 'generic | ' + font.family
|
||||||
|
}
|
||||||
|
})
|
||||||
|
break
|
||||||
|
}
|
||||||
|
})
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case 'underlay': {
|
||||||
|
if (value !== 'none') {
|
||||||
|
const newRule = {
|
||||||
|
component: 'Underlay',
|
||||||
|
directives: {}
|
||||||
|
}
|
||||||
|
if (value === 'opaque') {
|
||||||
|
newRule.directives.opacity = 1
|
||||||
|
newRule.directives.background = '--wallpaper'
|
||||||
|
}
|
||||||
|
if (value === 'transparent') {
|
||||||
|
newRule.directives.opacity = 0
|
||||||
|
}
|
||||||
|
hacks.push(newRule)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const ruleset = [
|
||||||
|
...theme2ruleset,
|
||||||
|
...hacks
|
||||||
|
]
|
||||||
|
|
||||||
|
applyTheme(
|
||||||
|
ruleset,
|
||||||
|
() => commit('setThemeApplied'),
|
||||||
|
themeDebug
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
return promise
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default interfaceMod
|
export default interfaceMod
|
||||||
|
|
||||||
|
export const normalizeThemeData = (input) => {
|
||||||
|
let themeData = input
|
||||||
|
|
||||||
|
if (Array.isArray(themeData)) {
|
||||||
|
themeData = { colors: {} }
|
||||||
|
themeData.colors.bg = input[1]
|
||||||
|
themeData.colors.fg = input[2]
|
||||||
|
themeData.colors.text = input[3]
|
||||||
|
themeData.colors.link = input[4]
|
||||||
|
themeData.colors.cRed = input[5]
|
||||||
|
themeData.colors.cGreen = input[6]
|
||||||
|
themeData.colors.cBlue = input[7]
|
||||||
|
themeData.colors.cOrange = input[8]
|
||||||
|
return generatePreset(themeData).theme
|
||||||
|
}
|
||||||
|
|
||||||
|
if (themeData.themeFileVerison === 1) {
|
||||||
|
return generatePreset(themeData).theme
|
||||||
|
}
|
||||||
|
|
||||||
|
// New theme presets don't have 'theme' property, they use 'source'
|
||||||
|
const themeSource = themeData.source
|
||||||
|
|
||||||
|
let out // shout, shout let it all out
|
||||||
|
if (!themeData.theme || (themeSource && themeSource.themeEngineVersion === CURRENT_VERSION)) {
|
||||||
|
out = themeSource || themeData
|
||||||
|
} else {
|
||||||
|
out = themeData.theme
|
||||||
|
}
|
||||||
|
|
||||||
|
// generatePreset here basically creates/updates "snapshot",
|
||||||
|
// while also fixing the 2.2 -> 2.3 colors/shadows/etc
|
||||||
|
return generatePreset(out).theme
|
||||||
|
}
|
||||||
|
|
|
@ -60,11 +60,12 @@
|
||||||
|
|
||||||
.panel-heading,
|
.panel-heading,
|
||||||
.panel-footer {
|
.panel-footer {
|
||||||
--panel-heading-height-padding: 0.6em;
|
--panel-heading-height-padding: calc(var(--panel-header-height) * 0.2);
|
||||||
--__panel-heading-gap: 0.5em;
|
--__panel-heading-gap: calc(var(--panel-header-height) * 0.1565);
|
||||||
--__panel-heading-height: 3.2em;
|
--__panel-heading-height: var(--panel-header-height);
|
||||||
--__panel-heading-height-inner: calc(var(--__panel-heading-height) - 2 * var(--panel-heading-height-padding, 0));
|
--__panel-heading-height-inner: calc(var(--__panel-heading-height) - 2 * var(--panel-heading-height-padding, 0));
|
||||||
|
|
||||||
|
font-size: calc(var(--panelHeaderSize) / 3.2);
|
||||||
backdrop-filter: var(--__panel-backdrop-filter);
|
backdrop-filter: var(--__panel-backdrop-filter);
|
||||||
position: relative;
|
position: relative;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
import { hex2rgb } from '../color_convert/color_convert.js'
|
import { hex2rgb } from '../color_convert/color_convert.js'
|
||||||
import { generatePreset } from '../theme_data/theme_data.service.js'
|
|
||||||
import { init, getEngineChecksum } from '../theme_data/theme_data_3.service.js'
|
import { init, getEngineChecksum } from '../theme_data/theme_data_3.service.js'
|
||||||
import { convertTheme2To3 } from '../theme_data/theme2_to_theme3.js'
|
|
||||||
import { getCssRules } from '../theme_data/css_utils.js'
|
import { getCssRules } from '../theme_data/css_utils.js'
|
||||||
import { defaultState } from '../../modules/config.js'
|
import { defaultState } from '../../modules/config.js'
|
||||||
import { chunk } from 'lodash'
|
import { chunk } from 'lodash'
|
||||||
|
@ -45,25 +43,21 @@ const adoptStyleSheets = (styles) => {
|
||||||
// is nothing to do here.
|
// is nothing to do here.
|
||||||
}
|
}
|
||||||
|
|
||||||
export const generateTheme = async (input, callbacks) => {
|
export const generateTheme = async (inputRuleset, callbacks, debug) => {
|
||||||
const {
|
const {
|
||||||
onNewRule = (rule, isLazy) => {},
|
onNewRule = (rule, isLazy) => {},
|
||||||
onLazyFinished = () => {},
|
onLazyFinished = () => {},
|
||||||
onEagerFinished = () => {}
|
onEagerFinished = () => {}
|
||||||
} = callbacks
|
} = callbacks
|
||||||
|
|
||||||
let extraRules
|
|
||||||
if (input.themeFileVersion === 1) {
|
|
||||||
extraRules = convertTheme2To3(input)
|
|
||||||
} else {
|
|
||||||
const { theme } = generatePreset(input)
|
|
||||||
extraRules = convertTheme2To3(theme)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Assuming that "worst case scenario background" is panel background since it's the most likely one
|
// Assuming that "worst case scenario background" is panel background since it's the most likely one
|
||||||
const themes3 = init(extraRules, extraRules[0].directives['--bg'].split('|')[1].trim())
|
const themes3 = init({
|
||||||
|
inputRuleset,
|
||||||
|
ultimateBackgroundColor: inputRuleset[0].directives['--bg'].split('|')[1].trim(),
|
||||||
|
debug
|
||||||
|
})
|
||||||
|
|
||||||
getCssRules(themes3.eager, themes3.staticVars).forEach(rule => {
|
getCssRules(themes3.eager, debug).forEach(rule => {
|
||||||
// Hacks to support multiple selectors on same component
|
// Hacks to support multiple selectors on same component
|
||||||
if (rule.match(/::-webkit-scrollbar-button/)) {
|
if (rule.match(/::-webkit-scrollbar-button/)) {
|
||||||
const parts = rule.split(/[{}]/g)
|
const parts = rule.split(/[{}]/g)
|
||||||
|
@ -93,7 +87,7 @@ export const generateTheme = async (input, callbacks) => {
|
||||||
const processChunk = () => {
|
const processChunk = () => {
|
||||||
const chunk = chunks[counter]
|
const chunk = chunks[counter]
|
||||||
Promise.all(chunk.map(x => x())).then(result => {
|
Promise.all(chunk.map(x => x())).then(result => {
|
||||||
getCssRules(result.filter(x => x), themes3.staticVars).forEach(rule => {
|
getCssRules(result.filter(x => x), debug).forEach(rule => {
|
||||||
if (rule.match(/\.modal-view/)) {
|
if (rule.match(/\.modal-view/)) {
|
||||||
const parts = rule.split(/[{}]/g)
|
const parts = rule.split(/[{}]/g)
|
||||||
const newRule = [
|
const newRule = [
|
||||||
|
@ -152,7 +146,7 @@ export const tryLoadCache = () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const applyTheme = async (input, onFinish = (data) => {}) => {
|
export const applyTheme = async (input, onFinish = (data) => {}, debug) => {
|
||||||
const eagerStyles = createStyleSheet(EAGER_STYLE_ID)
|
const eagerStyles = createStyleSheet(EAGER_STYLE_ID)
|
||||||
const lazyStyles = createStyleSheet(LAZY_STYLE_ID)
|
const lazyStyles = createStyleSheet(LAZY_STYLE_ID)
|
||||||
|
|
||||||
|
@ -177,7 +171,8 @@ export const applyTheme = async (input, onFinish = (data) => {}) => {
|
||||||
onFinish(cache)
|
onFinish(cache)
|
||||||
localStorage.setItem('pleroma-fe-theme-cache', JSON.stringify(cache))
|
localStorage.setItem('pleroma-fe-theme-cache', JSON.stringify(cache))
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
debug
|
||||||
)
|
)
|
||||||
|
|
||||||
setTimeout(lazyProcessFunc, 0)
|
setTimeout(lazyProcessFunc, 0)
|
||||||
|
@ -185,15 +180,52 @@ export const applyTheme = async (input, onFinish = (data) => {}) => {
|
||||||
return Promise.resolve()
|
return Promise.resolve()
|
||||||
}
|
}
|
||||||
|
|
||||||
const configColumns = ({ sidebarColumnWidth, contentColumnWidth, notifsColumnWidth, emojiReactionsScale }) =>
|
const extractStyleConfig = ({
|
||||||
({ sidebarColumnWidth, contentColumnWidth, notifsColumnWidth, emojiReactionsScale })
|
sidebarColumnWidth,
|
||||||
|
contentColumnWidth,
|
||||||
|
notifsColumnWidth,
|
||||||
|
emojiReactionsScale,
|
||||||
|
emojiSize,
|
||||||
|
navbarSize,
|
||||||
|
panelHeaderSize,
|
||||||
|
textSize,
|
||||||
|
forcedRoundness
|
||||||
|
}) => {
|
||||||
|
const result = {
|
||||||
|
sidebarColumnWidth,
|
||||||
|
contentColumnWidth,
|
||||||
|
notifsColumnWidth,
|
||||||
|
emojiReactionsScale,
|
||||||
|
emojiSize,
|
||||||
|
navbarSize,
|
||||||
|
panelHeaderSize,
|
||||||
|
textSize
|
||||||
|
}
|
||||||
|
|
||||||
const defaultConfigColumns = configColumns(defaultState)
|
switch (forcedRoundness) {
|
||||||
|
case 'disable':
|
||||||
|
break
|
||||||
|
case '0':
|
||||||
|
result.forcedRoundness = '0'
|
||||||
|
break
|
||||||
|
case '1':
|
||||||
|
result.forcedRoundness = '1px'
|
||||||
|
break
|
||||||
|
case '2':
|
||||||
|
result.forcedRoundness = '0.4rem'
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
export const applyConfig = (config) => {
|
return result
|
||||||
const columns = configColumns(config)
|
}
|
||||||
|
|
||||||
if (columns === defaultConfigColumns) {
|
const defaultStyleConfig = extractStyleConfig(defaultState)
|
||||||
|
|
||||||
|
export const applyConfig = (input) => {
|
||||||
|
const config = extractStyleConfig(input)
|
||||||
|
|
||||||
|
if (config === defaultStyleConfig) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -202,16 +234,25 @@ export const applyConfig = (config) => {
|
||||||
body.classList.add('hidden')
|
body.classList.add('hidden')
|
||||||
|
|
||||||
const rules = Object
|
const rules = Object
|
||||||
.entries(columns)
|
.entries(config)
|
||||||
.filter(([k, v]) => v)
|
.filter(([k, v]) => v)
|
||||||
.map(([k, v]) => `--${k}: ${v}`).join(';')
|
.map(([k, v]) => `--${k}: ${v}`).join(';')
|
||||||
|
|
||||||
|
document.getElementById('style-config')?.remove()
|
||||||
const styleEl = document.createElement('style')
|
const styleEl = document.createElement('style')
|
||||||
|
styleEl.id = 'style-config'
|
||||||
head.appendChild(styleEl)
|
head.appendChild(styleEl)
|
||||||
const styleSheet = styleEl.sheet
|
const styleSheet = styleEl.sheet
|
||||||
|
|
||||||
styleSheet.toString()
|
styleSheet.toString()
|
||||||
styleSheet.insertRule(`:root { ${rules} }`, 'index-max')
|
styleSheet.insertRule(`:root { ${rules} }`, 'index-max')
|
||||||
|
|
||||||
|
if (Object.prototype.hasOwnProperty.call(config, 'forcedRoundness')) {
|
||||||
|
styleSheet.insertRule(` * {
|
||||||
|
--roundness: var(--forcedRoundness) !important;
|
||||||
|
}`, 'index-max')
|
||||||
|
}
|
||||||
|
|
||||||
body.classList.remove('hidden')
|
body.classList.remove('hidden')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -269,5 +310,3 @@ export const getPreset = (val) => {
|
||||||
return { theme: data, source: theme.source }
|
return { theme: data, source: theme.source }
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export const setPreset = (val) => getPreset(val).then(data => applyTheme(data))
|
|
||||||
|
|
|
@ -2,11 +2,6 @@ import { convert } from 'chromatism'
|
||||||
|
|
||||||
import { hex2rgb, rgba2css } from '../color_convert/color_convert.js'
|
import { hex2rgb, rgba2css } from '../color_convert/color_convert.js'
|
||||||
|
|
||||||
// This changes what backgrounds are used to "stacked" solid colors so you can see
|
|
||||||
// what theme engine "thinks" is actual background color is for purposes of text color
|
|
||||||
// generation and for when --stacked variable is used
|
|
||||||
const DEBUG = false
|
|
||||||
|
|
||||||
export const parseCssShadow = (text) => {
|
export const parseCssShadow = (text) => {
|
||||||
const dimensions = /(\d[a-z]*\s?){2,4}/.exec(text)?.[0]
|
const dimensions = /(\d[a-z]*\s?){2,4}/.exec(text)?.[0]
|
||||||
const inset = /inset/.exec(text)?.[0]
|
const inset = /inset/.exec(text)?.[0]
|
||||||
|
@ -66,7 +61,10 @@ export const getCssShadowFilter = (input) => {
|
||||||
.join(' ')
|
.join(' ')
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getCssRules = (rules) => rules.map(rule => {
|
// `debug` changes what backgrounds are used to "stacked" solid colors so you can see
|
||||||
|
// what theme engine "thinks" is actual background color is for purposes of text color
|
||||||
|
// generation and for when --stacked variable is used
|
||||||
|
export const getCssRules = (rules, debug) => rules.map(rule => {
|
||||||
let selector = rule.selector
|
let selector = rule.selector
|
||||||
if (!selector) {
|
if (!selector) {
|
||||||
selector = 'html'
|
selector = 'html'
|
||||||
|
@ -93,7 +91,7 @@ export const getCssRules = (rules) => rules.map(rule => {
|
||||||
].join(';\n ')
|
].join(';\n ')
|
||||||
}
|
}
|
||||||
case 'background': {
|
case 'background': {
|
||||||
if (DEBUG) {
|
if (debug) {
|
||||||
return `
|
return `
|
||||||
--background: ${getCssColorString(rule.dynamicVars.stacked)};
|
--background: ${getCssColorString(rule.dynamicVars.stacked)};
|
||||||
background-color: ${getCssColorString(rule.dynamicVars.stacked)};
|
background-color: ${getCssColorString(rule.dynamicVars.stacked)};
|
||||||
|
@ -161,3 +159,15 @@ export const getCssRules = (rules) => rules.map(rule => {
|
||||||
footer
|
footer
|
||||||
].join('\n')
|
].join('\n')
|
||||||
}).filter(x => x)
|
}).filter(x => x)
|
||||||
|
|
||||||
|
export const getScopedVersion = (rules, newScope) => {
|
||||||
|
return rules.map(x => {
|
||||||
|
if (x.startsWith('html')) {
|
||||||
|
return x.replace('html', newScope)
|
||||||
|
} else if (x.startsWith('#content')) {
|
||||||
|
return x.replace('#content', newScope)
|
||||||
|
} else {
|
||||||
|
return newScope + ' > ' + x
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -39,7 +39,23 @@ export const getAllPossibleCombinations = (array) => {
|
||||||
return combos.reduce((acc, x) => [...acc, ...x], [])
|
return combos.reduce((acc, x) => [...acc, ...x], [])
|
||||||
}
|
}
|
||||||
|
|
||||||
// Converts rule, parents and their criteria into a CSS (or path if ignoreOutOfTreeSelector == true) selector
|
/**
|
||||||
|
* Converts rule, parents and their criteria into a CSS (or path if ignoreOutOfTreeSelector == true)
|
||||||
|
* selector.
|
||||||
|
*
|
||||||
|
* "path" here refers to "fake" selector that cannot be actually used in UI but is used for internal
|
||||||
|
* purposes
|
||||||
|
*
|
||||||
|
* @param {Object} components - object containing all components definitions
|
||||||
|
*
|
||||||
|
* @returns {Function}
|
||||||
|
* @param {Object} rule - rule in question to convert to CSS selector
|
||||||
|
* @param {boolean} ignoreOutOfTreeSelector - wthether to ignore aformentioned field in
|
||||||
|
* component definition and use selector
|
||||||
|
* @param {boolean} isParent - (mostly) internal argument used when recursing
|
||||||
|
*
|
||||||
|
* @returns {String} CSS selector (or path)
|
||||||
|
*/
|
||||||
export const genericRuleToSelector = components => (rule, ignoreOutOfTreeSelector, isParent) => {
|
export const genericRuleToSelector = components => (rule, ignoreOutOfTreeSelector, isParent) => {
|
||||||
if (!rule && !isParent) return null
|
if (!rule && !isParent) return null
|
||||||
const component = components[rule.component]
|
const component = components[rule.component]
|
||||||
|
@ -79,6 +95,17 @@ export const genericRuleToSelector = components => (rule, ignoreOutOfTreeSelecto
|
||||||
return selectors.trim()
|
return selectors.trim()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if combination matches
|
||||||
|
*
|
||||||
|
* @param {Object} criteria - criteria to match against
|
||||||
|
* @param {Object} subject - rule/combination to check match
|
||||||
|
* @param {boolean} strict - strict checking:
|
||||||
|
* By default every variant and state inherits from "normal" state/variant
|
||||||
|
* so when checking if combination matches, it WILL match against "normal"
|
||||||
|
* state/variant. In strict mode inheritance is ignored an "normal" does
|
||||||
|
* not match
|
||||||
|
*/
|
||||||
export const combinationsMatch = (criteria, subject, strict) => {
|
export const combinationsMatch = (criteria, subject, strict) => {
|
||||||
if (criteria.component !== subject.component) return false
|
if (criteria.component !== subject.component) return false
|
||||||
|
|
||||||
|
@ -101,6 +128,15 @@ export const combinationsMatch = (criteria, subject, strict) => {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search for rule that matches `criteria` in set of rules
|
||||||
|
* meant to be used in a ruleset.filter() function
|
||||||
|
*
|
||||||
|
* @param {Object} criteria - criteria to search for
|
||||||
|
* @param {boolean} strict - whether search strictly or not (see combinationsMatch)
|
||||||
|
*
|
||||||
|
* @return function that returns true/false if subject matches
|
||||||
|
*/
|
||||||
export const findRules = (criteria, strict) => subject => {
|
export const findRules = (criteria, strict) => subject => {
|
||||||
// If we searching for "general" rules - ignore "specific" ones
|
// If we searching for "general" rules - ignore "specific" ones
|
||||||
if (criteria.parent === null && !!subject.parent) return false
|
if (criteria.parent === null && !!subject.parent) return false
|
||||||
|
@ -125,6 +161,7 @@ export const findRules = (criteria, strict) => subject => {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Pre-fills 'normal' state/variant if missing
|
||||||
export const normalizeCombination = rule => {
|
export const normalizeCombination = rule => {
|
||||||
rule.variant = rule.variant ?? 'normal'
|
rule.variant = rule.variant ?? 'normal'
|
||||||
rule.state = [...new Set(['normal', ...(rule.state || [])])]
|
rule.state = [...new Set(['normal', ...(rule.state || [])])]
|
||||||
|
|
|
@ -12,7 +12,9 @@ export const basePaletteKeys = new Set([
|
||||||
'cBlue',
|
'cBlue',
|
||||||
'cRed',
|
'cRed',
|
||||||
'cGreen',
|
'cGreen',
|
||||||
'cOrange'
|
'cOrange',
|
||||||
|
|
||||||
|
'wallpaper'
|
||||||
])
|
])
|
||||||
|
|
||||||
export const fontsKeys = new Set([
|
export const fontsKeys = new Set([
|
||||||
|
@ -138,7 +140,7 @@ export const convertTheme2To3 = (data) => {
|
||||||
Object.keys(data.opacity || {}).forEach(key => {
|
Object.keys(data.opacity || {}).forEach(key => {
|
||||||
if (!opacityKeys.has(key) || data.opacity[key] === undefined) return null
|
if (!opacityKeys.has(key) || data.opacity[key] === undefined) return null
|
||||||
const originalOpacity = data.opacity[key]
|
const originalOpacity = data.opacity[key]
|
||||||
const rule = {}
|
const rule = { source: '2to3' }
|
||||||
|
|
||||||
switch (key) {
|
switch (key) {
|
||||||
case 'alert':
|
case 'alert':
|
||||||
|
@ -213,7 +215,7 @@ export const convertTheme2To3 = (data) => {
|
||||||
Object.keys(data.radii || {}).forEach(key => {
|
Object.keys(data.radii || {}).forEach(key => {
|
||||||
if (!radiiKeys.has(key) || data.radii[key] === undefined) return null
|
if (!radiiKeys.has(key) || data.radii[key] === undefined) return null
|
||||||
const originalRadius = data.radii[key]
|
const originalRadius = data.radii[key]
|
||||||
const rule = {}
|
const rule = { source: '2to3' }
|
||||||
|
|
||||||
switch (key) {
|
switch (key) {
|
||||||
case 'btn':
|
case 'btn':
|
||||||
|
@ -265,8 +267,9 @@ export const convertTheme2To3 = (data) => {
|
||||||
const newRules = []
|
const newRules = []
|
||||||
Object.keys(data.fonts || {}).forEach(key => {
|
Object.keys(data.fonts || {}).forEach(key => {
|
||||||
if (!fontsKeys.has(key)) return
|
if (!fontsKeys.has(key)) return
|
||||||
|
if (!data.fonts[key]) return
|
||||||
const originalFont = data.fonts[key].family
|
const originalFont = data.fonts[key].family
|
||||||
const rule = {}
|
const rule = { source: '2to3' }
|
||||||
|
|
||||||
switch (key) {
|
switch (key) {
|
||||||
case 'interface':
|
case 'interface':
|
||||||
|
@ -300,7 +303,7 @@ export const convertTheme2To3 = (data) => {
|
||||||
Object.keys(data.shadows || {}).forEach(key => {
|
Object.keys(data.shadows || {}).forEach(key => {
|
||||||
if (!shadowsKeys.has(key)) return
|
if (!shadowsKeys.has(key)) return
|
||||||
const originalShadow = data.shadows[key]
|
const originalShadow = data.shadows[key]
|
||||||
const rule = {}
|
const rule = { source: '2to3' }
|
||||||
|
|
||||||
switch (key) {
|
switch (key) {
|
||||||
case 'panel':
|
case 'panel':
|
||||||
|
@ -369,7 +372,7 @@ export const convertTheme2To3 = (data) => {
|
||||||
|
|
||||||
const extendedRules = Object.entries(extendedBaseKeys).map(([prefix, keys]) => {
|
const extendedRules = Object.entries(extendedBaseKeys).map(([prefix, keys]) => {
|
||||||
if (nonComponentPrefixes.has(prefix)) return null
|
if (nonComponentPrefixes.has(prefix)) return null
|
||||||
const rule = {}
|
const rule = { source: '2to3' }
|
||||||
if (prefix === 'alertPopup') {
|
if (prefix === 'alertPopup') {
|
||||||
rule.component = 'Alert'
|
rule.component = 'Alert'
|
||||||
rule.parent = { component: 'Popover' }
|
rule.parent = { component: 'Popover' }
|
||||||
|
@ -402,7 +405,7 @@ export const convertTheme2To3 = (data) => {
|
||||||
const leftoverKey = key.replace(prefix, '')
|
const leftoverKey = key.replace(prefix, '')
|
||||||
const parts = (leftoverKey || 'Bg').match(/[A-Z][a-z]*/g)
|
const parts = (leftoverKey || 'Bg').match(/[A-Z][a-z]*/g)
|
||||||
const last = parts.slice(-1)[0]
|
const last = parts.slice(-1)[0]
|
||||||
let newRule = { directives: {} }
|
let newRule = { source: '2to3', directives: {} }
|
||||||
let variantArray = []
|
let variantArray = []
|
||||||
|
|
||||||
switch (last) {
|
switch (last) {
|
||||||
|
@ -462,12 +465,12 @@ export const convertTheme2To3 = (data) => {
|
||||||
|
|
||||||
if (prefix === 'popover' && variantArray[0] === 'Post') {
|
if (prefix === 'popover' && variantArray[0] === 'Post') {
|
||||||
newRule.component = 'Post'
|
newRule.component = 'Post'
|
||||||
newRule.parent = { component: 'Popover' }
|
newRule.parent = { source: '2to3hack', component: 'Popover' }
|
||||||
variantArray = variantArray.filter(x => x !== 'Post')
|
variantArray = variantArray.filter(x => x !== 'Post')
|
||||||
}
|
}
|
||||||
|
|
||||||
if (prefix === 'selectedMenu' && variantArray[0] === 'Popover') {
|
if (prefix === 'selectedMenu' && variantArray[0] === 'Popover') {
|
||||||
newRule.parent = { component: 'Popover' }
|
newRule.parent = { source: '2to3hack', component: 'Popover' }
|
||||||
variantArray = variantArray.filter(x => x !== 'Popover')
|
variantArray = variantArray.filter(x => x !== 'Popover')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -477,12 +480,12 @@ export const convertTheme2To3 = (data) => {
|
||||||
case 'alert': {
|
case 'alert': {
|
||||||
const hasPanel = variantArray.find(x => x === 'Panel')
|
const hasPanel = variantArray.find(x => x === 'Panel')
|
||||||
if (hasPanel) {
|
if (hasPanel) {
|
||||||
newRule.parent = { component: 'PanelHeader' }
|
newRule.parent = { source: '2to3hack', component: 'PanelHeader', parent: newRule.parent }
|
||||||
variantArray = variantArray.filter(x => x !== 'Panel')
|
variantArray = variantArray.filter(x => x !== 'Panel')
|
||||||
}
|
}
|
||||||
const hasTop = variantArray.find(x => x === 'Top') // TopBar
|
const hasTop = variantArray.find(x => x === 'Top') // TopBar
|
||||||
if (hasTop) {
|
if (hasTop) {
|
||||||
newRule.parent = { component: 'TopBar' }
|
newRule.parent = { source: '2to3hack', component: 'TopBar', parent: newRule.parent }
|
||||||
variantArray = variantArray.filter(x => x !== 'Top' && x !== 'Bar')
|
variantArray = variantArray.filter(x => x !== 'Top' && x !== 'Bar')
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
|
|
|
@ -117,7 +117,6 @@ export const topoSort = (
|
||||||
// Put it into the output list
|
// Put it into the output list
|
||||||
output.push(node)
|
output.push(node)
|
||||||
} else if (grays.has(node)) {
|
} else if (grays.has(node)) {
|
||||||
console.debug('Cyclic depenency in topoSort, ignoring')
|
|
||||||
output.push(node)
|
output.push(node)
|
||||||
} else if (blacks.has(node)) {
|
} else if (blacks.has(node)) {
|
||||||
// do nothing
|
// do nothing
|
||||||
|
|
|
@ -149,16 +149,42 @@ const ruleToSelector = genericRuleToSelector(components)
|
||||||
|
|
||||||
export const getEngineChecksum = () => engineChecksum
|
export const getEngineChecksum = () => engineChecksum
|
||||||
|
|
||||||
export const init = (extraRuleset, ultimateBackgroundColor) => {
|
/**
|
||||||
|
* Initializes and compiles the theme according to the ruleset
|
||||||
|
*
|
||||||
|
* @param {Object[]} inputRuleset - set of rules to compile theme against. Acts as an override to
|
||||||
|
* component default rulesets
|
||||||
|
* @param {string} ultimateBackgroundColor - Color that will be the "final" background for
|
||||||
|
* calculating contrast ratios and making text automatically accessible. Really used for cases when
|
||||||
|
* stuff is transparent.
|
||||||
|
* @param {boolean} debug - print out debug information in console, mostly just performance stuff
|
||||||
|
* @param {boolean} liteMode - use validInnerComponentsLite instead of validInnerComponents, meant to
|
||||||
|
* generatate theme previews and such that need to be compiled faster and don't require a lot of other
|
||||||
|
* components present in "normal" mode
|
||||||
|
* @param {boolean} onlyNormalState - only use components 'normal' states, meant for generating theme
|
||||||
|
* previews since states are the biggest factor for compilation time and are completely unnecessary
|
||||||
|
* when previewing multiple themes at same time
|
||||||
|
* @param {string} rootComponentName - [UNTESTED] which component to start from, meant for previewing a
|
||||||
|
* part of the theme (i.e. just the button) for themes 3 editor.
|
||||||
|
*/
|
||||||
|
export const init = ({
|
||||||
|
inputRuleset,
|
||||||
|
ultimateBackgroundColor,
|
||||||
|
debug = false,
|
||||||
|
liteMode = false,
|
||||||
|
onlyNormalState = false,
|
||||||
|
rootComponentName = 'Root'
|
||||||
|
}) => {
|
||||||
|
if (!inputRuleset) throw new Error('Ruleset is null or undefined!')
|
||||||
const staticVars = {}
|
const staticVars = {}
|
||||||
const stacked = {}
|
const stacked = {}
|
||||||
const computed = {}
|
const computed = {}
|
||||||
|
|
||||||
const rulesetUnsorted = [
|
const rulesetUnsorted = [
|
||||||
...Object.values(components)
|
...Object.values(components)
|
||||||
.map(c => (c.defaultRules || []).map(r => ({ component: c.name, ...r })))
|
.map(c => (c.defaultRules || []).map(r => ({ component: c.name, ...r, source: 'Built-in' })))
|
||||||
.reduce((acc, arr) => [...acc, ...arr], []),
|
.reduce((acc, arr) => [...acc, ...arr], []),
|
||||||
...extraRuleset
|
...inputRuleset
|
||||||
].map(rule => {
|
].map(rule => {
|
||||||
normalizeCombination(rule)
|
normalizeCombination(rule)
|
||||||
let currentParent = rule.parent
|
let currentParent = rule.parent
|
||||||
|
@ -395,11 +421,16 @@ export const init = (extraRuleset, ultimateBackgroundColor) => {
|
||||||
const processInnerComponent = (component, parent) => {
|
const processInnerComponent = (component, parent) => {
|
||||||
const combinations = []
|
const combinations = []
|
||||||
const {
|
const {
|
||||||
validInnerComponents = [],
|
|
||||||
states: originalStates = {},
|
states: originalStates = {},
|
||||||
variants: originalVariants = {}
|
variants: originalVariants = {}
|
||||||
} = component
|
} = component
|
||||||
|
|
||||||
|
const validInnerComponents = (
|
||||||
|
liteMode
|
||||||
|
? (component.validInnerComponentsLite || component.validInnerComponents)
|
||||||
|
: component.validInnerComponents
|
||||||
|
) || []
|
||||||
|
|
||||||
// Normalizing states and variants to always include "normal"
|
// Normalizing states and variants to always include "normal"
|
||||||
const states = { normal: '', ...originalStates }
|
const states = { normal: '', ...originalStates }
|
||||||
const variants = { normal: '', ...originalVariants }
|
const variants = { normal: '', ...originalVariants }
|
||||||
|
@ -411,22 +442,26 @@ export const init = (extraRuleset, ultimateBackgroundColor) => {
|
||||||
|
|
||||||
// Optimization: we only really need combinations without "normal" because all states implicitly have it
|
// Optimization: we only really need combinations without "normal" because all states implicitly have it
|
||||||
const permutationStateKeys = Object.keys(states).filter(s => s !== 'normal')
|
const permutationStateKeys = Object.keys(states).filter(s => s !== 'normal')
|
||||||
const stateCombinations = [
|
const stateCombinations = onlyNormalState
|
||||||
['normal'],
|
? [
|
||||||
...getAllPossibleCombinations(permutationStateKeys)
|
['normal']
|
||||||
.map(combination => ['normal', ...combination])
|
]
|
||||||
.filter(combo => {
|
: [
|
||||||
// Optimization: filter out some hard-coded combinations that don't make sense
|
['normal'],
|
||||||
if (combo.indexOf('disabled') >= 0) {
|
...getAllPossibleCombinations(permutationStateKeys)
|
||||||
return !(
|
.map(combination => ['normal', ...combination])
|
||||||
combo.indexOf('hover') >= 0 ||
|
.filter(combo => {
|
||||||
combo.indexOf('focused') >= 0 ||
|
// Optimization: filter out some hard-coded combinations that don't make sense
|
||||||
combo.indexOf('pressed') >= 0
|
if (combo.indexOf('disabled') >= 0) {
|
||||||
)
|
return !(
|
||||||
}
|
combo.indexOf('hover') >= 0 ||
|
||||||
return true
|
combo.indexOf('focused') >= 0 ||
|
||||||
})
|
combo.indexOf('pressed') >= 0
|
||||||
]
|
)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
]
|
||||||
|
|
||||||
const stateVariantCombination = Object.keys(variants).map(variant => {
|
const stateVariantCombination = Object.keys(variants).map(variant => {
|
||||||
return stateCombinations.map(state => ({ variant, state }))
|
return stateCombinations.map(state => ({ variant, state }))
|
||||||
|
@ -451,9 +486,11 @@ export const init = (extraRuleset, ultimateBackgroundColor) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const t0 = performance.now()
|
const t0 = performance.now()
|
||||||
const combinations = processInnerComponent(components.Root)
|
const combinations = processInnerComponent(components[rootComponentName] ?? components.Root)
|
||||||
const t1 = performance.now()
|
const t1 = performance.now()
|
||||||
console.debug('Tree traveral took ' + (t1 - t0) + ' ms')
|
if (debug) {
|
||||||
|
console.debug('Tree traveral took ' + (t1 - t0) + ' ms')
|
||||||
|
}
|
||||||
|
|
||||||
const result = combinations.map((combination) => {
|
const result = combinations.map((combination) => {
|
||||||
if (combination.lazy) {
|
if (combination.lazy) {
|
||||||
|
@ -463,7 +500,9 @@ export const init = (extraRuleset, ultimateBackgroundColor) => {
|
||||||
}
|
}
|
||||||
}).filter(x => x)
|
}).filter(x => x)
|
||||||
const t2 = performance.now()
|
const t2 = performance.now()
|
||||||
console.debug('Eager processing took ' + (t2 - t1) + ' ms')
|
if (debug) {
|
||||||
|
console.debug('Eager processing took ' + (t2 - t1) + ' ms')
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
lazy: result.filter(x => typeof x === 'function'),
|
lazy: result.filter(x => typeof x === 'function'),
|
||||||
|
|
|
@ -66,7 +66,7 @@ describe('Theme Data 3', () => {
|
||||||
this.timeout(5000)
|
this.timeout(5000)
|
||||||
|
|
||||||
it('Test initialization without anything', () => {
|
it('Test initialization without anything', () => {
|
||||||
const out = init([], '#DEADAF')
|
const out = init({ inputRuleset: [], ultimateBackgroundColor: '#DEADAF' })
|
||||||
|
|
||||||
expect(out).to.have.property('eager')
|
expect(out).to.have.property('eager')
|
||||||
expect(out).to.have.property('lazy')
|
expect(out).to.have.property('lazy')
|
||||||
|
@ -85,13 +85,16 @@ describe('Theme Data 3', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Test initialization with a basic palette', () => {
|
it('Test initialization with a basic palette', () => {
|
||||||
const out = init([{
|
const out = init({
|
||||||
component: 'Root',
|
inputRuleset: [{
|
||||||
directives: {
|
component: 'Root',
|
||||||
'--bg': 'color | #008080',
|
directives: {
|
||||||
'--fg': 'color | #00C0A0'
|
'--bg': 'color | #008080',
|
||||||
}
|
'--fg': 'color | #00C0A0'
|
||||||
}], '#DEADAF')
|
}
|
||||||
|
}],
|
||||||
|
ultimateBackgroundColor: '#DEADAF'
|
||||||
|
})
|
||||||
|
|
||||||
expect(out.staticVars).to.have.property('bg').equal('#008080')
|
expect(out.staticVars).to.have.property('bg').equal('#008080')
|
||||||
expect(out.staticVars).to.have.property('fg').equal('#00C0A0')
|
expect(out.staticVars).to.have.property('fg').equal('#00C0A0')
|
||||||
|
@ -105,17 +108,20 @@ describe('Theme Data 3', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Test initialization with opacity', () => {
|
it('Test initialization with opacity', () => {
|
||||||
const out = init([{
|
const out = init({
|
||||||
component: 'Root',
|
inputRuleset: [{
|
||||||
directives: {
|
component: 'Root',
|
||||||
'--bg': 'color | #008080'
|
directives: {
|
||||||
}
|
'--bg': 'color | #008080'
|
||||||
}, {
|
}
|
||||||
component: 'Panel',
|
}, {
|
||||||
directives: {
|
component: 'Panel',
|
||||||
opacity: 0.5
|
directives: {
|
||||||
}
|
opacity: 0.5
|
||||||
}], '#DEADAF')
|
}
|
||||||
|
}],
|
||||||
|
ultimateBackgroundColor: '#DEADAF'
|
||||||
|
})
|
||||||
|
|
||||||
expect(out.staticVars).to.have.property('bg').equal('#008080')
|
expect(out.staticVars).to.have.property('bg').equal('#008080')
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue