Merge branch 'appearance-tab' into shigusegubu-themes3

This commit is contained in:
Henry Jameson 2024-06-26 00:05:28 +03:00
commit 6447f89d77
16 changed files with 267 additions and 111 deletions

View file

@ -0,0 +1 @@
Fix Themes v3 on Safari not working

View file

@ -4,6 +4,8 @@
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1,user-scalable=no"> <meta name="viewport" content="width=device-width, initial-scale=1,user-scalable=no">
<link rel="icon" type="image/png" href="/favicon.png"> <link rel="icon" type="image/png" href="/favicon.png">
<style id="pleroma-eager-styles" type="text/css"></style>
<style id="pleroma-lazy-styles" type="text/css"></style>
<!--server-generated-meta--> <!--server-generated-meta-->
</head> </head>
<body class="hidden"> <body class="hidden">

View file

@ -353,18 +353,20 @@ const afterStoreSetup = async ({ store, i18n }) => {
await setConfig({ store }) await setConfig({ store })
const { customTheme, customThemeSource, forceThemeRecompilation } = store.state.config const { customTheme, customThemeSource, forceThemeRecompilation, themeDebug } = store.state.config
const { theme } = store.state.instance const { theme } = store.state.instance
const customThemePresent = customThemeSource || customTheme const customThemePresent = customThemeSource || customTheme
if (!forceThemeRecompilation && tryLoadCache()) { console.log('DEBUG INITIAL', themeDebug, forceThemeRecompilation)
if (!forceThemeRecompilation && !themeDebug && tryLoadCache()) {
store.commit('setThemeApplied') store.commit('setThemeApplied')
} else { } else {
if (customThemePresent) { if (customThemePresent) {
if (customThemeSource && customThemeSource.themeEngineVersion === CURRENT_VERSION) { if (customThemeSource && customThemeSource.themeEngineVersion === CURRENT_VERSION) {
applyTheme(customThemeSource) applyTheme(customThemeSource, () => {}, themeDebug)
} else { } else {
applyTheme(customTheme) applyTheme(customTheme, () => {}, themeDebug)
} }
store.commit('setThemeApplied') store.commit('setThemeApplied')
} else if (theme) { } else if (theme) {

View file

@ -106,6 +106,9 @@ const EmojiPicker = {
} }
}, },
inject: ['popoversZLayer'], inject: ['popoversZLayer'],
mounted () {
this.updateEmojiSize()
},
data () { data () {
return { return {
keyword: '', keyword: '',
@ -120,6 +123,7 @@ const EmojiPicker = {
groupRefs: {}, groupRefs: {},
emojiRefs: {}, emojiRefs: {},
filteredEmojiGroups: [], filteredEmojiGroups: [],
emojiSize: 0,
width: 0 width: 0
} }
}, },
@ -130,6 +134,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 = 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()
@ -268,14 +289,25 @@ const EmojiPicker = {
minItemSize () { minItemSize () {
return this.emojiHeight return this.emojiHeight
}, },
// used to watch it
fontSize () {
this.$nextTick(() => {
this.updateEmojiSize()
})
return this.$store.getters.mergedConfig.fontSize
},
emojiHeight () { emojiHeight () {
return 32 + 4 return this.emojiSize
}, },
emojiWidth () { emojiWidth () {
return 32 + 4 return this.emojiSize
}, },
itemPerRow () { itemPerRow () {
return this.width ? Math.floor(this.width / this.emojiWidth - 1) : 6 console.log(
this.emojiWidth,
this.width
)
return this.width ? Math.floor(this.width / this.emojiWidth) : 6
}, },
activeGroupView () { activeGroupView () {
return this.showingStickers ? '' : this.activeGroup return this.showingStickers ? '' : this.activeGroup

View file

@ -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: 20.2em;
overflow: hidden; overflow: hidden;
} }
} }

View file

@ -22,6 +22,11 @@ const AppearanceTab = {
key: mode, key: mode,
value: mode, value: mode,
label: this.$t(`settings.third_column_mode_${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.forced_roundness_mode_${mode}`)
})) }))
} }
}, },

View file

