mirror of
https://github.com/zhom/donutbrowser.git
synced 2026-04-23 20:36:09 +02:00
refactor: color picker
This commit is contained in:
+1
-1
@@ -24,7 +24,7 @@ export default function RootLayout({
|
||||
return (
|
||||
<html lang="en" suppressHydrationWarning>
|
||||
<body
|
||||
className={`${geistSans.variable} ${geistMono.variable} antialiased overflow-hidden`}
|
||||
className={`${geistSans.variable} ${geistMono.variable} antialiased overflow-hidden bg-background`}
|
||||
>
|
||||
<CustomThemeProvider>
|
||||
<WindowDragArea />
|
||||
|
||||
@@ -187,6 +187,17 @@ export function SettingsDialog({ isOpen, onClose }: SettingsDialogProps) {
|
||||
}
|
||||
}, []);
|
||||
|
||||
// Apply or clear custom theme live without restart
|
||||
// Defer application until Save
|
||||
const _applyCustomTheme = useCallback((vars: Record<string, string>) => {
|
||||
const root = document.documentElement;
|
||||
Object.entries(vars).forEach(([k, v]) => root.style.setProperty(k, v));
|
||||
}, []);
|
||||
const _clearCustomTheme = useCallback(() => {
|
||||
const root = document.documentElement;
|
||||
THEME_VARIABLES.forEach(({ key }) => root.style.removeProperty(key));
|
||||
}, []);
|
||||
|
||||
const loadPermissions = useCallback(async () => {
|
||||
setIsLoadingPermissions(true);
|
||||
try {
|
||||
@@ -281,6 +292,26 @@ export function SettingsDialog({ isOpen, onClose }: SettingsDialogProps) {
|
||||
try {
|
||||
await invoke("save_app_settings", { settings });
|
||||
setTheme(settings.theme === "custom" ? "dark" : settings.theme);
|
||||
// Apply or clear custom variables only on Save
|
||||
if (settings.theme === "custom") {
|
||||
if (settings.custom_theme) {
|
||||
try {
|
||||
const root = document.documentElement;
|
||||
// Clear any previous custom vars first
|
||||
THEME_VARIABLES.forEach(({ key }) =>
|
||||
root.style.removeProperty(key),
|
||||
);
|
||||
Object.entries(settings.custom_theme).forEach(([k, v]) =>
|
||||
root.style.setProperty(k, v),
|
||||
);
|
||||
} catch {}
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
const root = document.documentElement;
|
||||
THEME_VARIABLES.forEach(({ key }) => root.style.removeProperty(key));
|
||||
} catch {}
|
||||
}
|
||||
setOriginalSettings(settings);
|
||||
onClose();
|
||||
} catch (error) {
|
||||
@@ -418,7 +449,7 @@ export function SettingsDialog({ isOpen, onClose }: SettingsDialogProps) {
|
||||
<button
|
||||
type="button"
|
||||
aria-label={label}
|
||||
className="w-8 h-8 rounded-md border shadow-sm"
|
||||
className="w-8 h-8 rounded-md border shadow-sm cursor-pointer"
|
||||
style={{ backgroundColor: colorValue }}
|
||||
/>
|
||||
</PopoverTrigger>
|
||||
@@ -432,17 +463,12 @@ export function SettingsDialog({ isOpen, onClose }: SettingsDialogProps) {
|
||||
onColorChange={([r, g, b, a]) => {
|
||||
const next = Color({ r, g, b }).alpha(a);
|
||||
const nextStr = next.hexa();
|
||||
updateSetting("custom_theme", {
|
||||
const nextTheme = {
|
||||
...(settings.custom_theme ?? {}),
|
||||
[key]: nextStr,
|
||||
});
|
||||
// Live preview
|
||||
try {
|
||||
document.documentElement.style.setProperty(
|
||||
key,
|
||||
nextStr,
|
||||
);
|
||||
} catch {}
|
||||
} as Record<string, string>;
|
||||
updateSetting("custom_theme", nextTheme);
|
||||
// No live preview; applied on Save
|
||||
}}
|
||||
>
|
||||
<ColorPickerSelection className="h-36 rounded" />
|
||||
|
||||
@@ -29,16 +29,7 @@ export function CustomThemeProvider({ children }: CustomThemeProviderProps) {
|
||||
const { invoke } = await import("@tauri-apps/api/core");
|
||||
const settings = await invoke<AppSettings>("get_app_settings");
|
||||
const themeValue = settings?.theme ?? "system";
|
||||
if (themeValue === "custom") {
|
||||
setDefaultTheme("light");
|
||||
const vars = settings.custom_theme ?? {};
|
||||
try {
|
||||
const root = document.documentElement;
|
||||
Object.entries(vars).forEach(([k, v]) => {
|
||||
root.style.setProperty(k, v);
|
||||
});
|
||||
} catch {}
|
||||
} else if (
|
||||
if (
|
||||
themeValue === "light" ||
|
||||
themeValue === "dark" ||
|
||||
themeValue === "system"
|
||||
|
||||
@@ -70,41 +70,74 @@ export const ColorPicker = ({
|
||||
children,
|
||||
...props
|
||||
}: ColorPickerProps) => {
|
||||
const selectedColor = Color(value);
|
||||
const selectedColor = Color(value ?? defaultValue);
|
||||
const defaultColor = Color(defaultValue);
|
||||
|
||||
const [hue, setHue] = useState(
|
||||
selectedColor.hue() || defaultColor.hue() || 0,
|
||||
);
|
||||
const [saturation, setSaturation] = useState(
|
||||
selectedColor.saturationl() || defaultColor.saturationl() || 100,
|
||||
);
|
||||
const [lightness, setLightness] = useState(
|
||||
selectedColor.lightness() || defaultColor.lightness() || 50,
|
||||
);
|
||||
const [alpha, setAlpha] = useState(
|
||||
selectedColor.alpha() * 100 || defaultColor.alpha() * 100,
|
||||
);
|
||||
const initialHue = Number.isFinite(selectedColor.hue())
|
||||
? selectedColor.hue()
|
||||
: Number.isFinite(defaultColor.hue())
|
||||
? defaultColor.hue()
|
||||
: 0;
|
||||
const initialSaturation = Number.isFinite(selectedColor.saturationl())
|
||||
? selectedColor.saturationl()
|
||||
: Number.isFinite(defaultColor.saturationl())
|
||||
? defaultColor.saturationl()
|
||||
: 100;
|
||||
const initialLightness = Number.isFinite(selectedColor.lightness())
|
||||
? selectedColor.lightness()
|
||||
: Number.isFinite(defaultColor.lightness())
|
||||
? defaultColor.lightness()
|
||||
: 50;
|
||||
const initialAlpha = Number.isFinite(selectedColor.alpha())
|
||||
? Math.round(selectedColor.alpha() * 100)
|
||||
: Math.round(defaultColor.alpha() * 100);
|
||||
|
||||
const [hue, setHue] = useState(initialHue);
|
||||
const [saturation, setSaturation] = useState(initialSaturation);
|
||||
const [lightness, setLightness] = useState(initialLightness);
|
||||
const [alpha, setAlpha] = useState(initialAlpha);
|
||||
const [mode, setMode] = useState("hex");
|
||||
const lastEmittedRef = useRef<string>(
|
||||
`${Math.round(initialHue)}|${Math.round(initialSaturation)}|${Math.round(initialLightness)}|${Math.round(initialAlpha)}`,
|
||||
);
|
||||
|
||||
// Update color when controlled value changes
|
||||
useEffect(() => {
|
||||
if (value) {
|
||||
const color = Color.rgb(value).rgb().object();
|
||||
if (value !== undefined) {
|
||||
const c = Color(value).hsl();
|
||||
const nextHue = Number.isFinite(c.hue()) ? c.hue() : 0;
|
||||
const nextSat = Number.isFinite(c.saturationl()) ? c.saturationl() : 0;
|
||||
const nextLight = Number.isFinite(c.lightness()) ? c.lightness() : 0;
|
||||
const nextAlpha = Math.round(
|
||||
(Number.isFinite(c.alpha()) ? c.alpha() : 1) * 100,
|
||||
);
|
||||
|
||||
setHue(color.r);
|
||||
setSaturation(color.g);
|
||||
setLightness(color.b);
|
||||
setAlpha(color.a);
|
||||
// Only update internal state if it actually changed
|
||||
if (
|
||||
Math.round(nextHue) !== Math.round(hue) ||
|
||||
Math.round(nextSat) !== Math.round(saturation) ||
|
||||
Math.round(nextLight) !== Math.round(lightness) ||
|
||||
Math.round(nextAlpha) !== Math.round(alpha)
|
||||
) {
|
||||
setHue(nextHue);
|
||||
setSaturation(nextSat);
|
||||
setLightness(nextLight);
|
||||
setAlpha(nextAlpha);
|
||||
}
|
||||
}
|
||||
}, [value]);
|
||||
}, [value, alpha, hue, lightness, saturation]);
|
||||
|
||||
// Notify parent of changes
|
||||
useEffect(() => {
|
||||
if (onColorChange) {
|
||||
const key = `${Math.round(hue)}|${Math.round(saturation)}|${Math.round(lightness)}|${Math.round(alpha)}`;
|
||||
if (key === lastEmittedRef.current) {
|
||||
return;
|
||||
}
|
||||
lastEmittedRef.current = key;
|
||||
|
||||
const color = Color.hsl(hue, saturation, lightness).alpha(alpha / 100);
|
||||
const rgba = color.rgb().array();
|
||||
|
||||
onColorChange([rgba[0], rgba[1], rgba[2], alpha / 100]);
|
||||
}
|
||||
}, [hue, saturation, lightness, alpha, onColorChange]);
|
||||
@@ -191,7 +224,7 @@ export const ColorPickerSelection = memo(
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn("relative rounded size-full cursor-crosshair", className)}
|
||||
className={cn("relative rounded cursor-pointer size-full", className)}
|
||||
onPointerDown={(e) => {
|
||||
e.preventDefault();
|
||||
setIsDragging(true);
|
||||
|
||||
Reference in New Issue
Block a user