Merge branch 'feature/property-pane-position' into 'release'
Property pane positioning See merge request theappsmith/internal-tools-client!60
This commit is contained in:
commit
618ede4ffb
|
|
@ -38,7 +38,9 @@
|
|||
"netlify-identity-widget": "^1.5.5",
|
||||
"node-sass": "^4.11.0",
|
||||
"normalizr": "^3.3.0",
|
||||
"popper.js": "^1.15.0",
|
||||
"prettier": "^1.18.2",
|
||||
"re-reselect": "^3.4.0",
|
||||
"react": "^16.7.0",
|
||||
"react-dnd": "^9.3.4",
|
||||
"react-dnd-html5-backend": "^9.3.4",
|
||||
|
|
@ -52,6 +54,7 @@
|
|||
"react-scripts": "^3.1.1",
|
||||
"redux": "^4.0.1",
|
||||
"redux-saga": "^1.0.0",
|
||||
"reselect": "^4.0.0",
|
||||
"styled-components": "^4.1.3",
|
||||
"ts-loader": "^6.0.4",
|
||||
"typescript": "^3.6.3"
|
||||
|
|
|
|||
18
app/client/src/actions/configsActions.tsx
Normal file
18
app/client/src/actions/configsActions.tsx
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
import { ReduxActionTypes } from "../constants/ReduxActionConstants";
|
||||
|
||||
export type EditorConfigIdsType = {
|
||||
propertyPaneConfigsId?: string;
|
||||
widgetCardsPaneId?: string;
|
||||
widgetConfigsId?: string;
|
||||
};
|
||||
|
||||
export const fetchEditorConfigs = (configsIds: EditorConfigIdsType) => {
|
||||
return {
|
||||
type: ReduxActionTypes.FETCH_CONFIGS_INIT,
|
||||
payload: {
|
||||
propertyPaneConfigsId: configsIds.propertyPaneConfigsId,
|
||||
widgetCardsPaneId: configsIds.widgetCardsPaneId,
|
||||
widgetConfigsId: configsIds.widgetConfigsId,
|
||||
},
|
||||
};
|
||||
};
|
||||
26
app/client/src/api/PropertPaneConfigsApi.tsx
Normal file
26
app/client/src/api/PropertPaneConfigsApi.tsx
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
import Api from "./Api";
|
||||
import { ApiResponse } from "./ApiResponses";
|
||||
import { PropertyConfig } from "../reducers/entityReducers/propertyPaneConfigReducer";
|
||||
|
||||
export interface PropertyPaneConfigsResponse extends ApiResponse {
|
||||
data: {
|
||||
config: PropertyConfig;
|
||||
};
|
||||
}
|
||||
|
||||
export interface PropertyPaneConfigsRequest {
|
||||
propertyPaneConfigsId: string;
|
||||
}
|
||||
|
||||
class PropertyPaneConfigsApi extends Api {
|
||||
static url = "v1/properties";
|
||||
static fetch(
|
||||
request: PropertyPaneConfigsRequest,
|
||||
): Promise<PropertyPaneConfigsResponse> {
|
||||
return Api.get(
|
||||
PropertyPaneConfigsApi.url + "/" + request.propertyPaneConfigsId,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default PropertyPaneConfigsApi;
|
||||
4
app/client/src/assets/icons/control/edit.svg
Normal file
4
app/client/src/assets/icons/control/edit.svg
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="10" cy="10" r="10" fill="#21282C"/>
|
||||
<path d="M5.5 12.625V14.5H7.375L12.905 8.97L11.03 7.095L5.5 12.625ZM14.355 7.52C14.4014 7.47374 14.4381 7.4188 14.4632 7.35831C14.4883 7.29783 14.5012 7.23299 14.5012 7.1675C14.5012 7.10202 14.4883 7.03718 14.4632 6.97669C14.4381 6.9162 14.4014 6.86126 14.355 6.815L13.185 5.645C13.1387 5.59865 13.0838 5.56188 13.0233 5.53679C12.9628 5.51169 12.898 5.49878 12.8325 5.49878C12.767 5.49878 12.7022 5.51169 12.6417 5.53679C12.5812 5.56188 12.5263 5.59865 12.48 5.645L11.565 6.56L13.44 8.435L14.355 7.52Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 669 B |
10
app/client/src/common/PaneWrapper.tsx
Normal file
10
app/client/src/common/PaneWrapper.tsx
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
import styled from "styled-components";
|
||||
|
||||
export default styled.div`
|
||||
background-color: ${props => props.theme.colors.paneBG};
|
||||
border-radius: ${props => props.theme.radii[1]}px;
|
||||
box-shadow: 0px 0px 3px ${props => props.theme.colors.paneBG};
|
||||
padding: 24px 16px;
|
||||
color: ${props => props.theme.colors.textOnDarkBG};
|
||||
text-transform: capitalize;
|
||||
`;
|
||||
|
|
@ -36,12 +36,14 @@ export const getEditorConfigs = () => {
|
|||
currentPageId: "5d807e7f795dc6000482bc78",
|
||||
currentLayoutId: "5d807e7f795dc6000482bc77",
|
||||
currentPageName: "page1",
|
||||
propertyPaneConfigsId: "5d8a04195cf8050004db6e30",
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
currentPageId: "5d807e76795dc6000482bc76",
|
||||
currentLayoutId: "5d807e76795dc6000482bc75",
|
||||
currentPageName: "page1",
|
||||
propertyPaneConfigsId: "5d8a04195cf8050004db6e30",
|
||||
};
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -18,6 +18,11 @@ export type ThemeBorder = {
|
|||
color: Color;
|
||||
};
|
||||
|
||||
type PropertyPaneTheme = {
|
||||
width: number;
|
||||
height: number;
|
||||
};
|
||||
|
||||
export type Theme = {
|
||||
radii: Array<number>;
|
||||
fontSizes: Array<number>;
|
||||
|
|
@ -27,6 +32,7 @@ export type Theme = {
|
|||
lineHeights: Array<number>;
|
||||
fonts: Array<FontFamily>;
|
||||
borders: ThemeBorder[];
|
||||
propertyPane: PropertyPaneTheme;
|
||||
headerHeight: string;
|
||||
sidebarWidth: string;
|
||||
};
|
||||
|
|
@ -52,6 +58,10 @@ export const theme: Theme = {
|
|||
fontSizes: [0, 10, 12, 14, 16, 18, 24, 28, 32, 48, 64],
|
||||
spaces: [0, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 30],
|
||||
fontWeights: [0, 400, 500, 700],
|
||||
propertyPane: {
|
||||
width: 250,
|
||||
height: 600,
|
||||
},
|
||||
colors: {
|
||||
primary: Colors.GREEN,
|
||||
error: Colors.RED,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { WidgetProps, WidgetCardProps } from "../widgets/BaseWidget";
|
||||
import { RefObject } from "react";
|
||||
|
||||
export const ReduxActionTypes: { [key: string]: string } = {
|
||||
REPORT_ERROR: "REPORT_ERROR",
|
||||
|
|
@ -35,6 +36,10 @@ export const ReduxActionTypes: { [key: string]: string } = {
|
|||
WIDGET_DELETE: "WIDGET_DELETE",
|
||||
SHOW_PROPERTY_PANE: "SHOW_PROPERTY_PANE",
|
||||
UPDATE_WIDGET_PROPERTY: "UPDATE_WIDGET_PROPERTY",
|
||||
FETCH_PROPERTY_PANE_CONFIGS_INIT: "FETCH_PROPERTY_PANE_CONFIGS_INIT",
|
||||
FETCH_PROPERTY_PANE_CONFIGS_SUCCESS: "FETCH_PROPERTY_PANE_CONFIGS_SUCCESS",
|
||||
FETCH_CONFIGS_INIT: "FETCH_CONFIGS_INIT",
|
||||
ADD_WIDGET_REF: "ADD_WIDGET_REF",
|
||||
};
|
||||
export type ReduxActionType = (typeof ReduxActionTypes)[keyof typeof ReduxActionTypes];
|
||||
|
||||
|
|
@ -49,6 +54,9 @@ export const ReduxActionErrorTypes: { [key: string]: string } = {
|
|||
SAVE_PAGE_ERROR: "SAVE_PAGE_ERROR",
|
||||
FETCH_WIDGET_CARDS_ERROR: "FETCH_WIDGET_CARDS_ERROR",
|
||||
WIDGET_OPERATION_ERROR: "WIDGET_OPERATION_ERROR",
|
||||
FETCH_PROPERTY_PANE_CONFIGS_ERROR: "FETCH_PROPERTY_PANE_CONFIGS_ERROR",
|
||||
FETCH_CONFIGS_ERROR: "FETCH_CONFIGS_ERROR",
|
||||
PROPERTY_PANE_ERROR: "PROPERTY_PANE_ERROR",
|
||||
};
|
||||
export type ReduxActionErrorType = (typeof ReduxActionErrorTypes)[keyof typeof ReduxActionErrorTypes];
|
||||
|
||||
|
|
@ -72,6 +80,8 @@ export interface UpdateCanvasPayload {
|
|||
|
||||
export interface ShowPropertyPanePayload {
|
||||
widgetId: string;
|
||||
node: RefObject<HTMLDivElement>;
|
||||
toggle: boolean;
|
||||
}
|
||||
|
||||
// export interface LoadAPIResponsePayload extends ExecuteActionResponse {}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,10 @@
|
|||
import React, { useContext, createContext, Context } from "react";
|
||||
import React, {
|
||||
useContext,
|
||||
createContext,
|
||||
useState,
|
||||
Context,
|
||||
useCallback,
|
||||
} from "react";
|
||||
import styled from "styled-components";
|
||||
import { WidgetProps, WidgetOperations } from "../widgets/BaseWidget";
|
||||
import { useDrag, DragPreviewImage, DragSourceMonitor } from "react-dnd";
|
||||
|
|
@ -34,6 +40,14 @@ const DragHandle = styled.div`
|
|||
`;
|
||||
|
||||
const DeleteControl = styled.div`
|
||||
position: absolute;
|
||||
right: ${props => props.theme.fontSizes[CONTROL_THEME_FONTSIZE_INDEX]}px;
|
||||
top: -${props => props.theme.fontSizes[CONTROL_THEME_FONTSIZE_INDEX] / 2}px;
|
||||
display: none;
|
||||
cursor: pointer;
|
||||
`;
|
||||
|
||||
const EditControl = styled.div`
|
||||
position: absolute;
|
||||
right: -${props => props.theme.fontSizes[CONTROL_THEME_FONTSIZE_INDEX] / 2}px;
|
||||
top: -${props => props.theme.fontSizes[CONTROL_THEME_FONTSIZE_INDEX] / 2}px;
|
||||
|
|
@ -41,6 +55,15 @@ const DeleteControl = styled.div`
|
|||
cursor: pointer;
|
||||
`;
|
||||
|
||||
const DraggableMask = styled.div`
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
z-index: -1;
|
||||
`;
|
||||
|
||||
const moveControlIcon = ControlIcons.MOVE_CONTROL({
|
||||
width: theme.fontSizes[CONTROL_THEME_FONTSIZE_INDEX],
|
||||
height: theme.fontSizes[CONTROL_THEME_FONTSIZE_INDEX],
|
||||
|
|
@ -51,41 +74,84 @@ const deleteControlIcon = ControlIcons.DELETE_CONTROL({
|
|||
height: theme.fontSizes[CONTROL_THEME_FONTSIZE_INDEX],
|
||||
});
|
||||
|
||||
const editControlIcon = ControlIcons.EDIT_CONTROL({
|
||||
width: theme.fontSizes[CONTROL_THEME_FONTSIZE_INDEX],
|
||||
height: theme.fontSizes[CONTROL_THEME_FONTSIZE_INDEX],
|
||||
});
|
||||
|
||||
type DraggableComponentProps = WidgetProps & ContainerProps;
|
||||
|
||||
export const DraggingContext: Context<{
|
||||
export const DraggableComponentContext: Context<{
|
||||
isDragging?: boolean;
|
||||
widgetNode?: HTMLDivElement;
|
||||
}> = createContext({});
|
||||
/* eslint-disable react/display-name */
|
||||
|
||||
//TODO(abhinav): the contexts and states are getting out of hand.
|
||||
// Refactor here and in ResizableComponent
|
||||
|
||||
const DraggableComponent = (props: DraggableComponentProps) => {
|
||||
const { isFocused, setFocus } = useContext(FocusContext);
|
||||
const { isFocused, setFocus, showPropertyPane } = useContext(FocusContext);
|
||||
const { updateWidget } = useContext(WidgetFunctionsContext);
|
||||
const [currentNode, setCurrentNode] = useState<HTMLDivElement>();
|
||||
const referenceRef = useCallback(
|
||||
node => {
|
||||
if (node !== null && node !== currentNode) {
|
||||
setCurrentNode(node);
|
||||
}
|
||||
},
|
||||
[setCurrentNode, currentNode],
|
||||
);
|
||||
const { isResizing } = useContext(ResizingContext);
|
||||
|
||||
const deleteWidget = () => {
|
||||
showPropertyPane && showPropertyPane();
|
||||
updateWidget &&
|
||||
updateWidget(WidgetOperations.DELETE, props.widgetId, {
|
||||
parentId: props.parentId,
|
||||
});
|
||||
};
|
||||
|
||||
const togglePropertyEditor = (e: any) => {
|
||||
if (showPropertyPane && props.widgetId && currentNode) {
|
||||
showPropertyPane(props.widgetId, currentNode, true);
|
||||
}
|
||||
e.stopPropagation();
|
||||
};
|
||||
|
||||
const [{ isDragging }, drag, preview] = useDrag({
|
||||
item: props,
|
||||
collect: (monitor: DragSourceMonitor) => ({
|
||||
isDragging: monitor.isDragging(),
|
||||
}),
|
||||
end: (widget, monitor) => {
|
||||
if (monitor.didDrop()) {
|
||||
if (isFocused === props.widgetId && showPropertyPane && currentNode) {
|
||||
showPropertyPane(props.widgetId, currentNode, true);
|
||||
}
|
||||
}
|
||||
},
|
||||
begin: () => {
|
||||
if (isFocused === props.widgetId && showPropertyPane && currentNode) {
|
||||
showPropertyPane(props.widgetId, undefined);
|
||||
}
|
||||
},
|
||||
canDrag: () => {
|
||||
return !isResizing && !!isFocused && isFocused === props.widgetId;
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<DraggingContext.Provider value={{ isDragging }}>
|
||||
<DraggableComponentContext.Provider
|
||||
value={{ isDragging, widgetNode: currentNode }}
|
||||
>
|
||||
<DragPreviewImage src={blankImage} connect={preview} />
|
||||
<DraggableWrapper
|
||||
ref={drag}
|
||||
onClick={(e: any) => {
|
||||
if (setFocus) {
|
||||
if (setFocus && showPropertyPane) {
|
||||
setFocus(props.widgetId);
|
||||
showPropertyPane(props.widgetId, currentNode);
|
||||
e.stopPropagation();
|
||||
}
|
||||
}}
|
||||
|
|
@ -106,6 +172,7 @@ const DraggableComponent = (props: DraggableComponentProps) => {
|
|||
props.style.componentHeight + (props.style.heightUnit || "px"),
|
||||
}}
|
||||
>
|
||||
<DraggableMask ref={referenceRef} />
|
||||
{props.children}
|
||||
<DragHandle className="control" ref={drag}>
|
||||
{moveControlIcon}
|
||||
|
|
@ -113,8 +180,11 @@ const DraggableComponent = (props: DraggableComponentProps) => {
|
|||
<DeleteControl className="control" onClick={deleteWidget}>
|
||||
{deleteControlIcon}
|
||||
</DeleteControl>
|
||||
<EditControl className="control" onClick={togglePropertyEditor}>
|
||||
{editControlIcon}
|
||||
</EditControl>
|
||||
</DraggableWrapper>
|
||||
</DraggingContext.Provider>
|
||||
</DraggableComponentContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import { OccupiedSpaceContext } from "../widgets/ContainerWidget";
|
|||
import { ContainerProps, ParentBoundsContext } from "./ContainerComponent";
|
||||
import { isDropZoneOccupied } from "../utils/WidgetPropsUtils";
|
||||
import { FocusContext } from "../pages/Editor/Canvas";
|
||||
import { DraggingContext } from "./DraggableComponent";
|
||||
import { DraggableComponentContext } from "./DraggableComponent";
|
||||
import { WidgetFunctionsContext } from "../pages/Editor/WidgetsEditor";
|
||||
import { ResizingContext } from "./DropTargetComponent";
|
||||
import {
|
||||
|
|
@ -88,11 +88,11 @@ const ResizableContainer = styled(Rnd)`
|
|||
`;
|
||||
|
||||
export const ResizableComponent = (props: ResizableComponentProps) => {
|
||||
const { isDragging } = useContext(DraggingContext);
|
||||
const { isDragging } = useContext(DraggableComponentContext);
|
||||
const { setIsResizing } = useContext(ResizingContext);
|
||||
const { boundingParent } = useContext(ParentBoundsContext);
|
||||
const { updateWidget } = useContext(WidgetFunctionsContext);
|
||||
const { isFocused, setFocus } = useContext(FocusContext);
|
||||
const { isFocused, setFocus, showPropertyPane } = useContext(FocusContext);
|
||||
const occupiedSpaces = useContext(OccupiedSpaceContext);
|
||||
|
||||
const [isColliding, setIsColliding] = useState(false);
|
||||
|
|
@ -199,6 +199,7 @@ export const ResizableComponent = (props: ResizableComponentProps) => {
|
|||
onResize={checkForCollision}
|
||||
onResizeStart={() => {
|
||||
setIsResizing && setIsResizing(true);
|
||||
showPropertyPane && showPropertyPane(props.widgetId, undefined);
|
||||
}}
|
||||
resizeGrid={[props.parentColumnSpace, props.parentRowSpace]}
|
||||
bounds={bounds}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import React from "react";
|
|||
import { IconProps, IconWrapper } from "../constants/IconConstants";
|
||||
import { ReactComponent as DeleteIcon } from "../assets/icons/control/delete.svg";
|
||||
import { ReactComponent as MoveIcon } from "../assets/icons/control/move.svg";
|
||||
import { ReactComponent as EditIcon } from "../assets/icons/control/edit.svg";
|
||||
|
||||
/* eslint-disable react/display-name */
|
||||
|
||||
|
|
@ -18,4 +19,9 @@ export const ControlIcons: {
|
|||
<MoveIcon />
|
||||
</IconWrapper>
|
||||
),
|
||||
EDIT_CONTROL: (props: IconProps) => (
|
||||
<IconWrapper {...props}>
|
||||
<EditIcon />
|
||||
</IconWrapper>
|
||||
),
|
||||
};
|
||||
|
|
|
|||
|
|
@ -17,12 +17,6 @@ const WidgetSidebarResponse: {
|
|||
widgetCardName: "Button",
|
||||
key: generateReactKey(),
|
||||
},
|
||||
{
|
||||
type: "SPINNER_WIDGET",
|
||||
icon: "icon-switch",
|
||||
widgetCardName: "Spinner",
|
||||
key: generateReactKey(),
|
||||
},
|
||||
{
|
||||
type: "CONTAINER_WIDGET",
|
||||
icon: "icon-container",
|
||||
|
|
@ -31,12 +25,6 @@ const WidgetSidebarResponse: {
|
|||
},
|
||||
],
|
||||
form: [
|
||||
{
|
||||
type: "BUTTON_WIDGET",
|
||||
icon: "icon-button",
|
||||
widgetCardName: "Button",
|
||||
key: generateReactKey(),
|
||||
},
|
||||
{
|
||||
type: "BUTTON_WIDGET",
|
||||
icon: "icon-button",
|
||||
|
|
@ -64,7 +52,7 @@ const WidgetSidebarResponse: {
|
|||
{
|
||||
type: "SWITCH_WIDGET",
|
||||
icon: "icon-switch",
|
||||
widgetCardName: "Toggle",
|
||||
widgetCardName: "Switch",
|
||||
key: generateReactKey(),
|
||||
},
|
||||
],
|
||||
|
|
@ -81,12 +69,6 @@ const WidgetSidebarResponse: {
|
|||
widgetCardName: "Container",
|
||||
key: generateReactKey(),
|
||||
},
|
||||
{
|
||||
type: "SPINNER_WIDGET",
|
||||
icon: "icon-spinner",
|
||||
widgetCardName: "Spinner",
|
||||
key: generateReactKey(),
|
||||
},
|
||||
{
|
||||
type: "TABLE_WIDGET",
|
||||
icon: "icon-table",
|
||||
|
|
|
|||
|
|
@ -1,15 +1,10 @@
|
|||
import React, {
|
||||
createContext,
|
||||
useState,
|
||||
Context,
|
||||
Dispatch,
|
||||
SetStateAction,
|
||||
} from "react";
|
||||
import React, { createContext, useState, Context } from "react";
|
||||
import styled from "styled-components";
|
||||
import WidgetFactory from "../../utils/WidgetFactory";
|
||||
import { RenderModes } from "../../constants/WidgetConstants";
|
||||
import { ContainerWidgetProps } from "../../widgets/ContainerWidget";
|
||||
import { WidgetProps } from "../../widgets/BaseWidget";
|
||||
import PropertyPane from "./PropertyPane";
|
||||
|
||||
const ArtBoard = styled.div`
|
||||
width: 100%;
|
||||
|
|
@ -20,17 +15,33 @@ const ArtBoard = styled.div`
|
|||
|
||||
interface CanvasProps {
|
||||
dsl: ContainerWidgetProps<WidgetProps>;
|
||||
showPropertyPane: (
|
||||
widgetId?: string,
|
||||
node?: HTMLDivElement,
|
||||
toggle?: boolean,
|
||||
) => void;
|
||||
}
|
||||
|
||||
export const FocusContext: Context<{
|
||||
isFocused?: string;
|
||||
setFocus?: Dispatch<SetStateAction<string>>;
|
||||
setFocus?: Function;
|
||||
showPropertyPane?: (
|
||||
widgetId?: string,
|
||||
node?: HTMLDivElement,
|
||||
toggle?: boolean,
|
||||
) => void;
|
||||
}> = createContext({});
|
||||
|
||||
/* eslint-disable react/display-name */
|
||||
/* eslint-disable react/prop-types */
|
||||
|
||||
const Canvas = (props: CanvasProps) => {
|
||||
const [isFocused, setFocus] = useState("");
|
||||
return (
|
||||
<FocusContext.Provider value={{ isFocused, setFocus }}>
|
||||
<FocusContext.Provider
|
||||
value={{ isFocused, setFocus, showPropertyPane: props.showPropertyPane }}
|
||||
>
|
||||
<PropertyPane />
|
||||
<ArtBoard>
|
||||
{props.dsl.widgetId &&
|
||||
WidgetFactory.createWidget(props.dsl, RenderModes.CANVAS)}
|
||||
|
|
|
|||
52
app/client/src/pages/Editor/Popper.tsx
Normal file
52
app/client/src/pages/Editor/Popper.tsx
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
import React, { useRef, useEffect } from "react";
|
||||
import styled from "styled-components";
|
||||
import { createPortal } from "react-dom";
|
||||
import PopperJS from "popper.js";
|
||||
import PaneWrapper from "../common/PaneWrapper";
|
||||
|
||||
type PopperProps = {
|
||||
isOpen: boolean;
|
||||
targetRefNode: HTMLDivElement;
|
||||
children: JSX.Element;
|
||||
};
|
||||
|
||||
const PopperWrapper = styled(PaneWrapper)`
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
height: ${props => props.theme.propertyPane.height}px;
|
||||
width: ${props => props.theme.propertyPane.width}px;
|
||||
margin: ${props => props.theme.spaces[6]}px;
|
||||
`;
|
||||
|
||||
/* eslint-disable react/display-name */
|
||||
export default (props: PopperProps) => {
|
||||
const contentRef = useRef(null);
|
||||
useEffect(() => {
|
||||
//TODO(abhinav): optimize this, remove previous Popper instance.
|
||||
new PopperJS(
|
||||
props.targetRefNode,
|
||||
(contentRef.current as unknown) as Element,
|
||||
{
|
||||
placement: "right",
|
||||
modifiers: {
|
||||
flip: {
|
||||
behavior: ["right", "left", "bottom", "top"],
|
||||
},
|
||||
keepTogether: {
|
||||
enabled: false,
|
||||
},
|
||||
arrow: {
|
||||
enabled: false,
|
||||
},
|
||||
preventOverflow: {
|
||||
boundariesElement: "viewport",
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
}, [props.targetRefNode]);
|
||||
return createPortal(
|
||||
<PopperWrapper ref={contentRef}>{props.children}</PopperWrapper>,
|
||||
document.body,
|
||||
);
|
||||
};
|
||||
|
|
@ -6,6 +6,14 @@ import _ from "lodash";
|
|||
import { ControlProps } from "../../propertyControls/BaseControl";
|
||||
import { PropertySection } from "../../reducers/entityReducers/propertyPaneConfigReducer";
|
||||
import { updateWidgetProperty } from "../../actions/controlActions";
|
||||
import {
|
||||
getCurrentWidgetId,
|
||||
getCurrentReferenceNode,
|
||||
getPropertyConfig,
|
||||
getIsPropertyPaneVisible,
|
||||
} from "../../selectors/propertyPaneSelectors";
|
||||
|
||||
import Popper from "./Popper";
|
||||
|
||||
class PropertyPane extends Component<
|
||||
PropertyPaneProps & PropertyPaneFunctions
|
||||
|
|
@ -16,27 +24,38 @@ class PropertyPane extends Component<
|
|||
}
|
||||
|
||||
render() {
|
||||
if (this.props.isVisible) {
|
||||
if (
|
||||
this.props.isVisible &&
|
||||
this.props.widgetId &&
|
||||
this.props.targetNode &&
|
||||
this.props.propertySections
|
||||
) {
|
||||
const content = this.renderPropertyPane(this.props.propertySections);
|
||||
return (
|
||||
<div>
|
||||
{!_.isNil(this.props.propertySections)
|
||||
? _.map(
|
||||
this.props.propertySections,
|
||||
(propertySection: PropertySection) => {
|
||||
return this.renderPropertySection(
|
||||
propertySection,
|
||||
propertySection.id,
|
||||
);
|
||||
},
|
||||
)
|
||||
: undefined}
|
||||
</div>
|
||||
<Popper isOpen={true} targetRefNode={this.props.targetNode}>
|
||||
{content}
|
||||
</Popper>
|
||||
);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
renderPropertyPane(propertySections?: PropertySection[]) {
|
||||
return (
|
||||
<div>
|
||||
{!_.isNil(propertySections)
|
||||
? _.map(propertySections, (propertySection: PropertySection) => {
|
||||
return this.renderPropertySection(
|
||||
propertySection,
|
||||
propertySection.id,
|
||||
);
|
||||
})
|
||||
: undefined}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderPropertySection(propertySection: PropertySection, key: string) {
|
||||
return (
|
||||
<div key={key}>
|
||||
|
|
@ -61,10 +80,14 @@ class PropertyPane extends Component<
|
|||
propertyControlOrSection.id,
|
||||
);
|
||||
} else {
|
||||
return PropertyControlFactory.createControl(
|
||||
propertyControlOrSection,
|
||||
{ onPropertyChange: this.onPropertyChange },
|
||||
);
|
||||
try {
|
||||
return PropertyControlFactory.createControl(
|
||||
propertyControlOrSection,
|
||||
{ onPropertyChange: this.onPropertyChange },
|
||||
);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
},
|
||||
)}
|
||||
|
|
@ -74,24 +97,20 @@ class PropertyPane extends Component<
|
|||
}
|
||||
|
||||
onPropertyChange(propertyName: string, propertyValue: any) {
|
||||
this.props.updateWidgetProperty(
|
||||
this.props.widgetId,
|
||||
propertyName,
|
||||
propertyValue,
|
||||
);
|
||||
// this.props.updateWidgetProperty(
|
||||
// this.props.widgetId,
|
||||
// propertyName,
|
||||
// propertyValue,
|
||||
// );
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = (state: AppState): PropertyPaneProps => {
|
||||
let propertyConfig = undefined;
|
||||
if (!_.isNil(state.ui.propertyPane.widgetId)) {
|
||||
const widget = state.entities.canvasWidgets[state.ui.propertyPane.widgetId];
|
||||
propertyConfig = state.entities.propertyConfig.config[widget.type];
|
||||
}
|
||||
return {
|
||||
propertySections: propertyConfig,
|
||||
widgetId: state.ui.propertyPane.widgetId,
|
||||
isVisible: state.ui.propertyPane.isVisible,
|
||||
propertySections: getPropertyConfig(state),
|
||||
widgetId: getCurrentWidgetId(state),
|
||||
isVisible: getIsPropertyPaneVisible(state),
|
||||
targetNode: getCurrentReferenceNode(state),
|
||||
};
|
||||
};
|
||||
|
||||
|
|
@ -109,6 +128,7 @@ export interface PropertyPaneProps {
|
|||
propertySections?: PropertySection[];
|
||||
widgetId?: string;
|
||||
isVisible: boolean;
|
||||
targetNode?: HTMLDivElement;
|
||||
}
|
||||
|
||||
export interface PropertyPaneFunctions {
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ export const Wrapper = styled.div`
|
|||
background: ${props => props.theme.colors.paneCard};
|
||||
border: 1px solid ${props => props.theme.colors.paneCard};
|
||||
color: ${props => props.theme.colors.textOnDarkBG};
|
||||
height: 80px;
|
||||
& > div {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
|
|
|||
41
app/client/src/pages/Editor/WidgetCardsPane.tsx
Normal file
41
app/client/src/pages/Editor/WidgetCardsPane.tsx
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
import React from "react";
|
||||
import WidgetCard from "./WidgetCard";
|
||||
import styled from "styled-components";
|
||||
import { WidgetCardProps } from "../../widgets/BaseWidget";
|
||||
import PaneWrapper from "../common/PaneWrapper";
|
||||
|
||||
type WidgetCardPaneProps = {
|
||||
cards?: { [id: string]: WidgetCardProps[] };
|
||||
};
|
||||
|
||||
const CardsWrapper = styled.div`
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
grid-gap: ${props => props.theme.spaces[1]}px;
|
||||
justify-items: stretch;
|
||||
align-items: stretch;
|
||||
`;
|
||||
|
||||
const WidgetCardsPane = (props: WidgetCardPaneProps) => {
|
||||
if (!props.cards) {
|
||||
return null;
|
||||
}
|
||||
const groups = Object.keys(props.cards);
|
||||
return (
|
||||
<PaneWrapper>
|
||||
{groups.map((group: string) => (
|
||||
<React.Fragment key={group}>
|
||||
<h5>{group}</h5>
|
||||
<CardsWrapper>
|
||||
{props.cards &&
|
||||
props.cards[group].map((card: WidgetCardProps) => (
|
||||
<WidgetCard details={card} key={card.key} />
|
||||
))}
|
||||
</CardsWrapper>
|
||||
</React.Fragment>
|
||||
))}
|
||||
</PaneWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default WidgetCardsPane;
|
||||
|
|
@ -4,7 +4,7 @@ import WidgetCard from "./WidgetCard";
|
|||
import styled from "styled-components";
|
||||
import { WidgetCardProps } from "../../widgets/BaseWidget";
|
||||
import { AppState } from "../../reducers";
|
||||
import { WidgetSidebarReduxState } from "../../reducers/uiReducers/widgetSidebarReducer";
|
||||
import { getWidgetCards } from "../../selectors/editorSelectors";
|
||||
|
||||
type WidgetSidebarProps = {
|
||||
cards: { [id: string]: WidgetCardProps[] };
|
||||
|
|
@ -22,39 +22,33 @@ const CardsWrapper = styled.div`
|
|||
align-items: stretch;
|
||||
`;
|
||||
|
||||
const WidgetSidebar: React.FC<WidgetSidebarProps> = (
|
||||
props: WidgetSidebarProps,
|
||||
) => {
|
||||
const groups = Object.keys(props.cards);
|
||||
return (
|
||||
<MainWrapper>
|
||||
{groups.map((group: string) => (
|
||||
<React.Fragment key={group}>
|
||||
<h5>{group}</h5>
|
||||
<CardsWrapper>
|
||||
{props.cards[group].map((card: WidgetCardProps) => (
|
||||
<WidgetCard details={card} key={card.key} />
|
||||
))}
|
||||
</CardsWrapper>
|
||||
</React.Fragment>
|
||||
))}
|
||||
</MainWrapper>
|
||||
);
|
||||
class WidgetSidebar extends React.Component<WidgetSidebarProps> {
|
||||
render(): React.ReactNode {
|
||||
const groups = Object.keys(this.props.cards);
|
||||
return (
|
||||
<MainWrapper>
|
||||
{groups.map((group: string) => (
|
||||
<React.Fragment key={group}>
|
||||
<h5>{group}</h5>
|
||||
<CardsWrapper>
|
||||
{this.props.cards[group].map((card: WidgetCardProps) => (
|
||||
<WidgetCard details={card} key={card.key} />
|
||||
))}
|
||||
</CardsWrapper>
|
||||
</React.Fragment>
|
||||
))}
|
||||
</MainWrapper>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = (state: AppState) => {
|
||||
return {
|
||||
cards: getWidgetCards(state),
|
||||
};
|
||||
};
|
||||
|
||||
export default connect(
|
||||
(state: AppState): WidgetSidebarReduxState => {
|
||||
// TODO(hetu) Should utilise reselect instead
|
||||
const cards = state.ui.widgetSidebar.cards;
|
||||
const groups: string[] = Object.keys(cards);
|
||||
groups.forEach((group: string) => {
|
||||
cards[group] = cards[group].map((widget: WidgetCardProps) => {
|
||||
const { rows, columns } = state.entities.widgetConfig.config[
|
||||
widget.type
|
||||
];
|
||||
return { ...widget, rows, columns };
|
||||
});
|
||||
});
|
||||
return { cards };
|
||||
},
|
||||
mapStateToProps,
|
||||
null,
|
||||
)(WidgetSidebar);
|
||||
|
|
|
|||
|
|
@ -4,8 +4,6 @@ import styled from "styled-components";
|
|||
import Canvas from "./Canvas";
|
||||
import PropertyPane from "./PropertyPane";
|
||||
import { AppState } from "../../reducers";
|
||||
import { EditorReduxState } from "../../reducers/uiReducers/editorReducer";
|
||||
import CanvasWidgetsNormalizer from "../../normalizers/CanvasWidgetsNormalizer";
|
||||
import {
|
||||
WidgetFunctions,
|
||||
WidgetOperation,
|
||||
|
|
@ -14,8 +12,21 @@ import {
|
|||
import { ActionPayload } from "../../constants/ActionConstants";
|
||||
import { executeAction } from "../../actions/widgetActions";
|
||||
import { fetchPage, savePage, updateWidget } from "../../actions/pageActions";
|
||||
import {
|
||||
getPropertyPaneConfigsId,
|
||||
getCurrentLayoutId,
|
||||
getCurrentPageId,
|
||||
getDenormalizedDSL,
|
||||
getCurrentPageName,
|
||||
getPageWidgetId,
|
||||
} from "../../selectors/editorSelectors";
|
||||
import { RenderModes } from "../../constants/WidgetConstants";
|
||||
import { ContainerWidgetProps } from "../../widgets/ContainerWidget";
|
||||
import {
|
||||
EditorConfigIdsType,
|
||||
fetchEditorConfigs,
|
||||
} from "../../actions/configsActions";
|
||||
import { ReduxActionTypes } from "../../constants/ReduxActionConstants";
|
||||
|
||||
const CanvasContainer = styled.section`
|
||||
height: 100%;
|
||||
|
|
@ -52,7 +63,13 @@ type EditorProps = {
|
|||
currentPageName: string;
|
||||
currentPageId: string;
|
||||
currentLayoutId: string;
|
||||
isSaving: boolean;
|
||||
showPropertyPane: (
|
||||
widgetId?: string,
|
||||
node?: HTMLDivElement,
|
||||
toggle?: boolean,
|
||||
) => void;
|
||||
fetchConfigs: Function;
|
||||
propertyPaneConfigsId: string;
|
||||
};
|
||||
|
||||
export const WidgetFunctionsContext: Context<WidgetFunctions> = createContext(
|
||||
|
|
@ -61,6 +78,11 @@ export const WidgetFunctionsContext: Context<WidgetFunctions> = createContext(
|
|||
|
||||
class WidgetsEditor extends React.Component<EditorProps> {
|
||||
componentDidMount() {
|
||||
this.props.fetchConfigs({
|
||||
propertyPaneConfigsId: this.props.propertyPaneConfigsId,
|
||||
// widgetCardsPaneId: this.props.widgetCardsPaneId,
|
||||
// widgetConfigsId: this.props.widgetConfigsId,
|
||||
});
|
||||
this.props.fetchCanvasWidgets(this.props.currentPageId);
|
||||
}
|
||||
|
||||
|
|
@ -74,7 +96,12 @@ class WidgetsEditor extends React.Component<EditorProps> {
|
|||
>
|
||||
<EditorWrapper>
|
||||
<CanvasContainer>
|
||||
{this.props.dsl && <Canvas dsl={this.props.dsl} />}
|
||||
{this.props.dsl && (
|
||||
<Canvas
|
||||
dsl={this.props.dsl}
|
||||
showPropertyPane={this.props.showPropertyPane}
|
||||
/>
|
||||
)}
|
||||
</CanvasContainer>
|
||||
<PropertyPane />
|
||||
</EditorWrapper>
|
||||
|
|
@ -83,26 +110,14 @@ class WidgetsEditor extends React.Component<EditorProps> {
|
|||
}
|
||||
}
|
||||
|
||||
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(
|
||||
state.ui.editor.pageWidgetId,
|
||||
state.entities,
|
||||
);
|
||||
|
||||
const mapStateToProps = (state: AppState) => {
|
||||
return {
|
||||
dsl,
|
||||
pageWidgetId: state.ui.editor.pageWidgetId,
|
||||
currentPageId: state.ui.editor.currentPageId,
|
||||
currentLayoutId: state.ui.editor.currentLayoutId,
|
||||
currentPageName: state.ui.editor.currentPageName,
|
||||
isSaving: state.ui.editor.isSaving,
|
||||
dsl: getDenormalizedDSL(state),
|
||||
pageWidgetId: getPageWidgetId(state),
|
||||
currentPageId: getCurrentPageId(state),
|
||||
currentLayoutId: getCurrentLayoutId(state),
|
||||
currentPageName: getCurrentPageName(state),
|
||||
propertyPaneConfigsId: getPropertyPaneConfigsId(state),
|
||||
};
|
||||
};
|
||||
|
||||
|
|
@ -122,6 +137,18 @@ const mapDispatchToProps = (dispatch: any) => {
|
|||
layoutId: string,
|
||||
dsl: ContainerWidgetProps<WidgetProps>,
|
||||
) => dispatch(savePage(pageId, layoutId, dsl)),
|
||||
fetchConfigs: (configsIds: EditorConfigIdsType) =>
|
||||
dispatch(fetchEditorConfigs(configsIds)),
|
||||
showPropertyPane: (
|
||||
widgetId?: string,
|
||||
node?: HTMLDivElement,
|
||||
toggle = false,
|
||||
) => {
|
||||
dispatch({
|
||||
type: ReduxActionTypes.SHOW_PROPERTY_PANE,
|
||||
payload: { widgetId, node, toggle },
|
||||
});
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
|||
10
app/client/src/pages/common/PaneWrapper.tsx
Normal file
10
app/client/src/pages/common/PaneWrapper.tsx
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
import styled from "styled-components";
|
||||
|
||||
export default styled.div`
|
||||
background-color: ${props => props.theme.colors.paneBG};
|
||||
border-radius: ${props => props.theme.radii[2]}px;
|
||||
box-shadow: 0px 0px 3px ${props => props.theme.colors.paneBG};
|
||||
padding: 5px 10px;
|
||||
color: ${props => props.theme.colors.textOnDarkBG};
|
||||
text-transform: capitalize;
|
||||
`;
|
||||
|
|
@ -4,6 +4,7 @@ import apiDataReducer from "./apiDataReducer";
|
|||
import queryDataReducer from "./queryDataReducer";
|
||||
import widgetConfigReducer from "./widgetConfigReducer";
|
||||
import actionsReducer from "./actionsReducer";
|
||||
import propertyPaneConfigReducer from "./propertyPaneConfigReducer";
|
||||
|
||||
const entityReducer = combineReducers({
|
||||
canvasWidgets: canvasWidgetsReducer,
|
||||
|
|
@ -11,5 +12,6 @@ const entityReducer = combineReducers({
|
|||
queryData: queryDataReducer,
|
||||
widgetConfig: widgetConfigReducer,
|
||||
actions: actionsReducer,
|
||||
propertyConfig: propertyPaneConfigReducer,
|
||||
});
|
||||
export default entityReducer;
|
||||
|
|
|
|||
|
|
@ -45,8 +45,8 @@ export interface PropertyPaneConfigState {
|
|||
configVersion: number;
|
||||
}
|
||||
|
||||
const widgetConfigReducer = createReducer(initialState, {
|
||||
[ReduxActionTypes.LOAD_PROPERTY_CONFIG]: (
|
||||
const propertyPaneConfigReducer = createReducer(initialState, {
|
||||
[ReduxActionTypes.FETCH_PROPERTY_PANE_CONFIGS_SUCCESS]: (
|
||||
state: PropertyPaneConfigState,
|
||||
action: ReduxAction<PropertyPaneConfigState>,
|
||||
) => {
|
||||
|
|
@ -54,4 +54,4 @@ const widgetConfigReducer = createReducer(initialState, {
|
|||
},
|
||||
});
|
||||
|
||||
export default widgetConfigReducer;
|
||||
export default propertyPaneConfigReducer;
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ export interface EditorReduxState {
|
|||
currentPageId: string;
|
||||
currentLayoutId: string;
|
||||
currentPageName: string;
|
||||
propertyPaneConfigsId: string;
|
||||
isSaving: boolean;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@ import {
|
|||
|
||||
const initialState: PropertyPaneReduxState = {
|
||||
isVisible: false,
|
||||
widgetId: undefined,
|
||||
node: undefined,
|
||||
};
|
||||
|
||||
const propertyPaneReducer = createReducer(initialState, {
|
||||
|
|
@ -14,13 +16,19 @@ const propertyPaneReducer = createReducer(initialState, {
|
|||
state: PropertyPaneReduxState,
|
||||
action: ReduxAction<ShowPropertyPanePayload>,
|
||||
) => {
|
||||
return { widgetId: action.payload };
|
||||
let isVisible = true;
|
||||
const { widgetId, node, toggle } = action.payload;
|
||||
if (toggle) {
|
||||
isVisible = !state.isVisible;
|
||||
}
|
||||
return { widgetId, node, isVisible };
|
||||
},
|
||||
});
|
||||
|
||||
export interface PropertyPaneReduxState {
|
||||
widgetId?: string;
|
||||
isVisible: boolean;
|
||||
node?: HTMLDivElement;
|
||||
}
|
||||
|
||||
export default propertyPaneReducer;
|
||||
|
|
|
|||
75
app/client/src/sagas/ConfigsSagas.tsx
Normal file
75
app/client/src/sagas/ConfigsSagas.tsx
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
import { all, call, put, takeLatest } from "redux-saga/effects";
|
||||
import {
|
||||
ReduxAction,
|
||||
ReduxActionTypes,
|
||||
ReduxActionErrorTypes,
|
||||
} from "../constants/ReduxActionConstants";
|
||||
|
||||
import PropertyPaneConfigsApi, {
|
||||
PropertyPaneConfigsResponse,
|
||||
PropertyPaneConfigsRequest,
|
||||
} from "../api/PropertPaneConfigsApi";
|
||||
|
||||
import { EditorConfigIdsType } from "../actions/configsActions";
|
||||
|
||||
import { validateResponse } from "./ErrorSagas";
|
||||
|
||||
export function* fetchPropertyPaneConfigsSaga(propertyPaneConfigsId: string) {
|
||||
const request: PropertyPaneConfigsRequest = { propertyPaneConfigsId };
|
||||
try {
|
||||
const response: PropertyPaneConfigsResponse = yield call(
|
||||
PropertyPaneConfigsApi.fetch,
|
||||
request,
|
||||
);
|
||||
const isValidResponse = yield validateResponse(response);
|
||||
if (isValidResponse) {
|
||||
yield put({
|
||||
type: ReduxActionTypes.FETCH_PROPERTY_PANE_CONFIGS_SUCCESS,
|
||||
payload: {
|
||||
config: response.data.config,
|
||||
},
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
yield put({
|
||||
type: ReduxActionErrorTypes.FETCH_PROPERTY_PANE_CONFIGS_ERROR,
|
||||
payload: {
|
||||
error,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function* configsSaga(configsIds: ReduxAction<EditorConfigIdsType>) {
|
||||
const {
|
||||
propertyPaneConfigsId,
|
||||
widgetCardsPaneId,
|
||||
widgetConfigsId,
|
||||
} = configsIds.payload;
|
||||
try {
|
||||
const sagasToCall = [];
|
||||
if (propertyPaneConfigsId) {
|
||||
sagasToCall.push(
|
||||
call(fetchPropertyPaneConfigsSaga, propertyPaneConfigsId),
|
||||
);
|
||||
}
|
||||
if (widgetCardsPaneId) {
|
||||
// sagasToCall.push(call(fetchWidgetCardsConfigsSaga, widgetCardsPaneId));
|
||||
}
|
||||
if (widgetConfigsId) {
|
||||
// sagasToCall.push(call(fetchWidgetConfigsSaga, widgetConfigsId));
|
||||
}
|
||||
yield all(sagasToCall);
|
||||
} catch (error) {
|
||||
yield put({
|
||||
type: ReduxActionErrorTypes.FETCH_CONFIGS_ERROR,
|
||||
payload: {
|
||||
error,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default function* configsSagas() {
|
||||
yield takeLatest(ReduxActionTypes.FETCH_CONFIGS_INIT, configsSaga);
|
||||
}
|
||||
|
|
@ -28,18 +28,23 @@ export function* validateResponse(response: ApiResponse) {
|
|||
}
|
||||
|
||||
type ErrorPayloadType = object | { message: string };
|
||||
|
||||
const ActionErrorDisplayMap: {
|
||||
let ActionErrorDisplayMap: {
|
||||
[key: string]: (error: ErrorPayloadType) => string;
|
||||
} = {
|
||||
} = {};
|
||||
|
||||
Object.keys(ReduxActionErrorTypes).forEach((type: string) => {
|
||||
ActionErrorDisplayMap[type] = () =>
|
||||
DEFAULT_ERROR_MESSAGE + " action: " + type;
|
||||
});
|
||||
|
||||
ActionErrorDisplayMap = {
|
||||
...ActionErrorDisplayMap,
|
||||
[ReduxActionErrorTypes.API_ERROR]: error =>
|
||||
_.get(error, "message", DEFAULT_ERROR_MESSAGE),
|
||||
[ReduxActionErrorTypes.FETCH_PAGE_ERROR]: () =>
|
||||
DEFAULT_ACTION_ERROR("fetching the page"),
|
||||
[ReduxActionErrorTypes.SAVE_PAGE_ERROR]: () =>
|
||||
DEFAULT_ACTION_ERROR("saving the page"),
|
||||
[ReduxActionErrorTypes.FETCH_WIDGET_CARDS_ERROR]: () => DEFAULT_ERROR_MESSAGE,
|
||||
[ReduxActionErrorTypes.WIDGET_OPERATION_ERROR]: () => DEFAULT_ERROR_MESSAGE,
|
||||
};
|
||||
|
||||
export function* errorSaga(
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { fetchWidgetCardsSaga } from "./WidgetSidebarSagas";
|
|||
import { watchExecuteActionSaga } from "./ActionSagas";
|
||||
import widgetOperationSagas from "./WidgetOperationSagas";
|
||||
import errorSagas from "./ErrorSagas";
|
||||
|
||||
import configsSagas from "./ConfigsSagas";
|
||||
export function* rootSaga() {
|
||||
yield all([
|
||||
spawn(pageSagas),
|
||||
|
|
@ -12,5 +12,6 @@ export function* rootSaga() {
|
|||
spawn(watchExecuteActionSaga),
|
||||
spawn(widgetOperationSagas),
|
||||
spawn(errorSagas),
|
||||
spawn(configsSagas),
|
||||
]);
|
||||
}
|
||||
|
|
|
|||
78
app/client/src/selectors/editorSelectors.tsx
Normal file
78
app/client/src/selectors/editorSelectors.tsx
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
import { createSelector } from "reselect";
|
||||
import createCachedSelector from "re-reselect";
|
||||
|
||||
import { AppState } from "../reducers";
|
||||
import { EditorReduxState } from "../reducers/uiReducers/editorReducer";
|
||||
import { WidgetConfigReducerState } from "../reducers/entityReducers/widgetConfigReducer";
|
||||
import { WidgetCardProps } from "../widgets/BaseWidget";
|
||||
import { WidgetSidebarReduxState } from "../reducers/uiReducers/widgetSidebarReducer";
|
||||
import CanvasWidgetsNormalizer from "../normalizers/CanvasWidgetsNormalizer";
|
||||
|
||||
const getEditorState = (state: AppState) => state.ui.editor;
|
||||
const getWidgetConfigs = (state: AppState) => state.entities.widgetConfig;
|
||||
const getEntities = (state: AppState) => state.entities;
|
||||
const getWidgetSideBar = (state: AppState) => state.ui.widgetSidebar;
|
||||
|
||||
export const getPropertyPaneConfigsId = createSelector(
|
||||
getEditorState,
|
||||
(editor: EditorReduxState) => editor.propertyPaneConfigsId,
|
||||
);
|
||||
|
||||
export const getCurrentPageId = createSelector(
|
||||
getEditorState,
|
||||
(editor: EditorReduxState) => editor.currentPageId,
|
||||
);
|
||||
|
||||
export const getCurrentLayoutId = createSelector(
|
||||
getEditorState,
|
||||
(editor: EditorReduxState) => editor.currentLayoutId,
|
||||
);
|
||||
|
||||
export const getPageWidgetId = createSelector(
|
||||
getEditorState,
|
||||
(editor: EditorReduxState) => editor.pageWidgetId,
|
||||
);
|
||||
|
||||
export const getCurrentPageName = createSelector(
|
||||
getEditorState,
|
||||
(editor: EditorReduxState) => editor.currentPageName,
|
||||
);
|
||||
|
||||
export const getIsPageSaving = createSelector(
|
||||
getEditorState,
|
||||
(editor: EditorReduxState) => editor.isSaving,
|
||||
);
|
||||
|
||||
export const getWidgetCards = createSelector(
|
||||
getWidgetSideBar,
|
||||
getWidgetConfigs,
|
||||
(
|
||||
widgetCards: WidgetSidebarReduxState,
|
||||
widgetConfigs: WidgetConfigReducerState,
|
||||
) => {
|
||||
const cards = widgetCards.cards;
|
||||
const groups: string[] = Object.keys(cards);
|
||||
groups.forEach((group: string) => {
|
||||
cards[group] = cards[group].map((widget: WidgetCardProps) => {
|
||||
const { rows, columns } = widgetConfigs.config[widget.type];
|
||||
return { ...widget, rows, columns };
|
||||
});
|
||||
});
|
||||
return cards;
|
||||
},
|
||||
);
|
||||
|
||||
// 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
|
||||
|
||||
export const getDenormalizedDSL = createCachedSelector(
|
||||
getPageWidgetId,
|
||||
getEntities,
|
||||
(pageWidgetId: string, entities: any) => {
|
||||
return CanvasWidgetsNormalizer.denormalize(pageWidgetId, entities);
|
||||
},
|
||||
)((pageWidgetId, entities) => entities || 0);
|
||||
47
app/client/src/selectors/propertyPaneSelectors.tsx
Normal file
47
app/client/src/selectors/propertyPaneSelectors.tsx
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
import { createSelector } from "reselect";
|
||||
import { AppState } from "../reducers";
|
||||
import { PropertyPaneReduxState } from "../reducers/uiReducers/propertyPaneReducer";
|
||||
import { PropertyPaneConfigState } from "../reducers/entityReducers/propertyPaneConfigReducer";
|
||||
import { CanvasWidgetsReduxState } from "../reducers/entityReducers/canvasWidgetsReducer";
|
||||
|
||||
const getPropertyPaneState = (state: AppState): PropertyPaneReduxState =>
|
||||
state.ui.propertyPane;
|
||||
|
||||
const getPropertyPaneConfig = (state: AppState): PropertyPaneConfigState =>
|
||||
state.entities.propertyConfig;
|
||||
|
||||
const getCanvasWidgets = (state: AppState): CanvasWidgetsReduxState =>
|
||||
state.entities.canvasWidgets;
|
||||
|
||||
export const getCurrentWidgetId = createSelector(
|
||||
getPropertyPaneState,
|
||||
(propertyPane: PropertyPaneReduxState) => propertyPane.widgetId,
|
||||
);
|
||||
|
||||
export const getCurrentReferenceNode = createSelector(
|
||||
getPropertyPaneState,
|
||||
(pane: PropertyPaneReduxState) => {
|
||||
return pane.widgetId && pane.node ? pane.node : undefined;
|
||||
},
|
||||
);
|
||||
|
||||
export const getPropertyConfig = createSelector(
|
||||
getPropertyPaneConfig,
|
||||
getPropertyPaneState,
|
||||
getCanvasWidgets,
|
||||
(
|
||||
configs: PropertyPaneConfigState,
|
||||
pane: PropertyPaneReduxState,
|
||||
widgets: CanvasWidgetsReduxState,
|
||||
) => {
|
||||
if (pane.widgetId && configs && widgets[pane.widgetId]) {
|
||||
return configs.config[widgets[pane.widgetId].type];
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
);
|
||||
|
||||
export const getIsPropertyPaneVisible = createSelector(
|
||||
getPropertyPaneState,
|
||||
(pane: PropertyPaneReduxState) => pane.isVisible,
|
||||
);
|
||||
|
|
@ -32,7 +32,7 @@ class PropertyControlFactory {
|
|||
} else {
|
||||
const ex: ControlCreationException = {
|
||||
message:
|
||||
"Control Builder not registered for control type" +
|
||||
"Control Builder not registered for control type " +
|
||||
controlData.controlType,
|
||||
};
|
||||
throw ex;
|
||||
|
|
|
|||
|
|
@ -252,11 +252,11 @@ export const generateWidgetProps = (
|
|||
...widgetConfig,
|
||||
type,
|
||||
widgetId: generateReactKey(),
|
||||
widgetName: widgetName || generateReactKey(), //TODO: figure out what this is to populate appropriately
|
||||
widgetName: widgetName,
|
||||
isVisible: true,
|
||||
parentColumnSpace,
|
||||
parentRowSpace,
|
||||
renderMode: RenderModes.CANVAS, //Is this required?
|
||||
renderMode: RenderModes.CANVAS,
|
||||
...sizes,
|
||||
...others,
|
||||
backgroundColor: Colors.WHITE,
|
||||
|
|
|
|||
|
|
@ -9531,6 +9531,11 @@ rc@^1.2.7:
|
|||
minimist "^1.2.0"
|
||||
strip-json-comments "~2.0.1"
|
||||
|
||||
re-reselect@^3.4.0:
|
||||
version "3.4.0"
|
||||
resolved "https://registry.yarnpkg.com/re-reselect/-/re-reselect-3.4.0.tgz#0f2303f3c84394f57f0cd31fea08a1ca4840a7cd"
|
||||
integrity sha512-JsecfN+JlckncVXTWFWjn0Vk6uInl8GSf4eEd9tTk5qXHlgqkPdILpnYpgZcISXNYAzvfvsCZviaDk8AxyS5sg==
|
||||
|
||||
re-resizable@6.1.0:
|
||||
version "6.1.0"
|
||||
resolved "https://registry.yarnpkg.com/re-resizable/-/re-resizable-6.1.0.tgz#ba4ece505b48f05691446d57837151349d7575e8"
|
||||
|
|
@ -10132,6 +10137,11 @@ requires-port@^1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
|
||||
integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=
|
||||
|
||||
reselect@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/reselect/-/reselect-4.0.0.tgz#f2529830e5d3d0e021408b246a206ef4ea4437f7"
|
||||
integrity sha512-qUgANli03jjAyGlnbYVAV5vvnOmJnODyABz51RdBN7M4WaVu8mecZWgyQNkG8Yqe3KRGRt0l4K4B3XVEULC4CA==
|
||||
|
||||
resize-observer-polyfill@^1.5.1:
|
||||
version "1.5.1"
|
||||
resolved "https://registry.yarnpkg.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz#0e9020dd3d21024458d4ebd27e23e40269810464"
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user