WIP: Canvas widget drop and positioning

This commit is contained in:
Abhinav Jha 2019-09-20 03:55:37 +05:30
parent c87552b951
commit ffb532fa7a
19 changed files with 390 additions and 93 deletions

View File

@ -1,11 +1,8 @@
import { FetchPageRequest } from "../api/PageApi"; import { FetchPageRequest } from "../api/PageApi";
import { ResponseMeta } from "../api/ApiResponses"; import { ResponseMeta } from "../api/ApiResponses";
import { RenderMode } from "../constants/WidgetConstants"; import { RenderMode } from "../constants/WidgetConstants";
import { import { WidgetProps, WidgetOperation } from "../widgets/BaseWidget";
WidgetProps, import { WidgetType } from "../constants/WidgetConstants";
WidgetDynamicProperty,
WidgetDynamicProperties,
} from "../widgets/BaseWidget";
import { import {
ReduxActionTypes, ReduxActionTypes,
ReduxAction, ReduxAction,
@ -96,19 +93,50 @@ export const savePageError = (payload: SavePageErrorPayload) => {
}; };
}; };
export const updateWidget = ( export type WidgetAddChild = {
property: WidgetDynamicProperty, widgetId: string;
widget: WidgetProps, type: WidgetType;
payload: any, left: number;
) => { top: number;
switch (property) { width: number;
case WidgetDynamicProperties.CHILDREN: height: number;
return; };
case WidgetDynamicProperties.EXISTENCE:
return; export type WidgetMove = {
case WidgetDynamicProperties.POSITION: widgetId: string;
return; left: number;
case WidgetDynamicProperties.SIZE: top: number;
return; /*
} If parentWidgetId is different from what we have in redux store,
then we got to delete this,
as it has been dropped in another container somewhere.
TODO(abhinav): Make sure to handle the scenario where they drop outside the canvas.
*/
parentWidgetId?: string;
};
export type WidgetRemoveChild = {
widgetId: string;
childWidgetId: string;
};
export type WidgetResize = {
widgetId: string;
width: number;
height: number;
};
export const updateWidget = (
operation: WidgetOperation,
widgetId: string,
payload: any,
): ReduxAction<
WidgetAddChild | WidgetMove | WidgetRemoveChild | WidgetResize
> => {
console.log(operation, widgetId, payload);
return {
type: ReduxActionTypes["WIDGET_" + operation],
payload: { widgetId, ...payload },
};
}; };

View File

@ -10,4 +10,5 @@ do
done; done;
# using relative path for now # using relative path for now
mv fonts ../../../public/ cp -rf fonts ../../../public/
rm -r fonts

View File

@ -1,6 +1,6 @@
import { WidgetProps, WidgetCardProps } from "../widgets/BaseWidget"; import { WidgetProps, WidgetCardProps } from "../widgets/BaseWidget";
export const ReduxActionTypes = { export const ReduxActionTypes: { [key: string]: string } = {
LOAD_CANVAS_WIDGETS: "LOAD_CANVAS_WIDGETS", LOAD_CANVAS_WIDGETS: "LOAD_CANVAS_WIDGETS",
FETCH_CANVAS: "FETCH_CANVAS", FETCH_CANVAS: "FETCH_CANVAS",
CLEAR_CANVAS: "CLEAR_CANVAS", CLEAR_CANVAS: "CLEAR_CANVAS",
@ -27,6 +27,11 @@ export const ReduxActionTypes = {
SAVE_PAGE_SUCCESS: "SAVE_PAGE_SUCCESS", SAVE_PAGE_SUCCESS: "SAVE_PAGE_SUCCESS",
SAVE_PAGE_ERROR: "SAVE_PAGE_ERROR", SAVE_PAGE_ERROR: "SAVE_PAGE_ERROR",
FETCH_PAGE_ERROR: "FETCH_PAGE_ERROR", FETCH_PAGE_ERROR: "FETCH_PAGE_ERROR",
UPDATE_LAYOUT: "UPDATE_LAYOUT",
WIDGET_ADD_CHILD: "WIDGET_ADD_CHILD",
WIDGET_REMOVE_CHILD: "WIDGET_REMOVE_CHILD",
WIDGET_MOVE: "WIDGET_MOVE",
WIDGET_RESIZE: "WIDGET_RESIZE",
}; };
export type ReduxActionType = (typeof ReduxActionTypes)[keyof typeof ReduxActionTypes]; export type ReduxActionType = (typeof ReduxActionTypes)[keyof typeof ReduxActionTypes];

View File

@ -5,6 +5,7 @@ import { Container } from "./ContainerComponent";
class ButtonComponent extends React.Component<ButtonComponentProps> { class ButtonComponent extends React.Component<ButtonComponentProps> {
render() { render() {
console.log("button props", this.props);
return ( return (
<Container {...this.props}> <Container {...this.props}>
<Button icon={this.props.icon} onClick={this.props.onClick}> <Button icon={this.props.icon} onClick={this.props.onClick}>

View File

@ -1,42 +1,89 @@
import React, { useState } from "react"; import React, { useState, useLayoutEffect, MutableRefObject } from "react";
import { WidgetProps, WidgetDynamicProperties } from "../widgets/BaseWidget"; import styled from "styled-components";
import { WidgetProps, WidgetOperations } from "../widgets/BaseWidget";
import { useDrop, XYCoord } 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 DropZone from "./Dropzone";
import { snapToGrid } from "../utils/helpers";
const DEFAULT_CELL_SIZE = 1;
const DEFAULT_WIDGET_WIDTH = 200;
const DEFAULT_WIDGET_HEIGHT = 50;
type DropTargetComponentProps = ContainerProps & { type DropTargetComponentProps = ContainerProps & {
onPropertyChange?: Function; updateWidget?: Function;
}; };
const WrappedDropTarget = styled.div`
background: white;
`;
const DropTargetMask = styled.div`
position: absolute;
z-index: -10;
left: 0;
right: 0;
`;
export const DropTargetComponent = (props: DropTargetComponentProps) => { export const DropTargetComponent = (props: DropTargetComponentProps) => {
const [isOver, setIsOver] = useState(false); const [dummyState, setDummyState] = useState({ x: 0, y: 0 });
const [, drop] = useDrop({ const [dropTargetTopLeft, setDropTargetTopLeft] = useState({ x: 0, y: 0 });
const dropTargetMask: MutableRefObject<HTMLDivElement | null> = React.useRef(
null,
);
useLayoutEffect(() => {
const el = dropTargetMask.current;
if (el) {
const rect = el.getBoundingClientRect();
setDropTargetTopLeft({
x: rect.left,
y: rect.top,
});
}
}, [setDropTargetTopLeft]);
const [{ isOver, clientOffset }, drop] = useDrop({
accept: Object.values(WidgetFactory.getWidgetTypes()), accept: Object.values(WidgetFactory.getWidgetTypes()),
drop(item: WidgetProps, monitor) { drop(item: WidgetProps, monitor) {
if (monitor.isOver({ shallow: true })) { if (monitor.isOver({ shallow: true })) {
const item = monitor.getItem(); const item = monitor.getItem();
const delta = monitor.getDifferenceFromInitialOffset() as XYCoord; if (clientOffset) {
const left = Math.round(item.left + delta.x); const [x, y] = snapToGrid(
const top = Math.round(item.top + delta.y); DEFAULT_CELL_SIZE,
props.onPropertyChange && clientOffset.x - dropTargetTopLeft.x,
props.onPropertyChange(WidgetDynamicProperties.CHILDREN, props, { clientOffset.y - dropTargetTopLeft.y,
item, );
left, props.updateWidget &&
top, props.updateWidget(WidgetOperations.ADD_CHILD, props.widgetId, {
type: item.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: monitor => ({
isOver: monitor.isOver({ shallow: true }),
}),
hover: (item, monitor) => { hover: (item, monitor) => {
setIsOver(monitor.isOver({ shallow: true })); setDummyState(monitor.getDifferenceFromInitialOffset() as XYCoord);
}, },
canDrop() { collect: monitor => ({
return true; hovered: !!monitor.isOver(),
isOver: !!monitor.isOver({ shallow: true }),
clientOffset: monitor.getClientOffset(),
}),
canDrop: (item, monitor) => {
return monitor.isOver({ shallow: true });
}, },
}); });
return ( return (
<div <WrappedDropTarget
ref={drop} ref={drop}
style={{ style={{
position: "absolute", position: "absolute",
@ -47,8 +94,18 @@ export const DropTargetComponent = (props: DropTargetComponentProps) => {
top: props.style.yPosition + props.style.yPositionUnit, top: props.style.yPosition + props.style.yPositionUnit,
}} }}
> >
{isOver ? undefined : props.children} <DropTargetMask ref={dropTargetMask} />
</div> <DropZone
parentOffset={dropTargetTopLeft}
width={DEFAULT_WIDGET_WIDTH}
height={DEFAULT_WIDGET_HEIGHT}
cellSize={DEFAULT_CELL_SIZE}
visible={isOver}
currentOffset={clientOffset as XYCoord}
dummyState={dummyState}
/>
{props.children}
</WrappedDropTarget>
); );
}; };

View File

@ -0,0 +1,65 @@
import React from "react";
import { XYCoord } from "react-dnd";
import styled from "styled-components";
import { snapToGrid } from "../utils/helpers";
type DropZoneWrapperProps = {
visible: boolean;
left: number;
width: number;
height: number;
top: number;
};
const DropZoneWrapper = styled.div<DropZoneWrapperProps>`
position: absolute;
z-index: 100;
display: ${props => (props.visible ? "block" : "none")};
left: ${props => props.left}px;
width: ${props => props.width}px;
top: ${props => props.top}px;
height: ${props => props.height}px;
background: ${props => props.theme.colors.hover};
border: 1px dashed ${props => props.theme.colors.textAnchor};
opacity: 0.7;
transition: all 0.2s ease-in-out 0s;
`;
type DropZoneProps = {
currentOffset: XYCoord;
height: number;
width: number;
visible: boolean;
parentOffset: XYCoord;
cellSize: number;
dummyState: XYCoord;
};
/* eslint-disable react/display-name */
export const DropZone = (props: DropZoneProps) => {
let wrapperProps = {
visible: false,
left: 0,
top: 0,
height: 0,
width: 0,
};
if (props.visible) {
if (props.currentOffset && props.currentOffset.x >= props.parentOffset.x) {
const [x, y] = snapToGrid(
props.cellSize,
props.currentOffset.x - props.parentOffset.x,
props.currentOffset.y - props.parentOffset.y,
);
wrapperProps = {
visible: true,
left: x,
top: y,
height: props.height,
width: props.width,
};
}
}
return <DropZoneWrapper {...wrapperProps} />;
};
export default DropZone;

View File

@ -14,7 +14,7 @@ const ArtBoard = styled.div`
`; `;
interface CanvasProps { interface CanvasProps {
layout: ContainerWidgetProps<WidgetProps>; dsl: ContainerWidgetProps<WidgetProps>;
widgetFunctions: WidgetFunctions; widgetFunctions: WidgetFunctions;
} }
@ -22,9 +22,9 @@ const Canvas = (props: CanvasProps) => {
return ( return (
<React.Fragment> <React.Fragment>
<ArtBoard> <ArtBoard>
{props.layout.widgetId && {props.dsl.widgetId &&
WidgetFactory.createWidget( WidgetFactory.createWidget(
props.layout, props.dsl,
props.widgetFunctions, props.widgetFunctions,
RenderModes.CANVAS, RenderModes.CANVAS,
)} )}

View File

@ -5,7 +5,7 @@ import Canvas from "./Canvas";
import { import {
WidgetCardProps, WidgetCardProps,
WidgetProps, WidgetProps,
WidgetDynamicProperty, WidgetOperation,
} from "../../widgets/BaseWidget"; } from "../../widgets/BaseWidget";
import { AppState } from "../../reducers"; import { AppState } from "../../reducers";
import { EditorReduxState } from "../../reducers/uiReducers/editorReducer"; import { EditorReduxState } from "../../reducers/uiReducers/editorReducer";
@ -49,9 +49,10 @@ const EditorWrapper = styled.div`
`; `;
type EditorProps = { type EditorProps = {
layout: ContainerWidgetProps<WidgetProps> | any; dsl: ContainerWidgetProps<WidgetProps> | any;
fetchCanvasWidgets: Function; fetchCanvasWidgets: Function;
executeAction: (actionPayloads?: ActionPayload[]) => void; executeAction: (actionPayloads?: ActionPayload[]) => void;
updateWidget: Function;
cards: { [id: string]: WidgetCardProps[] } | any; cards: { [id: string]: WidgetCardProps[] } | any;
updateWidgetProperty: Function; updateWidgetProperty: Function;
savePageLayout: Function; savePageLayout: Function;
@ -74,11 +75,13 @@ class Editor extends Component<EditorProps> {
<EditorDragLayer /> <EditorDragLayer />
<CanvasContainer> <CanvasContainer>
<Canvas <Canvas
layout={{ dsl={{
...this.props.layout, ...this.props.dsl,
onPropertyChange: this.props.updateWidgetProperty, }}
widgetFunctions={{
executeAction: this.props.executeAction,
updateWidget: this.props.updateWidget,
}} }}
widgetFunctions={{ executeAction: this.props.executeAction }}
/> />
</CanvasContainer> </CanvasContainer>
</EditorWrapper> </EditorWrapper>
@ -88,13 +91,13 @@ class Editor extends Component<EditorProps> {
} }
const mapStateToProps = (state: AppState): EditorReduxState => { const mapStateToProps = (state: AppState): EditorReduxState => {
const layout = CanvasWidgetsNormalizer.denormalize( const dsl = CanvasWidgetsNormalizer.denormalize(
state.ui.editor.pageWidgetId, state.ui.editor.pageWidgetId,
state.entities, state.entities,
); );
return { return {
cards: state.ui.widgetCardsPane.cards, cards: state.ui.widgetCardsPane.cards,
layout, dsl,
pageWidgetId: state.ui.editor.pageWidgetId, pageWidgetId: state.ui.editor.pageWidgetId,
currentPageId: state.ui.editor.currentPageId, currentPageId: state.ui.editor.currentPageId,
currentLayoutId: state.ui.editor.currentLayoutId, currentLayoutId: state.ui.editor.currentLayoutId,
@ -107,11 +110,11 @@ const mapDispatchToProps = (dispatch: any) => {
dispatch(executeAction(actionPayloads)), dispatch(executeAction(actionPayloads)),
fetchCanvasWidgets: (pageId: string) => fetchCanvasWidgets: (pageId: string) =>
dispatch(fetchPage(pageId, RenderModes.CANVAS)), dispatch(fetchPage(pageId, RenderModes.CANVAS)),
updateWidgetProperty: ( updateWidget: (
propertyType: WidgetDynamicProperty, operation: WidgetOperation,
widgetProps: WidgetProps, widgetId: string,
payload: any, payload: any,
) => dispatch(updateWidget(propertyType, widgetProps, payload)), ) => dispatch(updateWidget(operation, widgetId, payload)),
savePageLayout: ( savePageLayout: (
pageId: string, pageId: string,
layoutId: string, layoutId: string,

View File

@ -5,12 +5,13 @@ import {
ReduxAction, ReduxAction,
} from "../../constants/ReduxActionConstants"; } from "../../constants/ReduxActionConstants";
import { WidgetProps } from "../../widgets/BaseWidget"; import { WidgetProps } from "../../widgets/BaseWidget";
import { ContainerWidgetProps } from "../../widgets/ContainerWidget";
const initialState: CanvasWidgetsReduxState = {}; const initialState: CanvasWidgetsReduxState = {};
export interface FlattenedWidgetProps extends WidgetProps { export type FlattenedWidgetProps = ContainerWidgetProps<WidgetProps> & {
children?: string[]; children?: string[];
} };
const canvasWidgetsReducer = createReducer(initialState, { const canvasWidgetsReducer = createReducer(initialState, {
[ReduxActionTypes.LOAD_CANVAS_WIDGETS]: ( [ReduxActionTypes.LOAD_CANVAS_WIDGETS]: (
@ -19,24 +20,11 @@ const canvasWidgetsReducer = createReducer(initialState, {
) => { ) => {
return { ...action.payload.widgets }; return { ...action.payload.widgets };
}, },
[ReduxActionTypes.ADD_PAGE_WIDGET]: ( [ReduxActionTypes.UPDATE_LAYOUT]: (
state: CanvasWidgetsReduxState, state: CanvasWidgetsReduxState,
action: ReduxAction<{ pageId: string; widget: WidgetProps }>, action: ReduxAction<LoadCanvasWidgetsPayload>,
) => { ) => {
// const widget = action.payload.widget; return { ...action.payload.widgets };
// const widgetTree = CanvasWidgetsNormalizer.denormalize("0", {
// canvasWidgets: state,
// });
// const children = widgetTree.children || [];
// children.push(widget);
// widgetTree.children = children;
// const newState = CanvasWidgetsNormalizer.normalize({
// responseMeta: { responseCode: "SUCCESS" },
// layout: { dsl: widgetTree, actions: [] },
// }).entities;
// return newState.canvasWidgets;
console.log(action.payload.widget);
return state;
}, },
}); });

View File

@ -19,7 +19,7 @@ const editorReducer = createReducer(initialState, {
state: EditorReduxState, state: EditorReduxState,
action: ReduxAction<LoadWidgetCardsPanePayload>, action: ReduxAction<LoadWidgetCardsPanePayload>,
) => { ) => {
return { ...state.layout, ...action.payload }; return { ...state.dsl, ...action.payload };
}, },
[ReduxActionTypes.ADD_PAGE_WIDGET]: (state: EditorReduxState) => { [ReduxActionTypes.ADD_PAGE_WIDGET]: (state: EditorReduxState) => {
return state; return state;
@ -33,7 +33,7 @@ const editorReducer = createReducer(initialState, {
}); });
export interface EditorReduxState { export interface EditorReduxState {
layout?: ContainerWidgetProps<WidgetProps>; dsl?: ContainerWidgetProps<WidgetProps>;
cards?: { cards?: {
[id: string]: WidgetCardProps[]; [id: string]: WidgetCardProps[];
}; };

View File

@ -16,10 +16,13 @@ import PageApi, {
FetchPageRequest, FetchPageRequest,
SavePageRequest, SavePageRequest,
} from "../api/PageApi"; } from "../api/PageApi";
import { call, put, takeLatest, all } from "redux-saga/effects"; import { FlattenedWidgetProps } from "../reducers/entityReducers/canvasWidgetsReducer";
import { call, put, takeLatest, takeEvery, all } from "redux-saga/effects";
import { extractCurrentDSL } from "./utils"; import { extractCurrentDSL } from "./utils";
export function* fetchPage(pageRequestAction: ReduxAction<FetchPageRequest>) { export function* fetchPageSaga(
pageRequestAction: ReduxAction<FetchPageRequest>,
) {
const pageRequest = pageRequestAction.payload; const pageRequest = pageRequestAction.payload;
try { try {
const fetchPageResponse: FetchPageResponse = yield call( const fetchPageResponse: FetchPageResponse = yield call(
@ -49,7 +52,7 @@ export function* fetchPage(pageRequestAction: ReduxAction<FetchPageRequest>) {
} }
} }
export function* savePage(savePageAction: ReduxAction<SavePageRequest>) { export function* savePageSaga(savePageAction: ReduxAction<SavePageRequest>) {
const savePageRequest = savePageAction.payload; const savePageRequest = savePageAction.payload;
try { try {
const savePageResponse: SavePageResponse = yield call( const savePageResponse: SavePageResponse = yield call(
@ -63,9 +66,27 @@ export function* savePage(savePageAction: ReduxAction<SavePageRequest>) {
} }
} }
export function* updateLayoutSaga(
updateLayoutAction: ReduxAction<{
widgets: { [widgetId: string]: FlattenedWidgetProps };
}>,
) {
try {
const { widgets } = updateLayoutAction.payload;
const denormalizedDSL = CanvasWidgetsNormalizer.denormalize(
Object.keys(widgets)[0],
{ canvasWidgets: widgets },
);
console.log(denormalizedDSL);
} catch (err) {
console.log(err);
}
}
export default function* pageSagas() { export default function* pageSagas() {
yield all([ yield all([
takeLatest(ReduxActionTypes.FETCH_PAGE, fetchPage), takeLatest(ReduxActionTypes.FETCH_PAGE, fetchPageSaga),
takeLatest(ReduxActionTypes.SAVE_PAGE_INIT, savePage), takeLatest(ReduxActionTypes.SAVE_PAGE_INIT, savePageSaga),
takeEvery(ReduxActionTypes.UPDATE_LAYOUT, updateLayoutSaga),
]); ]);
} }

View File

@ -0,0 +1,59 @@
import {
ReduxActionTypes,
ReduxAction,
} from "../constants/ReduxActionConstants";
import { WidgetAddChild } from "../actions/pageActions";
import { FlattenedWidgetProps } from "../reducers/entityReducers/canvasWidgetsReducer";
import { getWidgets, getWidget } from "./selectors";
import { generateWidgetProps } from "./utils";
import { put, select, takeEvery, all } from "redux-saga/effects";
export function* addChildSaga(addChildAction: ReduxAction<WidgetAddChild>) {
try {
const { widgetId, type, left, top, width, height } = addChildAction.payload;
const widget = yield (select(
getWidget,
widgetId,
) as any) as FlattenedWidgetProps;
const widgets = yield select(getWidgets) as any;
const childWidget = generateWidgetProps(
widget,
type,
left,
top,
width,
height,
);
widgets[childWidget.widgetId] = childWidget;
if (widget && widget.children) {
widget.children.push(childWidget.widgetId);
}
widgets[widgetId] = widget;
yield put({
type: ReduxActionTypes.UPDATE_LAYOUT,
payload: { widgets },
});
} catch (err) {
yield put({
type: ReduxActionTypes.WIDGET_OPERATION_ERROR,
action: ReduxActionTypes.WIDGET_ADD_CHILD,
...err,
});
}
}
// export function* removeChildSaga() {}
// export function* moveSaga() {}
// export function* resizeSaga() {}
export default function* widgetOperationSagas() {
yield all([
takeEvery(ReduxActionTypes.WIDGET_ADD_CHILD, addChildSaga),
// takeEvery(ReduxActionTypes.WIDGET_REMOVE_CHILD, removeChildSaga),
// takeEvery(ReduxActionTypes.WIDGET_MOVE, moveSaga),
// takeEvery(ReduxActionTypes.WIDGET_RESIZE, resizeSaga),
]);
}

View File

@ -2,11 +2,13 @@ import { all, spawn } from "redux-saga/effects";
import pageSagas from "../sagas/PageSagas"; import pageSagas from "../sagas/PageSagas";
import { fetchWidgetCardsSaga } from "./WidgetCardsPaneSagas"; import { fetchWidgetCardsSaga } from "./WidgetCardsPaneSagas";
import { watchExecuteActionSaga } from "./ActionSagas"; import { watchExecuteActionSaga } from "./ActionSagas";
import widgetOperationSagas from "./WidgetOperationSagas";
export function* rootSaga() { export function* rootSaga() {
yield all([ yield all([
spawn(pageSagas), spawn(pageSagas),
spawn(fetchWidgetCardsSaga), spawn(fetchWidgetCardsSaga),
spawn(watchExecuteActionSaga), spawn(watchExecuteActionSaga),
spawn(widgetOperationSagas),
]); ]);
} }

View File

@ -0,0 +1,13 @@
import { AppState } from "../reducers";
import { FlattenedWidgetProps } from "../reducers/entityReducers/canvasWidgetsReducer";
import { WidgetProps } from "../widgets/BaseWidget";
export const getWidgets = (
state: AppState,
): { [widgetId: string]: FlattenedWidgetProps } => {
return state.entities.canvasWidgets;
};
export const getWidget = (state: AppState, widgetId: string): WidgetProps => {
return state.entities.canvasWidgets[widgetId];
};

View File

@ -1,6 +1,8 @@
import { FetchPageResponse } from "../api/PageApi"; import { FetchPageResponse } from "../api/PageApi";
import { ContainerWidgetProps } from "../widgets/ContainerWidget"; import { ContainerWidgetProps } from "../widgets/ContainerWidget";
import { WidgetProps } from "../widgets/BaseWidget"; import { WidgetProps } from "../widgets/BaseWidget";
import { WidgetType, RenderModes } from "../constants/WidgetConstants";
import { generateReactKey } from "../utils/generators";
export const extractCurrentDSL = ( export const extractCurrentDSL = (
fetchPageResponse: FetchPageResponse, fetchPageResponse: FetchPageResponse,
@ -8,6 +10,45 @@ export const extractCurrentDSL = (
return fetchPageResponse.data.layouts[0].dsl; return fetchPageResponse.data.layouts[0].dsl;
}; };
export const generateWidgetProps = (
parent: ContainerWidgetProps<WidgetProps>,
type: WidgetType,
left: number,
top: number,
width: number,
height: number,
): WidgetProps => {
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,
);
return {
type,
leftColumn: Math.floor(left / parentColumnWidth),
rightColumn: Math.floor((left + width) / parentColumnWidth),
topRow: Math.floor(top / parentRowHeight),
bottomRow: Math.floor((top + height) / parentRowHeight),
widgetId: generateReactKey(),
widgetName: generateReactKey(), //TODO: figure out what this is to populate appropriately
isVisible: true,
parentColumnSpace: parentColumnWidth,
parentRowSpace: parentRowHeight,
executeAction: () => {},
renderMode: RenderModes.CANVAS, //Is this required?
};
} else {
if (parent)
throw Error("Failed to create widget: Parent's size cannot be calculate");
else throw Error("Failed to create widget: Parent was not provided ");
}
};
export default { export default {
extractCurrentDSL, extractCurrentDSL,
generateWidgetProps,
}; };

View File

@ -27,6 +27,7 @@ class WidgetFactory {
...widgetData, ...widgetData,
...widgetFunctions, ...widgetFunctions,
}; };
console.log("=====", widgetData);
const widgetBuilder = this.widgetMap.get(widgetData.type); const widgetBuilder = this.widgetMap.get(widgetData.type);
if (widgetBuilder) { if (widgetBuilder) {
const widget = widgetBuilder.buildWidget(widgetProps); const widget = widgetBuilder.buildWidget(widgetProps);

View File

@ -17,6 +17,7 @@ class WidgetBuilderRegistry {
static registerWidgetBuilders() { static registerWidgetBuilders() {
WidgetFactory.registerWidgetBuilder("CONTAINER_WIDGET", { WidgetFactory.registerWidgetBuilder("CONTAINER_WIDGET", {
buildWidget(widgetData: ContainerWidgetProps<WidgetProps>): JSX.Element { buildWidget(widgetData: ContainerWidgetProps<WidgetProps>): JSX.Element {
console.log("Registry container", widgetData);
return <ContainerWidget {...widgetData} />; return <ContainerWidget {...widgetData} />;
}, },
}); });
@ -29,6 +30,7 @@ class WidgetBuilderRegistry {
WidgetFactory.registerWidgetBuilder("BUTTON_WIDGET", { WidgetFactory.registerWidgetBuilder("BUTTON_WIDGET", {
buildWidget(widgetData: ButtonWidgetProps): JSX.Element { buildWidget(widgetData: ButtonWidgetProps): JSX.Element {
console.log("Register button", widgetData);
return <ButtonWidget {...widgetData} />; return <ButtonWidget {...widgetData} />;
}, },
}); });

View File

@ -0,0 +1,5 @@
export const snapToGrid = (cellSize: number, x: number, y: number) => {
const snappedX = Math.floor(x / cellSize) * cellSize;
const snappedY = Math.floor(y / cellSize) * cellSize;
return [snappedX, snappedY];
};

View File

@ -29,6 +29,7 @@ abstract class BaseWidget<
initialState.height = 0; initialState.height = 0;
initialState.width = 0; initialState.width = 0;
this.state = initialState as K; this.state = initialState as K;
console.log(this.props);
} }
componentDidMount(): void { componentDidMount(): void {
@ -129,8 +130,8 @@ abstract class BaseWidget<
} }
static defaultProps: Partial<WidgetProps> = { static defaultProps: Partial<WidgetProps> = {
parentRowSpace: 64, parentRowSpace: 1,
parentColumnSpace: 64, parentColumnSpace: 1,
topRow: 0, topRow: 0,
leftColumn: 0, leftColumn: 0,
}; };
@ -171,6 +172,7 @@ export interface WidgetDataProps {
export interface WidgetFunctions { export interface WidgetFunctions {
executeAction: (actionPayloads?: ActionPayload[]) => void; executeAction: (actionPayloads?: ActionPayload[]) => void;
updateWidget?: Function;
} }
export interface WidgetCardProps { export interface WidgetCardProps {
@ -180,13 +182,16 @@ export interface WidgetCardProps {
icon: string; icon: string;
} }
export const WidgetDynamicProperties = { export const WidgetOperations = {
POSITION: "POSITION", // WidgetActivities?
SIZE: "SIZE", MOVE: "MOVE",
CHILDREN: "CHILDREN", RESIZE: "RESIZE",
EXISTENCE: "EXISTENCE", ADD_CHILD: "ADD_CHILD",
REMOVE_CHILD: "REMOVE_CHILD",
UPDATE_PROPERTY: "UPDATE_PROPERTY",
DELETE: "DELETE",
}; };
export type WidgetDynamicProperty = (typeof WidgetDynamicProperties)[keyof typeof WidgetDynamicProperties]; export type WidgetOperation = (typeof WidgetOperations)[keyof typeof WidgetOperations];
export default BaseWidget; export default BaseWidget;