* Refactor toast to be passed the dispatch hook externally * Add comments explaining dilemma * use store.dispatch instead of a hook * use alpha version * Change imports * Refactor DebugButton out * update release * fix issue with incorrectly merged package.lock * fix syntax of alpha version * bump ds vesion * copy lock from release * update lock to have alpha * make changes * delete Toast * DS package version updated * import change from release * use new alpha version * update ds version * update ds version * chore: migrate editable text and friends (#17285) * Delete empty components * use alpha for ds * Deleted EditableTextSubComponent, import changes * Delete EditableText, import changes * use ds alpha 10 * Delete EditableTextWrapper.tsx * update ds to use next minor version * use new alpha * fix issue with merge Co-authored-by: Albin <albin@appsmith.com> * chore: migrate file picker v2 (#17308) * use alpha ds * Delete FilePickerV2, import changes * Delete FilePicker, change imports * update alpha version * chore: move copy url form into setting components (#17322) * move CopyUrlForm to src/pages/settings/formgroup * update ds version to use next minor release * feat: Migrate table component to design system (#17329) * feat: Migrate table component to design system * removed commented code in ads index file * fix: table no data hover effect removed Co-authored-by: Tanvi Bhakta <tanvibhakta@gmail.com> * feat: Banner message component migrated to design system (#17327) * feat: Banner image component migrated to design system * Version update for design system package * design system version updated Co-authored-by: Tanvi Bhakta <tanvibhakta@gmail.com> * feat: Tabs component migrated to design system (#17321) * feat: Tabs component migrated to design system * design system package version updated * Update app/client/src/components/editorComponents/form/FormDialogComponent.tsx * Update app/client/src/pages/Editor/PropertyPane/PropertyPaneTab.tsx * Tab component expand issue fix Co-authored-by: Tanvi Bhakta <tanvibhakta@gmail.com> Co-authored-by: Albin <albin@appsmith.com> Co-authored-by: albinAppsmith <87797149+albinAppsmith@users.noreply.github.com>
396 lines
12 KiB
TypeScript
396 lines
12 KiB
TypeScript
import { Toaster } from "design-system";
|
|
import {
|
|
ReduxAction,
|
|
ReduxActionErrorTypes,
|
|
ReduxActionTypes,
|
|
} from "@appsmith/constants/ReduxActionConstants";
|
|
import {
|
|
CanvasWidgetsReduxState,
|
|
FlattenedWidgetProps,
|
|
} from "reducers/entityReducers/canvasWidgetsReducer";
|
|
import { all, call, put, select, takeLatest } from "redux-saga/effects";
|
|
import log from "loglevel";
|
|
import { cloneDeep } from "lodash";
|
|
import { updateAndSaveLayout, WidgetAddChild } from "actions/pageActions";
|
|
import { calculateDropTargetRows } from "components/editorComponents/DropTargetUtils";
|
|
import {
|
|
GridDefaults,
|
|
MAIN_CONTAINER_WIDGET_ID,
|
|
} from "constants/WidgetConstants";
|
|
import { WidgetProps } from "widgets/BaseWidget";
|
|
import {
|
|
getMainCanvasProps,
|
|
getOccupiedSpacesSelectorForContainer,
|
|
} from "selectors/editorSelectors";
|
|
import { OccupiedSpace } from "constants/CanvasEditorConstants";
|
|
import { collisionCheckPostReflow } from "utils/reflowHookUtils";
|
|
import { WidgetDraggingUpdateParams } from "pages/common/CanvasArenas/hooks/useBlocksToBeDraggedOnCanvas";
|
|
import { getWidget, getWidgets } from "sagas/selectors";
|
|
import { getUpdateDslAfterCreatingChild } from "sagas/WidgetAdditionSagas";
|
|
import { MainCanvasReduxState } from "reducers/uiReducers/mainCanvasReducer";
|
|
import { CANVAS_DEFAULT_MIN_HEIGHT_PX } from "constants/AppConstants";
|
|
import AnalyticsUtil from "utils/AnalyticsUtil";
|
|
|
|
export type WidgetMoveParams = {
|
|
widgetId: string;
|
|
leftColumn: number;
|
|
topRow: number;
|
|
bottomRow: number;
|
|
rightColumn: number;
|
|
parentId: string;
|
|
/*
|
|
If newParentId is different from what we have in redux store,
|
|
then we have to delete this,
|
|
as it has been dropped in another container somewhere.
|
|
*/
|
|
newParentId: string;
|
|
allWidgets: CanvasWidgetsReduxState;
|
|
};
|
|
|
|
export function* getCanvasSizeAfterWidgetMove(
|
|
canvasWidgetId: string,
|
|
movedWidgetIds: string[],
|
|
movedWidgetsBottomRow: number,
|
|
) {
|
|
const canvasWidget: WidgetProps = yield select(getWidget, canvasWidgetId);
|
|
|
|
//get mainCanvas's minHeight if the canvasWidget is mianCanvas
|
|
let mainCanvasMinHeight;
|
|
if (canvasWidgetId === MAIN_CONTAINER_WIDGET_ID) {
|
|
const mainCanvasProps: MainCanvasReduxState = yield select(
|
|
getMainCanvasProps,
|
|
);
|
|
mainCanvasMinHeight = mainCanvasProps?.height;
|
|
}
|
|
|
|
if (canvasWidget) {
|
|
const occupiedSpacesByChildren: OccupiedSpace[] | undefined = yield select(
|
|
getOccupiedSpacesSelectorForContainer(canvasWidgetId),
|
|
);
|
|
const canvasMinHeight =
|
|
mainCanvasMinHeight ||
|
|
canvasWidget.minHeight ||
|
|
CANVAS_DEFAULT_MIN_HEIGHT_PX;
|
|
const newRows = calculateDropTargetRows(
|
|
movedWidgetIds,
|
|
movedWidgetsBottomRow,
|
|
canvasMinHeight / GridDefaults.DEFAULT_GRID_ROW_HEIGHT - 1,
|
|
occupiedSpacesByChildren,
|
|
canvasWidgetId,
|
|
);
|
|
const rowsToPersist = Math.max(
|
|
canvasMinHeight / GridDefaults.DEFAULT_GRID_ROW_HEIGHT - 1,
|
|
newRows,
|
|
);
|
|
|
|
const originalSnapRows = canvasWidget.bottomRow - canvasWidget.topRow;
|
|
|
|
const newBottomRow = Math.round(
|
|
rowsToPersist * 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),
|
|
return newBottomRow;
|
|
}
|
|
return canvasWidget.bottomRow;
|
|
}
|
|
}
|
|
|
|
const getBottomMostRowAfterMove = (
|
|
draggedBlocksToUpdate: WidgetDraggingUpdateParams[],
|
|
allWidgets: CanvasWidgetsReduxState,
|
|
) => {
|
|
const bottomMostBlock =
|
|
draggedBlocksToUpdate[draggedBlocksToUpdate.length - 1];
|
|
const widget = allWidgets[bottomMostBlock.widgetId];
|
|
const { updateWidgetParams } = bottomMostBlock;
|
|
const widgetBottomRow =
|
|
updateWidgetParams.payload.topRow +
|
|
(updateWidgetParams.payload.rows || widget.bottomRow - widget.topRow);
|
|
return widgetBottomRow;
|
|
};
|
|
|
|
function* addWidgetAndMoveWidgetsSaga(
|
|
actionPayload: ReduxAction<{
|
|
newWidget: WidgetAddChild;
|
|
draggedBlocksToUpdate: WidgetDraggingUpdateParams[];
|
|
canvasId: string;
|
|
}>,
|
|
) {
|
|
const start = performance.now();
|
|
|
|
const { canvasId, draggedBlocksToUpdate, newWidget } = actionPayload.payload;
|
|
try {
|
|
const updatedWidgetsOnAddAndMove: CanvasWidgetsReduxState = yield call(
|
|
addWidgetAndMoveWidgets,
|
|
newWidget,
|
|
draggedBlocksToUpdate,
|
|
canvasId,
|
|
);
|
|
if (
|
|
!collisionCheckPostReflow(
|
|
updatedWidgetsOnAddAndMove,
|
|
draggedBlocksToUpdate.map((block) => block.widgetId),
|
|
canvasId,
|
|
)
|
|
) {
|
|
throw Error;
|
|
}
|
|
yield put(updateAndSaveLayout(updatedWidgetsOnAddAndMove));
|
|
yield put({
|
|
type: ReduxActionTypes.RECORD_RECENTLY_ADDED_WIDGET,
|
|
payload: [newWidget.newWidgetId],
|
|
});
|
|
log.debug("move computations took", performance.now() - start, "ms");
|
|
} catch (error) {
|
|
yield put({
|
|
type: ReduxActionErrorTypes.WIDGET_OPERATION_ERROR,
|
|
payload: {
|
|
action: ReduxActionTypes.WIDGETS_ADD_CHILD_AND_MOVE,
|
|
error,
|
|
},
|
|
});
|
|
}
|
|
}
|
|
|
|
function* addWidgetAndMoveWidgets(
|
|
newWidget: WidgetAddChild,
|
|
draggedBlocksToUpdate: WidgetDraggingUpdateParams[],
|
|
canvasId: string,
|
|
) {
|
|
const allWidgets: CanvasWidgetsReduxState = yield select(getWidgets);
|
|
const updatedWidgetsOnAddition: CanvasWidgetsReduxState = yield call(
|
|
getUpdateDslAfterCreatingChild,
|
|
{ ...newWidget, widgetId: canvasId },
|
|
);
|
|
const bottomMostRowOnAddition = updatedWidgetsOnAddition[canvasId]
|
|
? updatedWidgetsOnAddition[canvasId].bottomRow
|
|
: 0;
|
|
const allWidgetsAfterAddition = {
|
|
...allWidgets,
|
|
...updatedWidgetsOnAddition,
|
|
};
|
|
const updatedWidgetsOnMove: CanvasWidgetsReduxState = yield call(
|
|
moveAndUpdateWidgets,
|
|
allWidgetsAfterAddition,
|
|
draggedBlocksToUpdate,
|
|
canvasId,
|
|
);
|
|
const bottomMostRowOnMove = updatedWidgetsOnMove[canvasId]
|
|
? updatedWidgetsOnMove[canvasId].bottomRow
|
|
: 0;
|
|
|
|
const bottomMostRow =
|
|
bottomMostRowOnAddition > bottomMostRowOnMove
|
|
? bottomMostRowOnAddition
|
|
: bottomMostRowOnMove;
|
|
const updatedWidgets = {
|
|
...updatedWidgetsOnMove,
|
|
};
|
|
updatedWidgets[canvasId].bottomRow = bottomMostRow;
|
|
return updatedWidgets;
|
|
}
|
|
|
|
function* moveAndUpdateWidgets(
|
|
allWidgets: CanvasWidgetsReduxState,
|
|
draggedBlocksToUpdate: WidgetDraggingUpdateParams[],
|
|
canvasId: string,
|
|
) {
|
|
const widgets = cloneDeep(allWidgets);
|
|
const bottomMostRowAfterMove = getBottomMostRowAfterMove(
|
|
draggedBlocksToUpdate,
|
|
allWidgets,
|
|
);
|
|
// draggedBlocksToUpdate is already sorted based on bottomRow
|
|
const updatedWidgets = draggedBlocksToUpdate.reduce((widgetsObj, each) => {
|
|
return moveWidget({
|
|
...each.updateWidgetParams.payload,
|
|
widgetId: each.widgetId,
|
|
allWidgets: widgetsObj,
|
|
});
|
|
}, widgets);
|
|
const movedWidgetIds = draggedBlocksToUpdate.map((a) => a.widgetId);
|
|
const updatedCanvasBottomRow: number = yield call(
|
|
getCanvasSizeAfterWidgetMove,
|
|
canvasId,
|
|
movedWidgetIds,
|
|
bottomMostRowAfterMove,
|
|
);
|
|
if (updatedCanvasBottomRow) {
|
|
const canvasWidget = updatedWidgets[canvasId];
|
|
updatedWidgets[canvasId] = {
|
|
...canvasWidget,
|
|
bottomRow: updatedCanvasBottomRow,
|
|
};
|
|
}
|
|
return updatedWidgets;
|
|
}
|
|
|
|
function getParentWidgetType(
|
|
allWidgets: CanvasWidgetsReduxState,
|
|
widgetId: string,
|
|
) {
|
|
const widget = allWidgets[widgetId];
|
|
|
|
if (!widget.parentId) return "MAIN_CONTAINER";
|
|
|
|
const containerWidget = allWidgets[widget.parentId];
|
|
|
|
/**
|
|
* container widget can be of type FORM_WIDGET, STATBOX_WIDGET
|
|
*/
|
|
if (containerWidget.type !== "CONTAINER_WIDGET") {
|
|
return containerWidget.type;
|
|
}
|
|
|
|
/**
|
|
* Handling the case for list widget where we have
|
|
* canvas2 -> container -> canvas1 -> listWidget
|
|
*/
|
|
if (containerWidget.parentId) {
|
|
// Take the first parent that is canvas1
|
|
const containerParent = allWidgets[containerWidget.parentId];
|
|
|
|
// Now take the parent of canvas1 that is listWidget
|
|
if (containerParent.parentId) {
|
|
const mainParent = allWidgets[containerParent.parentId];
|
|
return mainParent.type;
|
|
}
|
|
}
|
|
|
|
return containerWidget.type;
|
|
}
|
|
|
|
function* moveWidgetsSaga(
|
|
actionPayload: ReduxAction<{
|
|
draggedBlocksToUpdate: WidgetDraggingUpdateParams[];
|
|
canvasId: string;
|
|
}>,
|
|
) {
|
|
const start = performance.now();
|
|
|
|
const { canvasId, draggedBlocksToUpdate } = actionPayload.payload;
|
|
try {
|
|
const allWidgets: CanvasWidgetsReduxState = yield select(getWidgets);
|
|
|
|
const updatedWidgetsOnMove: CanvasWidgetsReduxState = yield call(
|
|
moveAndUpdateWidgets,
|
|
allWidgets,
|
|
draggedBlocksToUpdate,
|
|
canvasId,
|
|
);
|
|
|
|
if (
|
|
!collisionCheckPostReflow(
|
|
updatedWidgetsOnMove,
|
|
draggedBlocksToUpdate.map((block) => block.widgetId),
|
|
canvasId,
|
|
)
|
|
) {
|
|
throw Error;
|
|
}
|
|
yield put(updateAndSaveLayout(updatedWidgetsOnMove));
|
|
|
|
const block = draggedBlocksToUpdate[0];
|
|
const oldParentId = block.updateWidgetParams.payload.parentId;
|
|
const newParentId = block.updateWidgetParams.payload.newParentId;
|
|
|
|
const oldParentWidgetType = getParentWidgetType(allWidgets, oldParentId);
|
|
const newParentWidgetType = getParentWidgetType(allWidgets, newParentId);
|
|
|
|
AnalyticsUtil.logEvent("WIDGET_DRAG", {
|
|
widgets: draggedBlocksToUpdate.map((block) => {
|
|
const widget = allWidgets[block.widgetId];
|
|
return {
|
|
widgetType: widget.type,
|
|
widgetName: widget.widgetName,
|
|
};
|
|
}),
|
|
multiple: draggedBlocksToUpdate.length > 1,
|
|
movedToNewWidget: oldParentId !== newParentId,
|
|
source: oldParentWidgetType,
|
|
destination: newParentWidgetType,
|
|
});
|
|
log.debug("move computations took", performance.now() - start, "ms");
|
|
} catch (error) {
|
|
yield put({
|
|
type: ReduxActionErrorTypes.WIDGET_OPERATION_ERROR,
|
|
payload: {
|
|
action: ReduxActionTypes.WIDGETS_MOVE,
|
|
error,
|
|
},
|
|
});
|
|
}
|
|
}
|
|
|
|
function moveWidget(widgetMoveParams: WidgetMoveParams) {
|
|
Toaster.clear();
|
|
const {
|
|
allWidgets,
|
|
bottomRow,
|
|
leftColumn,
|
|
newParentId,
|
|
parentId,
|
|
rightColumn,
|
|
topRow,
|
|
widgetId,
|
|
} = widgetMoveParams;
|
|
const stateWidget: FlattenedWidgetProps = allWidgets[widgetId];
|
|
let widget = Object.assign({}, stateWidget);
|
|
// Get all widgets from DSL/Redux Store
|
|
const widgets = Object.assign({}, allWidgets);
|
|
// Get parent from DSL/Redux Store
|
|
const stateParent: FlattenedWidgetProps = allWidgets[parentId];
|
|
const parent = {
|
|
...stateParent,
|
|
children: [...(stateParent.children || [])],
|
|
};
|
|
// Update position of widget
|
|
const updatedPosition = {
|
|
topRow,
|
|
bottomRow,
|
|
leftColumn,
|
|
rightColumn,
|
|
};
|
|
widget = { ...widget, ...updatedPosition };
|
|
|
|
// 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
|
|
|
|
if (parent.children && Array.isArray(parent.children)) {
|
|
const indexOfChild = parent.children.indexOf(widgetId);
|
|
if (indexOfChild > -1) delete parent.children[indexOfChild];
|
|
parent.children = parent.children.filter(Boolean);
|
|
}
|
|
|
|
// Add to new parent
|
|
|
|
widgets[parent.widgetId] = parent;
|
|
const newParent = {
|
|
...widgets[newParentId],
|
|
children: widgets[newParentId].children
|
|
? [...(widgets[newParentId].children || []), widgetId]
|
|
: [widgetId],
|
|
};
|
|
widgets[widgetId].parentId = newParentId;
|
|
widgets[newParentId] = newParent;
|
|
}
|
|
return widgets;
|
|
}
|
|
|
|
export default function* draggingCanvasSagas() {
|
|
yield all([
|
|
takeLatest(ReduxActionTypes.WIDGETS_MOVE, moveWidgetsSaga),
|
|
takeLatest(
|
|
ReduxActionTypes.WIDGETS_ADD_CHILD_AND_MOVE,
|
|
addWidgetAndMoveWidgetsSaga,
|
|
),
|
|
]);
|
|
}
|