2019-11-25 05:07:27 +00:00
|
|
|
import { FetchPageResponse } from "api/PageApi";
|
2020-01-16 11:46:21 +00:00
|
|
|
import {
|
|
|
|
|
CANVAS_DEFAULT_WIDTH_PX,
|
|
|
|
|
CANVAS_DEFAULT_HEIGHT_PX,
|
|
|
|
|
CANVAS_BACKGROUND_COLOR,
|
|
|
|
|
CANVAS_DEFAULT_GRID_HEIGHT_PX,
|
|
|
|
|
CANVAS_DEFAULT_GRID_WIDTH_PX,
|
|
|
|
|
} from "constants/AppConstants";
|
2019-09-25 17:24:23 +00:00
|
|
|
import { XYCoord } from "react-dnd";
|
2019-11-25 05:07:27 +00:00
|
|
|
import { ContainerWidgetProps } from "widgets/ContainerWidget";
|
|
|
|
|
import { WidgetConfigProps } from "reducers/entityReducers/widgetConfigReducer";
|
2019-11-13 07:00:25 +00:00
|
|
|
import {
|
|
|
|
|
WidgetProps,
|
|
|
|
|
WidgetOperations,
|
|
|
|
|
WidgetOperation,
|
2019-11-25 05:07:27 +00:00
|
|
|
} from "widgets/BaseWidget";
|
2020-01-17 09:28:26 +00:00
|
|
|
import { WidgetType } from "constants/WidgetConstants";
|
2019-11-25 05:07:27 +00:00
|
|
|
import { generateReactKey } from "utils/generators";
|
2020-01-16 11:46:21 +00:00
|
|
|
import {
|
|
|
|
|
GridDefaults,
|
|
|
|
|
WidgetTypes,
|
|
|
|
|
MAIN_CONTAINER_WIDGET_ID,
|
|
|
|
|
MAIN_CONTAINER_WIDGET_NAME,
|
2020-01-17 09:27:38 +00:00
|
|
|
CONTAINER_GRID_PADDING,
|
2020-01-16 11:46:21 +00:00
|
|
|
} from "constants/WidgetConstants";
|
2019-09-25 17:24:23 +00:00
|
|
|
import { snapToGrid } from "./helpers";
|
2019-11-13 07:00:25 +00:00
|
|
|
import { OccupiedSpace } from "constants/editorConstants";
|
2020-01-17 09:28:26 +00:00
|
|
|
import { DerivedPropFactory } from "utils/DerivedPropertiesFactory";
|
2019-11-13 07:00:25 +00:00
|
|
|
|
|
|
|
|
export type WidgetOperationParams = {
|
|
|
|
|
operation: WidgetOperation;
|
|
|
|
|
widgetId: string;
|
|
|
|
|
payload: any;
|
|
|
|
|
};
|
2019-09-22 20:25:05 +00:00
|
|
|
|
2020-01-16 11:46:21 +00:00
|
|
|
const { DEFAULT_GRID_COLUMNS, DEFAULT_GRID_ROW_HEIGHT } = GridDefaults;
|
2019-09-25 21:21:04 +00:00
|
|
|
type Rect = {
|
|
|
|
|
top: number;
|
|
|
|
|
left: number;
|
|
|
|
|
right: number;
|
|
|
|
|
bottom: number;
|
|
|
|
|
};
|
2019-09-18 10:48:56 +00:00
|
|
|
|
2019-09-26 11:11:28 +00:00
|
|
|
const defaultDSL = {
|
2020-01-16 11:46:21 +00:00
|
|
|
type: WidgetTypes.CONTAINER_WIDGET,
|
|
|
|
|
widgetId: MAIN_CONTAINER_WIDGET_ID,
|
|
|
|
|
widgetName: MAIN_CONTAINER_WIDGET_NAME,
|
|
|
|
|
|
|
|
|
|
backgroundColor: CANVAS_BACKGROUND_COLOR,
|
2019-09-26 11:11:28 +00:00
|
|
|
children: [],
|
2020-01-16 11:46:21 +00:00
|
|
|
|
2019-09-26 11:11:28 +00:00
|
|
|
leftColumn: 0,
|
2020-01-16 11:46:21 +00:00
|
|
|
rightColumn: CANVAS_DEFAULT_WIDTH_PX,
|
|
|
|
|
parentColumnSpace: CANVAS_DEFAULT_GRID_WIDTH_PX,
|
|
|
|
|
snapColumns: GridDefaults.DEFAULT_GRID_COLUMNS,
|
|
|
|
|
|
2019-09-26 11:11:28 +00:00
|
|
|
topRow: 0,
|
2020-01-16 11:46:21 +00:00
|
|
|
bottomRow: CANVAS_DEFAULT_HEIGHT_PX,
|
|
|
|
|
parentRowSpace: CANVAS_DEFAULT_GRID_HEIGHT_PX,
|
|
|
|
|
// 1 row needs to be removed, as padding top and bottom takes up some 1 row worth of space.
|
|
|
|
|
// Widget padding: 8px
|
|
|
|
|
// Container padding: 12px;
|
|
|
|
|
// Total = (8 + 12) * 2 = GridDefaults.DEFAULT_GRID_ROW_HEIGHT = 40
|
|
|
|
|
snapRows: CANVAS_DEFAULT_HEIGHT_PX / GridDefaults.DEFAULT_GRID_ROW_HEIGHT - 1,
|
2019-09-26 11:11:28 +00:00
|
|
|
};
|
|
|
|
|
|
2019-09-18 10:48:56 +00:00
|
|
|
export const extractCurrentDSL = (
|
|
|
|
|
fetchPageResponse: FetchPageResponse,
|
|
|
|
|
): ContainerWidgetProps<WidgetProps> => {
|
2019-10-31 08:36:04 +00:00
|
|
|
const currentDSL = fetchPageResponse.data.layouts[0].dsl || defaultDSL;
|
2020-01-16 11:46:21 +00:00
|
|
|
// 1 row needs to be removed, as padding top and bottom takes up some 1 row worth of space.
|
|
|
|
|
// Widget padding: 8px
|
|
|
|
|
// Container padding: 12px;
|
|
|
|
|
// Total = (8 + 12) * 2 = GridDefaults.DEFAULT_GRID_ROW_HEIGHT = 40
|
|
|
|
|
currentDSL.snapRows =
|
|
|
|
|
Math.floor(currentDSL.bottomRow / DEFAULT_GRID_ROW_HEIGHT) - 1;
|
2019-10-31 08:36:04 +00:00
|
|
|
return currentDSL;
|
2019-09-18 10:48:56 +00:00
|
|
|
};
|
|
|
|
|
|
2019-09-25 21:21:04 +00:00
|
|
|
export const getDropZoneOffsets = (
|
|
|
|
|
colWidth: number,
|
|
|
|
|
rowHeight: number,
|
|
|
|
|
dragOffset: XYCoord,
|
|
|
|
|
parentOffset: XYCoord,
|
|
|
|
|
) => {
|
|
|
|
|
// Calculate actual drop position by snapping based on x, y and grid cell size
|
|
|
|
|
return snapToGrid(
|
|
|
|
|
colWidth,
|
|
|
|
|
rowHeight,
|
2020-01-17 09:27:38 +00:00
|
|
|
dragOffset.x - parentOffset.x - CONTAINER_GRID_PADDING,
|
|
|
|
|
dragOffset.y - parentOffset.y - CONTAINER_GRID_PADDING,
|
2019-09-25 21:21:04 +00:00
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const areIntersecting = (r1: Rect, r2: Rect) => {
|
|
|
|
|
return !(
|
2019-10-03 15:14:50 +00:00
|
|
|
r2.left >= r1.right ||
|
|
|
|
|
r2.right <= r1.left ||
|
|
|
|
|
r2.top >= r1.bottom ||
|
|
|
|
|
r2.bottom <= r1.top
|
2019-09-25 21:21:04 +00:00
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export const isDropZoneOccupied = (
|
|
|
|
|
offset: Rect,
|
2019-10-08 06:19:10 +00:00
|
|
|
widgetId: string,
|
2019-11-13 07:00:25 +00:00
|
|
|
occupied?: OccupiedSpace[],
|
2019-09-25 21:21:04 +00:00
|
|
|
) => {
|
|
|
|
|
if (occupied) {
|
|
|
|
|
occupied = occupied.filter(widgetDetails => {
|
2019-10-02 21:14:58 +00:00
|
|
|
return (
|
2019-10-08 06:19:10 +00:00
|
|
|
widgetDetails.id !== widgetId && widgetDetails.parentId !== widgetId
|
2019-10-02 21:14:58 +00:00
|
|
|
);
|
2019-09-25 21:21:04 +00:00
|
|
|
});
|
|
|
|
|
for (let i = 0; i < occupied.length; i++) {
|
|
|
|
|
if (areIntersecting(occupied[i], offset)) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
};
|
|
|
|
|
|
2019-10-08 06:19:10 +00:00
|
|
|
export const isWidgetOverflowingParentBounds = (
|
|
|
|
|
parentRowCols: { rows?: number; cols?: number },
|
|
|
|
|
offset: Rect,
|
2020-01-16 11:46:21 +00:00
|
|
|
): boolean => {
|
|
|
|
|
const result =
|
|
|
|
|
offset.right < 0 ||
|
|
|
|
|
offset.top < 0 ||
|
2019-10-08 06:19:10 +00:00
|
|
|
(parentRowCols.cols || GridDefaults.DEFAULT_GRID_COLUMNS) < offset.right ||
|
2020-01-16 11:46:21 +00:00
|
|
|
(parentRowCols.rows || 0) < offset.bottom;
|
|
|
|
|
|
|
|
|
|
return result;
|
2019-10-08 06:19:10 +00:00
|
|
|
};
|
|
|
|
|
|
2019-09-25 21:21:04 +00:00
|
|
|
export const noCollision = (
|
|
|
|
|
clientOffset: XYCoord,
|
|
|
|
|
colWidth: number,
|
|
|
|
|
rowHeight: number,
|
|
|
|
|
widget: WidgetProps & Partial<WidgetConfigProps>,
|
|
|
|
|
dropTargetOffset: XYCoord,
|
2019-11-13 07:00:25 +00:00
|
|
|
occupiedSpaces?: OccupiedSpace[],
|
2019-10-08 06:19:10 +00:00
|
|
|
rows?: number,
|
|
|
|
|
cols?: number,
|
2019-09-25 21:21:04 +00:00
|
|
|
): boolean => {
|
|
|
|
|
if (clientOffset && dropTargetOffset && widget) {
|
|
|
|
|
const [left, top] = getDropZoneOffsets(
|
|
|
|
|
colWidth,
|
|
|
|
|
rowHeight,
|
|
|
|
|
clientOffset as XYCoord,
|
|
|
|
|
dropTargetOffset,
|
|
|
|
|
);
|
2020-01-16 11:46:21 +00:00
|
|
|
if (left < 0 || top < 0) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2019-09-25 21:21:04 +00:00
|
|
|
const widgetWidth = widget.columns
|
|
|
|
|
? widget.columns
|
|
|
|
|
: widget.rightColumn - widget.leftColumn;
|
|
|
|
|
const widgetHeight = widget.rows
|
|
|
|
|
? widget.rows
|
|
|
|
|
: widget.bottomRow - widget.topRow;
|
|
|
|
|
const currentOffset = {
|
|
|
|
|
left,
|
|
|
|
|
right: left + widgetWidth,
|
|
|
|
|
top,
|
|
|
|
|
bottom: top + widgetHeight,
|
|
|
|
|
};
|
2019-10-08 06:19:10 +00:00
|
|
|
return (
|
|
|
|
|
!isDropZoneOccupied(currentOffset, widget.widgetId, occupiedSpaces) &&
|
|
|
|
|
!isWidgetOverflowingParentBounds({ rows, cols }, currentOffset)
|
|
|
|
|
);
|
2019-09-25 21:21:04 +00:00
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
};
|
|
|
|
|
|
2020-01-16 11:46:21 +00:00
|
|
|
export const currentDropRow = (
|
|
|
|
|
dropTargetRowSpace: number,
|
|
|
|
|
dropTargetVerticalOffset: number,
|
|
|
|
|
draggableItemVerticalOffset: number,
|
|
|
|
|
widget: WidgetProps & Partial<WidgetConfigProps>,
|
|
|
|
|
) => {
|
|
|
|
|
const widgetHeight = widget.rows
|
|
|
|
|
? widget.rows
|
|
|
|
|
: widget.bottomRow - widget.topRow;
|
|
|
|
|
const top = Math.round(
|
|
|
|
|
(draggableItemVerticalOffset - dropTargetVerticalOffset) /
|
|
|
|
|
dropTargetRowSpace,
|
|
|
|
|
);
|
|
|
|
|
const currentBottomOffset = top + widgetHeight;
|
|
|
|
|
return currentBottomOffset;
|
|
|
|
|
};
|
|
|
|
|
|
2019-09-25 17:24:23 +00:00
|
|
|
export const widgetOperationParams = (
|
|
|
|
|
widget: WidgetProps & Partial<WidgetConfigProps>,
|
|
|
|
|
widgetOffset: XYCoord,
|
|
|
|
|
parentOffset: XYCoord,
|
|
|
|
|
parentColumnSpace: number,
|
|
|
|
|
parentRowSpace: number,
|
2019-11-13 07:00:25 +00:00
|
|
|
parentWidgetId: string, // parentWidget
|
|
|
|
|
): WidgetOperationParams => {
|
|
|
|
|
const [leftColumn, topRow] = getDropZoneOffsets(
|
|
|
|
|
parentColumnSpace,
|
|
|
|
|
parentRowSpace,
|
|
|
|
|
widgetOffset,
|
|
|
|
|
parentOffset,
|
|
|
|
|
);
|
|
|
|
|
// If this is an existing widget, we'll have the widgetId
|
|
|
|
|
// Therefore, this is a move operation on drop of the widget
|
|
|
|
|
if (widget.widgetId) {
|
|
|
|
|
return {
|
|
|
|
|
operation: WidgetOperations.MOVE,
|
|
|
|
|
widgetId: widget.widgetId,
|
|
|
|
|
payload: {
|
|
|
|
|
leftColumn,
|
|
|
|
|
topRow,
|
|
|
|
|
parentId: widget.parentId,
|
|
|
|
|
newParentId: parentWidgetId,
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
// If this is not an existing widget, we'll not have the widgetId
|
|
|
|
|
// Therefore, this is an operation to add child to this container
|
2019-09-25 17:24:23 +00:00
|
|
|
}
|
2019-11-13 07:00:25 +00:00
|
|
|
const widgetDimensions = {
|
|
|
|
|
columns: widget.columns,
|
|
|
|
|
rows: widget.rows,
|
|
|
|
|
};
|
|
|
|
|
return {
|
|
|
|
|
operation: WidgetOperations.ADD_CHILD,
|
|
|
|
|
widgetId: parentWidgetId,
|
|
|
|
|
payload: {
|
|
|
|
|
type: widget.type,
|
2020-01-02 12:42:02 +00:00
|
|
|
newWidgetId: generateReactKey(),
|
2019-11-13 07:00:25 +00:00
|
|
|
leftColumn,
|
|
|
|
|
topRow,
|
|
|
|
|
...widgetDimensions,
|
|
|
|
|
parentRowSpace,
|
|
|
|
|
parentColumnSpace,
|
|
|
|
|
},
|
|
|
|
|
};
|
2019-09-25 17:24:23 +00:00
|
|
|
};
|
|
|
|
|
|
2019-09-22 20:25:05 +00:00
|
|
|
export const updateWidgetPosition = (
|
|
|
|
|
widget: WidgetProps,
|
2019-09-25 17:24:23 +00:00
|
|
|
leftColumn: number,
|
|
|
|
|
topRow: number,
|
2019-09-22 20:25:05 +00:00
|
|
|
) => {
|
|
|
|
|
const newPositions = {
|
2019-09-25 17:24:23 +00:00
|
|
|
leftColumn,
|
|
|
|
|
topRow,
|
|
|
|
|
rightColumn: leftColumn + (widget.rightColumn - widget.leftColumn),
|
|
|
|
|
bottomRow: topRow + (widget.bottomRow - widget.topRow),
|
2019-09-22 20:25:05 +00:00
|
|
|
};
|
2020-01-16 11:46:21 +00:00
|
|
|
if (widget.type === WidgetTypes.CONTAINER_WIDGET) {
|
|
|
|
|
widget.snapRows = newPositions.bottomRow - newPositions.topRow - 1;
|
2019-09-22 20:25:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
...widget,
|
2020-01-16 11:46:21 +00:00
|
|
|
...newPositions,
|
2019-09-22 20:25:05 +00:00
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
2019-09-19 22:25:37 +00:00
|
|
|
export const generateWidgetProps = (
|
|
|
|
|
parent: ContainerWidgetProps<WidgetProps>,
|
|
|
|
|
type: WidgetType,
|
2019-09-25 17:24:23 +00:00
|
|
|
leftColumn: number,
|
|
|
|
|
topRow: number,
|
|
|
|
|
columns: number,
|
|
|
|
|
rows: number,
|
|
|
|
|
parentRowSpace: number,
|
|
|
|
|
parentColumnSpace: number,
|
2019-10-02 18:13:04 +00:00
|
|
|
widgetName: string,
|
|
|
|
|
widgetConfig: Partial<WidgetProps>,
|
2020-01-02 12:42:02 +00:00
|
|
|
): Partial<ContainerWidgetProps<WidgetProps>> => {
|
2019-09-19 22:25:37 +00:00
|
|
|
if (parent && parent.snapColumns && parent.snapRows) {
|
2019-09-22 20:25:05 +00:00
|
|
|
const sizes = {
|
2019-09-25 17:24:23 +00:00
|
|
|
leftColumn,
|
|
|
|
|
rightColumn: leftColumn + columns,
|
|
|
|
|
topRow,
|
|
|
|
|
bottomRow: topRow + rows,
|
2019-09-22 20:25:05 +00:00
|
|
|
};
|
|
|
|
|
let others = {};
|
|
|
|
|
if (type === WidgetTypes.CONTAINER_WIDGET) {
|
|
|
|
|
others = {
|
|
|
|
|
snapColumns: DEFAULT_GRID_COLUMNS,
|
2020-01-16 11:46:21 +00:00
|
|
|
snapRows: rows - 1,
|
2019-09-22 20:25:05 +00:00
|
|
|
orientation: "VERTICAL",
|
|
|
|
|
children: [],
|
|
|
|
|
};
|
|
|
|
|
}
|
2020-01-17 09:28:26 +00:00
|
|
|
const derivedProperties = DerivedPropFactory.getDerivedPropertiesOfWidgetType(
|
|
|
|
|
type,
|
|
|
|
|
widgetName,
|
|
|
|
|
);
|
|
|
|
|
const dynamicBindings: Record<string, true> = {};
|
|
|
|
|
Object.keys(derivedProperties).forEach(prop => {
|
|
|
|
|
dynamicBindings[prop] = true;
|
|
|
|
|
});
|
2019-09-22 20:25:05 +00:00
|
|
|
return {
|
2019-10-02 18:13:04 +00:00
|
|
|
...widgetConfig,
|
2019-09-22 20:25:05 +00:00
|
|
|
type,
|
2020-01-17 09:28:26 +00:00
|
|
|
widgetName,
|
2019-09-19 22:25:37 +00:00
|
|
|
isVisible: true,
|
2019-11-20 08:10:01 +00:00
|
|
|
isLoading: false,
|
2019-09-25 17:24:23 +00:00
|
|
|
parentColumnSpace,
|
|
|
|
|
parentRowSpace,
|
2020-01-17 09:28:26 +00:00
|
|
|
dynamicBindings,
|
2019-09-22 20:25:05 +00:00
|
|
|
...sizes,
|
|
|
|
|
...others,
|
2020-01-17 09:28:26 +00:00
|
|
|
...derivedProperties,
|
2019-09-19 22:25:37 +00:00
|
|
|
};
|
|
|
|
|
} else {
|
2020-01-16 11:46:21 +00:00
|
|
|
if (parent) {
|
2019-09-19 22:25:37 +00:00
|
|
|
throw Error("Failed to create widget: Parent's size cannot be calculate");
|
2020-01-16 11:46:21 +00:00
|
|
|
} else throw Error("Failed to create widget: Parent was not provided ");
|
2019-09-19 22:25:37 +00:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2019-09-18 10:48:56 +00:00
|
|
|
export default {
|
|
|
|
|
extractCurrentDSL,
|
2019-09-19 22:25:37 +00:00
|
|
|
generateWidgetProps,
|
2019-09-18 10:48:56 +00:00
|
|
|
};
|