import { Box, IconButton, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Typography, useTheme, } from "@mui/material"; import { useSnackbar } from "notistack"; import { useCallback, useEffect, useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; import { DefaultCloseAction } from "../../../Common/Snackbar/snackbar"; import { NoWrapTableCell, SecondaryButton, StyledCheckbox, StyledTableContainerPaper, } from "../../../Common/StyledComponents"; import Add from "../../../Icons/Add"; import Delete from "../../../Icons/Delete"; import Edit from "../../../Icons/Edit"; import HexColorInput from "../../FileSystem/HexColorInput.tsx"; import ThemeOptionEditDialog from "./ThemeOptionEditDialog"; export interface ThemeOptionsProps { value: string; onChange: (value: string) => void; defaultTheme: string; onDefaultThemeChange: (value: string) => void; } interface ThemeOption { id: string; config: { light: { palette: { primary: { main: string; light?: string; dark?: string; }; secondary: { main: string; light?: string; dark?: string; }; }; }; dark: { palette: { primary: { main: string; light?: string; dark?: string; }; secondary: { main: string; light?: string; dark?: string; }; }; }; }; } const ThemeOptions = ({ value, onChange, defaultTheme, onDefaultThemeChange }: ThemeOptionsProps) => { const { t } = useTranslation("dashboard"); const theme = useTheme(); const { enqueueSnackbar } = useSnackbar(); const [options, setOptions] = useState>({}); const [editingOption, setEditingOption] = useState<{ id: string; config: ThemeOption["config"] } | null>(null); const [isDialogOpen, setIsDialogOpen] = useState(false); useEffect(() => { try { const parsedOptions = JSON.parse(value); setOptions(parsedOptions); } catch (e) { setOptions({}); } }, [value]); const handleSave = useCallback( (newOptions: Record) => { onChange(JSON.stringify(newOptions)); }, [onChange], ); const handleDelete = useCallback( (id: string) => { // Prevent deleting the default theme if (id === defaultTheme) { enqueueSnackbar({ message: t("settings.cannotDeleteDefaultTheme"), variant: "warning", action: DefaultCloseAction, }); return; } const newOptions = { ...options }; delete newOptions[id]; handleSave(newOptions); }, [options, handleSave, defaultTheme, enqueueSnackbar, t], ); const handleEdit = useCallback( (id: string) => { setEditingOption({ id, config: options[id] }); setIsDialogOpen(true); }, [options], ); const handleAdd = useCallback(() => { // Generate a new default theme option with a random color const randomColor = `#${Math.floor(Math.random() * 16777215).toString(16)}`; setEditingOption({ id: randomColor, config: { light: { palette: { primary: { main: randomColor }, secondary: { main: "#f50057" }, }, }, dark: { palette: { primary: { main: randomColor }, secondary: { main: "#f50057" }, }, }, }, }); setIsDialogOpen(true); }, []); const handleDialogClose = useCallback(() => { setIsDialogOpen(false); setEditingOption(null); }, []); const handleDialogSave = useCallback( (id: string, newId: string, config: string) => { try { const parsedConfig = JSON.parse(config); const newOptions = { ...options }; // If ID has changed (primary color changed), delete the old entry and create a new one if (id !== newId) { // Check if the new ID already exists if (newOptions[newId]) { enqueueSnackbar({ message: t("settings.duplicateThemeColor"), variant: "warning", action: DefaultCloseAction, }); return; } // If we're changing the ID of the default theme, update the default theme reference if (id === defaultTheme) { onDefaultThemeChange(newId); } delete newOptions[id]; } newOptions[newId] = parsedConfig; handleSave(newOptions); setIsDialogOpen(false); setEditingOption(null); } catch (e) { // Handle error enqueueSnackbar({ message: t("settings.invalidThemeConfig"), variant: "warning", action: DefaultCloseAction, }); } }, [options, handleSave, enqueueSnackbar, defaultTheme, onDefaultThemeChange, t], ); const handleColorChange = useCallback( (id: string, type: "primary" | "secondary", mode: "light" | "dark", color: string) => { const newOptions = { ...options }; if (type === "primary" && mode === "light") { // If changing the primary color (which is the ID), we need to create a new entry const newId = color; // Check if the new ID already exists if (newOptions[newId] && newId !== id) { enqueueSnackbar({ message: t("settings.duplicateThemeColor"), variant: "warning", action: DefaultCloseAction, }); return; } const config = { ...newOptions[id] }; config[mode].palette[type].main = color; // Delete old entry and create new one with the updated ID delete newOptions[id]; newOptions[newId] = config; // If we're changing the ID of the default theme, update the default theme reference if (id === defaultTheme) { onDefaultThemeChange(newId); } } else { // For other colors, just update the value newOptions[id][mode].palette[type].main = color; } handleSave(newOptions); }, [options, handleSave, enqueueSnackbar, t, defaultTheme, onDefaultThemeChange], ); const handleDefaultThemeChange = useCallback( (id: string) => { onDefaultThemeChange(id); }, [onDefaultThemeChange], ); const optionsArray = useMemo(() => { return Object.entries(options).map(([id, config]) => ({ id, config, })); }, [options]); return ( {t("settings.themeOptions")} {t("settings.themeOptionsDes")} {optionsArray.length > 0 && ( {t("settings.defaultTheme")} {t("settings.primaryColor")} {t("settings.secondaryColor")} {t("settings.primaryColorDark")} {t("settings.secondaryColorDark")} {optionsArray.map((option) => ( handleDefaultThemeChange(option.id)} /> handleColorChange(option.id, "primary", "light", color)} /> handleColorChange(option.id, "secondary", "light", color)} /> handleColorChange(option.id, "primary", "dark", color)} /> handleColorChange(option.id, "secondary", "dark", color)} /> handleEdit(option.id)}> handleDelete(option.id)} disabled={optionsArray.length === 1 || option.id === defaultTheme} > ))}
)} } onClick={handleAdd} sx={{ mt: 2 }}> {t("settings.addThemeOption")} {editingOption && ( )}
); }; export default ThemeOptions;