diff --git a/app/client/package.json b/app/client/package.json index bfcb0700e6..e1339008c2 100644 --- a/app/client/package.json +++ b/app/client/package.json @@ -26,8 +26,8 @@ "normalizr": "^3.3.0", "prettier": "^1.16.0", "react": "^16.7.0", - "react-dnd": "^7.4.3", - "react-dnd-html5-backend": "^7.4.0", + "react-dnd": "^9.3.4", + "react-dnd-html5-backend": "^9.3.4", "react-dom": "^16.7.0", "react-redux": "^6.0.0", "react-router": "^4.3.1", diff --git a/app/client/src/actions/pageActions.tsx b/app/client/src/actions/pageActions.tsx index 2fe48582fa..f54af9f794 100644 --- a/app/client/src/actions/pageActions.tsx +++ b/app/client/src/actions/pageActions.tsx @@ -4,6 +4,7 @@ import { } from "../constants/ActionConstants" import { PageRequest } from "../api/PageApi" import { RenderMode } from "../constants/WidgetConstants"; +import { IWidgetProps } from "../widgets/BaseWidget"; export const fetchPage = (pageId: string, renderMode: RenderMode): ReduxAction => { return { @@ -14,3 +15,23 @@ export const fetchPage = (pageId: string, renderMode: RenderMode): ReduxAction

=> { + return { + type: ActionTypes.ADD_PAGE_WIDGET, + payload: { + pageId, + widget, + } + } +} + +export const removeWidget = (pageId: string, widgetId: string): ReduxAction<{ pageId: string, widgetId: string}> => { + return { + type: ActionTypes.REMOVE_PAGE_WIDGET, + payload: { + pageId, + widgetId, + } + } +} \ No newline at end of file diff --git a/app/client/src/actions/widgetCardsPaneActions.tsx b/app/client/src/actions/widgetCardsPaneActions.tsx index 30c0c0158f..5f6642b891 100644 --- a/app/client/src/actions/widgetCardsPaneActions.tsx +++ b/app/client/src/actions/widgetCardsPaneActions.tsx @@ -10,16 +10,22 @@ export const fetchWidgetCards = () => { } } -export const errorFetchingWidgetCards = (error: T) => { +export const errorFetchingWidgetCards = (error: any) => { return { type: ActionTypes.ERROR_FETCHING_WIDGET_CARDS, error } } -export const successFetchingWidgetCards = (cards: ) => { +export const successFetchingWidgetCards = (cards: { [id: string]: IWidgetCardProps[] }) => { return { type: ActionTypes.SUCCESS_FETCHING_WIDGET_CARDS, cards } } + +export default { + fetchWidgetCards, + errorFetchingWidgetCards, + successFetchingWidgetCards +} \ No newline at end of file diff --git a/app/client/src/api/ApiRequests.tsx b/app/client/src/api/ApiRequests.tsx index 43e52b6e57..0e890a592f 100644 --- a/app/client/src/api/ApiRequests.tsx +++ b/app/client/src/api/ApiRequests.tsx @@ -4,7 +4,6 @@ export interface ApiHeaders { Accept: ContentType "Content-Type": ContentType dataType: DataType - "Accept-Encoding": EncodingType } export interface ApiRequest { diff --git a/app/client/src/api/WidgetCardsPaneApi.tsx b/app/client/src/api/WidgetCardsPaneApi.tsx index 82331baf0a..ccb6fde68d 100644 --- a/app/client/src/api/WidgetCardsPaneApi.tsx +++ b/app/client/src/api/WidgetCardsPaneApi.tsx @@ -4,7 +4,7 @@ import { IContainerWidgetProps } from "../widgets/ContainerWidget" import { ApiResponse } from "./ApiResponses" export interface WidgetCardsPaneResponse { - cards : Map + cards : { [id: string]: IWidgetCardProps[]} } export interface WidgetCardsPaneRequest {} diff --git a/app/client/src/constants/ActionConstants.tsx b/app/client/src/constants/ActionConstants.tsx index 9508809cf8..5659b8c5ad 100644 --- a/app/client/src/constants/ActionConstants.tsx +++ b/app/client/src/constants/ActionConstants.tsx @@ -17,6 +17,8 @@ export type ActionType = | "FETCH_WIDGET_CARDS" | "SUCCESS_FETCHING_WIDGET_CARDS" | "ERROR_FETCHING_WIDGET_CARDS" + | "ADD_PAGE_WIDGET" + | "REMOVE_PAGE_WIDGET" export const ActionTypes: { [id: string]: ActionType } = { UPDATE_CANVAS: "UPDATE_CANVAS", @@ -31,8 +33,11 @@ export const ActionTypes: { [id: string]: ActionType } = { UNDO_CANVAS_ACTION: "UNDO_CANVAS_ACTION", REDO_CANVAS_ACTION: "REDO_CANVAS_ACTION", PUBLISH: "PUBLISH", + FETCH_WIDGET_CARDS: "FETCH_WIDGET_CARDS", SUCCESS_FETCHING_WIDGET_CARDS: "SUCCESS_FETCHING_WIDGET_CARDS", - ERROR_FETCHING_WIDGET_CARDS: "ERROR_FETCHING_WIDGET_CARDS" + ERROR_FETCHING_WIDGET_CARDS: "ERROR_FETCHING_WIDGET_CARDS", + ADD_PAGE_WIDGET: "ADD_PAGE_WIDGET", + REMOVE_PAGE_WIDGET: "REMOVE_PAGE_WIDGET" } export interface ReduxAction { diff --git a/app/client/src/constants/ApiConstants.tsx b/app/client/src/constants/ApiConstants.tsx index a586555cdc..ae6654120e 100644 --- a/app/client/src/constants/ApiConstants.tsx +++ b/app/client/src/constants/ApiConstants.tsx @@ -5,7 +5,7 @@ export type ContentType = "application/json" | "application/x-www-form-urlencode export type EncodingType = "gzip" export const PROD_BASE_URL = "https://mobtools.com/api/" -export const MOCK_BASE_URL = "https://12064212-8b45-4d81-882a-7a044f3c9555.mock.pstmn.io" +export const MOCK_BASE_URL = "https://f1a8b631-ef37-4932-8f41-75cf127fcc55.mock.pstmn.io" export const STAGE_BASE_URL = "https://14157cb0-190f-4082-a791-886a8df05930.mock.pstmn.io" export const BASE_URL = MOCK_BASE_URL export const REQUEST_TIMEOUT_MS = 2000 @@ -13,5 +13,4 @@ export const REQUEST_HEADERS: ApiHeaders = { Accept: "application/json", "Content-Type": "application/json", dataType: "json", - "Accept-Encoding": "gzip" } diff --git a/app/client/src/constants/WidgetConstants.tsx b/app/client/src/constants/WidgetConstants.tsx index 02f2139567..fdc4acb161 100644 --- a/app/client/src/constants/WidgetConstants.tsx +++ b/app/client/src/constants/WidgetConstants.tsx @@ -15,6 +15,14 @@ export type WidgetType = | "RADIO_GROUP_WIDGET" | "INPUT_WIDGET" | "TOGGLE_WIDGET" + +export const WidgetTypes: {[id: string]: WidgetType } = { + BUTTON_WIDGET: "BUTTON_WIDGET", + TEXT_WIDGET: "TEXT_WIDGET", + INPUT_WIDGET: "INPUT_WIDGET", + TOGGLE_WIDGET: "TOGGLE_WIDGET", +} + export type ContainerOrientation = "HORIZONTAL" | "VERTICAL" export type PositionType = "ABSOLUTE" | "CONTAINER_DIRECTION" export type CSSUnit = diff --git a/app/client/src/editorComponents/BaseComponent.tsx b/app/client/src/editorComponents/BaseComponent.tsx index 98bcf07f10..53cef2b78c 100644 --- a/app/client/src/editorComponents/BaseComponent.tsx +++ b/app/client/src/editorComponents/BaseComponent.tsx @@ -1,6 +1,7 @@ import { Component } from "react" import React from "react" import { PositionType, CSSUnit } from "../constants/WidgetConstants" +import { Color } from "../constants/StyleConstants" /*** * Components are responsible for binding render inputs to corresponding UI SDKs */ @@ -22,6 +23,8 @@ export interface BaseStyle { yPositionUnit: CSSUnit heightUnit?: CSSUnit widthUnit?: CSSUnit + backgroundColor?: Color + } export interface IComponentProps { diff --git a/app/client/src/editorComponents/ButtonComponent.tsx b/app/client/src/editorComponents/ButtonComponent.tsx index e1449a7a92..b39cd0d34a 100644 --- a/app/client/src/editorComponents/ButtonComponent.tsx +++ b/app/client/src/editorComponents/ButtonComponent.tsx @@ -7,7 +7,7 @@ class ButtonComponent extends React.Component { render() { return ( - ) } diff --git a/app/client/src/index.tsx b/app/client/src/index.tsx index 6346822212..ba1aaaf963 100755 --- a/app/client/src/index.tsx +++ b/app/client/src/index.tsx @@ -15,7 +15,7 @@ import createSagaMiddleware from 'redux-saga' import { rootSaga } from "./sagas" import { ActionType, ReduxAction } from "./constants/ActionConstants"; -import { DragDropContextProvider } from "react-dnd" +import { DndProvider } from "react-dnd" import HTML5Backend from "react-dnd-html5-backend" WidgetBuilderRegistry.registerWidgetBuilders(); @@ -25,18 +25,19 @@ sagaMiddleware.run(rootSaga) ReactDOM.render( - - - - - - - - - - - - , + + + + + + + + + + + + + , document.getElementById("root") ); diff --git a/app/client/src/mockResponses/CanvasResponse.tsx b/app/client/src/mockResponses/CanvasResponse.tsx deleted file mode 100644 index 658259a0e6..0000000000 --- a/app/client/src/mockResponses/CanvasResponse.tsx +++ /dev/null @@ -1,173 +0,0 @@ -import { IContainerWidgetProps } from "../widgets/ContainerWidget" -import { RenderModes } from "../constants/WidgetConstants" - -const CanvasResponse: IContainerWidgetProps = { - widgetId: "0", - widgetType: "CONTAINER_WIDGET", - snapColumns: 10, - snapRows: 10, - topRow: 60, - bottomRow: 700, - leftColumn: 300, - rightColumn: 1000, - parentColumnSpace: 1, - parentRowSpace: 1, - renderMode: RenderModes.CANVAS, - children: [ - { - widgetId: "1", - widgetType: "TEXT_WIDGET", - topRow: 2, - leftColumn: 1, - bottomRow: 5, - rightColumn: 3, - text: "Lorem Ipsum asda asd kjhsadjhas kdh kashkjdas kdhas d as", - renderMode: RenderModes.CANVAS - }, - { - widgetId: "2", - widgetType: "BUTTON_WIDGET", - topRow: 2, - leftColumn: 4, - bottomRow: 5, - rightColumn: 5, - text: "Lorem Ipsum", - renderMode: RenderModes.CANVAS - }, - { - widgetId: "3", - widgetType: "INPUT_GROUP_WIDGET", - topRow: 1, - leftColumn: 1, - bottomRow: 1, - rightColumn: 5, - placeholder: "Lorem Ipsum", - renderMode: RenderModes.CANVAS - }, - { - widgetId: "4", - widgetType: "CALLOUT_WIDGET", - topRow: 2, - leftColumn: 8, - bottomRow: 4, - rightColumn: 14, - id: "sample_id", - title: "Visually important content", - description: - "The component is a simple wrapper around the CSS API that provides props for modifiers and optional title element. Any additional HTML props will be spread to the rendered

element.", - icon: "", - intent: "success", - renderMode: RenderModes.CANVAS - }, - { - widgetId: "5", - widgetType: "ICON_WIDGET", - topRow: 4, - leftColumn: 2, - bottomRow: 5, - rightColumn: 3, - icon: "globe", - iconSize: "20", - intent: "warning", - renderMode: RenderModes.CANVAS - }, - { - widgetId: "6", - widgetType: "SPINNER_WIDGET", - topRow: 5, - leftColumn: 6, - bottomRow: 5, - rightColumn: 5, - size: 20 - }, - { - widgetId: "7", - widgetType: "BREADCRUMBS_WIDGET", - topRow: 6, - leftColumn: 2, - bottomRow: 5, - rightColumn: 5, - width: "100%", - collapseFrom: "start", - className: "breadcrumbs", - size: 20, - renderMode: RenderModes.CANVAS, - items: [ - { icon: "folder-close", text: "All files" }, - { icon: "folder-close", text: "Users" }, - { icon: "folder-close", text: "Janet" }, - { href: "#", icon: "folder-close", text: "Photos" }, - { href: "#", icon: "folder-close", text: "Wednesday" }, - { icon: "document", text: "image.jpg" } - ] - }, - { - widgetId: "8", - widgetType: "TAG_INPUT_WIDGET", - topRow: 7, - leftColumn: 1, - bottomRow: 5, - rightColumn: 5, - palceholder: "Lorem, Ipsum", - values: ["abx", "somf", "soke"], - renderMode: RenderModes.CANVAS - }, - { - widgetId: "9", - widgetType: "NUMERIC_INPUT_WIDGET", - topRow: 4, - leftColumn: 1, - bottomRow: 5, - rightColumn: 5, - palceholder: "Numeric input", - allowNumericCharactersOnly: true, - renderMode: RenderModes.CANVAS - }, - { - widgetId: "10", - widgetType: "CHECKBOX_WIDGET", - topRow: 6, - leftColumn: 1, - bottomRow: 5, - rightColumn: 5, - items: [ - { - label: "a", - value: 1 - }, - { - label: "b", - value: 2 - }, - { - label: "c", - value: 3 - } - ] - }, - { - widgetId: "11", - widgetType: "RADIO_GROUP_WIDGET", - topRow: 6, - leftColumn: 1, - bottomRow: 5, - rightColumn: 5, - items: [ - { - label: "a", - value: 1 - }, - { - label: "b", - value: 2 - }, - { - label: "c", - value: 3 - } - ] - } - ] -} - -export default CanvasResponse diff --git a/app/client/src/normalizers/CanvasWidgetsNormalizer.tsx b/app/client/src/normalizers/CanvasWidgetsNormalizer.tsx index 25220b4d49..4799c91397 100644 --- a/app/client/src/normalizers/CanvasWidgetsNormalizer.tsx +++ b/app/client/src/normalizers/CanvasWidgetsNormalizer.tsx @@ -2,9 +2,9 @@ import { normalize, schema, denormalize } from 'normalizr'; import { PageResponse } from '../api/PageApi'; import { IContainerWidgetProps } from '../widgets/ContainerWidget'; -export const widgetSchema = new schema.Entity('canvasWidgets', { }, { idAttribute: "widgetId" }, ); -const widgets = new schema.Array(widgetSchema); -widgetSchema.define({ children: widgets }); +export const widgetSchema = new schema.Entity('canvasWidgets', { }, { idAttribute: "widgetId" }); +// const widgets = new schema.Array(widgetSchema); +widgetSchema.define({ children: [widgetSchema] }); class CanvasWidgetsNormalizer { diff --git a/app/client/src/pages/Editor/Canvas.tsx b/app/client/src/pages/Editor/Canvas.tsx index 1e9d55ddc3..af17e861c8 100644 --- a/app/client/src/pages/Editor/Canvas.tsx +++ b/app/client/src/pages/Editor/Canvas.tsx @@ -1,47 +1,30 @@ -import React, { Component } from "react" -import { connect } from "react-redux" -import { AppState } from "../../reducers" +import React, { Props } from "react" import WidgetFactory from "../../utils/WidgetFactory" -import CanvasWidgetsNormalizer, { - widgetSchema -} from "../../normalizers/CanvasWidgetsNormalizer" +import { WidgetTypes } from "../../constants/WidgetConstants" +import { DraggableWidget } from "../../widgets/BaseWidget" +import { useDrop } from 'react-dnd' import { IContainerWidgetProps } from "../../widgets/ContainerWidget" -import { fetchPage } from "../../actions/pageActions" -import { RenderModes } from "../../constants/WidgetConstants" -class Canvas extends Component<{ +interface CanvasProps { pageWidget: IContainerWidgetProps - fetchPage: Function -}> { - componentDidMount() { - this.props.fetchPage("1") - } - - render() { - const pageWidget = this.props.pageWidget - return pageWidget ? WidgetFactory.createWidget(pageWidget) : null - } + addWidget: Function + removeWidget: Function } -const mapStateToProps = (state: AppState, props: any) => { - const pageWidget = CanvasWidgetsNormalizer.denormalize( - state.ui.canvas.pageWidgetId, - state.entities +const Canvas : React.SFC = (props) => { + const [, drop] = useDrop({ + accept: Object.values(WidgetTypes), + drop(item: DraggableWidget, monitor) { + console.log("dropped") + props.addWidget(item.type); + return undefined + }, + }) + return ( +
+ {props.pageWidget && WidgetFactory.createWidget(props.pageWidget)} +
) - return { - pageWidget: pageWidget - } } -const mapDispatchToProps = (dispatch: any) => { - return { - fetchPage: (pageId: string) => { - return dispatch(fetchPage(pageId, RenderModes.CANVAS)) - } - } -} - -export default connect( - mapStateToProps, - mapDispatchToProps -)(Canvas) +export default Canvas \ No newline at end of file diff --git a/app/client/src/pages/Editor/EditorDragLayer.tsx b/app/client/src/pages/Editor/EditorDragLayer.tsx new file mode 100644 index 0000000000..7d4dc284f8 --- /dev/null +++ b/app/client/src/pages/Editor/EditorDragLayer.tsx @@ -0,0 +1,87 @@ +import React from 'react' +import styled from 'styled-components' +import { XYCoord, useDragLayer } from 'react-dnd' +import snapToGrid from './snapToGrid' +import WidgetFactory from '../../utils/WidgetFactory'; +import { RenderModes, WidgetTypes, WidgetType } from '../../constants/WidgetConstants'; + +const WrappedDragLayer = styled.div` + position: fixed; + pointer-events: none; + z-index: 100; + left: 0; + top: 0; + width: 100%; + height: 100%; +`; + + +function getItemStyles( + initialOffset: XYCoord | null, + currentOffset: XYCoord | null +) { + if (!initialOffset || !currentOffset) { + return { + display: 'none', + } + } + + let { x, y } = currentOffset + + x -= initialOffset.x + y -= initialOffset.y + ;[x, y] = snapToGrid(x, y) + x += initialOffset.x + y += initialOffset.y + + const transform = `translate(${x}px, ${y}px)` + return { + transform, + WebkitTransform: transform, + } +} + + +const EditorDragLayer: React.FC = () => { + const { + itemType, + isDragging, + item, + initialOffset, + currentOffset, + } = useDragLayer(monitor => ({ + item: monitor.getItem(), + itemType: monitor.getItemType(), + initialOffset: monitor.getInitialSourceClientOffset(), + currentOffset: monitor.getSourceClientOffset(), + isDragging: monitor.isDragging(), + })) + + function renderItem() { + return WidgetFactory.createWidget({ + widgetType: itemType as WidgetType, + widgetId: '2', + topRow: 10, + leftColumn: 10, + bottomRow: 14, + rightColumn: 20, + parentColumnSpace: 1, + parentRowSpace: 1, + renderMode: RenderModes.CANVAS + }) + } + + if (!isDragging) { + return null + } + return ( + +
+ {renderItem()} +
+
+ ) +} +export default EditorDragLayer diff --git a/app/client/src/pages/Editor/WidgetCard.tsx b/app/client/src/pages/Editor/WidgetCard.tsx new file mode 100644 index 0000000000..9596799a5b --- /dev/null +++ b/app/client/src/pages/Editor/WidgetCard.tsx @@ -0,0 +1,55 @@ +import React from 'react'; +import { useDrag, DragSourceMonitor } from 'react-dnd' +import { IWidgetCardProps } from '../../widgets/BaseWidget' +import styled from 'styled-components' +import { Icon } from '@blueprintjs/core' +import { IconNames } from '@blueprintjs/icons' + +interface WidgetCardProps { + details: IWidgetCardProps +} + +const Wrapper = styled.div` + display:flex; + flex-direction: row; + justify-content: center; + align-items: center; + flex: 1; + padding: 10px 5px 10px 5px; + margin: 0px 10px 0 0; + border-radius: 5px; + background: #eee + border: 1px solid #eee; + &:hover{ + background: #fff; + cursor: grab; + } +`; +const IconLabel = styled.h5` + text-align: center; + padding: 10px 0; + margin: 0; + text-transform: uppercase; + font-weight: normal; + flex-shrink: 1; + font-size: 0.5rem; +`; + +const WidgetCard: React.SFC = (props) => { + const [{ isDragging }, drag] = useDrag({ + item: { type: props.details.widgetType, widget: props.details }, + collect: (monitor: DragSourceMonitor) => ({ + isDragging: monitor.isDragging(), + }), + }) + return ( + +
+ + {props.details.label} +
+
+ ) +} + +export default WidgetCard \ No newline at end of file diff --git a/app/client/src/pages/Editor/WidgetCardsPane.tsx b/app/client/src/pages/Editor/WidgetCardsPane.tsx index ebe24d05e2..b5d7129cd7 100644 --- a/app/client/src/pages/Editor/WidgetCardsPane.tsx +++ b/app/client/src/pages/Editor/WidgetCardsPane.tsx @@ -1,30 +1,42 @@ import React, { Component } from "react" -import { connect } from "react-redux" -import { AppState } from "../../reducers" -import { WidgetCardsPaneReduxState } from "../../reducers/uiReducers/widgetCardsPaneReducer"; +import WidgetCard from "./WidgetCard" +import styled from "styled-components" +import { IWidgetCardProps } from "../../widgets/BaseWidget" -class WidgetCardsPane extends Component { - render() { - const groups = Object.keys(this.props.cards) - return (
- {groups.map((group: string) => { - - })} -
) - } +interface WidgetCardPaneProps { + cards: { [id: string]: IWidgetCardProps[]} } -const mapStateToProps = (state: AppState, props: any): WidgetCardsPaneReduxState => { - return { - cards: state.ui.widgetCardsPane.cards - } +const CardsPaneWrapper = styled.div` + width: 300px; + background-color: #fff; + border-radius: 5px; + box-shadow: 0px 0px 3px #ccc; + padding: 5px 10px; +`; + +const CardsWrapper = styled.div` + display: flex; + flex-flow: row wrap; + justify-content: flex-start; +`; + +const WidgetCardsPane: React.SFC = (props) => { + const groups = Object.keys(props.cards) + return ( + + { + groups.map((group: string) => + +
{group}
+ + { props.cards[group].map((card: IWidgetCardProps) => ) } + +
+ ) + } +
+ ) } -const mapDispatchToProps = (dispatch: any) => { - return {} -} - -export default connect( - mapStateToProps, - mapDispatchToProps -)(WidgetCardsPane) +export default WidgetCardsPane \ No newline at end of file diff --git a/app/client/src/pages/Editor/index.tsx b/app/client/src/pages/Editor/index.tsx index 4ff2e8c069..d62a78e5ba 100644 --- a/app/client/src/pages/Editor/index.tsx +++ b/app/client/src/pages/Editor/index.tsx @@ -1,9 +1,19 @@ import React, { Component } from "react" +import { connect } from "react-redux" import styled from "styled-components" import Canvas from "./Canvas" +import { IWidgetCardProps, IWidgetProps } from '../../widgets/BaseWidget' +import { AppState } from "../../reducers" +import { EditorReduxState } from "../../reducers/uiReducers/editorReducer" import WidgetCardsPane from "./WidgetCardsPane" import EditorHeader from "./EditorHeader" -import { CALCULATOR } from "@blueprintjs/icons/lib/esm/generated/iconContents"; +import { WidgetType } from "../../constants/WidgetConstants" +import CanvasWidgetsNormalizer from "../../normalizers/CanvasWidgetsNormalizer" +import { fetchWidgetCards } from "../../actions/widgetCardsPaneActions" +import { IContainerWidgetProps } from "../../widgets/ContainerWidget" +import { fetchPage, addWidget } from "../../actions/pageActions" +import { RenderModes } from "../../constants/WidgetConstants" +import EditorDragLayer from "./EditorDragLayer" const ArtBoard = styled.section` height: 100%; @@ -12,32 +22,95 @@ const ArtBoard = styled.section` overflow-x: hidden; overflow-y: auto; margin: 0px 10px; + &:before { + position: absolute; + top: 0; right: 0; bottom: 0; left: 0; + z-index: 1000; + pointer-events: none; + } `; -class Editor extends Component { +const EditorWrapper = styled.div` + display: flex; + flex-direction: row; + align-items: stretch; + justify-content: flex-start; + width: 100vw; + overflow: hidden; + padding: 10px; + height: calc(100vh - 60px); +`; + +class Editor extends Component<{ + pageWidget: IContainerWidgetProps | any + fetchCanvasWidgets: Function + fetchWidgetCardsPane: Function + cards: { [id: string] : IWidgetCardProps[] } | any + addPageWidget: Function +}> { + componentDidMount() { + this.props.fetchWidgetCardsPane() + this.props.fetchCanvasWidgets("1") + } + + addWidgetToCanvas = (widgetType: WidgetType) => { + this.props.addPageWidget("1", { + key: "12", + bottomRow: 9, + leftColumn: 1, + parentColumnSpace: 90, + parentRowSpace: 50, + renderMode: RenderModes.CANVAS, + rightColumn: 3, + snapColumns: 20, + snapRows: 20, + children: [], + topRow: 1, + widgetId: "2", + widgetType: widgetType + }) + } + + removeWidgetFromCanvas = (widgetId: string) => { + + } + render() { return ( -
+ -
- - - - -
-
+ + + + + + + + ) } } -export default Editor +const mapStateToProps = (state: AppState, props: any): EditorReduxState => { + const pageWidget = CanvasWidgetsNormalizer.denormalize( + state.ui.canvas.pageWidgetId, + state.entities + ) + return { + cards: state.ui.widgetCardsPane.cards, + pageWidget, + } +} + +const mapDispatchToProps = (dispatch: any) => { + return { + fetchCanvasWidgets: (pageId: string) => dispatch(fetchPage(pageId, RenderModes.CANVAS)), + fetchWidgetCardsPane: () => dispatch(fetchWidgetCards()), + addPageWidget: (pageId:string, widgetProps: IWidgetProps) => dispatch(addWidget(pageId, widgetProps)) + } +} + +export default connect( + mapStateToProps, + mapDispatchToProps +)(Editor) diff --git a/app/client/src/pages/Editor/snapToGrid.ts b/app/client/src/pages/Editor/snapToGrid.ts new file mode 100644 index 0000000000..7c4bd67571 --- /dev/null +++ b/app/client/src/pages/Editor/snapToGrid.ts @@ -0,0 +1,5 @@ +export default function snapToGrid(x: number, y: number) { + const snappedX = Math.round(x / 32) * 32 + const snappedY = Math.round(y / 32) * 32 + return [snappedX, snappedY] +} diff --git a/app/client/src/reducers/entityReducers/canvasWidgetsReducers.tsx b/app/client/src/reducers/entityReducers/canvasWidgetsReducers.tsx index e4287fe0a6..cd4292984a 100644 --- a/app/client/src/reducers/entityReducers/canvasWidgetsReducers.tsx +++ b/app/client/src/reducers/entityReducers/canvasWidgetsReducers.tsx @@ -5,9 +5,13 @@ import { ReduxAction } from "../../constants/ActionConstants" import { IWidgetProps } from "../../widgets/BaseWidget" +import CanvasWidgetsNormalizer, { widgetSchema } from "../../normalizers/CanvasWidgetsNormalizer"; -const initialState: CanvasWidgetsReduxState = { - +const initialState: CanvasWidgetsReduxState = {} + + +export interface IFlattenedWidgetProps extends IWidgetProps { + children?: string[] } const canvasWidgetsReducer = createReducer(initialState, { @@ -16,11 +20,26 @@ const canvasWidgetsReducer = createReducer(initialState, { action: ReduxAction ) => { return { ...action.payload.widgets } + }, + [ActionTypes.ADD_PAGE_WIDGET]: ( + state: CanvasWidgetsReduxState, + action: ReduxAction<{pageId: string, widget: IWidgetProps}> + ) => { + const widget = action.payload.widget + const widgetTree = CanvasWidgetsNormalizer.denormalize("0", { canvasWidgets: state }) + const children = widgetTree.children || [] + children.push(widget) + widgetTree.children = children + const newState = CanvasWidgetsNormalizer.normalize({ + responseMeta: {}, + pageWidget: widgetTree + }).entities + return newState.canvasWidgets } }) export interface CanvasWidgetsReduxState { - [widgetId: string]: IWidgetProps + [widgetId: string]: IFlattenedWidgetProps } export default canvasWidgetsReducer diff --git a/app/client/src/reducers/index.tsx b/app/client/src/reducers/index.tsx index ff8f07a12c..5b42b897f5 100644 --- a/app/client/src/reducers/index.tsx +++ b/app/client/src/reducers/index.tsx @@ -5,6 +5,7 @@ import { CanvasReduxState } from "./uiReducers/canvasReducer" import { CanvasWidgetsReduxState } from "./entityReducers/canvasWidgetsReducers" import { WidgetCardsPaneReduxState } from "./uiReducers/widgetCardsPaneReducer" import { EditorHeaderReduxState } from "./uiReducers/editorHeaderReducer" +import { EditorReduxState } from "./uiReducers/editorReducer" const appReducer = combineReducers({ entities: entityReducer, @@ -18,6 +19,7 @@ export interface AppState { canvas: CanvasReduxState widgetCardsPane: WidgetCardsPaneReduxState editorHeader: EditorHeaderReduxState + editor: EditorReduxState } entities: { canvasWidgets: CanvasWidgetsReduxState diff --git a/app/client/src/reducers/uiReducers/canvasReducer.tsx b/app/client/src/reducers/uiReducers/canvasReducer.tsx index be0923b80f..dc308ae8d8 100644 --- a/app/client/src/reducers/uiReducers/canvasReducer.tsx +++ b/app/client/src/reducers/uiReducers/canvasReducer.tsx @@ -14,6 +14,7 @@ const canvasReducer = createReducer(initialState, { state: CanvasReduxState, action: ReduxAction ) => { + console.log(action.payload); return { pageWidgetId: action.payload.pageWidgetId } } }) diff --git a/app/client/src/reducers/uiReducers/editorReducer.tsx b/app/client/src/reducers/uiReducers/editorReducer.tsx new file mode 100644 index 0000000000..65ae622ade --- /dev/null +++ b/app/client/src/reducers/uiReducers/editorReducer.tsx @@ -0,0 +1,41 @@ +import { createReducer } from "../../utils/PicassoUtils" +import { + ActionTypes, + ReduxAction, + LoadCanvasPayload, + LoadWidgetCardsPanePayload +} from "../../constants/ActionConstants" +import { IWidgetCardProps, IWidgetProps } from "../../widgets/BaseWidget" +import { IContainerWidgetProps } from "../../widgets/ContainerWidget" + +const initialState: EditorReduxState = {} + +const editorReducer = createReducer(initialState, { + [ActionTypes.SUCCESS_FETCHING_WIDGET_CARDS]: ( + state: EditorReduxState, + action: ReduxAction + ) => { + return { ...state.pageWidget, ...action.payload } + }, + [ActionTypes.ADD_PAGE_WIDGET]: ( + state: EditorReduxState, + action: ReduxAction<{pageId: string, widget: IWidgetProps}> + ) => { + return state + }, + [ActionTypes.UPDATE_CANVAS]: ( + state: EditorReduxState, + action: ReduxAction + ) => { + return { pageWidgetId: action.payload.pageWidgetId } + } +}) + +export interface EditorReduxState { + pageWidget?: IContainerWidgetProps + cards?: { + [id: string]: IWidgetCardProps[] + } +} + +export default editorReducer diff --git a/app/client/src/reducers/uiReducers/index.tsx b/app/client/src/reducers/uiReducers/index.tsx index 2870c33339..723df6ce0f 100644 --- a/app/client/src/reducers/uiReducers/index.tsx +++ b/app/client/src/reducers/uiReducers/index.tsx @@ -2,6 +2,7 @@ import { combineReducers } from "redux" import canvasReducer from "./canvasReducer" import widgetCardsPaneReducer from "./widgetCardsPaneReducer" import editorHeaderReducer from "./editorHeaderReducer" +import editorReducer from "./editorReducer" -const uiReducer = combineReducers({ canvas: canvasReducer, widgetCardsPane: widgetCardsPaneReducer, editorHeader: editorHeaderReducer }) +const uiReducer = combineReducers({ canvas: canvasReducer, widgetCardsPane: widgetCardsPaneReducer, editorHeader: editorHeaderReducer, editor: editorReducer }) export default uiReducer diff --git a/app/client/src/reducers/uiReducers/widgetCardsPaneReducer.tsx b/app/client/src/reducers/uiReducers/widgetCardsPaneReducer.tsx index 6c1dcebd68..821b3cb76a 100644 --- a/app/client/src/reducers/uiReducers/widgetCardsPaneReducer.tsx +++ b/app/client/src/reducers/uiReducers/widgetCardsPaneReducer.tsx @@ -10,10 +10,11 @@ import WidgetCardsPaneResponse from "../../mockResponses/WidgetCardsPaneResponse const initialState: WidgetCardsPaneReduxState = WidgetCardsPaneResponse const widgetCardsPaneReducer = createReducer(initialState, { - [ActionTypes.SUCCESS_FETCHING_WIDGET_CARDS]: ( + [ActionTypes.ERROR_FETCHING_WIDGET_CARDS]: ( state: WidgetCardsPaneReduxState, action: ReduxAction ) => { + console.log(action) return { cards: action.payload.cards } } }) diff --git a/app/client/src/sagas/PageSagas.tsx b/app/client/src/sagas/PageSagas.tsx index 8a8bb28a6e..2d8c45cb01 100644 --- a/app/client/src/sagas/PageSagas.tsx +++ b/app/client/src/sagas/PageSagas.tsx @@ -6,15 +6,21 @@ import { RenderModes } from "../constants/WidgetConstants" export function* fetchPageSaga(pageRequestAction: ReduxAction) { const pageRequest = pageRequestAction.payload - const pageResponse: PageResponse = yield call(PageApi.fetchPage, pageRequest) - if (pageRequest.renderMode === RenderModes.CANVAS) { - const normalizedResponse = CanvasWidgetsNormalizer.normalize(pageResponse) - const payload = { - pageWidgetId: normalizedResponse.result, - widgets: normalizedResponse.entities.canvasWidgets + try { + const pageResponse: PageResponse = yield call(PageApi.fetchPage, pageRequest) + if (pageRequest.renderMode === RenderModes.CANVAS) { + const normalizedResponse = CanvasWidgetsNormalizer.normalize(pageResponse) + console.log(normalizedResponse); + const payload = { + pageWidgetId: normalizedResponse.result, + widgets: normalizedResponse.entities.canvasWidgets + } + yield put({ type: ActionTypes.UPDATE_CANVAS, payload }) } - yield put({ type: ActionTypes.UPDATE_CANVAS, payload }) + } catch(err) { + //TODO(abhinav): REFACTOR THIS } + } export function* watchFetchPage() { diff --git a/app/client/src/sagas/WidgetCardsPaneSagas.tsx b/app/client/src/sagas/WidgetCardsPaneSagas.tsx index 235b3fc74c..633d3aa70a 100644 --- a/app/client/src/sagas/WidgetCardsPaneSagas.tsx +++ b/app/client/src/sagas/WidgetCardsPaneSagas.tsx @@ -1,12 +1,14 @@ import CanvasWidgetsNormalizer from "../normalizers/CanvasWidgetsNormalizer" import { ActionTypes, ReduxAction } from "../constants/ActionConstants" import WidgetCardsPaneApi, { WidgetCardsPaneResponse, WidgetCardsPaneRequest } from "../api/WidgetCardsPaneApi" +import { successFetchingWidgetCards } from "../actions/widgetCardsPaneActions" import { call, put, takeLeading, all, takeEvery, takeLatest } from "redux-saga/effects" + export function* fetchWidgetCards(widgetCardsRequestAction: ReduxAction) { try { const widgetCards: WidgetCardsPaneResponse = yield call(WidgetCardsPaneApi.fetchWidgetCards) - yield put({ type: ActionTypes.SUCCESS_FETCHING_WIDGET_CARDS, widgetCards}) + yield put(successFetchingWidgetCards(widgetCards.cards)) } catch(err) { yield put({ type: ActionTypes.ERROR_FETCHING_WIDGET_CARDS, err}) } diff --git a/app/client/src/utils/WidgetRegistry.tsx b/app/client/src/utils/WidgetRegistry.tsx index 06049d5fc2..c1962705c9 100644 --- a/app/client/src/utils/WidgetRegistry.tsx +++ b/app/client/src/utils/WidgetRegistry.tsx @@ -99,6 +99,7 @@ class WidgetBuilderRegistry { return } }) + } } diff --git a/app/client/src/widgets/BaseWidget.tsx b/app/client/src/widgets/BaseWidget.tsx index c49706ce89..a1e9912ede 100644 --- a/app/client/src/widgets/BaseWidget.tsx +++ b/app/client/src/widgets/BaseWidget.tsx @@ -71,7 +71,7 @@ abstract class BaseWidget< widgetState.height !== this.state.height || widgetState.width !== this.state.width ) { - console.log("*** " + this.props.widgetId + " " + JSON.stringify(widgetState)) + // console.log("*** " + this.props.widgetId + " " + JSON.stringify(widgetState)) this.setState(widgetState) } } @@ -143,6 +143,11 @@ export interface IWidgetState { width: number } +export interface DraggableWidget { + type: string, + widget: IWidgetProps +} + export interface IWidgetBuilder { buildWidget(data: T): JSX.Element } diff --git a/app/client/src/widgets/ButtonWidget.tsx b/app/client/src/widgets/ButtonWidget.tsx index a337f7f8d6..25cfea76b7 100644 --- a/app/client/src/widgets/ButtonWidget.tsx +++ b/app/client/src/widgets/ButtonWidget.tsx @@ -16,7 +16,7 @@ class ButtonWidget extends BaseWidget { style={this.getPositionStyle()} widgetId={this.props.widgetId} key={this.props.widgetId} - text={this.props.text} + text={this.props.text || "Button"} /> ) } diff --git a/app/client/tsconfig.json b/app/client/tsconfig.json index 0980b23fa1..ffaa0bf153 100644 --- a/app/client/tsconfig.json +++ b/app/client/tsconfig.json @@ -4,7 +4,10 @@ "lib": [ "dom", "dom.iterable", - "esnext" + "esnext", + // "es5", + // "es2015.collection", + // "es2015.iterable" ], "allowJs": true, "skipLibCheck": true, @@ -17,7 +20,9 @@ "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, - "jsx": "preserve" + "jsx": "preserve", + // "downlevelIteration": true, + // "importHelpers": true }, "include": [ "src" diff --git a/app/client/yarn.lock b/app/client/yarn.lock index d950239b51..d6ada73f62 100755 --- a/app/client/yarn.lock +++ b/app/client/yarn.lock @@ -914,6 +914,11 @@ "@svgr/core" "^2.4.1" loader-utils "^1.1.0" +"@types/asap@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@types/asap/-/asap-2.0.0.tgz#d529e9608c83499a62ae08c871c5e62271aa2963" + integrity sha512-upIS0Gt9Mc8eEpCbYMZ1K8rhNosfKUtimNcINce+zLwJF5UpM3Vv7yz3S5l/1IX+DxTa8lTkUjqynvjRXyJzsg== + "@types/axios@^0.14.0": version "0.14.0" resolved "https://registry.yarnpkg.com/@types/axios/-/axios-0.14.0.tgz#ec2300fbe7d7dddd7eb9d3abf87999964cafce46" @@ -934,6 +939,19 @@ dependencies: "@types/react" "*" +"@types/hoist-non-react-statics@^3.3.1": + version "3.3.1" + resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz#1124aafe5118cb591977aeb1ceaaed1070eb039f" + integrity sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA== + dependencies: + "@types/react" "*" + hoist-non-react-statics "^3.3.0" + +"@types/invariant@^2.2.30": + version "2.2.30" + resolved "https://registry.yarnpkg.com/@types/invariant/-/invariant-2.2.30.tgz#20efa342807606ada5483731a8137cb1561e5fe9" + integrity sha512-98fB+yo7imSD2F7PF7GIpELNgtLNgo5wjivu0W5V4jx+KVVJxo6p/qN4zdzSTBWy4/sN3pPyXwnhRSD28QX+ag== + "@types/jest@^23.3.13": version "23.3.14" resolved "https://registry.yarnpkg.com/@types/jest/-/jest-23.3.14.tgz#37daaf78069e7948520474c87b80092ea912520a" @@ -1003,6 +1021,11 @@ "@types/prop-types" "*" csstype "^2.2.0" +"@types/shallowequal@^1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@types/shallowequal/-/shallowequal-1.1.1.tgz#aad262bb3f2b1257d94c71d545268d592575c9b1" + integrity sha512-Lhni3aX80zbpdxRuWhnuYPm8j8UQaa571lHP/xI4W+7BAFhSIhRReXnqjEgT/XzPoXZTJkCqstFMJ8CZTK6IlQ== + "@types/styled-components@^4.1.8": version "4.1.12" resolved "https://registry.yarnpkg.com/@types/styled-components/-/styled-components-4.1.12.tgz#62c19bd1aa37b1a904d36c179a0fec66c24ac99c" @@ -2953,20 +2976,13 @@ dir-glob@^2.0.0: arrify "^1.0.1" path-type "^3.0.0" -dnd-core@^7.4.0: - version "7.4.0" - resolved "https://registry.yarnpkg.com/dnd-core/-/dnd-core-7.4.0.tgz#ff12742de12422b396bc79d10b2644a34dabc846" - dependencies: - asap "^2.0.6" - invariant "^2.2.4" - lodash "^4.17.11" - redux "^4.0.1" - -dnd-core@^7.7.0: - version "7.7.0" - resolved "https://registry.yarnpkg.com/dnd-core/-/dnd-core-7.7.0.tgz#3166aefc8c5b85ca4ade4ae836712a3108975fab" - integrity sha512-+YqwflWEY1MEAEl2QiEiRaglYkCwIZryyQwximQGuTOm/ns7fS6Lg/i7OCkrtjM10D5FhArf/VUHIL4ZaRBK0g== +dnd-core@^9.3.4: + version "9.3.4" + resolved "https://registry.yarnpkg.com/dnd-core/-/dnd-core-9.3.4.tgz#56b5fdc165aa7d102506d3d5a08ec1fa789e0775" + integrity sha512-sDzBiGXgpj9bQhs8gtPWFIKMg4WY8ywI9RI81rRAUWI4oNj/Sm/ztjS67UjCvMa+fWoQ2WNIV3U9oDqeBN0+2g== dependencies: + "@types/asap" "^2.0.0" + "@types/invariant" "^2.2.30" asap "^2.0.6" invariant "^2.2.4" redux "^4.0.1" @@ -4615,7 +4631,7 @@ internal-ip@^3.0.1: default-gateway "^2.6.0" ipaddr.js "^1.5.2" -invariant@^2.1.0, invariant@^2.2.1, invariant@^2.2.2, invariant@^2.2.4: +invariant@^2.2.1, invariant@^2.2.2, invariant@^2.2.4: version "2.2.4" resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" dependencies: @@ -7704,21 +7720,22 @@ react-dev-utils@^7.0.1: strip-ansi "4.0.0" text-table "0.2.0" -react-dnd-html5-backend@^7.4.0: - version "7.7.0" - resolved "https://registry.yarnpkg.com/react-dnd-html5-backend/-/react-dnd-html5-backend-7.7.0.tgz#e2b8ea61a1bc34ba743b89cafcda6213b006c06a" - integrity sha512-JgKmWOxqorFyfGPeWV+SAPhVGFe6+LrOR24jETE9rrYZU5hCppzwK9ujjS508kWibeDvbfEgi9j5qC2wB1/MoQ== +react-dnd-html5-backend@^9.3.4: + version "9.3.4" + resolved "https://registry.yarnpkg.com/react-dnd-html5-backend/-/react-dnd-html5-backend-9.3.4.tgz#5d1f5ac608206d7b294b7407b9e1a336589eedd7" + integrity sha512-s+Xu0j7fHV9bLMSaOCuX76baQKcZfycAx0EzDmkxcFXPBiiFlI8l6rzwURdSJCjNcvLYXd8MLb4VkSNSq5ISZQ== dependencies: - dnd-core "^7.7.0" + dnd-core "^9.3.4" -react-dnd@^7.4.3: - version "7.4.3" - resolved "https://registry.yarnpkg.com/react-dnd/-/react-dnd-7.4.3.tgz#797d29ea2791e828eec96ac1603fcdee010ee2b7" +react-dnd@^9.3.4: + version "9.3.4" + resolved "https://registry.yarnpkg.com/react-dnd/-/react-dnd-9.3.4.tgz#ebab4b5b430b72f3580c058a29298054e1f9d2b8" + integrity sha512-UUtyoHFRrryMxVMEGYa3EdZIdibnys/ax7ZRs6CKpETHlnJQOFhHE3rpI+ManvKS0o3MFc1DZ+aoudAFtrOvFA== dependencies: - dnd-core "^7.4.0" + "@types/hoist-non-react-statics" "^3.3.1" + "@types/shallowequal" "^1.1.1" + dnd-core "^9.3.4" hoist-non-react-statics "^3.3.0" - invariant "^2.1.0" - lodash "^4.17.11" shallowequal "^1.1.0" react-dom@^16.7.0: