Add Navbar and API Pane (#79)
This commit is contained in:
parent
c49aecc8a8
commit
7d56e10f8f
|
|
@ -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<WidgetCardsPaneResponse> {
|
||||
return Api.get(WidgetCardsPaneApi.url);
|
||||
}
|
||||
}
|
||||
|
||||
export default WidgetCardsPaneApi;
|
||||
16
app/client/src/api/WidgetSidebarApi.tsx
Normal file
16
app/client/src/api/WidgetSidebarApi.tsx
Normal file
|
|
@ -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<WidgetSidebarResponse> {
|
||||
return Api.get(WidgetSidebarApi.url);
|
||||
}
|
||||
}
|
||||
|
||||
export default WidgetSidebarApi;
|
||||
6
app/client/src/assets/icons/menu/api.svg
Normal file
6
app/client/src/assets/icons/menu/api.svg
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M14.1377 11.552H13.1407V12.6018H14.1378C14.4272 12.6019 14.6627 12.3663 14.6627 12.0769C14.6627 11.7874 14.4272 11.552 14.1377 11.552Z" fill="white"/>
|
||||
<path d="M9.06019 11.5521C8.64059 11.5521 8.29917 11.8934 8.29917 12.3131V12.6019H9.82121V12.3131C9.82121 11.8934 9.4798 11.5521 9.06019 11.5521Z" fill="white"/>
|
||||
<path d="M2 8.29199V17.1501C2 18.1701 2.82988 19 3.84992 19H21.1981C22.2181 19 23.048 18.1701 23.048 17.1501V8.29199H2ZM11.0545 15.0041C11.0545 15.3447 10.7784 15.6208 10.4378 15.6208C10.0973 15.6208 9.82119 15.3447 9.82119 15.0041V13.8352H8.29919V15.0041C8.29919 15.3447 8.0231 15.6208 7.68255 15.6208C7.342 15.6208 7.06591 15.3447 7.06591 15.0041V12.3131H7.06587C7.06587 11.2134 7.96054 10.3188 9.06017 10.3188C10.1598 10.3188 11.0545 11.2134 11.0545 12.3131V15.0041ZM14.1378 13.8351H13.1406V15.0041C13.1406 15.3446 12.8646 15.6207 12.524 15.6207C12.1835 15.6207 11.9074 15.3446 11.9074 15.0041V10.9353C11.9074 10.5948 12.1835 10.3187 12.524 10.3187H14.1378C15.1072 10.3187 15.896 11.1074 15.896 12.0769C15.896 13.0464 15.1072 13.8351 14.1378 13.8351ZM17.9821 15.004C17.9821 15.3446 17.7061 15.6207 17.3655 15.6207C17.025 15.6207 16.7489 15.3446 16.7489 15.004V10.9353C16.7489 10.5948 17.025 10.3187 17.3655 10.3187C17.7061 10.3187 17.9821 10.5948 17.9821 10.9353V15.004Z" fill="white"/>
|
||||
<path d="M21.1981 4H3.84992C2.82988 4 2 4.82988 2 5.84992V7.05875H23.048V5.84992C23.048 4.82988 22.2181 4 21.1981 4Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
4
app/client/src/assets/icons/menu/widgets.svg
Normal file
4
app/client/src/assets/icons/menu/widgets.svg
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M3.52917 3C3.23692 3 3 3.23692 3 3.52917V10.4708C3 10.7631 3.23692 11 3.52917 11H10.4708C10.7631 11 11 10.7631 11 10.4708V3.52917C11 3.23692 10.7631 3 10.4708 3H3.52917ZM3.52917 13C3.23692 13 3 13.2369 3 13.5292V20.4708C3 20.7631 3.23692 21 3.52917 21H10.4708C10.7631 21 11 20.7631 11 20.4708V13.5292C11 13.2369 10.7631 13 10.4708 13H3.52917ZM17 13C15.9391 13 14.9217 13.4214 14.1716 14.1716C13.4214 14.9217 13 15.9391 13 17C13 18.0609 13.4214 19.0783 14.1716 19.8284C14.9217 20.5786 15.9391 21 17 21C18.0609 21 19.0783 20.5786 19.8284 19.8284C20.5786 19.0783 21 18.0609 21 17C21 15.9391 20.5786 14.9217 19.8284 14.1716C19.0783 13.4214 18.0609 13 17 13Z" fill="#2E3D49"/>
|
||||
<path d="M13.1254 9.51436L16.4258 3.57348C16.8068 2.88767 17.7932 2.88767 18.1742 3.57348L21.4746 9.51436C21.8449 10.1809 21.363 11 20.6005 11H13.9995C13.237 11 12.7551 10.1809 13.1254 9.51436Z" fill="#2E3D49"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 996 B |
|
|
@ -8,10 +8,12 @@ export const Colors: Record<string, string> = {
|
|||
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",
|
||||
|
|
|
|||
|
|
@ -27,6 +27,8 @@ export type Theme = {
|
|||
lineHeights: Array<number>;
|
||||
fonts: Array<FontFamily>;
|
||||
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 };
|
||||
|
|
|
|||
|
|
@ -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[] };
|
||||
}
|
||||
|
||||
|
|
|
|||
4
app/client/src/constants/routes.ts
Normal file
4
app/client/src/constants/routes.ts
Normal file
|
|
@ -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`;
|
||||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -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 & {
|
||||
|
|
|
|||
51
app/client/src/editorComponents/NavBar.tsx
Normal file
51
app/client/src/editorComponents/NavBar.tsx
Normal file
|
|
@ -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 (
|
||||
<Container>
|
||||
<NavBarContainer>
|
||||
{ROUTES.map(config => (
|
||||
<NavBarItem key={config.path} {...config} />
|
||||
))}
|
||||
</NavBarContainer>
|
||||
<Sidebar />
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default NavBar;
|
||||
72
app/client/src/editorComponents/NavBarItem.tsx
Normal file
72
app/client/src/editorComponents/NavBarItem.tsx
Normal file
|
|
@ -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<ActiveProps>`
|
||||
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<ActiveProps>`
|
||||
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<Props> {
|
||||
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 (
|
||||
<ItemContainer active={isActive} onClick={this.handleItemClick}>
|
||||
<IconContainer active={isActive}>{icon()}</IconContainer>
|
||||
{title}
|
||||
</ItemContainer>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withRouter(NavBarItem);
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
30
app/client/src/editorComponents/Sidebar.tsx
Normal file
30
app/client/src/editorComponents/Sidebar.tsx
Normal file
|
|
@ -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 (
|
||||
<React.Fragment>
|
||||
<SidebarWrapper>
|
||||
<Switch>
|
||||
<Route exact path={BUILDER_URL} component={WidgetSidebar} />
|
||||
<Route exact path={API_EDITOR_URL} component={ApiSidebar} />
|
||||
</Switch>
|
||||
</SidebarWrapper>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Sidebar;
|
||||
21
app/client/src/icons/MenuIcons.tsx
Normal file
21
app/client/src/icons/MenuIcons.tsx
Normal file
|
|
@ -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) => (
|
||||
<IconWrapper {...props}>
|
||||
<WidgetsIcon />
|
||||
</IconWrapper>
|
||||
),
|
||||
APIS_ICON: (props: IconProps) => (
|
||||
<IconWrapper {...props}>
|
||||
<ApisIcon />
|
||||
</IconWrapper>
|
||||
),
|
||||
};
|
||||
|
|
@ -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(
|
|||
<ThemeProvider theme={theme}>
|
||||
<BrowserRouter>
|
||||
<Switch>
|
||||
<Route exact path="/" component={App} />
|
||||
<ProtectedRoute path="/builder" component={Editor} />
|
||||
<Route exact path="/login" component={LoginPage} />
|
||||
<Route exact path={BASE_URL} component={App} />
|
||||
<ProtectedRoute path={BUILDER_URL} component={Editor} />
|
||||
<Route exact path={LOGIN_URL} component={LoginPage} />
|
||||
<Route component={PageNotFound} />
|
||||
</Switch>
|
||||
</BrowserRouter>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
9
app/client/src/pages/Editor/ApiEditor.tsx
Normal file
9
app/client/src/pages/Editor/ApiEditor.tsx
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import React from "react";
|
||||
|
||||
class ApiEditor extends React.Component {
|
||||
render() {
|
||||
return <span>API editor</span>;
|
||||
}
|
||||
}
|
||||
|
||||
export default ApiEditor;
|
||||
9
app/client/src/pages/Editor/ApiSidebar.tsx
Normal file
9
app/client/src/pages/Editor/ApiSidebar.tsx
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import React from "react";
|
||||
|
||||
class ApiSidebar extends React.Component {
|
||||
render() {
|
||||
return <p>Apis</p>;
|
||||
}
|
||||
}
|
||||
|
||||
export default ApiSidebar;
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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<WidgetCardPaneProps> = (
|
||||
props: WidgetCardPaneProps,
|
||||
) => {
|
||||
const groups = Object.keys(props.cards);
|
||||
return (
|
||||
<CardsPaneWrapper>
|
||||
{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>
|
||||
))}
|
||||
</CardsPaneWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default WidgetCardsPane;
|
||||
60
app/client/src/pages/Editor/WidgetSidebar.tsx
Normal file
60
app/client/src/pages/Editor/WidgetSidebar.tsx
Normal file
|
|
@ -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<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>
|
||||
);
|
||||
};
|
||||
|
||||
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);
|
||||
131
app/client/src/pages/Editor/WidgetsEditor.tsx
Normal file
131
app/client/src/pages/Editor/WidgetsEditor.tsx
Normal file
|
|
@ -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<WidgetProps> | any;
|
||||
fetchCanvasWidgets: Function;
|
||||
executeAction: (actionPayloads?: ActionPayload[]) => void;
|
||||
updateWidget: Function;
|
||||
savePageLayout: Function;
|
||||
currentPageName: string;
|
||||
currentPageId: string;
|
||||
currentLayoutId: string;
|
||||
isSaving: boolean;
|
||||
};
|
||||
|
||||
export const WidgetFunctionsContext: Context<WidgetFunctions> = createContext(
|
||||
{},
|
||||
);
|
||||
|
||||
class WidgetsEditor extends React.Component<EditorProps> {
|
||||
componentDidMount() {
|
||||
this.props.fetchCanvasWidgets(this.props.currentPageId);
|
||||
}
|
||||
|
||||
render(): React.ReactNode {
|
||||
return (
|
||||
<WidgetFunctionsContext.Provider
|
||||
value={{
|
||||
executeAction: this.props.executeAction,
|
||||
updateWidget: this.props.updateWidget,
|
||||
}}
|
||||
>
|
||||
<EditorWrapper>
|
||||
<CanvasContainer>
|
||||
{this.props.dsl && <Canvas dsl={this.props.dsl} />}
|
||||
</CanvasContainer>
|
||||
<PropertyPane />
|
||||
</EditorWrapper>
|
||||
</WidgetFunctionsContext.Provider>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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<WidgetProps>,
|
||||
) => dispatch(savePage(pageId, layoutId, dsl)),
|
||||
};
|
||||
};
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps,
|
||||
)(WidgetsEditor);
|
||||
|
|
@ -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<WidgetProps> | 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<WidgetFunctions> = createContext(
|
||||
{},
|
||||
);
|
||||
|
||||
class Editor extends Component<EditorProps> {
|
||||
componentDidMount() {
|
||||
this.props.fetchCanvasWidgets(this.props.currentPageId);
|
||||
}
|
||||
|
||||
public render() {
|
||||
return (
|
||||
<WidgetFunctionsContext.Provider
|
||||
value={{
|
||||
executeAction: this.props.executeAction,
|
||||
updateWidget: this.props.updateWidget,
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<EditorHeader
|
||||
notificationText={this.props.isSaving ? "Saving page..." : undefined}
|
||||
pageName={this.props.currentPageName}
|
||||
/>
|
||||
<EditorWrapper>
|
||||
<WidgetCardsPane cards={this.props.cards} />
|
||||
<CanvasContainer>
|
||||
{this.props.dsl && <Canvas dsl={this.props.dsl} />}
|
||||
</CanvasContainer>
|
||||
<PropertyPane />
|
||||
</EditorWrapper>
|
||||
</WidgetFunctionsContext.Provider>
|
||||
<MainContainer>
|
||||
<NavBar />
|
||||
<EditorsRouter />
|
||||
<WidgetsEditor />
|
||||
</MainContainer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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<WidgetProps>,
|
||||
) => dispatch(savePage(pageId, layoutId, dsl)),
|
||||
};
|
||||
};
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps,
|
||||
)(Editor);
|
||||
export default connect(mapStateToProps)(Editor);
|
||||
|
|
|
|||
58
app/client/src/pages/Editor/routes.tsx
Normal file
58
app/client/src/pages/Editor/routes.tsx
Normal file
|
|
@ -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<RouteComponentProps, RouterState> {
|
||||
constructor(props: RouteComponentProps) {
|
||||
super(props);
|
||||
this.state = {
|
||||
drawerOpen: this.props.location.pathname !== BUILDER_URL,
|
||||
};
|
||||
}
|
||||
componentDidUpdate(prevProps: Readonly<RouteComponentProps>): 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 (
|
||||
<MainWrapper>
|
||||
<Drawer
|
||||
isOpen={drawerOpen}
|
||||
position={Position.LEFT}
|
||||
usePortal={false}
|
||||
size="75%"
|
||||
>
|
||||
<Switch>
|
||||
<Route exact path={API_EDITOR_URL} component={ApiEditor} />
|
||||
</Switch>
|
||||
</Drawer>
|
||||
</MainWrapper>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withRouter(EditorsRouter);
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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<LoadWidgetCardsPanePayload>,
|
||||
) => {
|
||||
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<WidgetProps>;
|
||||
cards: {
|
||||
[id: string]: WidgetCardProps[];
|
||||
};
|
||||
pageWidgetId: string;
|
||||
currentPageId: string;
|
||||
currentLayoutId: string;
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
26
app/client/src/reducers/uiReducers/widgetSidebarReducer.ts
Normal file
26
app/client/src/reducers/uiReducers/widgetSidebarReducer.ts
Normal file
|
|
@ -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<LoadWidgetSidebarPayload>,
|
||||
) => {
|
||||
return { ...state, ...action.payload };
|
||||
},
|
||||
});
|
||||
|
|
@ -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) {
|
||||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user