import { createImmerReducer } from "utils/ReducerUtils"; import type { ReduxAction } from "actions/ReduxActionTypes"; import { ReduxActionTypes } from "ee/constants/ReduxActionConstants"; import type { WidgetProps } from "widgets/BaseWidget"; import { uniq, get, set } from "lodash"; import type { Diff } from "deep-diff"; import { diff } from "deep-diff"; import { getCanvasBottomRow, getCanvasWidgetHeightsToUpdate, } from "utils/WidgetSizeUtils"; import { klona } from "klona"; import type { UpdateCanvasPayload } from "actions/pageActions"; import type { SetWidgetDynamicPropertyPayload } from "../../../actions/controlActions"; /* This type is an object whose keys are widgetIds and values are arrays with property paths and property values For example: { "xyz123": [{ propertyPath: "bottomRow", propertyValue: 20 }] } */ export type UpdateWidgetsPayload = Record< string, Array<{ propertyPath: string; propertyValue: unknown; }> >; export const initialState: CanvasWidgetsReduxState = {}; export type FlattenedWidgetProps = | (WidgetProps & { children?: string[]; }) | orType; /** * * @param updateLayoutDiff * @returns list of widgets that were updated */ export function getUpdatedWidgetLists( updateLayoutDiff: Diff< CanvasWidgetsReduxState, { [widgetId: string]: WidgetProps; } >[], ) { return uniq( updateLayoutDiff .map((diff: Diff) => diff.path?.[0]) .filter((widgetId) => !!widgetId), ); } export const handlers = { [ReduxActionTypes.INIT_CANVAS_LAYOUT]: ( state: CanvasWidgetsReduxState, action: ReduxAction, ) => { const { widgets } = action.payload; for (const [widgetId, widgetProps] of Object.entries(widgets)) { if (widgetProps.type === "CANVAS_WIDGET") { const bottomRow = getCanvasBottomRow(widgetId, widgets); widgets[widgetId].bottomRow = bottomRow; } } return widgets; }, [ReduxActionTypes.UPDATE_LAYOUT]: ( state: CanvasWidgetsReduxState, action: ReduxAction, ) => { let listOfUpdatedWidgets; // if payload has knowledge of which widgets were changed, use that if (action.payload.updatedWidgetIds) { listOfUpdatedWidgets = action.payload.updatedWidgetIds; } // else diff out the widgets that need to be updated else { const updatedLayoutDiffs = diff(state, action.payload.widgets); if (!updatedLayoutDiffs) return state; listOfUpdatedWidgets = getUpdatedWidgetLists(updatedLayoutDiffs); } //update only the widgets that need to be updated. for (const widgetId of listOfUpdatedWidgets) { const updatedWidget = action.payload.widgets[widgetId]; if (updatedWidget) { state[widgetId] = updatedWidget; } else { delete state[widgetId]; } } const canvasWidgetHeightsToUpdate: Record = getCanvasWidgetHeightsToUpdate(listOfUpdatedWidgets, state); for (const widgetId in canvasWidgetHeightsToUpdate) { state[widgetId] = { ...state[widgetId], bottomRow: canvasWidgetHeightsToUpdate[widgetId], }; } }, [ReduxActionTypes.UPDATE_MULTIPLE_WIDGET_PROPERTIES]: ( state: CanvasWidgetsReduxState, action: ReduxAction<{ widgetsToUpdate: UpdateWidgetsPayload; shouldEval: boolean; }>, ) => { // For each widget whose properties we would like to update for (const [widgetId, propertyPathsToUpdate] of Object.entries( action.payload.widgetsToUpdate, )) { // Iterate through each property to update in `widgetId` propertyPathsToUpdate.forEach(({ propertyPath, propertyValue }) => { const path = `${widgetId}.${propertyPath}`; // Get original value in reducer const originalPropertyValue = get(state, path); // If the original and new values are different if (propertyValue !== originalPropertyValue) // Set the new values set(state, path, propertyValue); }); } const canvasWidgetHeightsToUpdate: Record = getCanvasWidgetHeightsToUpdate( Object.keys(action.payload.widgetsToUpdate), state, ); for (const widgetId in canvasWidgetHeightsToUpdate) { state[widgetId].bottomRow = canvasWidgetHeightsToUpdate[widgetId]; } }, [ReduxActionTypes.RESET_EDITOR_REQUEST]: () => { return klona(initialState); }, [ReduxActionTypes.SET_WIDGET_DYNAMIC_PROPERTY]: ( state: CanvasWidgetsReduxState, action: ReduxAction, ) => { const { isDynamic, propertyPath, widgetId } = action.payload; const widget = state[widgetId]; // When options JS mode is disabled, reset the optionLabel and optionValue to standard values if ( widget.type === "WDS_SELECT_WIDGET" && propertyPath === "options" && !isDynamic ) { set(state, `${widgetId}.optionLabel`, "label"); set(state, `${widgetId}.optionValue`, "value"); } return state; }, }; export interface CanvasWidgetsReduxState { [widgetId: string]: FlattenedWidgetProps; } const canvasWidgetsReducer = createImmerReducer(initialState, handlers); export default canvasWidgetsReducer;