WIP: Canvas widget drop and positioning
This commit is contained in:
parent
c87552b951
commit
ffb532fa7a
|
|
@ -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 },
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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];
|
||||||
|
|
|
||||||
|
|
@ -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}>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
65
app/client/src/editorComponents/Dropzone.tsx
Normal file
65
app/client/src/editorComponents/Dropzone.tsx
Normal 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;
|
||||||
|
|
@ -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,
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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;
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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[];
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -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),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
59
app/client/src/sagas/WidgetOperationSagas.tsx
Normal file
59
app/client/src/sagas/WidgetOperationSagas.tsx
Normal 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),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
@ -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),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
13
app/client/src/sagas/selectors.tsx
Normal file
13
app/client/src/sagas/selectors.tsx
Normal 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];
|
||||||
|
};
|
||||||
|
|
@ -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,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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} />;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
||||||
5
app/client/src/utils/helpers.tsx
Normal file
5
app/client/src/utils/helpers.tsx
Normal 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];
|
||||||
|
};
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user