diff --git a/app/client/src/actions/widgetCardsPaneActions.tsx b/app/client/src/actions/widgetSidebarActions.tsx similarity index 100% rename from app/client/src/actions/widgetCardsPaneActions.tsx rename to app/client/src/actions/widgetSidebarActions.tsx diff --git a/app/client/src/api/WidgetCardsPaneApi.tsx b/app/client/src/api/WidgetCardsPaneApi.tsx deleted file mode 100644 index babcacdff1..0000000000 --- a/app/client/src/api/WidgetCardsPaneApi.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import Api from "./Api"; -import { WidgetCardProps } from "../widgets/BaseWidget"; - -export interface WidgetCardsPaneResponse { - cards: { [id: string]: WidgetCardProps[] }; -} -// export interface WidgetCardsPaneRequest {} - -class WidgetCardsPaneApi extends Api { - static url = "/widgetCards"; - static fetchWidgetCards(): Promise { - return Api.get(WidgetCardsPaneApi.url); - } -} - -export default WidgetCardsPaneApi; diff --git a/app/client/src/api/WidgetSidebarApi.tsx b/app/client/src/api/WidgetSidebarApi.tsx new file mode 100644 index 0000000000..45f972f51a --- /dev/null +++ b/app/client/src/api/WidgetSidebarApi.tsx @@ -0,0 +1,16 @@ +import Api from "./Api"; +import { WidgetCardProps } from "../widgets/BaseWidget"; + +export interface WidgetSidebarResponse { + cards: { [id: string]: WidgetCardProps[] }; +} +// export interface WidgetCardsPaneRequest {} + +class WidgetSidebarApi extends Api { + static url = "/widgetCards"; + static fetchWidgetCards(): Promise { + return Api.get(WidgetSidebarApi.url); + } +} + +export default WidgetSidebarApi; diff --git a/app/client/src/assets/icons/menu/api.svg b/app/client/src/assets/icons/menu/api.svg new file mode 100644 index 0000000000..78dcf2a32f --- /dev/null +++ b/app/client/src/assets/icons/menu/api.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/app/client/src/assets/icons/menu/widgets.svg b/app/client/src/assets/icons/menu/widgets.svg new file mode 100644 index 0000000000..a48d556b21 --- /dev/null +++ b/app/client/src/assets/icons/menu/widgets.svg @@ -0,0 +1,4 @@ + + + + diff --git a/app/client/src/constants/Colors.tsx b/app/client/src/constants/Colors.tsx index 60fd21b8c1..deb0f5aafe 100644 --- a/app/client/src/constants/Colors.tsx +++ b/app/client/src/constants/Colors.tsx @@ -8,10 +8,12 @@ export const Colors: Record = { BLACK: "#000000", BLACK_PEARL: "#040627", SHARK: "#21282C", - OUTER_SPACE: "#272E32", + DEEP_SPACE: "#272E32", + OUTER_SPACE: "#363E44", SLATE_GRAY: "#768896", PORCELAIN: "#EBEEF0", HIT_GRAY: "#A1ACB3", + JUNGLE_MIST: "#BCCCD9", GREEN: "#29CCA3", RED: "#CE4257", diff --git a/app/client/src/constants/DefaultTheme.tsx b/app/client/src/constants/DefaultTheme.tsx index ab83878fd1..235f802393 100644 --- a/app/client/src/constants/DefaultTheme.tsx +++ b/app/client/src/constants/DefaultTheme.tsx @@ -27,6 +27,8 @@ export type Theme = { lineHeights: Array; fonts: Array; borders: ThemeBorder[]; + headerHeight: string; + sidebarWidth: string; }; export const getColorWithOpacity = (color: Color, opacity: number) => { @@ -60,8 +62,11 @@ export const theme: Theme = { border: Colors.GEYSER, paneCard: Colors.SHARK, paneBG: Colors.OUTER_SPACE, + navBG: Colors.DEEP_SPACE, grid: Colors.GEYSER, containerBorder: Colors.FRENCH_PASS, + menuButtonBGInactive: Colors.JUNGLE_MIST, + menuIconColorInactive: Colors.OXFORD_BLUE, }, lineHeights: [0, 14, 18, 22, 24, 28, 36, 48, 64, 80], fonts: [FontFamilies.DMSans, FontFamilies.AppsmithWidget], @@ -77,6 +82,8 @@ export const theme: Theme = { color: Colors.FRENCH_PASS, }, ], + sidebarWidth: "350px", + headerHeight: "50px", }; export { css, createGlobalStyle, keyframes, ThemeProvider }; diff --git a/app/client/src/constants/ReduxActionConstants.tsx b/app/client/src/constants/ReduxActionConstants.tsx index a40adcf4e7..85a7c744b2 100644 --- a/app/client/src/constants/ReduxActionConstants.tsx +++ b/app/client/src/constants/ReduxActionConstants.tsx @@ -78,11 +78,11 @@ export interface ShowPropertyPanePayload { // export interface LoadQueryResponsePayload extends ExecuteActionResponse {} -export interface LoadWidgetPanePayload { +export interface LoadWidgetEditorPayload { widgets: WidgetProps[]; } -export interface LoadWidgetCardsPanePayload { +export interface LoadWidgetSidebarPayload { cards: { [id: string]: WidgetCardProps[] }; } diff --git a/app/client/src/constants/routes.ts b/app/client/src/constants/routes.ts new file mode 100644 index 0000000000..0fb09fd601 --- /dev/null +++ b/app/client/src/constants/routes.ts @@ -0,0 +1,4 @@ +export const BASE_URL = "/"; +export const LOGIN_URL = "/login"; +export const BUILDER_URL = "/builder"; +export const API_EDITOR_URL = `${BUILDER_URL}/api`; diff --git a/app/client/src/editorComponents/DraggableComponent.tsx b/app/client/src/editorComponents/DraggableComponent.tsx index faf99c3db9..444fcb985d 100644 --- a/app/client/src/editorComponents/DraggableComponent.tsx +++ b/app/client/src/editorComponents/DraggableComponent.tsx @@ -5,7 +5,7 @@ import { useDrag, DragPreviewImage, DragSourceMonitor } from "react-dnd"; import blankImage from "../assets/images/blank.png"; import { ContainerProps } from "./ContainerComponent"; import { FocusContext } from "../pages/Editor/Canvas"; -import { WidgetFunctionsContext } from "../pages/Editor"; +import { WidgetFunctionsContext } from "../pages/Editor/WidgetsEditor"; import { ControlIcons } from "../icons/ControlIcons"; import { theme } from "../constants/DefaultTheme"; import { ResizingContext } from "./DropTargetComponent"; diff --git a/app/client/src/editorComponents/DropTargetComponent.tsx b/app/client/src/editorComponents/DropTargetComponent.tsx index 7702a5d1a6..668b62e460 100644 --- a/app/client/src/editorComponents/DropTargetComponent.tsx +++ b/app/client/src/editorComponents/DropTargetComponent.tsx @@ -7,7 +7,7 @@ import { ContainerProps } from "./ContainerComponent"; import WidgetFactory from "../utils/WidgetFactory"; import { widgetOperationParams, noCollision } from "../utils/WidgetPropsUtils"; import DragLayerComponent from "./DragLayerComponent"; -import { WidgetFunctionsContext } from "../pages/Editor"; +import { WidgetFunctionsContext } from "../pages/Editor/WidgetsEditor"; import { FocusContext } from "../pages/Editor/Canvas"; type DropTargetComponentProps = ContainerProps & { diff --git a/app/client/src/editorComponents/NavBar.tsx b/app/client/src/editorComponents/NavBar.tsx new file mode 100644 index 0000000000..2a77bbec4c --- /dev/null +++ b/app/client/src/editorComponents/NavBar.tsx @@ -0,0 +1,51 @@ +import React from "react"; +import styled from "styled-components"; +import NavBarItem from "./NavBarItem"; +import { MenuIcons } from "../icons/MenuIcons"; +import { BUILDER_URL, API_EDITOR_URL } from "../constants/routes"; +import Sidebar from "./Sidebar"; + +const Container = styled.div` + height: calc(100vh - ${props => props.theme.headerHeight}); + display: flex; + box-shadow: 0px 1px 3px ${props => props.theme.colors.paneBG}; + z-index: 30; + width: ${props => props.theme.sidebarWidth}; +`; + +const NavBarContainer = styled.div` + flex: 2; + background-color: ${props => props.theme.colors.navBG}; + color: ${props => props.theme.colors.textOnDarkBG}; + padding: ${props => props.theme.spaces[3]}px 0px; +`; + +const ROUTES = [ + { + icon: MenuIcons.WIDGETS_ICON, + path: BUILDER_URL, + title: "Widgets", + }, + { + icon: MenuIcons.APIS_ICON, + path: API_EDITOR_URL, + title: "APIs", + }, +]; + +class NavBar extends React.Component { + render(): React.ReactNode { + return ( + + + {ROUTES.map(config => ( + + ))} + + + + ); + } +} + +export default NavBar; diff --git a/app/client/src/editorComponents/NavBarItem.tsx b/app/client/src/editorComponents/NavBarItem.tsx new file mode 100644 index 0000000000..026dd13c2a --- /dev/null +++ b/app/client/src/editorComponents/NavBarItem.tsx @@ -0,0 +1,72 @@ +import React from "react"; +import styled from "styled-components"; +import { withRouter, RouteComponentProps } from "react-router"; + +type MenuBarItemProps = { + icon: Function; + path: string; + title: string; +}; + +type Props = MenuBarItemProps & RouteComponentProps; + +type ActiveProps = { + active: boolean; +}; + +const ItemContainer = styled.div` + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + height: 60px; + font-size: ${props => props.theme.fontSizes[1]}px; + cursor: pointer; + margin: ${props => props.theme.spaces[3]}px 0; + background-color: ${props => + props.active ? props.theme.colors.paneBG : props.theme.colors.navBG} + &:hover { + background-color: ${props => props.theme.colors.paneBG}; + } +`; + +const IconContainer = styled.div` + display: flex; + align-items: center; + justify-content: center; + padding: 5px; + margin-bottom: 2px; + background-color: ${props => + props.active + ? props.theme.colors.primary + : props.theme.colors.menuButtonBGInactive}; + border-radius: ${props => props.theme.radii[1]}px; + height: 32px; + width: 32px; + svg path { + fill: ${props => + props.active + ? props.theme.colors.textOnDarkBG + : props.theme.colors.menuIconColorInactive}; + } +`; + +class NavBarItem extends React.Component { + handleItemClick = () => { + const { history, path } = this.props; + history.push(path); + }; + + render(): React.ReactNode { + const { title, icon, location, path } = this.props; + const isActive = location.pathname === path; + return ( + + {icon()} + {title} + + ); + } +} + +export default withRouter(NavBarItem); diff --git a/app/client/src/editorComponents/ResizableComponent.tsx b/app/client/src/editorComponents/ResizableComponent.tsx index bf46d2dd3a..e6a206be20 100644 --- a/app/client/src/editorComponents/ResizableComponent.tsx +++ b/app/client/src/editorComponents/ResizableComponent.tsx @@ -8,7 +8,7 @@ import { ContainerProps, ParentBoundsContext } from "./ContainerComponent"; import { isDropZoneOccupied } from "../utils/WidgetPropsUtils"; import { FocusContext } from "../pages/Editor/Canvas"; import { DraggingContext } from "./DraggableComponent"; -import { WidgetFunctionsContext } from "../pages/Editor"; +import { WidgetFunctionsContext } from "../pages/Editor/WidgetsEditor"; import { ResizingContext } from "./DropTargetComponent"; import { theme, diff --git a/app/client/src/editorComponents/Sidebar.tsx b/app/client/src/editorComponents/Sidebar.tsx new file mode 100644 index 0000000000..3e83f8cd4e --- /dev/null +++ b/app/client/src/editorComponents/Sidebar.tsx @@ -0,0 +1,30 @@ +import React from "react"; +import { Switch, Route } from "react-router"; +import styled from "styled-components"; +import { API_EDITOR_URL, BUILDER_URL } from "../constants/routes"; +import WidgetSidebar from "../pages/Editor/WidgetSidebar"; +import ApiSidebar from "../pages/Editor/ApiSidebar"; + +const SidebarWrapper = styled.div` + flex: 7; + background-color: ${props => props.theme.colors.paneBG}; + padding: 5px 10px; + color: ${props => props.theme.colors.textOnDarkBG}; +`; + +class Sidebar extends React.Component { + render() { + return ( + + + + + + + + + ); + } +} + +export default Sidebar; diff --git a/app/client/src/icons/MenuIcons.tsx b/app/client/src/icons/MenuIcons.tsx new file mode 100644 index 0000000000..71dc787e7a --- /dev/null +++ b/app/client/src/icons/MenuIcons.tsx @@ -0,0 +1,21 @@ +import React from "react"; +import { IconProps, IconWrapper } from "../constants/IconConstants"; +import { ReactComponent as WidgetsIcon } from "../assets/icons/menu/widgets.svg"; +import { ReactComponent as ApisIcon } from "../assets/icons/menu/api.svg"; + +/* eslint-disable react/display-name */ + +export const MenuIcons: { + [id: string]: Function; +} = { + WIDGETS_ICON: (props: IconProps) => ( + + + + ), + APIS_ICON: (props: IconProps) => ( + + + + ), +}; diff --git a/app/client/src/index.tsx b/app/client/src/index.tsx index 027636dc5f..7a15fe1647 100755 --- a/app/client/src/index.tsx +++ b/app/client/src/index.tsx @@ -19,6 +19,7 @@ import HTML5Backend from "react-dnd-html5-backend"; import { appInitializer } from "./utils/AppsmithUtils"; import ProtectedRoute from "./pages/common/ProtectedRoute"; import { composeWithDevTools } from "redux-devtools-extension/logOnlyInProduction"; +import { BASE_URL, BUILDER_URL, LOGIN_URL } from "./constants/routes"; appInitializer(); const sagaMiddleware = createSagaMiddleware(); @@ -33,9 +34,9 @@ ReactDOM.render( - - - + + + diff --git a/app/client/src/mockResponses/WidgetCardsPaneResponse.tsx b/app/client/src/mockResponses/WidgetSidebarResponse.tsx similarity index 96% rename from app/client/src/mockResponses/WidgetCardsPaneResponse.tsx rename to app/client/src/mockResponses/WidgetSidebarResponse.tsx index eb7239fdad..7eefbbfb0c 100644 --- a/app/client/src/mockResponses/WidgetCardsPaneResponse.tsx +++ b/app/client/src/mockResponses/WidgetSidebarResponse.tsx @@ -1,7 +1,7 @@ import { WidgetCardProps } from "../widgets/BaseWidget"; import { generateReactKey } from "../utils/generators"; -const WidgetCardsPaneResponse: { +const WidgetSidebarResponse: { [id: string]: WidgetCardProps[]; } = { common: [ @@ -96,4 +96,4 @@ const WidgetCardsPaneResponse: { ], }; -export default WidgetCardsPaneResponse; +export default WidgetSidebarResponse; diff --git a/app/client/src/pages/Editor/ApiEditor.tsx b/app/client/src/pages/Editor/ApiEditor.tsx new file mode 100644 index 0000000000..52530818b9 --- /dev/null +++ b/app/client/src/pages/Editor/ApiEditor.tsx @@ -0,0 +1,9 @@ +import React from "react"; + +class ApiEditor extends React.Component { + render() { + return API editor; + } +} + +export default ApiEditor; diff --git a/app/client/src/pages/Editor/ApiSidebar.tsx b/app/client/src/pages/Editor/ApiSidebar.tsx new file mode 100644 index 0000000000..c1405d5071 --- /dev/null +++ b/app/client/src/pages/Editor/ApiSidebar.tsx @@ -0,0 +1,9 @@ +import React from "react"; + +class ApiSidebar extends React.Component { + render() { + return

