fix(dashboard): live CSS preview in PropertiesModal (#38960)

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Michael S. Molina
2026-03-31 15:00:20 -03:00
committed by GitHub
parent 4fae5758d5
commit d4d22909cb

View File

@@ -53,6 +53,7 @@ import {
setColorScheme,
setDashboardMetadata,
} from 'src/dashboard/actions/dashboardState';
import { dashboardInfoChanged } from 'src/dashboard/actions/dashboardInfo';
import { areObjectsEqual } from 'src/reduxUtils';
import { StandardModal, useModalValidation } from 'src/components/Modal';
import { validateRefreshFrequency } from '../RefreshFrequency';
@@ -143,6 +144,8 @@ const PropertiesModal = ({
>([]);
const categoricalSchemeRegistry = getCategoricalSchemeRegistry();
const originalDashboardMetadata = useRef<Record<string, any>>({});
const originalCss = useRef<string | null>(null);
const cssDebounceTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
const handleErrorResponse = async (response: Response) => {
const { error, statusText, message } = await getClientErrorObject(response);
@@ -195,6 +198,9 @@ const PropertiesModal = ({
setOwners(owners);
setRoles(roles);
setCustomCss(css || '');
if (originalCss.current === null) {
originalCss.current = css || '';
}
setCurrentColorScheme(metadata?.color_scheme);
setSelectedThemeId(theme?.id || null);
@@ -269,7 +275,19 @@ const PropertiesModal = ({
setRoles(parsedRoles);
};
const handleOnCancel = () => onHide();
const handleOnCancel = () => {
if (cssDebounceTimer.current) {
clearTimeout(cssDebounceTimer.current);
cssDebounceTimer.current = null;
}
if (originalCss.current !== null) {
dispatch(dashboardInfoChanged({ css: originalCss.current }));
dispatch(
setColorScheme(originalDashboardMetadata.current.color_scheme ?? ''),
);
}
onHide();
};
const onColorSchemeChange = (
colorScheme = '',
@@ -429,6 +447,14 @@ const PropertiesModal = ({
}
};
// Must be defined before the data-loading effect so it runs first when show
// becomes true, ensuring handleDashboardData sees null and captures original CSS
useEffect(() => {
if (show) {
originalCss.current = null;
}
}, [show]);
useEffect(() => {
if (show) {
// Reset loading state when modal opens
@@ -596,6 +622,32 @@ const PropertiesModal = ({
const isDataReady = !isLoading && dashboardInfo;
// Debounced live CSS preview so changes are reflected on the dashboard
// without clicking Apply. Called only on user edits, not on data load.
const handleCustomCssChange = useCallback(
(css: string) => {
setCustomCss(css);
if (cssDebounceTimer.current) {
clearTimeout(cssDebounceTimer.current);
cssDebounceTimer.current = null;
}
cssDebounceTimer.current = setTimeout(() => {
dispatch(dashboardInfoChanged({ css }));
}, 500);
},
[dispatch],
);
useEffect(
() => () => {
if (cssDebounceTimer.current) {
clearTimeout(cssDebounceTimer.current);
cssDebounceTimer.current = null;
}
},
[],
);
// Validate basic section when title changes or data loads
useEffect(() => {
if (isDataReady) {
@@ -722,7 +774,7 @@ const PropertiesModal = ({
showChartTimestamps={showChartTimestamps}
onThemeChange={handleThemeChange}
onColorSchemeChange={onColorSchemeChange}
onCustomCssChange={setCustomCss}
onCustomCssChange={handleCustomCssChange}
onShowChartTimestampsChange={setShowChartTimestamps}
addDangerToast={addDangerToast}
/>