We are changing how we maintain meta values in our architecture, Earlier metaHOC component state was used to store meta values that were debounced and pushed in batch to metaReducer and trigger evaluation. We remove the metaHOC state and directly update values to metaReducer and trigger evaluation separately via a debouched function.
325 lines
8.9 KiB
TypeScript
325 lines
8.9 KiB
TypeScript
import {
|
|
all,
|
|
select,
|
|
call,
|
|
put,
|
|
takeLatest,
|
|
takeEvery,
|
|
delay,
|
|
} from "redux-saga/effects";
|
|
|
|
import { generateReactKey } from "utils/generators";
|
|
import {
|
|
updateAndSaveLayout,
|
|
WidgetAddChild,
|
|
ModalWidgetResize,
|
|
} from "actions/pageActions";
|
|
import {
|
|
GridDefaults,
|
|
MAIN_CONTAINER_WIDGET_ID,
|
|
} from "constants/WidgetConstants";
|
|
import {
|
|
ReduxActionErrorTypes,
|
|
ReduxActionTypes,
|
|
ReduxAction,
|
|
WidgetReduxActionTypes,
|
|
} from "@appsmith/constants/ReduxActionConstants";
|
|
|
|
import {
|
|
getWidget,
|
|
getWidgets,
|
|
getWidgetByName,
|
|
getWidgetsMeta,
|
|
getWidgetIdsByType,
|
|
getWidgetMetaProps,
|
|
} from "sagas/selectors";
|
|
import {
|
|
CanvasWidgetsReduxState,
|
|
FlattenedWidgetProps,
|
|
} from "reducers/entityReducers/canvasWidgetsReducer";
|
|
import { updateWidgetMetaPropAndEval } from "actions/metaActions";
|
|
import { focusWidget } from "actions/widgetActions";
|
|
import log from "loglevel";
|
|
import { flatten } from "lodash";
|
|
import AppsmithConsole from "utils/AppsmithConsole";
|
|
|
|
import WidgetFactory from "utils/WidgetFactory";
|
|
import { Toaster } from "components/ads/Toast";
|
|
import { deselectAllInitAction } from "actions/widgetSelectionActions";
|
|
import { navigateToCanvas } from "pages/Editor/Explorer/Widgets/utils";
|
|
import { getCurrentPageId } from "selectors/editorSelectors";
|
|
import { APP_MODE } from "entities/App";
|
|
import { getAppMode } from "selectors/applicationSelectors";
|
|
const WidgetTypes = WidgetFactory.widgetTypes;
|
|
|
|
export function* createModalSaga(action: ReduxAction<{ modalName: string }>) {
|
|
try {
|
|
const modalWidgetId = generateReactKey();
|
|
const props: WidgetAddChild = {
|
|
widgetId: MAIN_CONTAINER_WIDGET_ID,
|
|
widgetName: action.payload.modalName,
|
|
type: WidgetTypes.MODAL_WIDGET,
|
|
newWidgetId: modalWidgetId,
|
|
parentRowSpace: 1,
|
|
parentColumnSpace: 1,
|
|
leftColumn: 0,
|
|
topRow: 0,
|
|
columns: 0,
|
|
rows: 0,
|
|
tabId: "",
|
|
};
|
|
yield put({
|
|
type: WidgetReduxActionTypes.WIDGET_ADD_CHILD,
|
|
payload: props,
|
|
});
|
|
|
|
yield put({
|
|
type: ReduxActionTypes.SHOW_MODAL,
|
|
payload: { modalId: modalWidgetId },
|
|
});
|
|
} catch (error) {
|
|
log.error(error);
|
|
yield put({
|
|
type: ReduxActionErrorTypes.CREATE_MODAL_ERROR,
|
|
payload: { error },
|
|
});
|
|
}
|
|
}
|
|
|
|
export function* showModalByNameSaga(
|
|
action: ReduxAction<{ modalName: string }>,
|
|
) {
|
|
const widgets: { [widgetId: string]: FlattenedWidgetProps } = yield select(
|
|
getWidgets,
|
|
);
|
|
const modal: FlattenedWidgetProps | undefined = Object.values(widgets).find(
|
|
(widget: FlattenedWidgetProps) =>
|
|
widget.widgetName === action.payload.modalName,
|
|
);
|
|
if (modal) {
|
|
AppsmithConsole.info({
|
|
text: action.payload.modalName
|
|
? `showModal('${action.payload.modalName}') was triggered`
|
|
: `showModal() was triggered`,
|
|
});
|
|
|
|
yield put({
|
|
type: ReduxActionTypes.SHOW_MODAL,
|
|
payload: {
|
|
modalId: modal.widgetId,
|
|
},
|
|
});
|
|
}
|
|
}
|
|
|
|
export function* showIfModalSaga(
|
|
action: ReduxAction<{ widgetId: string; type: string }>,
|
|
) {
|
|
if (action.payload.type === "MODAL_WIDGET") {
|
|
yield put({
|
|
type: ReduxActionTypes.SHOW_MODAL,
|
|
payload: { modalId: action.payload.widgetId },
|
|
});
|
|
}
|
|
}
|
|
|
|
export function* showModalSaga(action: ReduxAction<{ modalId: string }>) {
|
|
// First we close the currently open modals (if any)
|
|
// Notice the empty payload.
|
|
yield call(closeModalSaga, {
|
|
type: ReduxActionTypes.CLOSE_MODAL,
|
|
payload: {
|
|
exclude: action.payload.modalId,
|
|
},
|
|
});
|
|
|
|
const pageId: string = yield select(getCurrentPageId);
|
|
const appMode: APP_MODE = yield select(getAppMode);
|
|
|
|
if (appMode === APP_MODE.EDIT)
|
|
navigateToCanvas({ pageId, widgetId: action.payload.modalId });
|
|
|
|
yield put({
|
|
type: ReduxActionTypes.SELECT_WIDGET_INIT,
|
|
payload: { widgetId: action.payload.modalId },
|
|
});
|
|
yield put(focusWidget(action.payload.modalId));
|
|
|
|
const metaProps = yield select(getWidgetMetaProps, action.payload.modalId);
|
|
if (!metaProps || !metaProps.isVisible) {
|
|
// Then show the modal we would like to show.
|
|
yield put(
|
|
updateWidgetMetaPropAndEval(action.payload.modalId, "isVisible", true),
|
|
);
|
|
yield delay(1000);
|
|
}
|
|
yield put({
|
|
type: ReduxActionTypes.SHOW_PROPERTY_PANE,
|
|
payload: {
|
|
widgetId: action.payload.modalId,
|
|
callForDragOrResize: undefined,
|
|
force: true,
|
|
},
|
|
});
|
|
}
|
|
|
|
export function* closeModalSaga(
|
|
action: ReduxAction<{ modalName?: string; exclude?: string }>,
|
|
) {
|
|
try {
|
|
const { modalName } = action.payload;
|
|
let widgetIds: string[] = [];
|
|
// If modalName is provided, we just want to close this modal
|
|
if (modalName) {
|
|
const widget = yield select(getWidgetByName, modalName);
|
|
widgetIds = [widget.widgetId];
|
|
yield put({
|
|
type: ReduxActionTypes.SHOW_PROPERTY_PANE,
|
|
payload: {},
|
|
});
|
|
} else {
|
|
// If modalName is not provided, find all open modals
|
|
// Get all meta prop records
|
|
const metaProps: Record<string, any> = yield select(getWidgetsMeta);
|
|
|
|
// Get widgetIds of all widgets of type MODAL_WIDGET
|
|
const modalWidgetIds: string[] = yield select(
|
|
getWidgetIdsByType,
|
|
WidgetTypes.MODAL_WIDGET,
|
|
);
|
|
|
|
// Loop through all modal widgetIds
|
|
modalWidgetIds.forEach((widgetId: string) => {
|
|
// Check if modal is open
|
|
if (metaProps[widgetId] && metaProps[widgetId].isVisible) {
|
|
// Add to our list of widgetIds
|
|
widgetIds.push(widgetId);
|
|
}
|
|
});
|
|
}
|
|
widgetIds = action.payload.exclude
|
|
? widgetIds.filter((id: string) => id !== action.payload.exclude)
|
|
: widgetIds;
|
|
// If we have modals to close, set its isVisible to false to close.
|
|
if (widgetIds) {
|
|
yield all(
|
|
flatten(
|
|
widgetIds.map((widgetId: string) => {
|
|
return [
|
|
put(updateWidgetMetaPropAndEval(widgetId, "isVisible", false)),
|
|
];
|
|
}),
|
|
),
|
|
);
|
|
}
|
|
if (modalName) {
|
|
yield put(deselectAllInitAction());
|
|
yield put(focusWidget(MAIN_CONTAINER_WIDGET_ID));
|
|
}
|
|
} catch (error) {
|
|
log.error(error);
|
|
}
|
|
}
|
|
|
|
export function* resizeModalSaga(resizeAction: ReduxAction<ModalWidgetResize>) {
|
|
try {
|
|
Toaster.clear();
|
|
const start = performance.now();
|
|
const { canvasWidgetId, height, widgetId, width } = resizeAction.payload;
|
|
|
|
const stateWidget: FlattenedWidgetProps = yield select(getWidget, widgetId);
|
|
const stateWidgets = yield select(getWidgets);
|
|
|
|
let widget = { ...stateWidget };
|
|
const widgets = { ...stateWidgets };
|
|
|
|
widget = { ...widget, height, width };
|
|
widgets[widgetId] = widget;
|
|
|
|
if (canvasWidgetId) {
|
|
const bottomRow = getModalCanvasBottomRow(
|
|
widgets,
|
|
canvasWidgetId,
|
|
height,
|
|
);
|
|
const stateModalContainerWidget: FlattenedWidgetProps = yield select(
|
|
getWidget,
|
|
canvasWidgetId,
|
|
);
|
|
let modalContainerWidget = { ...stateModalContainerWidget };
|
|
|
|
modalContainerWidget = {
|
|
...modalContainerWidget,
|
|
bottomRow,
|
|
minHeight: height,
|
|
};
|
|
|
|
widgets[canvasWidgetId] = modalContainerWidget;
|
|
}
|
|
|
|
log.debug("resize computations took", performance.now() - start, "ms");
|
|
yield put(updateAndSaveLayout(widgets));
|
|
} catch (error) {
|
|
yield put({
|
|
type: ReduxActionErrorTypes.WIDGET_OPERATION_ERROR,
|
|
payload: {
|
|
action: WidgetReduxActionTypes.WIDGET_RESIZE,
|
|
error,
|
|
},
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Note: returns bottomRow of the lowest widget on the canvas
|
|
* @param finalWidgets
|
|
* @param parentId
|
|
* @param height
|
|
*/
|
|
const getModalCanvasBottomRow = (
|
|
finalWidgets: CanvasWidgetsReduxState,
|
|
parentId: string,
|
|
height: number,
|
|
): number => {
|
|
if (
|
|
!finalWidgets[parentId] ||
|
|
finalWidgets[parentId].type !== WidgetTypes.CANVAS_WIDGET
|
|
) {
|
|
return height;
|
|
}
|
|
const lowestBottomRowHeight =
|
|
height -
|
|
GridDefaults.CANVAS_EXTENSION_OFFSET *
|
|
GridDefaults.DEFAULT_GRID_ROW_HEIGHT -
|
|
GridDefaults.DEFAULT_GRID_ROW_HEIGHT;
|
|
|
|
let lowestBottomRow = Math.ceil(
|
|
lowestBottomRowHeight / GridDefaults.DEFAULT_GRID_ROW_HEIGHT,
|
|
);
|
|
const childIds = finalWidgets[parentId].children || [];
|
|
|
|
// find lowest row
|
|
childIds.forEach((cId: string) => {
|
|
const child = finalWidgets[cId];
|
|
|
|
if (child.bottomRow > lowestBottomRow) {
|
|
lowestBottomRow = child.bottomRow;
|
|
}
|
|
});
|
|
return (
|
|
(lowestBottomRow + GridDefaults.CANVAS_EXTENSION_OFFSET) *
|
|
GridDefaults.DEFAULT_GRID_ROW_HEIGHT
|
|
);
|
|
};
|
|
|
|
export default function* modalSagas() {
|
|
yield all([
|
|
takeEvery(ReduxActionTypes.CLOSE_MODAL, closeModalSaga),
|
|
takeLatest(ReduxActionTypes.CREATE_MODAL_INIT, createModalSaga),
|
|
takeLatest(ReduxActionTypes.SHOW_MODAL, showModalSaga),
|
|
takeLatest(ReduxActionTypes.SHOW_MODAL_BY_NAME, showModalByNameSaga),
|
|
takeLatest(WidgetReduxActionTypes.WIDGET_CHILD_ADDED, showIfModalSaga),
|
|
takeLatest(WidgetReduxActionTypes.WIDGET_MODAL_RESIZE, resizeModalSaga),
|
|
]);
|
|
}
|