WIP: Use rows and columns accross the board, cleanup DropTargetMonitor
This commit is contained in:
parent
92eb493d62
commit
b155efe3c0
|
|
@ -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,
|
||||||
|
|
|
||||||
17
app/client/src/api/WidgetConfigsApi.tsx
Normal file
17
app/client/src/api/WidgetConfigsApi.tsx
Normal 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;
|
||||||
|
|
@ -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];
|
||||||
|
|
|
||||||
|
|
@ -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],
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
45
app/client/src/editorComponents/DropTargetMask.tsx
Normal file
45
app/client/src/editorComponents/DropTargetMask.tsx
Normal 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;
|
||||||
|
|
@ -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,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 },
|
||||||
|
|
|
||||||
|
|
@ -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 });
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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(),
|
||||||
}}
|
}}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user