PromucFlow_constructor/app/client/src/sagas/WidgetOperationSagas.tsx

430 lines
13 KiB
TypeScript

import {
ReduxActionTypes,
ReduxActionErrorTypes,
ReduxAction,
} from "constants/ReduxActionConstants";
import {
WidgetAddChild,
WidgetResize,
WidgetMove,
WidgetDelete,
updateAndSaveLayout,
} from "actions/pageActions";
import { FlattenedWidgetProps } from "reducers/entityReducers/canvasWidgetsReducer";
import { getWidgets, getWidget, getDefaultWidgetConfig } from "./selectors";
import {
generateWidgetProps,
updateWidgetPosition,
} from "utils/WidgetPropsUtils";
import {
call,
put,
select,
takeEvery,
takeLatest,
all,
} from "redux-saga/effects";
import { convertToString, getNextEntityName } from "utils/AppsmithUtils";
import {
SetWidgetDynamicPropertyPayload,
updateWidgetProperty,
UpdateWidgetPropertyRequestPayload,
} from "actions/controlActions";
import { isDynamicValue } from "utils/DynamicBindingUtils";
import { WidgetProps } from "widgets/BaseWidget";
import _ from "lodash";
import WidgetFactory from "utils/WidgetFactory";
import {
buildWidgetBlueprint,
executeWidgetBlueprintOperations,
} from "sagas/WidgetBlueprintSagas";
import { resetWidgetMetaProperty } from "actions/metaActions";
import { GridDefaults, WidgetTypes } from "constants/WidgetConstants";
import { ContainerWidgetProps } from "widgets/ContainerWidget";
import ValidationFactory from "utils/ValidationFactory";
function* getChildWidgetProps(
parent: ContainerWidgetProps<WidgetProps>,
params: WidgetAddChild,
widgets: { [widgetId: string]: FlattenedWidgetProps },
) {
const { leftColumn, topRow, newWidgetId, props, type } = params;
let { rows, columns, parentColumnSpace, parentRowSpace, widgetName } = params;
let minHeight = undefined;
const defaultConfig = yield select(getDefaultWidgetConfig, type);
if (!widgetName) {
const widgetNames = Object.keys(widgets).map(w => widgets[w].widgetName);
widgetName = getNextEntityName(defaultConfig.widgetName, widgetNames);
}
if (type === WidgetTypes.CANVAS_WIDGET) {
columns =
(parent.rightColumn - parent.leftColumn) * parent.parentColumnSpace;
parentColumnSpace = 1;
rows = (parent.bottomRow - parent.topRow) * parent.parentRowSpace;
parentRowSpace = 1;
minHeight = rows;
if (props) props.children = [];
}
const widgetProps = { ...defaultConfig, ...props, columns, rows, minHeight };
const widget = generateWidgetProps(
parent,
type,
leftColumn,
topRow,
parentRowSpace,
parentColumnSpace,
widgetName,
widgetProps,
);
widget.widgetId = newWidgetId;
return widget;
}
type GeneratedWidgetPayload = {
widgetId: string;
widgets: { [widgetId: string]: FlattenedWidgetProps };
};
function* generateChildWidgets(
parent: ContainerWidgetProps<WidgetProps>,
params: WidgetAddChild,
widgets: { [widgetId: string]: FlattenedWidgetProps },
): any {
const widget = yield getChildWidgetProps(parent, params, widgets);
widgets[widget.widgetId] = widget;
if (widget.blueprint && widget.blueprint.view) {
const childWidgetList: WidgetAddChild[] = yield call(
buildWidgetBlueprint,
widget.blueprint,
widget.widgetId,
);
const childPropsList: GeneratedWidgetPayload[] = yield all(
childWidgetList.map((props: WidgetAddChild) => {
return generateChildWidgets(widget, props, widgets);
}),
);
widget.children = [];
childPropsList.forEach((props: GeneratedWidgetPayload) => {
widget.children.push(props.widgetId);
widgets = props.widgets;
});
}
widgets[widget.widgetId] = widget;
if (
widget.blueprint &&
widget.blueprint.operations &&
widget.blueprint.operations.length > 0
) {
widgets = yield call(
executeWidgetBlueprintOperations,
widget.blueprint.operations,
widgets,
widget.widgetId,
);
}
return { widgetId: widget.widgetId, widgets };
}
export function* addChildSaga(addChildAction: ReduxAction<WidgetAddChild>) {
try {
const { widgetId } = addChildAction.payload;
// Get the current parent widget whose child will be the new widget.
const parent: FlattenedWidgetProps = yield select(getWidget, widgetId);
// Get all the widgets from the canvasWidgetsReducer
const widgets = yield select(getWidgets);
// Generate the full WidgetProps of the widget to be added.
const childWidgetPayload: GeneratedWidgetPayload = yield generateChildWidgets(
parent,
addChildAction.payload,
widgets,
);
// Update widgets to put back in the canvasWidgetsReducer
// TODO(abhinav): This won't work if dont already have an empty children: []
if (parent.children) {
parent.children.push(childWidgetPayload.widgetId);
}
widgets[parent.widgetId] = parent;
yield put(updateAndSaveLayout(widgets));
} catch (error) {
console.log(error);
yield put({
type: ReduxActionErrorTypes.WIDGET_OPERATION_ERROR,
payload: {
action: ReduxActionTypes.WIDGET_ADD_CHILD,
error,
},
});
}
}
export function* deleteSaga(deleteAction: ReduxAction<WidgetDelete>) {
try {
const { widgetId, parentId } = deleteAction.payload;
const widgets = yield select(getWidgets);
const widget = yield select(getWidget, widgetId);
const parent = yield select(getWidget, parentId);
// Remove entry from parent's children
parent.children = parent.children.filter(
(child: string) => child !== widgetId,
);
widgets[parentId] = parent;
// Remove child widgets if any
if (widget.children && widget.children.length > 0) {
yield all(
widget.children.map((child: string) => {
return deleteSaga({
type: "",
payload: { parentId: widget.widgetId, widgetId: child },
});
}),
);
}
// Remove widget
delete widgets[widgetId];
yield put(updateAndSaveLayout(widgets));
} catch (error) {
yield put({
type: ReduxActionErrorTypes.WIDGET_OPERATION_ERROR,
payload: {
action: ReduxActionTypes.WIDGET_DELETE,
error,
},
});
}
}
export function* moveSaga(moveAction: ReduxAction<WidgetMove>) {
try {
const {
widgetId,
leftColumn,
topRow,
parentId,
newParentId,
} = moveAction.payload;
let widget: FlattenedWidgetProps = yield select(getWidget, widgetId);
// Get all widgets from DSL/Redux Store
const widgets = yield select(getWidgets) as any;
// Get parent from DSL/Redux Store
const parent = yield select(getWidget, parentId);
// Update position of widget
widget = updateWidgetPosition(widget, leftColumn, topRow);
// Replace widget with update widget props
widgets[widgetId] = widget;
// If the parent has changed i.e parentWidgetId is not parent.widgetId
if (parent.widgetId !== newParentId && widgetId !== newParentId) {
// Remove from the previous parent
parent.children = parent.children.filter(
(child: string) => child !== widgetId,
);
widgets[parent.widgetId] = parent;
// Add to new parent
widgets[newParentId].children.push(widgetId);
}
yield put(updateAndSaveLayout(widgets));
} catch (error) {
yield put({
type: ReduxActionErrorTypes.WIDGET_OPERATION_ERROR,
payload: {
action: ReduxActionTypes.WIDGET_MOVE,
error,
},
});
}
}
export function* resizeSaga(resizeAction: ReduxAction<WidgetResize>) {
try {
const {
widgetId,
leftColumn,
rightColumn,
topRow,
bottomRow,
} = resizeAction.payload;
let widget: FlattenedWidgetProps = yield select(getWidget, widgetId);
const widgets = yield select(getWidgets);
widget = { ...widget, leftColumn, rightColumn, topRow, bottomRow };
widgets[widgetId] = widget;
yield put(updateAndSaveLayout(widgets));
} catch (error) {
yield put({
type: ReduxActionErrorTypes.WIDGET_OPERATION_ERROR,
payload: {
action: ReduxActionTypes.WIDGET_RESIZE,
error,
},
});
}
}
function* updateDynamicTriggers(
widget: WidgetProps,
propertyName: string,
propertyValue: string,
) {
// TODO WIDGETFACTORY
const triggerProperties = WidgetFactory.getWidgetTriggerPropertiesMap(
widget.type,
);
if (propertyName in triggerProperties) {
let dynamicTriggers: Record<string, true> = widget.dynamicTriggers || {};
if (propertyValue && !(propertyName in dynamicTriggers)) {
dynamicTriggers[propertyName] = true;
}
if (!propertyValue && propertyName in dynamicTriggers) {
dynamicTriggers = _.omit(dynamicTriggers, propertyName);
}
yield put(
updateWidgetProperty(widget.widgetId, "dynamicTriggers", dynamicTriggers),
);
return true;
}
return false;
}
function* updateDynamicBindings(
widget: WidgetProps,
propertyName: string,
propertyValue: string,
) {
const isDynamic = isDynamicValue(propertyValue);
let dynamicBindings: Record<string, boolean> = widget.dynamicBindings || {};
if (!isDynamic && propertyName in dynamicBindings) {
dynamicBindings = _.omit(dynamicBindings, propertyName);
}
if (isDynamic && !(propertyName in dynamicBindings)) {
dynamicBindings[propertyName] = true;
}
yield put(
updateWidgetProperty(widget.widgetId, "dynamicBindings", dynamicBindings),
);
}
function* updateWidgetPropertySaga(
updateAction: ReduxAction<UpdateWidgetPropertyRequestPayload>,
) {
const {
payload: { propertyValue, propertyName, widgetId },
} = updateAction;
const widget: WidgetProps = yield select(getWidget, widgetId);
const dynamicTriggersUpdated = yield updateDynamicTriggers(
widget,
propertyName,
propertyValue,
);
if (!dynamicTriggersUpdated)
yield updateDynamicBindings(widget, propertyName, propertyValue);
yield put(updateWidgetProperty(widgetId, propertyName, propertyValue));
const widgets = yield select(getWidgets);
yield put(updateAndSaveLayout(widgets));
}
function* setWidgetDynamicPropertySaga(
action: ReduxAction<SetWidgetDynamicPropertyPayload>,
) {
const { isDynamic, propertyName, widgetId } = action.payload;
const widget: WidgetProps = yield select(getWidget, widgetId);
const propertyValue = widget[propertyName];
const dynamicProperties: Record<string, true> = {
...widget.dynamicProperties,
};
if (isDynamic) {
dynamicProperties[propertyName] = true;
const value = convertToString(propertyValue);
yield put(updateWidgetProperty(widgetId, propertyName, value));
} else {
delete dynamicProperties[propertyName];
const { parsed } = ValidationFactory.validateWidgetProperty(
widget.type,
propertyName,
propertyValue,
widget,
);
yield put(updateWidgetProperty(widgetId, propertyName, parsed));
}
yield put(
updateWidgetProperty(widgetId, "dynamicProperties", dynamicProperties),
);
}
function* getWidgetChildren(widgetId: string): any {
const childrenIds: string[] = [];
const widget = yield select(getWidget, widgetId);
const { children } = widget;
if (children && children.length) {
for (const childIndex in children) {
const child = children[childIndex];
childrenIds.push(child);
const grandChildren = yield call(getWidgetChildren, child);
if (grandChildren.length) {
childrenIds.push(...grandChildren);
}
}
}
return childrenIds;
}
function* resetChildrenMetaSaga(action: ReduxAction<{ widgetId: string }>) {
const parentWidgetId = action.payload.widgetId;
const childrenIds: string[] = yield call(getWidgetChildren, parentWidgetId);
for (const childIndex in childrenIds) {
const childId = childrenIds[childIndex];
yield put(resetWidgetMetaProperty(childId));
}
}
function* updateCanvasSize(
action: ReduxAction<{ canvasWidgetId: string; snapRows: number }>,
) {
const { canvasWidgetId, snapRows } = action.payload;
const canvasWidget = yield select(getWidget, canvasWidgetId);
const originalSnapRows = canvasWidget.bottomRow - canvasWidget.topRow;
const newBottomRow = Math.round(
snapRows * GridDefaults.DEFAULT_GRID_ROW_HEIGHT,
);
/* Update the canvas's rows, ONLY if it has changed since the last render */
if (originalSnapRows !== newBottomRow) {
// TODO(abhinav): This considers that the topRow will always be zero
// Check this out when non canvas widgets are updating snapRows
// erstwhile: Math.round((rows * props.snapRowSpace) / props.parentRowSpace),
yield put(updateWidgetProperty(canvasWidgetId, "bottomRow", newBottomRow));
}
}
export default function* widgetOperationSagas() {
yield all([
takeEvery(ReduxActionTypes.WIDGET_ADD_CHILD, addChildSaga),
takeEvery(ReduxActionTypes.WIDGET_DELETE, deleteSaga),
takeLatest(ReduxActionTypes.WIDGET_MOVE, moveSaga),
takeLatest(ReduxActionTypes.WIDGET_RESIZE, resizeSaga),
takeEvery(
ReduxActionTypes.UPDATE_WIDGET_PROPERTY_REQUEST,
updateWidgetPropertySaga,
),
takeEvery(
ReduxActionTypes.SET_WIDGET_DYNAMIC_PROPERTY,
setWidgetDynamicPropertySaga,
),
takeEvery(
ReduxActionTypes.RESET_CHILDREN_WIDGET_META,
resetChildrenMetaSaga,
),
takeLatest(ReduxActionTypes.UPDATE_CANVAS_SIZE, updateCanvasSize),
]);
}