refactor: better theme initialization

This commit is contained in:
zhom
2025-08-15 10:08:15 +04:00
parent fac99f4a51
commit 95ee807f3b
3 changed files with 99 additions and 22 deletions
+39 -8
View File
@@ -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
+41
View File
@@ -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;
+19 -14
View File
@@ -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)) {