@ -128,11 +128,36 @@
<div class="setting-item"> <div class="setting-item">
<h2>{{ $t('settings.visual_tweaks') }}</h2> <h2>{{ $t('settings.visual_tweaks') }}</h2>
<ul class="setting-list"> <ul class="setting-list">
<li>
<ChoiceSetting
id="forcedRoundness"
path="forcedRoundness"
:options="forcedRoundnessOptions"
>
{{ $t('settings.force_interface_roundness') }}
</ChoiceSetting>
</li>
<li v-if="instanceWallpaperUsed"> <li v-if="instanceWallpaperUsed">
<BooleanSetting path="hideInstanceWallpaper"> <BooleanSetting path="hideInstanceWallpaper">
{{ $t('settings.hide_wallpaper') }} {{ $t('settings.hide_wallpaper') }}
</BooleanSetting> </BooleanSetting>
</li> </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> </ul>
</div> </div>
</div> </div>

View file

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

View file

@ -386,6 +386,13 @@
"navbar_size": "Top bar size", "navbar_size": "Top bar size",
"panel_header_size": "Panel header size", "panel_header_size": "Panel header size",
"visual_tweaks": "Minor visual tweaks", "visual_tweaks": "Minor visual tweaks",
"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",
"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",

View file

@ -119,6 +119,8 @@ export const defaultState = {
textSize: undefined, // instance default textSize: undefined, // instance default
emojiSize: undefined, // instance default emojiSize: undefined, // instance default
navbarSize: 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
@ -145,6 +147,7 @@ export const defaultState = {
maxDepthInThread: undefined, // instance default maxDepthInThread: undefined, // instance default
autocompleteSelect: undefined, // instance default autocompleteSelect: undefined, // instance default
closingDrawerMarksAsSeen: undefined, // instance default closingDrawerMarksAsSeen: undefined, // instance default
themeDebug: false,
unseenAtTop: undefined, // instance default unseenAtTop: undefined, // instance default
ignoreInactionableSeen: undefined // instance default ignoreInactionableSeen: undefined // instance default
} }
@ -266,14 +269,22 @@ const config = {
case 'textSize': case 'textSize':
case 'navbarSize': case 'navbarSize':
case 'panelHeaderSize': case 'panelHeaderSize':
case 'forcedRoundness':
case 'emojiSize': case 'emojiSize':
case 'emojiReactionsScale': case 'emojiReactionsScale':
applyConfig(state) applyConfig(state)
break break
case 'customTheme': case 'customTheme':
case 'customThemeSource': case 'customThemeSource': {
applyTheme(value) const { themeDebug } = state
applyTheme(value, () => {}, themeDebug)
break break
}
case 'themeDebug': {
const { customTheme, customThemeSource } = state
applyTheme(customTheme || customThemeSource, () => {}, value)
break
}
case 'interfaceLanguage': case 'interfaceLanguage':
messages.setLanguage(this.getters.i18n, value) messages.setLanguage(this.getters.i18n, value)
dispatch('loadUnicodeEmojiData', value) dispatch('loadUnicodeEmojiData', value)

View file

@ -103,6 +103,7 @@ const defaultState = {
emojiSize: '2.2rem', emojiSize: '2.2rem',
navbarSize: '3.5rem', navbarSize: '3.5rem',
panelHeaderSize: '3.2rem', panelHeaderSize: '3.2rem',
forcedRoundness: -1,
virtualScrolling: true, virtualScrolling: true,
sensitiveByDefault: false, sensitiveByDefault: false,
conversationDisplay: 'linear', conversationDisplay: 'linear',
@ -382,16 +383,16 @@ const instance = {
.then(themeData => { .then(themeData => {
commit('setInstanceOption', { name: 'themeData', value: themeData }) commit('setInstanceOption', { name: 'themeData', value: themeData })
// No need to apply theme if there's user theme already // No need to apply theme if there's user theme already
const { customTheme } = rootState.config const { customTheme, themeDebug } = rootState.config
const { themeApplied } = rootState.interface const { themeApplied } = rootState.interface
if (customTheme || themeApplied) return if (customTheme || themeApplied) return
// New theme presets don't have 'theme' property, they use 'source' // New theme presets don't have 'theme' property, they use 'source'
const themeSource = themeData.source const themeSource = themeData.source
if (!themeData.theme || (themeSource && themeSource.themeEngineVersion === CURRENT_VERSION)) { if (!themeData.theme || (themeSource && themeSource.themeEngineVersion === CURRENT_VERSION)) {
applyTheme(themeSource) applyTheme(themeSource, null, themeDebug)
} else { } else {
applyTheme(themeData.theme) applyTheme(themeData.theme, null, themeDebug)
} }
commit('setThemeApplied') commit('setThemeApplied')
}) })

View file

@ -6,7 +6,46 @@ 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'
export const generateTheme = async (input, callbacks) => { // On platforms where this is not supported, it will return undefined
// Otherwise it will return an array
const supportsAdoptedStyleSheets = !!document.adoptedStyleSheets
const createStyleSheet = (id) => {
if (supportsAdoptedStyleSheets) {
return {
el: null,
sheet: new CSSStyleSheet(),
rules: []
}
}
const el = document.getElementById(id)
// Clear all rules in it
for (let i = el.sheet.cssRules.length - 1; i >= 0; --i) {
el.sheet.deleteRule(i)
}
return {
el,
sheet: el.sheet,
rules: []
}
}
const EAGER_STYLE_ID = 'pleroma-eager-styles'
const LAZY_STYLE_ID = 'pleroma-lazy-styles'
const adoptStyleSheets = (styles) => {
if (supportsAdoptedStyleSheets) {
document.adoptedStyleSheets = styles.map(s => s.sheet)
}
// Some older browsers do not support document.adoptedStyleSheets.
// In this case, we use the <style> elements.
// Since the <style> elements we need are already in the DOM, there
// is nothing to do here.
}
export const generateTheme = async (input, callbacks, debug) => {
const { const {
onNewRule = (rule, isLazy) => {}, onNewRule = (rule, isLazy) => {},
onLazyFinished = () => {}, onLazyFinished = () => {},
@ -22,9 +61,11 @@ export const generateTheme = async (input, callbacks) => {
} }
// 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(extraRules, extraRules[0].directives['--bg'].split('|')[1].trim(), debug)
getCssRules(themes3.eager, themes3.staticVars).forEach(rule => { console.log('DEBUG 2 IS', debug)
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)
@ -54,7 +95,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 = [
@ -98,13 +139,13 @@ export const tryLoadCache = () => {
return false return false
} }
if (cache.engineChecksum === getEngineChecksum()) { if (cache.engineChecksum === getEngineChecksum()) {
const styleSheet = new CSSStyleSheet() const eagerStyles = createStyleSheet(EAGER_STYLE_ID)
const lazyStyleSheet = new CSSStyleSheet() const lazyStyles = createStyleSheet(LAZY_STYLE_ID)
cache.data[0].forEach(rule => styleSheet.insertRule(rule, 'index-max')) cache.data[0].forEach(rule => eagerStyles.sheet.insertRule(rule, 'index-max'))
cache.data[1].forEach(rule => lazyStyleSheet.insertRule(rule, 'index-max')) cache.data[1].forEach(rule => lazyStyles.sheet.insertRule(rule, 'index-max'))
document.adoptedStyleSheets = [styleSheet, lazyStyleSheet] adoptStyleSheets([eagerStyles, lazyStyles])
return true return true
} else { } else {
@ -113,34 +154,35 @@ export const tryLoadCache = () => {
} }
} }
export const applyTheme = async (input, onFinish = (data) => {}) => { export const applyTheme = async (input, onFinish = (data) => {}, debug) => {
const styleSheet = new CSSStyleSheet() const eagerStyles = createStyleSheet(EAGER_STYLE_ID)
const styleArray = [] const lazyStyles = createStyleSheet(LAZY_STYLE_ID)
const lazyStyleSheet = new CSSStyleSheet()
const lazyStyleArray = [] console.log('DEBUG IS', debug)
const { lazyProcessFunc } = await generateTheme( const { lazyProcessFunc } = await generateTheme(
input, input,
{ {
onNewRule (rule, isLazy) { onNewRule (rule, isLazy) {
if (isLazy) { if (isLazy) {
lazyStyleSheet.insertRule(rule, 'index-max') lazyStyles.sheet.insertRule(rule, 'index-max')
lazyStyleArray.push(rule) lazyStyles.rules.push(rule)
} else { } else {
styleSheet.insertRule(rule, 'index-max') eagerStyles.sheet.insertRule(rule, 'index-max')
styleArray.push(rule) eagerStyles.rules.push(rule)
} }
}, },
onEagerFinished () { onEagerFinished () {
document.adoptedStyleSheets = [styleSheet] adoptStyleSheets([eagerStyles])
}, },
onLazyFinished () { onLazyFinished () {
document.adoptedStyleSheets = [styleSheet, lazyStyleSheet] adoptStyleSheets([eagerStyles, lazyStyles])
const cache = { engineChecksum: getEngineChecksum(), data: [styleArray, lazyStyleArray] } const cache = { engineChecksum: getEngineChecksum(), data: [eagerStyles.rules, lazyStyles.rules] }
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)
@ -153,22 +195,41 @@ const extractStyleConfig = ({
contentColumnWidth, contentColumnWidth,
notifsColumnWidth, notifsColumnWidth,
emojiReactionsScale, emojiReactionsScale,
roundnessOverride,
emojiSize, emojiSize,
navbarSize, navbarSize,
panelHeaderSize, panelHeaderSize,
textSize textSize,
}) => ({ forcedRoundness
sidebarColumnWidth, }) => {
contentColumnWidth, const result = {
notifsColumnWidth, sidebarColumnWidth,
emojiReactionsScale, contentColumnWidth,
roundnessOverride, notifsColumnWidth,
emojiSize, emojiReactionsScale,
navbarSize, emojiSize,
panelHeaderSize, navbarSize,
textSize panelHeaderSize,
}) textSize
}
console.log(forcedRoundness)
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:
}
return result
}
const defaultStyleConfig = extractStyleConfig(defaultState) const defaultStyleConfig = extractStyleConfig(defaultState)
@ -188,13 +249,21 @@ export const applyConfig = (input) => {
.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')
} }

View file

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

View file

@ -1,3 +1,5 @@
import { sortBy } from 'lodash'
// "Unrolls" a tree structure of item: { parent: { ...item2, parent: { ...item3, parent: {...} } }} // "Unrolls" a tree structure of item: { parent: { ...item2, parent: { ...item3, parent: {...} } }}
// into an array [item2, item3] for iterating // into an array [item2, item3] for iterating
export const unroll = (item) => { export const unroll = (item) => {
@ -24,7 +26,7 @@ export const getAllPossibleCombinations = (array) => {
}) })
const flatCombos = newCombos.reduce((acc, x) => [...acc, ...x], []) const flatCombos = newCombos.reduce((acc, x) => [...acc, ...x], [])
const uniqueComboStrings = new Set() const uniqueComboStrings = new Set()
const uniqueCombos = flatCombos.map(x => x.toSorted()).filter(x => { const uniqueCombos = flatCombos.map(sortBy).filter(x => {
if (uniqueComboStrings.has(x.join())) { if (uniqueComboStrings.has(x.join())) {
return false return false
} else { } else {
@ -64,7 +66,7 @@ export const genericRuleToSelector = components => (rule, ignoreOutOfTreeSelecto
} }
const selectors = [realSelector, applicableVariant, ...applicableStates] const selectors = [realSelector, applicableVariant, ...applicableStates]
.toSorted((a, b) => { .sort((a, b) => {
if (a.startsWith(':')) return 1 if (a.startsWith(':')) return 1
if (/^[a-z]/.exec(a)) return -1 if (/^[a-z]/.exec(a)) return -1
else return 0 else return 0

View file

@ -138,7 +138,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 +213,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':
@ -266,7 +266,7 @@ export const convertTheme2To3 = (data) => {
Object.keys(data.fonts || {}).forEach(key => { Object.keys(data.fonts || {}).forEach(key => {
if (!fontsKeys.has(key)) return if (!fontsKeys.has(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 +300,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 +369,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 +402,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 +462,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 +477,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

View file

@ -1,6 +1,6 @@
import { convert, brightness } from 'chromatism' import { convert, brightness } from 'chromatism'
import sum from 'hash-sum' import sum from 'hash-sum'
import { flattenDeep } from 'lodash' import { flattenDeep, sortBy } from 'lodash'
import { import {
alphaBlend, alphaBlend,
getTextColor, getTextColor,
@ -149,14 +149,14 @@ const ruleToSelector = genericRuleToSelector(components)
export const getEngineChecksum = () => engineChecksum export const getEngineChecksum = () => engineChecksum
export const init = (extraRuleset, ultimateBackgroundColor) => { export const init = (extraRuleset, ultimateBackgroundColor, debug) => {
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 ...extraRuleset
].map(rule => { ].map(rule => {
@ -226,7 +226,7 @@ export const init = (extraRuleset, ultimateBackgroundColor) => {
combination.variant === 'normal' combination.variant === 'normal'
? '' ? ''
: combination.variant[0].toUpperCase() + combination.variant.slice(1).toLowerCase(), : combination.variant[0].toUpperCase() + combination.variant.slice(1).toLowerCase(),
...combination.state.filter(x => x !== 'normal').toSorted().map(state => state[0].toUpperCase() + state.slice(1).toLowerCase()) ...sortBy(combination.state.filter(x => x !== 'normal')).map(state => state[0].toUpperCase() + state.slice(1).toLowerCase())
].join('') ].join('')
let inheritedTextColor = computedDirectives.textColor let inheritedTextColor = computedDirectives.textColor