WIP: Use rows and columns accross the board, cleanup DropTargetMonitor

This commit is contained in:
Abhinav Jha 2019-09-25 22:54:23 +05:30
parent 92eb493d62
commit b155efe3c0
18 changed files with 339 additions and 163 deletions

View File

@ -96,16 +96,18 @@ export const savePageError = (payload: SavePageErrorPayload) => {
export type WidgetAddChild = { export type WidgetAddChild = {
widgetId: string; widgetId: string;
type: WidgetType; type: WidgetType;
left: number; leftColumn: number;
top: number; topRow: number;
width: number; columns: number;
height: number; rows: number;
parentRowSpace: number;
parentColumnSpace: number;
}; };
export type WidgetMove = { export type WidgetMove = {
widgetId: string; widgetId: string;
left: number; leftColumn: number;
top: number; topRow: number;
/* /*
If parentWidgetId is different from what we have in redux store, If parentWidgetId is different from what we have in redux store,
then we have to delete this, then we have to delete this,

View File

@ -0,0 +1,17 @@
import Api from "./Api";
import { WidgetType } from "../constants/WidgetConstants";
import { WidgetProps } from "../widgets/BaseWidget";
import { WidgetConfigProps } from "../reducers/entityReducers/widgetConfigReducer";
export interface WidgetConfigsResponse {
config: Record<WidgetType, Partial<WidgetProps> & WidgetConfigProps>;
}
class WidgetConfigsApi extends Api {
static url = "/widgetConfigs";
static fetchWidgetConfigs(): Promise<WidgetConfigsResponse> {
return Api.get(WidgetConfigsApi.url);
}
}
export default WidgetConfigsApi;

View File

@ -2,16 +2,21 @@
export const Colors: Record<string, string> = { export const Colors: Record<string, string> = {
WHITE: "#FFFFFF", WHITE: "#FFFFFF",
POLAR: "#E9FAF3", POLAR: "#E9FAF3",
GEYSER: "#D0D7DD", GEYSER: "#D3DEE3",
ATHENS_GRAY: "#FAFBFC",
BLACK: "#000000", BLACK: "#000000",
BLACK_PEARL: "#040627", BLACK_PEARL: "#040627",
SHARK: "#21282C", SHARK: "#21282C",
OUTER_SPACE: "#272E32", OUTER_SPACE: "#272E32",
SLATE_GRAY: "#768896",
PORCELAIN: "#EBEEF0",
HIT_GRAY: "#A1ACB3",
GREEN: "#29CCA3", GREEN: "#29CCA3",
RED: "#CE4257", RED: "#CE4257",
PURPLE: "#6871EF", PURPLE: "#6871EF",
OXFORD_BLUE: "#2E3D49",
}; };
export type Color = (typeof Colors)[keyof typeof Colors]; export type Color = (typeof Colors)[keyof typeof Colors];

View File

@ -38,6 +38,7 @@ export const theme: Theme = {
border: Colors.GEYSER, border: Colors.GEYSER,
paneCard: Colors.SHARK, paneCard: Colors.SHARK,
paneBG: Colors.OUTER_SPACE, paneBG: Colors.OUTER_SPACE,
grid: Colors.POLAR,
}, },
lineHeights: [0, 14, 18, 22, 24, 28, 36, 48, 64, 80], lineHeights: [0, 14, 18, 22, 24, 28, 36, 48, 64, 80],
fonts: [FontFamilies.DMSans, FontFamilies.AppsmithWidget], fonts: [FontFamilies.DMSans, FontFamilies.AppsmithWidget],

View File

@ -8,8 +8,8 @@ import { Color } from "../constants/Colors";
abstract class BaseComponent<T extends ComponentProps> extends Component<T> {} abstract class BaseComponent<T extends ComponentProps> extends Component<T> {}
export interface BaseStyle { export interface BaseStyle {
componentHeight?: number; componentHeight: number;
componentWidth?: number; componentWidth: number;
positionType: PositionType; positionType: PositionType;
xPosition: number; xPosition: number;
yPosition: number; yPosition: number;

View File

@ -15,25 +15,38 @@ const WrappedDragLayer = styled.div`
`; `;
type DragLayerProps = { type DragLayerProps = {
height: number;
width: number;
parentOffset: XYCoord; parentOffset: XYCoord;
cellSize: number; parentRowHeight: number;
parentColumnWidth: number;
visible: boolean; visible: boolean;
}; };
const DragLayerComponent = (props: DragLayerProps) => { const DragLayerComponent = (props: DragLayerProps) => {
const { isDragging, currentOffset } = useDragLayer(monitor => ({ const { isDragging, currentOffset, widget } = useDragLayer(monitor => ({
isDragging: monitor.isDragging(), isDragging: monitor.isDragging(),
currentOffset: monitor.getClientOffset(), currentOffset: monitor.getClientOffset(),
widget: monitor.getItem(),
})); }));
let widgetWidth = 0;
let widgetHeight = 0;
if (widget) {
widgetWidth = widget.columns
? widget.columns
: widget.rightColumn - widget.leftColumn;
widgetHeight = widget.rows ? widget.rows : widget.bottomRow - widget.topRow;
}
if (!isDragging) { if (!isDragging || !props.visible) {
return null; return null;
} }
return ( return (
<WrappedDragLayer> <WrappedDragLayer>
<DropZone {...props} currentOffset={currentOffset as XYCoord} /> <DropZone
{...props}
width={widgetWidth}
height={widgetHeight}
currentOffset={currentOffset as XYCoord}
/>
</WrappedDragLayer> </WrappedDragLayer>
); );
}; };

View File

@ -1,112 +1,109 @@
import React, { useState, useLayoutEffect, MutableRefObject } from "react"; import React, { useState } from "react";
import styled from "styled-components"; import { WidgetProps } from "../widgets/BaseWidget";
import { WidgetProps, WidgetOperations } from "../widgets/BaseWidget"; import { WidgetConfigProps } from "../reducers/entityReducers/widgetConfigReducer";
import { useDrop } from "react-dnd"; import { useDrop, XYCoord } from "react-dnd";
import { ContainerProps } from "./ContainerComponent"; import { ContainerProps } from "./ContainerComponent";
import WidgetFactory from "../utils/WidgetFactory"; import WidgetFactory from "../utils/WidgetFactory";
import { snapToGrid } from "../utils/helpers";
import { widgetOperationParams } from "../utils/WidgetPropsUtils";
import DragLayerComponent from "./DragLayerComponent"; import DragLayerComponent from "./DragLayerComponent";
import DropTargetMask from "./DropTargetMask";
import { GridDefaults } from "../constants/WidgetConstants"; /*TODO:
- Try to keep only component props, state and drop hook here - DONE
const { - Move all child components to their own file - DONE
DEFAULT_CELL_SIZE, - Provide Draglayer with the actual component size if exists
DEFAULT_WIDGET_HEIGHT, - else pull it from widgetConfig - DONE
DEFAULT_WIDGET_WIDTH, - Provide Draglayer with rows, columns, rowHeight, columnWidth instead of width height pixels - DONE
} = GridDefaults; - Return rows and columns to the drop handler (updateWidget) - DONE
- Update WidgetOperations to handle rows and columns
- Increase default canvas rowHeight
- Fix child container positioning
*/
type DropTargetComponentProps = ContainerProps & { type DropTargetComponentProps = ContainerProps & {
updateWidget?: Function; updateWidget?: Function;
snapColumns?: number;
snapRows?: number;
snapColumnSpace: number;
snapRowSpace: number;
}; };
const WrappedDropTarget = styled.div` type DropTargetBounds = {
background: white; x: number;
`; y: number;
const DropTargetMask = styled.div` width: number;
position: absolute; height: number;
z-index: -10; };
left: 0;
right: 0;
`;
export const DropTargetComponent = (props: DropTargetComponentProps) => { export const DropTargetComponent = (props: DropTargetComponentProps) => {
const [dropTargetTopLeft, setDropTargetTopLeft] = useState({ x: 0, y: 0 }); // Hook to keep the bounds of the drop target container in state
const dropTargetMask: MutableRefObject<HTMLDivElement | null> = React.useRef( const [dropTargetOffset, setDropTargetOffset] = useState({ x: 0, y: 0 });
null,
);
useLayoutEffect(() => {
const el = dropTargetMask.current;
if (el) {
const rect = el.getBoundingClientRect();
setDropTargetTopLeft({
x: rect.left,
y: rect.top,
});
}
}, [setDropTargetTopLeft]);
// Make this component a drop target
const [{ isOver }, drop] = useDrop({ const [{ isOver }, drop] = useDrop({
accept: Object.values(WidgetFactory.getWidgetTypes()), accept: Object.values(WidgetFactory.getWidgetTypes()),
drop(widget: WidgetProps, monitor) { drop(widget: WidgetProps & Partial<WidgetConfigProps>, monitor) {
if (monitor.isOver({ shallow: true })) { // Make sure we're dropping in this container.
const clientOffset = monitor.getClientOffset(); if (isOver) {
if (clientOffset) { props.updateWidget &&
const [x, y] = snapToGrid( props.updateWidget(
DEFAULT_CELL_SIZE, ...widgetOperationParams(
clientOffset.x - dropTargetTopLeft.x, widget,
clientOffset.y - dropTargetTopLeft.y, monitor.getClientOffset() as XYCoord,
dropTargetOffset,
props.snapColumnSpace,
props.snapRowSpace,
props.widgetId,
),
); );
if (widget.widgetId) {
props.updateWidget &&
props.updateWidget(WidgetOperations.MOVE, widget.widgetId, {
left: x,
top: y,
});
} else {
props.updateWidget &&
props.updateWidget(WidgetOperations.ADD_CHILD, props.widgetId, {
type: widget.type,
left: x,
top: y,
width:
Math.round(DEFAULT_WIDGET_WIDTH / DEFAULT_CELL_SIZE) *
DEFAULT_CELL_SIZE,
height:
Math.round(DEFAULT_WIDGET_HEIGHT / DEFAULT_CELL_SIZE) *
DEFAULT_CELL_SIZE,
});
}
}
} }
return undefined; return undefined;
}, },
// Collect isOver for ui transforms when hovering over this component
collect: monitor => ({ collect: monitor => ({
isOver: !!monitor.isOver({ shallow: true }), isOver: !!monitor.isOver({ shallow: true }),
}), }),
// Only allow drop if the drag object is directly over this component
// As opposed to the drag object being over a child component, or outside the component bounds
canDrop: (widget, monitor) => { canDrop: (widget, monitor) => {
return monitor.isOver({ shallow: true }); return monitor.isOver({ shallow: true });
}, },
}); });
const handleBoundsUpdate = (rect: DOMRect) => {
if (rect.x !== dropTargetOffset.x || rect.y !== dropTargetOffset.y) {
setDropTargetOffset({
x: rect.x,
y: rect.y,
});
}
};
return ( return (
<WrappedDropTarget <div
ref={drop} ref={drop}
style={{ style={{
position: "relative",
left: props.style.xPosition + props.style.xPositionUnit, left: props.style.xPosition + props.style.xPositionUnit,
height: props.style.componentHeight, height: props.style.componentHeight,
width: props.style.componentWidth, width: props.style.componentWidth,
top: props.style.yPosition + props.style.yPositionUnit, top: props.style.yPosition + props.style.yPositionUnit,
}} }}
> >
<DropTargetMask ref={dropTargetMask} /> <DropTargetMask
rowHeight={props.snapRowSpace}
columnWidth={props.snapColumnSpace}
setBounds={handleBoundsUpdate}
/>
<DragLayerComponent <DragLayerComponent
parentOffset={dropTargetTopLeft} parentOffset={dropTargetOffset}
width={DEFAULT_WIDGET_WIDTH} parentRowHeight={props.snapRowSpace}
height={DEFAULT_WIDGET_HEIGHT} parentColumnWidth={props.snapColumnSpace}
cellSize={DEFAULT_CELL_SIZE}
visible={isOver} visible={isOver}
/> />
{props.children} {props.children}
</WrappedDropTarget> </div>
); );
}; };

View File

@ -0,0 +1,45 @@
import React, { useLayoutEffect, MutableRefObject } from "react";
import styled from "styled-components";
type DropTargetMaskProps = {
rowHeight: number;
columnWidth: number;
setBounds: Function;
};
export const DropTargetMaskWrapper = styled.div<DropTargetMaskProps>`
position: absolute;
left: 0;
top: 0;
bottom: 0;
right: 0;
width: 100%;
height: 100%;
background: white;
background-image: radial-gradient(
circle,
${props => props.theme.colors.grid} 2px,
transparent 0
);
background-size: ${props => props.columnWidth}px ${props => props.rowHeight}px;
background-position: -50% -50%;
`;
/* eslint-disable react/display-name */
export const DropTargetMask = (props: DropTargetMaskProps) => {
// An underlay div for Grid markers and calculating the width, height, x and y positions
const dropTargetMask: MutableRefObject<HTMLDivElement | null> = React.useRef(
null,
);
// Fetch new height, width, x and y positions
useLayoutEffect(() => {
const el = dropTargetMask.current;
if (el) {
const rect = el.getBoundingClientRect();
props.setBounds(rect);
}
});
return <DropTargetMaskWrapper {...props} ref={dropTargetMask} />;
};
export default DropTargetMask;

View File

@ -24,7 +24,8 @@ type DropZoneProps = {
width: number; width: number;
visible: boolean; visible: boolean;
parentOffset: XYCoord; parentOffset: XYCoord;
cellSize: number; parentRowHeight: number;
parentColumnWidth: number;
}; };
/* eslint-disable react/display-name */ /* eslint-disable react/display-name */
@ -39,17 +40,18 @@ export const DropZone = (props: DropZoneProps) => {
if (props.visible) { if (props.visible) {
if (props.currentOffset && props.currentOffset.x >= props.parentOffset.x) { if (props.currentOffset && props.currentOffset.x >= props.parentOffset.x) {
const [x, y] = snapToGrid( const [leftColumn, topRow] = snapToGrid(
props.cellSize, props.parentColumnWidth,
props.parentRowHeight,
props.currentOffset.x - props.parentOffset.x, props.currentOffset.x - props.parentOffset.x,
props.currentOffset.y - props.parentOffset.y, props.currentOffset.y - props.parentOffset.y,
); );
wrapperProps = { wrapperProps = {
visible: true, visible: true,
left: x, left: leftColumn * props.parentColumnWidth,
top: y, top: topRow * props.parentRowHeight,
height: props.height, height: props.height * props.parentRowHeight,
width: props.width, width: props.width * props.parentColumnWidth,
}; };
} }
} }

View File

@ -28,7 +28,7 @@ const CanvasContainer = styled.section`
height: 100%; height: 100%;
width: 100%; width: 100%;
position: relative; position: relative;
overflow-x: hidden; overflow-x: auto;
overflow-y: auto; overflow-y: auto;
margin: 0px 10px; margin: 0px 10px;
&:before { &:before {
@ -109,12 +109,29 @@ class Editor extends Component<EditorProps> {
} }
const mapStateToProps = (state: AppState): EditorReduxState => { const mapStateToProps = (state: AppState): EditorReduxState => {
// TODO(abhinav) : Benchmark this, see how many times this is called in the application
// lifecycle. Move to using flattend redux state for widgets if necessary.
// Also, try to merge the widgetCards and widgetConfigs in the fetch Saga.
// No point in storing widgetCards, without widgetConfig
// Alternatively, try to see if we can continue to use only WidgetConfig and eliminate WidgetCards
const dsl = CanvasWidgetsNormalizer.denormalize( const dsl = CanvasWidgetsNormalizer.denormalize(
state.ui.editor.pageWidgetId, state.ui.editor.pageWidgetId,
state.entities, state.entities,
); );
const configs = state.entities.widgetConfig.config;
const cards = state.ui.widgetCardsPane.cards;
Object.keys(cards).forEach((group: string) => {
cards[group] = cards[group].map((widget: WidgetCardProps) => ({
...widget,
...configs[widget.type],
}));
});
return { return {
cards: state.ui.widgetCardsPane.cards, cards,
dsl, dsl,
pageWidgetId: state.ui.editor.pageWidgetId, pageWidgetId: state.ui.editor.pageWidgetId,
currentPageId: state.ui.editor.currentPageId, currentPageId: state.ui.editor.currentPageId,

View File

@ -9,6 +9,7 @@ import { QueryDataState } from "./entityReducers/queryDataReducer";
import { ActionDataState } from "./entityReducers/actionsReducer"; import { ActionDataState } from "./entityReducers/actionsReducer";
import { PropertyPaneConfigState } from "./entityReducers/propertyPaneConfigReducer"; import { PropertyPaneConfigState } from "./entityReducers/propertyPaneConfigReducer";
import { PropertyPaneReduxState } from "./uiReducers/propertyPaneReducer"; import { PropertyPaneReduxState } from "./uiReducers/propertyPaneReducer";
import { WidgetConfigReducerState } from "./entityReducers/widgetConfigReducer";
const appReducer = combineReducers({ const appReducer = combineReducers({
entities: entityReducer, entities: entityReducer,
@ -29,5 +30,6 @@ export interface AppState {
queryData: QueryDataState; queryData: QueryDataState;
actions: ActionDataState; actions: ActionDataState;
propertyConfig: PropertyPaneConfigState; propertyConfig: PropertyPaneConfigState;
widgetConfig: WidgetConfigReducerState;
}; };
} }

View File

@ -81,7 +81,6 @@ export function* saveLayoutSaga(
) { ) {
try { try {
const { widgets } = updateLayoutAction.payload; const { widgets } = updateLayoutAction.payload;
const denormalizedDSL = CanvasWidgetsNormalizer.denormalize( const denormalizedDSL = CanvasWidgetsNormalizer.denormalize(
Object.keys(widgets)[0], Object.keys(widgets)[0],
{ canvasWidgets: widgets }, { canvasWidgets: widgets },

View File

@ -4,13 +4,13 @@ import WidgetCardsPaneApi, {
WidgetCardsPaneResponse, WidgetCardsPaneResponse,
} from "../api/WidgetCardsPaneApi"; } from "../api/WidgetCardsPaneApi";
import { successFetchingWidgetCards } from "../actions/widgetCardsPaneActions"; import { successFetchingWidgetCards } from "../actions/widgetCardsPaneActions";
import { call, put, takeLatest } from "redux-saga/effects"; import { call, put, takeLatest, all } from "redux-saga/effects";
export function* fetchWidgetCards() { export function* fetchWidgetCards() {
try { try {
const widgetCards: WidgetCardsPaneResponse = yield call( const widgetCards: WidgetCardsPaneResponse = yield all([
WidgetCardsPaneApi.fetchWidgetCards, call(WidgetCardsPaneApi.fetchWidgetCards),
); ]);
yield put(successFetchingWidgetCards(widgetCards.cards)); yield put(successFetchingWidgetCards(widgetCards.cards));
} catch (err) { } catch (err) {
yield put({ type: ReduxActionTypes.ERROR_FETCHING_WIDGET_CARDS, err }); yield put({ type: ReduxActionTypes.ERROR_FETCHING_WIDGET_CARDS, err });

View File

@ -19,17 +19,28 @@ import { put, select, takeEvery, takeLatest, all } from "redux-saga/effects";
export function* addChildSaga(addChildAction: ReduxAction<WidgetAddChild>) { export function* addChildSaga(addChildAction: ReduxAction<WidgetAddChild>) {
try { try {
const { widgetId, type, left, top, width, height } = addChildAction.payload; const {
widgetId,
type,
leftColumn,
topRow,
columns,
rows,
parentRowSpace,
parentColumnSpace,
} = addChildAction.payload;
const widget: FlattenedWidgetProps = yield select(getWidget, widgetId); const widget: FlattenedWidgetProps = yield select(getWidget, widgetId);
const widgets = yield select(getWidgets); const widgets = yield select(getWidgets);
const childWidget = generateWidgetProps( const childWidget = generateWidgetProps(
widget, widget,
type, type,
left, leftColumn,
top, topRow,
width, columns,
height, rows,
parentRowSpace,
parentColumnSpace,
); );
widgets[childWidget.widgetId] = childWidget; widgets[childWidget.widgetId] = childWidget;
if (widget && widget.children) { if (widget && widget.children) {
@ -81,14 +92,14 @@ export function* deleteSaga(deleteAction: ReduxAction<WidgetDelete>) {
export function* moveSaga(moveAction: ReduxAction<WidgetMove>) { export function* moveSaga(moveAction: ReduxAction<WidgetMove>) {
try { try {
const { widgetId, left, top, parentWidgetId } = moveAction.payload; const { widgetId, leftColumn, topRow, parentWidgetId } = moveAction.payload;
let widget: FlattenedWidgetProps = yield select(getWidget, widgetId); let widget: FlattenedWidgetProps = yield select(getWidget, widgetId);
const widgets = yield select(getWidgets) as any; const widgets = yield select(getWidgets) as any;
let parentWidget = null; let parentWidget = null;
if (parentWidgetId) { if (parentWidgetId) {
parentWidget = yield select(getWidget, parentWidgetId); parentWidget = yield select(getWidget, parentWidgetId);
} }
widget = updateWidgetPosition(widget, left, top, parentWidget); widget = updateWidgetPosition(widget, leftColumn, topRow, parentWidget);
widgets[widgetId] = widget; widgets[widgetId] = widget;
if (parentWidgetId) { if (parentWidgetId) {
widgets[parentWidgetId].children.push(widgetId); widgets[parentWidgetId].children.push(widgetId);

View File

@ -1,10 +1,13 @@
import { FetchPageResponse } from "../api/PageApi"; import { FetchPageResponse } from "../api/PageApi";
import { XYCoord } from "react-dnd";
import { ContainerWidgetProps } from "../widgets/ContainerWidget"; import { ContainerWidgetProps } from "../widgets/ContainerWidget";
import { WidgetProps } from "../widgets/BaseWidget"; import { WidgetConfigProps } from "../reducers/entityReducers/widgetConfigReducer";
import { WidgetProps, WidgetOperations } from "../widgets/BaseWidget";
import { WidgetType, RenderModes } from "../constants/WidgetConstants"; import { WidgetType, RenderModes } from "../constants/WidgetConstants";
import { generateReactKey } from "../utils/generators"; import { generateReactKey } from "../utils/generators";
import { Colors } from "../constants/Colors"; import { Colors } from "../constants/Colors";
import { GridDefaults, WidgetTypes } from "../constants/WidgetConstants"; import { GridDefaults, WidgetTypes } from "../constants/WidgetConstants";
import { snapToGrid } from "./helpers";
const { DEFAULT_GRID_COLUMNS, DEFAULT_GRID_ROWS } = GridDefaults; const { DEFAULT_GRID_COLUMNS, DEFAULT_GRID_ROWS } = GridDefaults;
@ -14,21 +17,67 @@ export const extractCurrentDSL = (
return fetchPageResponse.data.layouts[0].dsl; return fetchPageResponse.data.layouts[0].dsl;
}; };
export const widgetOperationParams = (
widget: WidgetProps & Partial<WidgetConfigProps>,
widgetOffset: XYCoord,
parentOffset: XYCoord,
parentColumnSpace: number,
parentRowSpace: number,
widgetId?: string,
) => {
if (widgetOffset) {
// Calculate actual drop position by snapping based on x, y and grid cell size
const [leftColumn, topRow] = snapToGrid(
parentColumnSpace,
parentRowSpace,
widgetOffset.x - parentOffset.x,
widgetOffset.y - parentOffset.y,
);
// 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 [
WidgetOperations.MOVE,
widget.widgetId,
{
leftColumn,
topRow,
},
];
// If this is not an existing widget, we'll not have the widgetId
// Therefore, this is an operation to add child to this container
} else {
const widgetDimensions = {
columns: widget.columns,
rows: widget.rows,
};
return [
WidgetOperations.ADD_CHILD,
widgetId,
{
type: widget.type,
leftColumn,
topRow,
...widgetDimensions,
parentRowSpace,
parentColumnSpace,
},
];
}
}
};
export const updateWidgetPosition = ( export const updateWidgetPosition = (
widget: WidgetProps, widget: WidgetProps,
left: number, leftColumn: number,
top: number, topRow: number,
parent?: WidgetProps, parent?: WidgetProps,
) => { ) => {
const newPositions = { const newPositions = {
leftColumn: Math.floor(left / widget.parentColumnSpace), leftColumn,
topRow: Math.floor(top / widget.parentRowSpace), topRow,
rightColumn: rightColumn: leftColumn + (widget.rightColumn - widget.leftColumn),
Math.floor(left / widget.parentColumnSpace) + bottomRow: topRow + (widget.bottomRow - widget.topRow),
(widget.rightColumn - widget.leftColumn),
bottomRow:
Math.floor(top / widget.parentRowSpace) +
(widget.bottomRow - widget.topRow),
}; };
if (parent) { if (parent) {
} }
@ -60,25 +109,19 @@ export const updateWidgetSize = (
export const generateWidgetProps = ( export const generateWidgetProps = (
parent: ContainerWidgetProps<WidgetProps>, parent: ContainerWidgetProps<WidgetProps>,
type: WidgetType, type: WidgetType,
left: number, leftColumn: number,
top: number, topRow: number,
width: number, columns: number,
height: number, rows: number,
parentRowSpace: number,
parentColumnSpace: number,
): ContainerWidgetProps<WidgetProps> => { ): ContainerWidgetProps<WidgetProps> => {
if (parent && parent.snapColumns && parent.snapRows) { if (parent && parent.snapColumns && parent.snapRows) {
const parentColumnWidth = Math.floor(
((parent.rightColumn - parent.leftColumn) * parent.parentColumnSpace) /
parent.snapColumns,
);
const parentRowHeight = Math.floor(
((parent.bottomRow - parent.topRow) * parent.parentRowSpace) /
parent.snapRows,
);
const sizes = { const sizes = {
leftColumn: Math.floor(left / parentColumnWidth), leftColumn,
rightColumn: Math.floor((left + width) / parentColumnWidth), rightColumn: leftColumn + columns,
topRow: Math.floor(top / parentRowHeight), topRow,
bottomRow: Math.floor((top + height) / parentRowHeight), bottomRow: topRow + rows,
}; };
let others = {}; let others = {};
if (type === WidgetTypes.CONTAINER_WIDGET) { if (type === WidgetTypes.CONTAINER_WIDGET) {
@ -96,8 +139,8 @@ export const generateWidgetProps = (
widgetId: generateReactKey(), widgetId: generateReactKey(),
widgetName: generateReactKey(), //TODO: figure out what this is to populate appropriately widgetName: generateReactKey(), //TODO: figure out what this is to populate appropriately
isVisible: true, isVisible: true,
parentColumnSpace: parentColumnWidth, parentColumnSpace,
parentRowSpace: parentRowHeight, parentRowSpace,
renderMode: RenderModes.CANVAS, //Is this required? renderMode: RenderModes.CANVAS, //Is this required?
...sizes, ...sizes,
...others, ...others,

View File

@ -1,5 +1,22 @@
export const snapToGrid = (cellSize: number, x: number, y: number) => { export const snapToGrid = (
const snappedX = Math.floor(x / cellSize) * cellSize; columnWidth: number,
const snappedY = Math.floor(y / cellSize) * cellSize; rowHeight: number,
x: number,
y: number,
) => {
const snappedX = Math.floor(x / columnWidth);
const snappedY = Math.floor(y / rowHeight);
return [snappedX, snappedY]; return [snappedX, snappedY];
}; };
export const getRowColSizes = (
rowCount: number,
columnCount: number,
width: number,
height: number,
): { rowHeight: number; columnWidth: number } => {
return {
columnWidth: Math.floor(width / columnCount),
rowHeight: Math.floor(height / rowCount),
};
};

View File

@ -24,11 +24,11 @@ abstract class BaseWidget<
constructor(props: T) { constructor(props: T) {
super(props); super(props);
const initialState: WidgetState = { const initialState: WidgetState = {
height: 0, componentHeight: 0,
width: 0, componentWidth: 0,
}; };
initialState.height = 0; initialState.componentHeight = 0;
initialState.width = 0; initialState.componentWidth = 0;
this.state = initialState as K; this.state = initialState as K;
} }
@ -63,13 +63,13 @@ abstract class BaseWidget<
parentRowSpace: number, parentRowSpace: number,
) { ) {
const widgetState: WidgetState = { const widgetState: WidgetState = {
width: (rightColumn - leftColumn) * parentColumnSpace, componentWidth: (rightColumn - leftColumn) * parentColumnSpace,
height: (bottomRow - topRow) * parentRowSpace, componentHeight: (bottomRow - topRow) * parentRowSpace,
}; };
if ( if (
_.isNil(this.state) || _.isNil(this.state) ||
widgetState.height !== this.state.height || widgetState.componentHeight !== this.state.componentHeight ||
widgetState.width !== this.state.width widgetState.componentWidth !== this.state.componentWidth
) { ) {
this.setState(widgetState); this.setState(widgetState);
} }
@ -112,8 +112,8 @@ abstract class BaseWidget<
getPositionStyle(): BaseStyle { getPositionStyle(): BaseStyle {
return { return {
positionType: "CONTAINER_DIRECTION", positionType: "CONTAINER_DIRECTION",
componentHeight: this.state.height, componentHeight: this.state.componentHeight,
componentWidth: this.state.width, componentWidth: this.state.componentWidth,
yPosition: this.props.topRow * this.props.parentRowSpace, yPosition: this.props.topRow * this.props.parentRowSpace,
xPosition: this.props.leftColumn * this.props.parentColumnSpace, xPosition: this.props.leftColumn * this.props.parentColumnSpace,
xPositionUnit: CSSUnits.PIXEL, xPositionUnit: CSSUnits.PIXEL,
@ -130,8 +130,8 @@ abstract class BaseWidget<
} }
export interface WidgetState { export interface WidgetState {
height: number; componentHeight: number;
width: number; componentWidth: number;
} }
export interface DraggableWidget { export interface DraggableWidget {
@ -160,6 +160,7 @@ export interface WidgetDataProps {
parentColumnSpace: number; parentColumnSpace: number;
parentRowSpace: number; parentRowSpace: number;
isVisible?: boolean; isVisible?: boolean;
isRoot?: boolean;
} }
export interface WidgetFunctions { export interface WidgetFunctions {

View File

@ -22,10 +22,10 @@ class ContainerWidget extends BaseWidget<
super(props); super(props);
this.renderChildWidget = this.renderChildWidget.bind(this); this.renderChildWidget = this.renderChildWidget.bind(this);
this.state = { this.state = {
width: 0, componentWidth: 0,
height: 0, componentHeight: 0,
snapColumnSpace: DEFAULT_GRID_COLUMNS, snapColumnSpace: 0,
snapRowSpace: DEFAULT_GRID_ROWS, snapRowSpace: 0,
}; };
} }
@ -33,19 +33,22 @@ class ContainerWidget extends BaseWidget<
super.componentDidUpdate(previousProps); super.componentDidUpdate(previousProps);
let snapColumnSpace = this.state.snapColumnSpace; let snapColumnSpace = this.state.snapColumnSpace;
let snapRowSpace = this.state.snapRowSpace; let snapRowSpace = this.state.snapRowSpace;
if (this.state.width) if (this.state.componentWidth)
snapColumnSpace = snapColumnSpace = Math.floor(
this.state.width / (this.props.snapColumns || DEFAULT_GRID_COLUMNS); this.state.componentWidth /
if (this.state.height) (this.props.snapColumns || DEFAULT_GRID_COLUMNS),
snapRowSpace = );
this.state.height / (this.props.snapRows || DEFAULT_GRID_ROWS); if (this.state.componentHeight)
snapRowSpace = Math.floor(
this.state.componentHeight / (this.props.snapRows || DEFAULT_GRID_ROWS),
);
if ( if (
this.state.snapColumnSpace !== snapColumnSpace || this.state.snapColumnSpace !== snapColumnSpace ||
this.state.snapRowSpace !== snapRowSpace this.state.snapRowSpace !== snapRowSpace
) { ) {
this.setState({ this.setState({
snapColumnSpace: snapColumnSpace, snapColumnSpace,
snapRowSpace: snapRowSpace, snapRowSpace,
}); });
} }
} }
@ -79,6 +82,7 @@ class ContainerWidget extends BaseWidget<
return ( return (
<DropTargetComponent <DropTargetComponent
{...this.props} {...this.props}
{...this.state}
style={{ style={{
...this.getPositionStyle(), ...this.getPositionStyle(),
}} }}