Apis

; + } +} + +export default ApiSidebar; diff --git a/app/client/src/pages/Editor/EditorHeader.tsx b/app/client/src/pages/Editor/EditorHeader.tsx index bb3a96a1a5..b29af5a434 100644 --- a/app/client/src/pages/Editor/EditorHeader.tsx +++ b/app/client/src/pages/Editor/EditorHeader.tsx @@ -6,7 +6,7 @@ const Header = styled.header` display: flex; justify-content: space-around; align-items: center; - height: 50px; + height: ${props => props.theme.headerHeight}; padding: 0px 30px; box-shadow: 0px 0px 3px #ccc; background: #fff; diff --git a/app/client/src/pages/Editor/WidgetCardsPane.tsx b/app/client/src/pages/Editor/WidgetCardsPane.tsx deleted file mode 100644 index 5c4a048979..0000000000 --- a/app/client/src/pages/Editor/WidgetCardsPane.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import React from "react"; -import WidgetCard from "./WidgetCard"; -import styled from "styled-components"; -import { WidgetCardProps } from "../../widgets/BaseWidget"; - -type WidgetCardPaneProps = { - cards: { [id: string]: WidgetCardProps[] }; -}; - -const CardsPaneWrapper = 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; -`; - -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: React.SFC = ( - props: WidgetCardPaneProps, -) => { - const groups = Object.keys(props.cards); - return ( - - {groups.map((group: string) => ( - -
{group}
- - {props.cards[group].map((card: WidgetCardProps) => ( - - ))} - -
- ))} -
- ); -}; - -export default WidgetCardsPane; diff --git a/app/client/src/pages/Editor/WidgetSidebar.tsx b/app/client/src/pages/Editor/WidgetSidebar.tsx new file mode 100644 index 0000000000..edc4693399 --- /dev/null +++ b/app/client/src/pages/Editor/WidgetSidebar.tsx @@ -0,0 +1,60 @@ +import React from "react"; +import { connect } from "react-redux"; +import WidgetCard from "./WidgetCard"; +import styled from "styled-components"; +import { WidgetCardProps } from "../../widgets/BaseWidget"; +import { AppState } from "../../reducers"; +import { WidgetSidebarReduxState } from "../../reducers/uiReducers/widgetSidebarReducer"; + +type WidgetSidebarProps = { + cards: { [id: string]: WidgetCardProps[] }; +}; + +const MainWrapper = styled.div` + text-transform: capitalize; +`; + +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 WidgetSidebar: React.FC = ( + props: WidgetSidebarProps, +) => { + const groups = Object.keys(props.cards); + return ( + + {groups.map((group: string) => ( + +
{group}
+ + {props.cards[group].map((card: WidgetCardProps) => ( + + ))} + +
+ ))} +
+ ); +}; + +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 }; + }, +)(WidgetSidebar); diff --git a/app/client/src/pages/Editor/WidgetsEditor.tsx b/app/client/src/pages/Editor/WidgetsEditor.tsx new file mode 100644 index 0000000000..b51864794b --- /dev/null +++ b/app/client/src/pages/Editor/WidgetsEditor.tsx @@ -0,0 +1,131 @@ +import React, { Context, createContext } from "react"; +import { connect } from "react-redux"; +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, + WidgetProps, +} from "../../widgets/BaseWidget"; +import { ActionPayload } from "../../constants/ActionConstants"; +import { executeAction } from "../../actions/widgetActions"; +import { fetchPage, savePage, updateWidget } from "../../actions/pageActions"; +import { RenderModes } from "../../constants/WidgetConstants"; +import { ContainerWidgetProps } from "../../widgets/ContainerWidget"; + +const CanvasContainer = styled.section` + height: 100%; + width: 100%; + position: relative; + overflow-x: auto; + overflow-y: auto; + margin: 10px 0; + &:before { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + pointer-events: none; + } +`; + +const EditorWrapper = styled.div` + display: flex; + flex-direction: row; + align-items: stretch; + justify-content: flex-start; + overflow: hidden; + height: calc(100vh - ${props => props.theme.headerHeight}); +`; + +type EditorProps = { + dsl: ContainerWidgetProps | any; + fetchCanvasWidgets: Function; + executeAction: (actionPayloads?: ActionPayload[]) => void; + updateWidget: Function; + savePageLayout: Function; + currentPageName: string; + currentPageId: string; + currentLayoutId: string; + isSaving: boolean; +}; + +export const WidgetFunctionsContext: Context = createContext( + {}, +); + +class WidgetsEditor extends React.Component { + componentDidMount() { + this.props.fetchCanvasWidgets(this.props.currentPageId); + } + + render(): React.ReactNode { + return ( + + + + {this.props.dsl && } + + + + + ); + } +} + +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, + ); + + 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, + }; +}; + +const mapDispatchToProps = (dispatch: any) => { + return { + executeAction: (actionPayloads?: ActionPayload[]) => + dispatch(executeAction(actionPayloads)), + fetchCanvasWidgets: (pageId: string) => + dispatch(fetchPage(pageId, RenderModes.CANVAS)), + updateWidget: ( + operation: WidgetOperation, + widgetId: string, + payload: any, + ) => dispatch(updateWidget(operation, widgetId, payload)), + savePageLayout: ( + pageId: string, + layoutId: string, + dsl: ContainerWidgetProps, + ) => dispatch(savePage(pageId, layoutId, dsl)), + }; +}; + +export default connect( + mapStateToProps, + mapDispatchToProps, +)(WidgetsEditor); diff --git a/app/client/src/pages/Editor/index.tsx b/app/client/src/pages/Editor/index.tsx index 02591f4716..2670ec01bf 100644 --- a/app/client/src/pages/Editor/index.tsx +++ b/app/client/src/pages/Editor/index.tsx @@ -1,152 +1,42 @@ -import React, { Component, Context, createContext } from "react"; +import React, { Component } from "react"; import { connect } from "react-redux"; import styled from "styled-components"; -import Canvas from "./Canvas"; -import { - WidgetCardProps, - WidgetProps, - WidgetOperation, - WidgetFunctions, -} from "../../widgets/BaseWidget"; import { AppState } from "../../reducers"; -import { EditorReduxState } from "../../reducers/uiReducers/editorReducer"; -import WidgetCardsPane from "./WidgetCardsPane"; import EditorHeader from "./EditorHeader"; -import CanvasWidgetsNormalizer from "../../normalizers/CanvasWidgetsNormalizer"; -import { ContainerWidgetProps } from "../../widgets/ContainerWidget"; -import { fetchPage, updateWidget, savePage } from "../../actions/pageActions"; -import { RenderModes } from "../../constants/WidgetConstants"; -import { executeAction } from "../../actions/widgetActions"; -import { ActionPayload } from "../../constants/ActionConstants"; -import PropertyPane from "./PropertyPane"; +import EditorsRouter from "./routes"; +import NavBar from "../../editorComponents/NavBar"; +import WidgetsEditor from "./WidgetsEditor"; -const CanvasContainer = styled.section` - height: 100%; - width: 100%; - position: relative; - overflow-x: auto; - overflow-y: auto; - margin: 0px 10px; - &:before { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - pointer-events: none; - } -`; - -const EditorWrapper = styled.div` +const MainContainer = styled.div` display: flex; - flex-direction: row; - align-items: stretch; - justify-content: flex-start; - width: 100vw; - overflow: hidden; - padding: 10px; - height: calc(100vh - 60px); `; type EditorProps = { - dsl: ContainerWidgetProps | any; - fetchCanvasWidgets: Function; - executeAction: (actionPayloads?: ActionPayload[]) => void; - updateWidget: Function; - cards: { [id: string]: WidgetCardProps[] } | any; - savePageLayout: Function; currentPageName: string; - currentPageId: string; - currentLayoutId: string; isSaving: boolean; }; -export const WidgetFunctionsContext: Context = createContext( - {}, -); - class Editor extends Component { - componentDidMount() { - this.props.fetchCanvasWidgets(this.props.currentPageId); - } - public render() { return ( - +
- - - - {this.props.dsl && } - - - - + + + + + +
); } } -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. +const mapStateToProps = (state: AppState): EditorProps => ({ + currentPageName: state.ui.editor.currentPageName, + isSaving: state.ui.editor.isSaving, +}); - // 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 cards = state.ui.editor.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, - 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, - }; -}; - -const mapDispatchToProps = (dispatch: any) => { - return { - executeAction: (actionPayloads?: ActionPayload[]) => - dispatch(executeAction(actionPayloads)), - fetchCanvasWidgets: (pageId: string) => - dispatch(fetchPage(pageId, RenderModes.CANVAS)), - updateWidget: ( - operation: WidgetOperation, - widgetId: string, - payload: any, - ) => dispatch(updateWidget(operation, widgetId, payload)), - savePageLayout: ( - pageId: string, - layoutId: string, - dsl: ContainerWidgetProps, - ) => dispatch(savePage(pageId, layoutId, dsl)), - }; -}; - -export default connect( - mapStateToProps, - mapDispatchToProps, -)(Editor); +export default connect(mapStateToProps)(Editor); diff --git a/app/client/src/pages/Editor/routes.tsx b/app/client/src/pages/Editor/routes.tsx new file mode 100644 index 0000000000..ca99e3e826 --- /dev/null +++ b/app/client/src/pages/Editor/routes.tsx @@ -0,0 +1,58 @@ +import React from "react"; +import { + Route, + Switch, + withRouter, + RouteComponentProps, +} from "react-router-dom"; +import ApiEditor from "./ApiEditor"; +import { API_EDITOR_URL, BUILDER_URL } from "../../constants/routes"; +import { Drawer, Position } from "@blueprintjs/core"; +import styled from "styled-components"; + +const MainWrapper = styled.div` + position: absolute; + width: calc(100vw - ${props => props.theme.sidebarWidth}); + height: calc(100vh - ${props => props.theme.headerHeight}); + left: ${props => props.theme.sidebarWidth}; +`; + +interface RouterState { + drawerOpen: boolean; +} + +class EditorsRouter extends React.Component { + constructor(props: RouteComponentProps) { + super(props); + this.state = { + drawerOpen: this.props.location.pathname !== BUILDER_URL, + }; + } + componentDidUpdate(prevProps: Readonly): void { + if (this.props.location.pathname !== prevProps.location.pathname) { + this.setState({ + drawerOpen: this.props.location.pathname !== BUILDER_URL, + }); + } + } + + render(): React.ReactNode { + const drawerOpen = this.props.location.pathname !== BUILDER_URL; + return ( + + + + + + + + ); + } +} + +export default withRouter(EditorsRouter); diff --git a/app/client/src/reducers/index.tsx b/app/client/src/reducers/index.tsx index 0b703388e1..1d66188377 100644 --- a/app/client/src/reducers/index.tsx +++ b/app/client/src/reducers/index.tsx @@ -10,6 +10,7 @@ import { ActionDataState } from "./entityReducers/actionsReducer"; import { PropertyPaneConfigState } from "./entityReducers/propertyPaneConfigReducer"; import { PropertyPaneReduxState } from "./uiReducers/propertyPaneReducer"; import { WidgetConfigReducerState } from "./entityReducers/widgetConfigReducer"; +import { WidgetSidebarReduxState } from "./uiReducers/widgetSidebarReducer"; const appReducer = combineReducers({ entities: entityReducer, @@ -20,6 +21,7 @@ export default appReducer; export interface AppState { ui: { + widgetSidebar: WidgetSidebarReduxState; editor: EditorReduxState; propertyPane: PropertyPaneReduxState; errors: ErrorReduxState; diff --git a/app/client/src/reducers/uiReducers/editorReducer.tsx b/app/client/src/reducers/uiReducers/editorReducer.tsx index 952d322032..07dc533d1a 100644 --- a/app/client/src/reducers/uiReducers/editorReducer.tsx +++ b/app/client/src/reducers/uiReducers/editorReducer.tsx @@ -1,29 +1,17 @@ import { createReducer } from "../../utils/AppsmithUtils"; import { getEditorConfigs } from "../../constants/ApiConstants"; -import { - ReduxActionTypes, - ReduxAction, - LoadWidgetCardsPanePayload, -} from "../../constants/ReduxActionConstants"; -import { WidgetCardProps, WidgetProps } from "../../widgets/BaseWidget"; +import { ReduxActionTypes } from "../../constants/ReduxActionConstants"; +import { WidgetProps } from "../../widgets/BaseWidget"; import { ContainerWidgetProps } from "../../widgets/ContainerWidget"; -import WidgetCardsPaneResponse from "../../mockResponses/WidgetCardsPaneResponse"; const editorConfigs = getEditorConfigs(); const initialState: EditorReduxState = { pageWidgetId: "0", ...editorConfigs, isSaving: false, - cards: WidgetCardsPaneResponse, }; const editorReducer = createReducer(initialState, { - [ReduxActionTypes.FETCH_WIDGET_CARDS_SUCCESS]: ( - state: EditorReduxState, - action: ReduxAction, - ) => { - return { ...state, ...action.payload }; - }, [ReduxActionTypes.SAVE_PAGE_INIT]: (state: EditorReduxState) => { return { ...state, isSaving: true }; }, @@ -34,9 +22,6 @@ const editorReducer = createReducer(initialState, { export interface EditorReduxState { dsl?: ContainerWidgetProps; - cards: { - [id: string]: WidgetCardProps[]; - }; pageWidgetId: string; currentPageId: string; currentLayoutId: string; diff --git a/app/client/src/reducers/uiReducers/index.tsx b/app/client/src/reducers/uiReducers/index.tsx index 73b606e0f0..66de037705 100644 --- a/app/client/src/reducers/uiReducers/index.tsx +++ b/app/client/src/reducers/uiReducers/index.tsx @@ -2,8 +2,10 @@ import { combineReducers } from "redux"; import editorReducer from "./editorReducer"; import errorReducer from "./errorReducer"; import propertyPaneReducer from "./propertyPaneReducer"; +import { widgetSidebarReducer } from "./widgetSidebarReducer"; const uiReducer = combineReducers({ + widgetSidebar: widgetSidebarReducer, editor: editorReducer, errors: errorReducer, propertyPane: propertyPaneReducer, diff --git a/app/client/src/reducers/uiReducers/widgetSidebarReducer.ts b/app/client/src/reducers/uiReducers/widgetSidebarReducer.ts new file mode 100644 index 0000000000..a35d83ef86 --- /dev/null +++ b/app/client/src/reducers/uiReducers/widgetSidebarReducer.ts @@ -0,0 +1,26 @@ +import { createReducer } from "../../utils/AppsmithUtils"; +import { WidgetCardProps } from "../../widgets/BaseWidget"; +import WidgetSidebarResponse from "../../mockResponses/WidgetSidebarResponse"; +import { + LoadWidgetSidebarPayload, + ReduxAction, + ReduxActionTypes, +} from "../../constants/ReduxActionConstants"; +import { EditorReduxState } from "./editorReducer"; + +export interface WidgetSidebarReduxState { + cards: { [id: string]: WidgetCardProps[] }; +} + +const initialState: WidgetSidebarReduxState = { + cards: WidgetSidebarResponse, +}; + +export const widgetSidebarReducer = createReducer(initialState, { + [ReduxActionTypes.FETCH_WIDGET_CARDS_SUCCESS]: ( + state: EditorReduxState, + action: ReduxAction, + ) => { + return { ...state, ...action.payload }; + }, +}); diff --git a/app/client/src/sagas/WidgetCardsPaneSagas.tsx b/app/client/src/sagas/WidgetSidebarSagas.tsx similarity index 64% rename from app/client/src/sagas/WidgetCardsPaneSagas.tsx rename to app/client/src/sagas/WidgetSidebarSagas.tsx index 4d4a1b7f42..131f0ddbac 100644 --- a/app/client/src/sagas/WidgetCardsPaneSagas.tsx +++ b/app/client/src/sagas/WidgetSidebarSagas.tsx @@ -2,16 +2,16 @@ import { ReduxActionTypes, ReduxActionErrorTypes, } from "../constants/ReduxActionConstants"; -import WidgetCardsPaneApi, { - WidgetCardsPaneResponse, -} from "../api/WidgetCardsPaneApi"; -import { successFetchingWidgetCards } from "../actions/widgetCardsPaneActions"; +import WidgetSidebarApi, { + WidgetSidebarResponse, +} from "../api/WidgetSidebarApi"; +import { successFetchingWidgetCards } from "../actions/widgetSidebarActions"; import { call, put, takeLatest, all } from "redux-saga/effects"; export function* fetchWidgetCards() { try { - const widgetCards: WidgetCardsPaneResponse = yield all([ - call(WidgetCardsPaneApi.fetchWidgetCards), + const widgetCards: WidgetSidebarResponse = yield all([ + call(WidgetSidebarApi.fetchWidgetCards), ]); yield put(successFetchingWidgetCards(widgetCards.cards)); } catch (err) { diff --git a/app/client/src/sagas/index.tsx b/app/client/src/sagas/index.tsx index 52593510e2..e20cb128ae 100644 --- a/app/client/src/sagas/index.tsx +++ b/app/client/src/sagas/index.tsx @@ -1,6 +1,6 @@ import { all, spawn } from "redux-saga/effects"; import pageSagas from "../sagas/PageSagas"; -import { fetchWidgetCardsSaga } from "./WidgetCardsPaneSagas"; +import { fetchWidgetCardsSaga } from "./WidgetSidebarSagas"; import { watchExecuteActionSaga } from "./ActionSagas"; import widgetOperationSagas from "./WidgetOperationSagas"; import errorSagas from "./ErrorSagas"; diff --git a/app/client/src/widgets/BaseWidget.tsx b/app/client/src/widgets/BaseWidget.tsx index 81cd952d9a..4efb6f47c3 100644 --- a/app/client/src/widgets/BaseWidget.tsx +++ b/app/client/src/widgets/BaseWidget.tsx @@ -15,7 +15,7 @@ import _ from "lodash"; import DraggableComponent from "../editorComponents/DraggableComponent"; import ResizableComponent from "../editorComponents/ResizableComponent"; import { ActionPayload } from "../constants/ActionConstants"; -import { WidgetFunctionsContext } from "../pages/Editor"; +import { WidgetFunctionsContext } from "../pages/Editor/WidgetsEditor"; abstract class BaseWidget< T extends WidgetProps,