mirror of
https://github.com/coollabsio/coolify.git
synced 2026-11-06 21:18:11 -04:00
fix(theme): fall back to default color for invalid stored theme color
Closes #12287
This commit is contained in:
@@ -6,8 +6,16 @@
|
||||
// The OS color picker only speaks hex. Convert it once so the whole theme
|
||||
// cascade is authored in OKLCH (sRGB -> linear -> OKLab -> OKLCH).
|
||||
const srgbToLinear = (c) => c <= 0.04045 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4;
|
||||
// localStorage can hold values from older versions or manual edits, so accept
|
||||
// only #rgb / #rrggbb and fall back to the default color for anything else.
|
||||
window.normalizeThemeColor = (color) => {
|
||||
const hex = String(color ?? '').trim().replace(/^#/, '').toLowerCase();
|
||||
if (/^[a-f\d]{6}$/.test(hex)) return `#${hex}`;
|
||||
if (/^[a-f\d]{3}$/.test(hex)) return `#${hex.replace(/./g, '$&$&')}`;
|
||||
return '#6b16ed';
|
||||
};
|
||||
window.hexToOklch = (hex) => {
|
||||
const [r, g, b] = hex.match(/[a-f\d]{2}/gi).map((channel) => srgbToLinear(parseInt(channel, 16) / 255));
|
||||
const [r, g, b] = window.normalizeThemeColor(hex).match(/[a-f\d]{2}/gi).map((channel) => srgbToLinear(parseInt(channel, 16) / 255));
|
||||
const l = Math.cbrt(0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b);
|
||||
const m = Math.cbrt(0.2119034982 * r + 0.6806995451 * g + 0.1073969566 * b);
|
||||
const s = Math.cbrt(0.0883024619 * r + 0.2817188376 * g + 0.6299787005 * b);
|
||||
@@ -20,7 +28,7 @@
|
||||
return `oklch(${(okL * 100).toFixed(2)}% ${chroma.toFixed(4)} ${hue.toFixed(2)})`;
|
||||
};
|
||||
window.themeAccentForeground = (color) => {
|
||||
const channels = color.match(/[a-f\d]{2}/gi).map(channel => parseInt(channel, 16) * 0.85 + 255 * 0.15);
|
||||
const channels = window.normalizeThemeColor(color).match(/[a-f\d]{2}/gi).map(channel => parseInt(channel, 16) * 0.85 + 255 * 0.15);
|
||||
const luminance = channels
|
||||
.map(channel => channel / 255)
|
||||
.map(channel => channel <= 0.04045 ? channel / 12.92 : ((channel + 0.055) / 1.055) ** 2.4)
|
||||
@@ -30,7 +38,7 @@
|
||||
};
|
||||
window.applyStoredTheme = () => {
|
||||
const theme = localStorage.theme === 'purple' ? 'custom' : (localStorage.theme || 'dark');
|
||||
const themeColor = localStorage.themeColor || '#6b16ed';
|
||||
const themeColor = window.normalizeThemeColor(localStorage.themeColor);
|
||||
const customMode = localStorage.customMode || 'dark';
|
||||
document.documentElement.dataset.navStyle = localStorage.navStyle === 'floating' ? 'floating' : 'default';
|
||||
document.documentElement.dataset.navItems = localStorage.navItems === 'flat' ? 'flat' : '3d';
|
||||
@@ -39,6 +47,7 @@
|
||||
|| (theme === 'system' && matchMedia('(prefers-color-scheme: dark)').matches);
|
||||
|
||||
localStorage.theme = theme;
|
||||
if (localStorage.themeColor) localStorage.themeColor = themeColor;
|
||||
document.documentElement.classList.toggle('dark', isDark);
|
||||
document.documentElement.dataset.theme = theme === 'custom' ? 'custom' : (isDark ? 'dark' : 'light');
|
||||
document.documentElement.style.setProperty('--theme-base-color', window.hexToOklch(themeColor));
|
||||
@@ -49,7 +58,7 @@
|
||||
// Appearance page and the profile dropdown via x-data="themeControls()".
|
||||
window.themeControls = () => ({
|
||||
theme: localStorage.getItem('theme') === 'purple' ? 'custom' : (localStorage.getItem('theme') || 'dark'),
|
||||
themeColor: localStorage.getItem('themeColor') || '#6b16ed',
|
||||
themeColor: window.normalizeThemeColor(localStorage.getItem('themeColor')),
|
||||
customMode: localStorage.getItem('customMode') || 'dark',
|
||||
pageWidth: localStorage.getItem('pageWidth') || 'centered',
|
||||
navStyle: localStorage.getItem('navStyle') === 'floating' ? 'floating' : 'default',
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
beforeEach(function () {
|
||||
seedBrowserResourceStack();
|
||||
});
|
||||
|
||||
it('falls back to the default theme color when the stored color is not hex', function () {
|
||||
loginAndSkipBoarding();
|
||||
|
||||
$page = visit('/')->assertSee('Dashboard');
|
||||
$page->script("localStorage.setItem('theme', 'custom'); localStorage.setItem('themeColor', 'purple');");
|
||||
|
||||
$page = $page->navigate('/')->assertSee('Dashboard')->assertNoJavaScriptErrors();
|
||||
|
||||
$result = $page->script(<<<'JS'
|
||||
() => ({
|
||||
base: document.documentElement.style.getPropertyValue('--theme-base-color'),
|
||||
foreground: document.documentElement.style.getPropertyValue('--theme-accent-foreground'),
|
||||
stored: localStorage.getItem('themeColor'),
|
||||
shorthand: window.hexToOklch('#fff'),
|
||||
rgb: window.hexToOklch('rgb(107,22,237)'),
|
||||
oklch: window.themeAccentForeground('oklch(50% 0.2 290)'),
|
||||
})
|
||||
JS);
|
||||
|
||||
$page->screenshot(filename: 'theme-color-fallback');
|
||||
|
||||
expect($result)->toBe([
|
||||
'base' => 'oklch(49.65% 0.2709 289.33)',
|
||||
'foreground' => 'oklch(100% 0 0)',
|
||||
'stored' => '#6b16ed',
|
||||
'shorthand' => 'oklch(100.00% 0.0000 89.88)',
|
||||
'rgb' => 'oklch(49.65% 0.2709 289.33)',
|
||||
'oklch' => 'oklch(100% 0 0)',
|
||||
]);
|
||||
});
|
||||
Reference in New Issue
Block a user