mirror of
https://github.com/zhom/donutbrowser.git
synced 2026-04-23 12:26:17 +02:00
refactor: better theme initialization
This commit is contained in:
@@ -187,13 +187,14 @@ 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 applyCustomTheme = useCallback((vars: Record<string, string>) => {
|
||||
const root = document.documentElement;
|
||||
Object.entries(vars).forEach(([k, v]) => root.style.setProperty(k, v));
|
||||
Object.entries(vars).forEach(([k, v]) =>
|
||||
root.style.setProperty(k, v, "important"),
|
||||
);
|
||||
}, []);
|
||||
const _clearCustomTheme = useCallback(() => {
|
||||
|
||||
const clearCustomTheme = useCallback(() => {
|
||||
const root = document.documentElement;
|
||||
THEME_VARIABLES.forEach(({ key }) => root.style.removeProperty(key));
|
||||
}, []);
|
||||
@@ -302,7 +303,7 @@ export function SettingsDialog({ isOpen, onClose }: SettingsDialogProps) {
|
||||
root.style.removeProperty(key),
|
||||
);
|
||||
Object.entries(settings.custom_theme).forEach(([k, v]) =>
|
||||
root.style.setProperty(k, v),
|
||||
root.style.setProperty(k, v, "important"),
|
||||
);
|
||||
} catch {}
|
||||
}
|
||||
@@ -331,6 +332,36 @@ export function SettingsDialog({ isOpen, onClose }: SettingsDialogProps) {
|
||||
[],
|
||||
);
|
||||
|
||||
const handleClose = useCallback(() => {
|
||||
// Restore original theme when closing without saving
|
||||
if (originalSettings.theme === "custom" && originalSettings.custom_theme) {
|
||||
applyCustomTheme(originalSettings.custom_theme);
|
||||
} else {
|
||||
clearCustomTheme();
|
||||
}
|
||||
onClose();
|
||||
}, [
|
||||
originalSettings.theme,
|
||||
originalSettings.custom_theme,
|
||||
applyCustomTheme,
|
||||
clearCustomTheme,
|
||||
onClose,
|
||||
]);
|
||||
|
||||
// Apply custom theme live when editing
|
||||
useEffect(() => {
|
||||
if (settings.theme === "custom" && settings.custom_theme) {
|
||||
applyCustomTheme(settings.custom_theme);
|
||||
} else if (settings.theme !== "custom") {
|
||||
clearCustomTheme();
|
||||
}
|
||||
}, [
|
||||
settings.theme,
|
||||
settings.custom_theme,
|
||||
applyCustomTheme,
|
||||
clearCustomTheme,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
loadSettings().catch(console.error);
|
||||
@@ -390,7 +421,7 @@ export function SettingsDialog({ isOpen, onClose }: SettingsDialogProps) {
|
||||
JSON.stringify(originalSettings.custom_theme ?? {});
|
||||
|
||||
return (
|
||||
<Dialog open={isOpen} onOpenChange={onClose}>
|
||||
<Dialog open={isOpen} onOpenChange={handleClose}>
|
||||
<DialogContent className="max-w-md max-h-[80vh] my-8 flex flex-col">
|
||||
<DialogHeader className="flex-shrink-0">
|
||||
<DialogTitle>Settings</DialogTitle>
|
||||
@@ -613,7 +644,7 @@ export function SettingsDialog({ isOpen, onClose }: SettingsDialogProps) {
|
||||
</div>
|
||||
|
||||
<DialogFooter className="flex-shrink-0">
|
||||
<RippleButton variant="outline" onClick={onClose}>
|
||||
<RippleButton variant="outline" onClick={handleClose}>
|
||||
Cancel
|
||||
</RippleButton>
|
||||
<LoadingButton
|
||||
|
||||
@@ -29,12 +29,29 @@ 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 === "light" ||
|
||||
themeValue === "dark" ||
|
||||
themeValue === "system"
|
||||
) {
|
||||
setDefaultTheme(themeValue);
|
||||
} else if (themeValue === "custom") {
|
||||
setDefaultTheme("light");
|
||||
if (
|
||||
settings.custom_theme &&
|
||||
Object.keys(settings.custom_theme).length > 0
|
||||
) {
|
||||
try {
|
||||
const root = document.documentElement;
|
||||
// Apply with !important to override CSS defaults
|
||||
Object.entries(settings.custom_theme).forEach(([k, v]) => {
|
||||
root.style.setProperty(k, v, "important");
|
||||
});
|
||||
} catch (error) {
|
||||
console.warn("Failed to apply custom theme variables:", error);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
setDefaultTheme("system");
|
||||
}
|
||||
@@ -53,6 +70,30 @@ export function CustomThemeProvider({ children }: CustomThemeProviderProps) {
|
||||
void loadTheme();
|
||||
}, []);
|
||||
|
||||
// Additional effect to ensure custom theme is applied after mount
|
||||
useEffect(() => {
|
||||
if (!isLoading && _mounted) {
|
||||
const reapplyCustomTheme = async () => {
|
||||
try {
|
||||
const { invoke } = await import("@tauri-apps/api/core");
|
||||
const settings = await invoke<AppSettings>("get_app_settings");
|
||||
|
||||
if (settings?.theme === "custom" && settings.custom_theme) {
|
||||
const root = document.documentElement;
|
||||
Object.entries(settings.custom_theme).forEach(([k, v]) => {
|
||||
root.style.setProperty(k, v, "important");
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn("Failed to reapply custom theme:", error);
|
||||
}
|
||||
};
|
||||
|
||||
// Apply after a short delay to ensure CSS has loaded
|
||||
setTimeout(reapplyCustomTheme, 100);
|
||||
}
|
||||
}, [isLoading, _mounted]);
|
||||
|
||||
if (isLoading) {
|
||||
// Keep UI simple during initial settings load to avoid flicker
|
||||
return null;
|
||||
|
||||
@@ -112,20 +112,13 @@ export const ColorPicker = ({
|
||||
(Number.isFinite(c.alpha()) ? c.alpha() : 1) * 100,
|
||||
);
|
||||
|
||||
// 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);
|
||||
}
|
||||
// Update internal state unconditionally when value prop changes
|
||||
setHue(nextHue);
|
||||
setSaturation(nextSat);
|
||||
setLightness(nextLight);
|
||||
setAlpha(nextAlpha);
|
||||
}
|
||||
}, [value, alpha, hue, lightness, saturation]);
|
||||
}, [value]); // Remove state values from dependency array to prevent infinite loop
|
||||
|
||||
// Notify parent of changes
|
||||
useEffect(() => {
|
||||
@@ -175,7 +168,8 @@ export const ColorPickerSelection = memo(
|
||||
const [isDragging, setIsDragging] = useState(false);
|
||||
const [positionX, setPositionX] = useState(0);
|
||||
const [positionY, setPositionY] = useState(0);
|
||||
const { hue, setSaturation, setLightness } = useColorPicker();
|
||||
const { hue, saturation, lightness, setSaturation, setLightness } =
|
||||
useColorPicker();
|
||||
|
||||
const backgroundGradient = useMemo(() => {
|
||||
return `linear-gradient(0deg, rgba(0,0,0,1), rgba(0,0,0,0)),
|
||||
@@ -183,6 +177,17 @@ export const ColorPickerSelection = memo(
|
||||
hsl(${hue}, 100%, 50%)`;
|
||||
}, [hue]);
|
||||
|
||||
// Update position indicators when saturation/lightness change externally
|
||||
useEffect(() => {
|
||||
if (!isDragging) {
|
||||
const x = saturation / 100;
|
||||
const topLightness = x < 0.01 ? 100 : 50 + 50 * (1 - x);
|
||||
const y = topLightness > 0 ? 1 - lightness / topLightness : 0;
|
||||
setPositionX(x);
|
||||
setPositionY(Math.max(0, Math.min(1, y)));
|
||||
}
|
||||
}, [saturation, lightness, isDragging]);
|
||||
|
||||
const handlePointerMove = useCallback(
|
||||
(event: PointerEvent) => {
|
||||
if (!(isDragging && containerRef.current)) {
|
||||
|
||||
Reference in New Issue
Block a user