Merge branch 'feature/canvas-fixes-1' into 'release'

Fixes: Saving notification, global error handling, show controls on hover, fixed row height

See merge request theappsmith/internal-tools-client!36
This commit is contained in:
Nikhil Nandagopal 2019-10-03 06:50:30 +00:00
commit a308337172
27 changed files with 382 additions and 291 deletions

View File

@ -1,10 +1,3 @@
## GIT Commit Hooks
This project has scripts to ESLint fix and Prettier write the code on the git commit hook.
It is recommended to install ESLint and Prettier global for successful git commits
`yarn global add eslint`
`yarn global add prettier`
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).

View File

@ -1,5 +1,4 @@
import { FetchPageRequest } from "../api/PageApi"; import { FetchPageRequest } from "../api/PageApi";
import { ResponseMeta } from "../api/ApiResponses";
import { RenderMode } from "../constants/WidgetConstants"; import { RenderMode } from "../constants/WidgetConstants";
import { WidgetProps, WidgetOperation } from "../widgets/BaseWidget"; import { WidgetProps, WidgetOperation } from "../widgets/BaseWidget";
import { WidgetType } from "../constants/WidgetConstants"; import { WidgetType } from "../constants/WidgetConstants";
@ -8,7 +7,6 @@ import {
ReduxAction, ReduxAction,
UpdateCanvasPayload, UpdateCanvasPayload,
SavePagePayload, SavePagePayload,
SavePageErrorPayload,
SavePageSuccessPayload, SavePageSuccessPayload,
} from "../constants/ReduxActionConstants"; } from "../constants/ReduxActionConstants";
import { ContainerWidgetProps } from "../widgets/ContainerWidget"; import { ContainerWidgetProps } from "../widgets/ContainerWidget";
@ -26,13 +24,6 @@ export const fetchPage = (
}; };
}; };
export const fetchPageError = (payload: ResponseMeta) => {
return {
type: ReduxActionTypes.FETCH_PAGE_ERROR,
payload,
};
};
export const addWidget = ( export const addWidget = (
pageId: string, pageId: string,
widget: WidgetProps, widget: WidgetProps,
@ -86,13 +77,6 @@ export const savePageSuccess = (payload: SavePageSuccessPayload) => {
}; };
}; };
export const savePageError = (payload: SavePageErrorPayload) => {
return {
type: ReduxActionTypes.SAVE_PAGE_ERROR,
payload,
};
};
export type WidgetAddChild = { export type WidgetAddChild = {
widgetId: string; widgetId: string;
type: WidgetType; type: WidgetType;

View File

@ -1,4 +1,7 @@
import { ReduxActionTypes } from "../constants/ReduxActionConstants"; import {
ReduxActionTypes,
ReduxActionErrorTypes,
} from "../constants/ReduxActionConstants";
import { WidgetCardProps } from "../widgets/BaseWidget"; import { WidgetCardProps } from "../widgets/BaseWidget";
export const fetchWidgetCards = () => { export const fetchWidgetCards = () => {
@ -9,7 +12,7 @@ export const fetchWidgetCards = () => {
export const errorFetchingWidgetCards = (error: any) => { export const errorFetchingWidgetCards = (error: any) => {
return { return {
type: ReduxActionTypes.ERROR_FETCHING_WIDGET_CARDS, type: ReduxActionErrorTypes.FETCH_WIDGET_CARDS_ERROR,
error, error,
}; };
}; };
@ -18,7 +21,7 @@ export const successFetchingWidgetCards = (cards: {
[id: string]: WidgetCardProps[]; [id: string]: WidgetCardProps[];
}) => { }) => {
return { return {
type: ReduxActionTypes.SUCCESS_FETCHING_WIDGET_CARDS, type: ReduxActionTypes.FETCH_WIDGET_CARDS_SUCCESS,
cards, cards,
}; };
}; };

View File

@ -31,7 +31,7 @@ export type FetchPageResponse = ApiResponse & {
}; };
}; };
export interface SavePageResponse { export interface SavePageResponse extends ApiResponse {
pageId: string; pageId: string;
} }

View File

@ -35,13 +35,13 @@ export const getEditorConfigs = () => {
return { return {
currentPageId: "5d807e7f795dc6000482bc78", currentPageId: "5d807e7f795dc6000482bc78",
currentLayoutId: "5d807e7f795dc6000482bc77", currentLayoutId: "5d807e7f795dc6000482bc77",
currentPageName: "page1",
}; };
} else { } else {
return { return {
currentPageId: "5d807e76795dc6000482bc76", currentPageId: "5d807e76795dc6000482bc76",
currentLayoutId: "5d807e76795dc6000482bc75", currentLayoutId: "5d807e76795dc6000482bc75",
currentPageName: "page1",
}; };
} }
}; };
console.log("here", process.env.NODE_ENV);

View File

@ -1,6 +1,8 @@
import { WidgetProps, WidgetCardProps } from "../widgets/BaseWidget"; import { WidgetProps, WidgetCardProps } from "../widgets/BaseWidget";
export const ReduxActionTypes: { [key: string]: string } = { export const ReduxActionTypes: { [key: string]: string } = {
REPORT_ERROR: "REPORT_ERROR",
FLUSH_ERRORS: "FLUSH_ERRORS",
UPDATE_CANVAS: "UPDATE_CANVAS", UPDATE_CANVAS: "UPDATE_CANVAS",
FETCH_CANVAS: "FETCH_CANVAS", FETCH_CANVAS: "FETCH_CANVAS",
CLEAR_CANVAS: "CLEAR_CANVAS", CLEAR_CANVAS: "CLEAR_CANVAS",
@ -16,8 +18,7 @@ export const ReduxActionTypes: { [key: string]: string } = {
LOAD_PROPERTY_CONFIG: "LOAD_PROPERTY_CONFIG", LOAD_PROPERTY_CONFIG: "LOAD_PROPERTY_CONFIG",
PUBLISH: "PUBLISH", PUBLISH: "PUBLISH",
FETCH_WIDGET_CARDS: "FETCH_WIDGET_CARDS", FETCH_WIDGET_CARDS: "FETCH_WIDGET_CARDS",
SUCCESS_FETCHING_WIDGET_CARDS: "SUCCESS_FETCHING_WIDGET_CARDS", FETCH_WIDGET_CARDS_SUCCESS: "FETCH_WIDGET_CARDS_SUCCESS",
ERROR_FETCHING_WIDGET_CARDS: "ERROR_FETCHING_WIDGET_CARDS",
ADD_PAGE_WIDGET: "ADD_PAGE_WIDGET", ADD_PAGE_WIDGET: "ADD_PAGE_WIDGET",
REMOVE_PAGE_WIDGET: "REMOVE_PAGE_WIDGET", REMOVE_PAGE_WIDGET: "REMOVE_PAGE_WIDGET",
LOAD_API_RESPONSE: "LOAD_API_RESPONSE", LOAD_API_RESPONSE: "LOAD_API_RESPONSE",
@ -26,8 +27,6 @@ export const ReduxActionTypes: { [key: string]: string } = {
LOAD_CANVAS_ACTIONS: "LOAD_CANVAS_ACTIONS", LOAD_CANVAS_ACTIONS: "LOAD_CANVAS_ACTIONS",
SAVE_PAGE_INIT: "SAVE_PAGE_INIT", SAVE_PAGE_INIT: "SAVE_PAGE_INIT",
SAVE_PAGE_SUCCESS: "SAVE_PAGE_SUCCESS", SAVE_PAGE_SUCCESS: "SAVE_PAGE_SUCCESS",
SAVE_PAGE_ERROR: "SAVE_PAGE_ERROR",
FETCH_PAGE_ERROR: "FETCH_PAGE_ERROR",
UPDATE_LAYOUT: "UPDATE_LAYOUT", UPDATE_LAYOUT: "UPDATE_LAYOUT",
WIDGET_ADD_CHILD: "WIDGET_ADD_CHILD", WIDGET_ADD_CHILD: "WIDGET_ADD_CHILD",
WIDGET_REMOVE_CHILD: "WIDGET_REMOVE_CHILD", WIDGET_REMOVE_CHILD: "WIDGET_REMOVE_CHILD",
@ -37,18 +36,37 @@ export const ReduxActionTypes: { [key: string]: string } = {
SHOW_PROPERTY_PANE: "SHOW_PROPERTY_PANE", SHOW_PROPERTY_PANE: "SHOW_PROPERTY_PANE",
UPDATE_WIDGET_PROPERTY: "UPDATE_WIDGET_PROPERTY", UPDATE_WIDGET_PROPERTY: "UPDATE_WIDGET_PROPERTY",
}; };
export type ReduxActionType = (typeof ReduxActionTypes)[keyof typeof ReduxActionTypes]; export type ReduxActionType = (typeof ReduxActionTypes)[keyof typeof ReduxActionTypes];
export const ReduxActionErrorTypes: { [key: string]: string } = {
API_ERROR: "API_ERROR",
WIDGET_DELETE_ERROR: "WIDGET_DELETE_ERROR",
WIDGET_MOVE_ERROR: "WIDGET_MOVE_ERROR",
WIDGET_RESIZE_ERROR: "WIDGET_RESIZE_ERROR",
WIDGET_REMOVE_CHILD_ERROR: "WIDGET_REMOVE_CHILD_ERROR",
WIDGET_ADD_CHILD_ERROR: "WIDGET_ADD_CHILD_ERROR",
FETCH_PAGE_ERROR: "FETCH_PAGE_ERROR",
SAVE_PAGE_ERROR: "SAVE_PAGE_ERROR",
FETCH_WIDGET_CARDS_ERROR: "FETCH_WIDGET_CARDS_ERROR",
};
export type ReduxActionErrorType = (typeof ReduxActionErrorTypes)[keyof typeof ReduxActionErrorTypes];
export interface ReduxAction<T> { export interface ReduxAction<T> {
type: ReduxActionType; type: ReduxActionType | ReduxActionErrorType;
payload: T; payload: T;
} }
export interface ReduxActionErrorPayload {
message: string;
source?: string;
}
export interface UpdateCanvasPayload { export interface UpdateCanvasPayload {
pageWidgetId: string; pageWidgetId: string;
widgets: { [widgetId: string]: WidgetProps }; widgets: { [widgetId: string]: WidgetProps };
layoutId: string; currentLayoutId: string;
currentPageId: string;
currentPageName: string;
} }
export interface ShowPropertyPanePayload { export interface ShowPropertyPanePayload {

View File

@ -71,4 +71,5 @@ export const GridDefaults = {
DEFAULT_WIDGET_HEIGHT: 100, DEFAULT_WIDGET_HEIGHT: 100,
DEFAULT_GRID_COLUMNS: 16, DEFAULT_GRID_COLUMNS: 16,
DEFAULT_GRID_ROWS: 32, DEFAULT_GRID_ROWS: 32,
DEFAULT_GRID_ROW_HEIGHT: 40,
}; };

View File

@ -6,17 +6,25 @@ import { useDrag, DragPreviewImage, DragSourceMonitor } from "react-dnd";
import blankImage from "../assets/images/blank.png"; import blankImage from "../assets/images/blank.png";
import { ContainerProps } from "./ContainerComponent"; import { ContainerProps } from "./ContainerComponent";
const DraggableWrapper = styled.div`
&:hover > div {
display: block;
}
`;
const DragHandle = styled.div` const DragHandle = styled.div`
position: absolute; position: absolute;
left: ${props => props.theme.spaces[2]}px; left: ${props => props.theme.spaces[2]}px;
top: -${props => props.theme.spaces[8]}px; top: -${props => props.theme.spaces[8]}px;
cursor: move; cursor: move;
display: none;
`; `;
const DeleteControl = styled.div` const DeleteControl = styled.div`
position: absolute; position: absolute;
right: ${props => props.theme.spaces[2]}px; right: ${props => props.theme.spaces[2]}px;
top: -${props => props.theme.spaces[8]}px; top: -${props => props.theme.spaces[8]}px;
display: none;
`; `;
type DraggableComponentProps = WidgetProps & ContainerProps; type DraggableComponentProps = WidgetProps & ContainerProps;
@ -35,7 +43,7 @@ const DraggableComponent = (props: DraggableComponentProps) => {
return ( return (
<React.Fragment> <React.Fragment>
<DragPreviewImage src={blankImage} connect={preview} /> <DragPreviewImage src={blankImage} connect={preview} />
<div <DraggableWrapper
ref={preview} ref={preview}
style={{ style={{
display: isDragging ? "none" : "flex", display: isDragging ? "none" : "flex",
@ -56,7 +64,7 @@ const DraggableComponent = (props: DraggableComponentProps) => {
<Icon icon="trash" iconSize={20} /> <Icon icon="trash" iconSize={20} />
</DeleteControl> </DeleteControl>
{props.children} {props.children}
</div> </DraggableWrapper>
</React.Fragment> </React.Fragment>
); );
}; };

View File

@ -13,7 +13,13 @@ const ResizableContainer = styled(Resizable)`
}}; }};
`; `;
const CustomHandle = (props: any) => <div {...props} />; const DisplayHandleWrapper = styled.div`
display: none;
${ResizableContainer}:hover & {
display: block;
}
`;
const CustomHandle = (props: any) => <DisplayHandleWrapper {...props} />;
const BottomRightHandle = () => ( const BottomRightHandle = () => (
<CustomHandle> <CustomHandle>
<Icon iconSize={15} icon="arrow-bottom-right" /> <Icon iconSize={15} icon="arrow-bottom-right" />

View File

@ -1,8 +1,9 @@
import { WidgetCardsPaneReduxState } from "../reducers/uiReducers/widgetCardsPaneReducer"; import { WidgetCardProps } from "../widgets/BaseWidget";
import { generateReactKey } from "../utils/generators"; import { generateReactKey } from "../utils/generators";
const WidgetCardsPaneResponse: WidgetCardsPaneReduxState = { const WidgetCardsPaneResponse: {
cards: { [id: string]: WidgetCardProps[];
} = {
common: [ common: [
{ {
type: "TEXT_WIDGET", type: "TEXT_WIDGET",
@ -93,7 +94,6 @@ const WidgetCardsPaneResponse: WidgetCardsPaneReduxState = {
key: generateReactKey(), key: generateReactKey(),
}, },
], ],
},
}; };
export default WidgetCardsPaneResponse; export default WidgetCardsPaneResponse;

View File

@ -1,35 +1,57 @@
import React, { Component } from "react"; import React from "react";
import styled from "styled-components"; import styled from "styled-components";
// import { connect } from "react-redux"; import { Breadcrumbs, IBreadcrumbProps, Spinner } from "@blueprintjs/core";
// import { AppState } from "../../reducers";
// import { EditorHeaderReduxState } from "../../reducers/uiReducers/editorHeaderReducer";
const Header = styled.header` const Header = styled.header`
display: flex;
justify-content: space-around;
align-items: center;
height: 50px; height: 50px;
padding: 0px 30px;
box-shadow: 0px 0px 3px #ccc; box-shadow: 0px 0px 3px #ccc;
background: #fff; background: #fff;
font-size: ${props => props.theme.fontSizes[1]}px;
`; `;
class EditorHeader extends Component { const NotificationText = styled.div`
render() { display: flex;
return <Header></Header>; justify-content: space-evenly;
align-items: center;
flex-grow: 1;
`;
const StretchedBreadCrumb = styled(Breadcrumbs)`
flex-grow: 10;
* {
font-family: ${props => props.theme.fonts[0]};
font-size: ${props => props.theme.fontSizes[2]}px;
} }
li:after {
background: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M10.71 7.29l-4-4a1.003 1.003 0 0 0-1.42 1.42L8.59 8 5.3 11.29c-.19.18-.3.43-.3.71a1.003 1.003 0 0 0 1.71.71l4-4c.18-.18.29-.43.29-.71 0-.28-.11-.53-.29-.71z' fill='rgba(92,112,128,1)'/%3E%3C/svg%3E");
} }
`;
type EditorHeaderProps = {
notificationText?: string;
pageName: string;
};
export const EditorHeader = (props: EditorHeaderProps) => {
const navigation: IBreadcrumbProps[] = [
{ href: "#", icon: "folder-close", text: "appsmith-dev" },
{ href: "#", icon: "folder-close", text: "application" },
{ icon: "page-layout", text: props.pageName, current: true },
];
return (
<Header>
<StretchedBreadCrumb items={navigation} />
<NotificationText>
{props.notificationText && <Spinner size={Spinner.SIZE_SMALL} />}
<span>{props.notificationText}</span>
</NotificationText>
</Header>
);
};
export default EditorHeader; export default EditorHeader;
// const mapStateToProps = (
// state: AppState,
// props: any,
// ): EditorHeaderReduxState => {
// return state;
// };
// const mapDispatchToProps = (dispatch: any) => {
// return {};
// };
// export default connect(
// mapStateToProps,
// mapDispatchToProps,
// )(EditorHeader);

View File

@ -29,6 +29,9 @@ export const Wrapper = styled.div`
path { path {
fill: ${props => props.theme.colors.textDefault}; fill: ${props => props.theme.colors.textDefault};
} }
rect {
stroke: ${props => props.theme.colors.textDefault};
}
} }
} }
& i { & i {

View File

@ -1,5 +1,4 @@
import React, { Component } from "react"; import React, { Component } from "react";
import { Position, Toaster } from "@blueprintjs/core";
import { connect } from "react-redux"; import { connect } from "react-redux";
import styled from "styled-components"; import styled from "styled-components";
import Canvas from "./Canvas"; import Canvas from "./Canvas";
@ -20,10 +19,6 @@ import { executeAction } from "../../actions/widgetActions";
import { ActionPayload } from "../../constants/ActionConstants"; import { ActionPayload } from "../../constants/ActionConstants";
import PropertyPane from "./PropertyPane"; import PropertyPane from "./PropertyPane";
const SaveToast = Toaster.create({
position: Position.TOP,
});
const CanvasContainer = styled.section` const CanvasContainer = styled.section`
height: 100%; height: 100%;
width: 100%; width: 100%;
@ -60,7 +55,7 @@ type EditorProps = {
updateWidget: Function; updateWidget: Function;
cards: { [id: string]: WidgetCardProps[] } | any; cards: { [id: string]: WidgetCardProps[] } | any;
savePageLayout: Function; savePageLayout: Function;
page: string; currentPageName: string;
currentPageId: string; currentPageId: string;
currentLayoutId: string; currentLayoutId: string;
isSaving: boolean; isSaving: boolean;
@ -71,23 +66,13 @@ class Editor extends Component<EditorProps> {
this.props.fetchCanvasWidgets(this.props.currentPageId); this.props.fetchCanvasWidgets(this.props.currentPageId);
} }
componentDidUpdate(prevProps: EditorProps) {
if (this.props.isSaving && prevProps.isSaving !== this.props.isSaving) {
SaveToast.clear();
SaveToast.show({ message: "Saving Page..." });
} else if (
!this.props.isSaving &&
prevProps.isSaving !== this.props.isSaving
) {
SaveToast.clear();
SaveToast.show({ message: "Page Saved" });
}
}
public render() { public render() {
return ( return (
<React.Fragment> <React.Fragment>
<EditorHeader></EditorHeader> <EditorHeader
notificationText={this.props.isSaving ? "Saving page..." : undefined}
pageName={this.props.currentPageName}
/>
<EditorWrapper> <EditorWrapper>
<WidgetCardsPane cards={this.props.cards} /> <WidgetCardsPane cards={this.props.cards} />
<CanvasContainer> <CanvasContainer>
@ -122,8 +107,9 @@ const mapStateToProps = (state: AppState): EditorReduxState => {
); );
const configs = state.entities.widgetConfig.config; const configs = state.entities.widgetConfig.config;
const cards = state.ui.widgetCardsPane.cards; const cards = state.ui.editor.cards;
Object.keys(cards).forEach((group: string) => { const groups: string[] = Object.keys(cards);
groups.forEach((group: string) => {
cards[group] = cards[group].map((widget: WidgetCardProps) => ({ cards[group] = cards[group].map((widget: WidgetCardProps) => ({
...widget, ...widget,
...configs[widget.type], ...configs[widget.type],
@ -136,6 +122,7 @@ const mapStateToProps = (state: AppState): EditorReduxState => {
pageWidgetId: state.ui.editor.pageWidgetId, pageWidgetId: state.ui.editor.pageWidgetId,
currentPageId: state.ui.editor.currentPageId, currentPageId: state.ui.editor.currentPageId,
currentLayoutId: state.ui.editor.currentLayoutId, currentLayoutId: state.ui.editor.currentLayoutId,
currentPageName: state.ui.editor.currentPageName,
isSaving: state.ui.editor.isSaving, isSaving: state.ui.editor.isSaving,
}; };
}; };

View File

@ -2,8 +2,8 @@ import { combineReducers } from "redux";
import entityReducer from "./entityReducers"; import entityReducer from "./entityReducers";
import uiReducer from "./uiReducers"; import uiReducer from "./uiReducers";
import { CanvasWidgetsReduxState } from "./entityReducers/canvasWidgetsReducer"; import { CanvasWidgetsReduxState } from "./entityReducers/canvasWidgetsReducer";
import { WidgetCardsPaneReduxState } from "./uiReducers/widgetCardsPaneReducer";
import { EditorReduxState } from "./uiReducers/editorReducer"; import { EditorReduxState } from "./uiReducers/editorReducer";
import { ErrorReduxState } from "./uiReducers/errorReducer";
import { APIDataState } from "./entityReducers/apiDataReducer"; import { APIDataState } from "./entityReducers/apiDataReducer";
import { QueryDataState } from "./entityReducers/queryDataReducer"; import { QueryDataState } from "./entityReducers/queryDataReducer";
import { ActionDataState } from "./entityReducers/actionsReducer"; import { ActionDataState } from "./entityReducers/actionsReducer";
@ -20,9 +20,9 @@ export default appReducer;
export interface AppState { export interface AppState {
ui: { ui: {
widgetCardsPane: WidgetCardsPaneReduxState;
editor: EditorReduxState; editor: EditorReduxState;
propertyPane: PropertyPaneReduxState; propertyPane: PropertyPaneReduxState;
errors: ErrorReduxState;
}; };
entities: { entities: {
canvasWidgets: CanvasWidgetsReduxState; canvasWidgets: CanvasWidgetsReduxState;

View File

@ -1,9 +0,0 @@
import { createReducer } from "../../utils/AppsmithUtils";
const initialState = {};
const editorHeaderReducer = createReducer(initialState, {});
// export interface EditorHeaderReduxState {}
export default editorHeaderReducer;

View File

@ -7,15 +7,18 @@ import {
} from "../../constants/ReduxActionConstants"; } from "../../constants/ReduxActionConstants";
import { WidgetCardProps, WidgetProps } from "../../widgets/BaseWidget"; import { WidgetCardProps, WidgetProps } from "../../widgets/BaseWidget";
import { ContainerWidgetProps } from "../../widgets/ContainerWidget"; import { ContainerWidgetProps } from "../../widgets/ContainerWidget";
import WidgetCardsPaneResponse from "../../mockResponses/WidgetCardsPaneResponse";
const editorConfigs = getEditorConfigs(); const editorConfigs = getEditorConfigs();
const initialState: EditorReduxState = { const initialState: EditorReduxState = {
pageWidgetId: "0", pageWidgetId: "0",
...editorConfigs, ...editorConfigs,
isSaving: false, isSaving: false,
cards: WidgetCardsPaneResponse,
}; };
const editorReducer = createReducer(initialState, { const editorReducer = createReducer(initialState, {
[ReduxActionTypes.SUCCESS_FETCHING_WIDGET_CARDS]: ( [ReduxActionTypes.FETCH_WIDGET_CARDS_SUCCESS]: (
state: EditorReduxState, state: EditorReduxState,
action: ReduxAction<LoadWidgetCardsPanePayload>, action: ReduxAction<LoadWidgetCardsPanePayload>,
) => { ) => {
@ -27,19 +30,17 @@ const editorReducer = createReducer(initialState, {
[ReduxActionTypes.SAVE_PAGE_SUCCESS]: (state: EditorReduxState) => { [ReduxActionTypes.SAVE_PAGE_SUCCESS]: (state: EditorReduxState) => {
return { ...state, isSaving: false }; return { ...state, isSaving: false };
}, },
[ReduxActionTypes.SAVE_PAGE_ERROR]: (state: EditorReduxState) => {
return { ...state, isSaving: false };
},
}); });
export interface EditorReduxState { export interface EditorReduxState {
dsl?: ContainerWidgetProps<WidgetProps>; dsl?: ContainerWidgetProps<WidgetProps>;
cards?: { cards: {
[id: string]: WidgetCardProps[]; [id: string]: WidgetCardProps[];
}; };
pageWidgetId: string; pageWidgetId: string;
currentPageId: string; currentPageId: string;
currentLayoutId: string; currentLayoutId: string;
currentPageName: string;
isSaving: boolean; isSaving: boolean;
} }

View File

@ -0,0 +1,31 @@
import { createReducer } from "../../utils/AppsmithUtils";
import {
ReduxAction,
ReduxActionTypes,
ReduxActionErrorPayload,
} from "../../constants/ReduxActionConstants";
const initialState: ErrorReduxState = { sourceAction: "", message: "" };
const errorReducer = createReducer(initialState, {
[ReduxActionTypes.REPORT_ERROR]: (
state: ErrorReduxState,
action: ReduxAction<ReduxActionErrorPayload>,
) => {
return {
sourceAction: action.payload.source,
message: action.payload.message,
};
},
[ReduxActionTypes.FLUSH_ERRORS]: () => {
return {};
},
});
export interface ErrorReduxState {
// Expiration?
sourceAction?: string;
message?: string;
}
export default errorReducer;

View File

@ -1,13 +1,11 @@
import { combineReducers } from "redux"; import { combineReducers } from "redux";
import widgetCardsPaneReducer from "./widgetCardsPaneReducer";
import editorHeaderReducer from "./editorHeaderReducer";
import editorReducer from "./editorReducer"; import editorReducer from "./editorReducer";
import errorReducer from "./errorReducer";
import propertyPaneReducer from "./propertyPaneReducer"; import propertyPaneReducer from "./propertyPaneReducer";
const uiReducer = combineReducers({ const uiReducer = combineReducers({
widgetCardsPane: widgetCardsPaneReducer,
editorHeader: editorHeaderReducer,
editor: editorReducer, editor: editorReducer,
errors: errorReducer,
propertyPane: propertyPaneReducer, propertyPane: propertyPaneReducer,
}); });
export default uiReducer; export default uiReducer;

View File

@ -1,27 +0,0 @@
import { createReducer } from "../../utils/AppsmithUtils";
import {
ReduxActionTypes,
ReduxAction,
LoadWidgetCardsPanePayload,
} from "../../constants/ReduxActionConstants";
import { WidgetCardProps } from "../../widgets/BaseWidget";
import WidgetCardsPaneResponse from "../../mockResponses/WidgetCardsPaneResponse";
const initialState: WidgetCardsPaneReduxState = WidgetCardsPaneResponse;
const widgetCardsPaneReducer = createReducer(initialState, {
[ReduxActionTypes.ERROR_FETCHING_WIDGET_CARDS]: (
state: WidgetCardsPaneReduxState,
action: ReduxAction<LoadWidgetCardsPanePayload>,
) => {
return { cards: action.payload.cards };
},
});
export interface WidgetCardsPaneReduxState {
cards: {
[id: string]: WidgetCardProps[];
};
}
export default widgetCardsPaneReducer;

View File

@ -0,0 +1,38 @@
import {
ReduxActionTypes,
ReduxActionErrorTypes,
ReduxAction,
} from "../constants/ReduxActionConstants";
import { ApiResponse } from "../api/ApiResponses";
import { put, takeLatest } from "redux-saga/effects";
export function* validateResponse(response: ApiResponse) {
if (response.responseMeta.success) {
return true;
} else {
yield put({
type: ReduxActionErrorTypes.API_ERROR,
payload: {
error: response.responseMeta.error,
},
});
return false;
}
}
export function* errorSaga(errorAction: ReduxAction<{ error: any }>) {
// Just a pass through for now.
// Add procedures to customize errors here
yield put({
type: ReduxActionTypes.REPORT_ERROR,
payload: {
message: errorAction.payload.error,
source: errorAction.type,
},
});
}
export default function* errorSagas() {
yield takeLatest(Object.values(ReduxActionErrorTypes), errorSaga);
}

View File

@ -1,15 +1,11 @@
import CanvasWidgetsNormalizer from "../normalizers/CanvasWidgetsNormalizer"; import CanvasWidgetsNormalizer from "../normalizers/CanvasWidgetsNormalizer";
import { import {
ReduxActionTypes, ReduxActionTypes,
ReduxActionErrorTypes,
ReduxAction, ReduxAction,
UpdateCanvasPayload, UpdateCanvasPayload,
} from "../constants/ReduxActionConstants"; } from "../constants/ReduxActionConstants";
import { import { updateCanvas, savePageSuccess } from "../actions/pageActions";
updateCanvas,
savePageError,
savePageSuccess,
fetchPageError,
} from "../actions/pageActions";
import PageApi, { import PageApi, {
FetchPageResponse, FetchPageResponse,
SavePageResponse, SavePageResponse,
@ -25,27 +21,31 @@ import {
takeEvery, takeEvery,
all, all,
} from "redux-saga/effects"; } from "redux-saga/effects";
import { extractCurrentDSL } from "../utils/WidgetPropsUtils"; import { extractCurrentDSL } from "../utils/WidgetPropsUtils";
import { getEditorConfigs } from "./selectors"; import { getEditorConfigs } from "./selectors";
import { validateResponse } from "./ErrorSagas";
export function* fetchPageSaga( export function* fetchPageSaga(
pageRequestAction: ReduxAction<FetchPageRequest>, pageRequestAction: ReduxAction<FetchPageRequest>,
) { ) {
const pageRequest = pageRequestAction.payload;
try { try {
const pageRequest = pageRequestAction.payload;
const fetchPageResponse: FetchPageResponse = yield call( const fetchPageResponse: FetchPageResponse = yield call(
PageApi.fetchPage, PageApi.fetchPage,
pageRequest, pageRequest,
); );
const isValidResponse = yield validateResponse(fetchPageResponse);
if (fetchPageResponse.responseMeta.success) { if (isValidResponse) {
const normalizedResponse = CanvasWidgetsNormalizer.normalize( const normalizedResponse = CanvasWidgetsNormalizer.normalize(
extractCurrentDSL(fetchPageResponse), extractCurrentDSL(fetchPageResponse),
); );
const canvasWidgetsPayload: UpdateCanvasPayload = { const canvasWidgetsPayload: UpdateCanvasPayload = {
pageWidgetId: normalizedResponse.result, pageWidgetId: normalizedResponse.result,
currentPageName: fetchPageResponse.data.name,
currentPageId: fetchPageResponse.data.id,
widgets: normalizedResponse.entities.canvasWidgets, widgets: normalizedResponse.entities.canvasWidgets,
layoutId: fetchPageResponse.data.layouts[0].id, // TODO(abhinav): Handle for multiple layouts currentLayoutId: fetchPageResponse.data.layouts[0].id, // TODO(abhinav): Handle for multiple layouts
}; };
yield all([ yield all([
put(updateCanvas(canvasWidgetsPayload)), put(updateCanvas(canvasWidgetsPayload)),
@ -56,8 +56,12 @@ export function* fetchPageSaga(
]); ]);
} }
} catch (error) { } catch (error) {
console.log(error); yield put({
yield put(fetchPageError(error)); type: ReduxActionErrorTypes.FETCH_PAGE_ERROR,
payload: {
error,
},
});
} }
} }
@ -68,10 +72,17 @@ export function* savePageSaga(savePageAction: ReduxAction<SavePageRequest>) {
PageApi.savePage, PageApi.savePage,
savePageRequest, savePageRequest,
); );
const isValidResponse = validateResponse(savePageResponse);
if (isValidResponse) {
yield put(savePageSuccess(savePageResponse)); yield put(savePageSuccess(savePageResponse));
} catch (err) { }
console.log(err); } catch (error) {
yield put(savePageError(err)); yield put({
type: ReduxActionErrorTypes.SAVE_PAGE_ERROR,
payload: {
error,
},
});
} }
} }
@ -87,7 +98,6 @@ export function* saveLayoutSaga(
{ canvasWidgets: widgets }, { canvasWidgets: widgets },
); );
const editorConfigs = yield select(getEditorConfigs) as any; const editorConfigs = yield select(getEditorConfigs) as any;
console.log(editorConfigs);
yield put({ yield put({
type: ReduxActionTypes.SAVE_PAGE_INIT, type: ReduxActionTypes.SAVE_PAGE_INIT,
payload: { payload: {

View File

@ -1,5 +1,7 @@
// import CanvasWidgetsNormalizer from "../normalizers/CanvasWidgetsNormalizer" import {
import { ReduxActionTypes } from "../constants/ReduxActionConstants"; ReduxActionTypes,
ReduxActionErrorTypes,
} from "../constants/ReduxActionConstants";
import WidgetCardsPaneApi, { import WidgetCardsPaneApi, {
WidgetCardsPaneResponse, WidgetCardsPaneResponse,
} from "../api/WidgetCardsPaneApi"; } from "../api/WidgetCardsPaneApi";
@ -13,7 +15,7 @@ export function* fetchWidgetCards() {
]); ]);
yield put(successFetchingWidgetCards(widgetCards.cards)); yield put(successFetchingWidgetCards(widgetCards.cards));
} catch (err) { } catch (err) {
yield put({ type: ReduxActionTypes.ERROR_FETCHING_WIDGET_CARDS, err }); yield put({ type: ReduxActionErrorTypes.FETCH_WIDGET_CARDS_ERROR, err });
} }
} }

View File

@ -3,6 +3,7 @@ import pageSagas from "../sagas/PageSagas";
import { fetchWidgetCardsSaga } from "./WidgetCardsPaneSagas"; import { fetchWidgetCardsSaga } from "./WidgetCardsPaneSagas";
import { watchExecuteActionSaga } from "./ActionSagas"; import { watchExecuteActionSaga } from "./ActionSagas";
import widgetOperationSagas from "./WidgetOperationSagas"; import widgetOperationSagas from "./WidgetOperationSagas";
import errorSagas from "./ErrorSagas";
export function* rootSaga() { export function* rootSaga() {
yield all([ yield all([
@ -10,5 +11,6 @@ export function* rootSaga() {
spawn(fetchWidgetCardsSaga), spawn(fetchWidgetCardsSaga),
spawn(watchExecuteActionSaga), spawn(watchExecuteActionSaga),
spawn(widgetOperationSagas), spawn(widgetOperationSagas),
spawn(errorSagas),
]); ]);
} }

View File

@ -0,0 +1,27 @@
import {
call as ReduxSagaCall,
select as ReduxSagaSelect,
put as ReduxSagaPut,
takeLatest as ReduxSagaTakeLatest,
takeEvery as ReduxSagaTakeEvery,
all as ReduxSagaAll,
} from "redux-saga/effects";
function* safe(effect: any) {
try {
return yield effect;
} catch (error) {
console.log(error);
}
}
export const call = (fn: any, ...args: unknown[]) =>
safe(ReduxSagaCall(fn, ...args));
export const select = (state: any, ...args: any[]) =>
safe(ReduxSagaSelect(state, ...args));
export const put = (action: any) => safe(ReduxSagaPut(action));
export const takeLatest = (pattern: any, worker: any) =>
safe(ReduxSagaTakeLatest(pattern, worker));
export const takeEvery = (pattern: any, worker: any) =>
safe(ReduxSagaTakeEvery(pattern, worker));
export const all = (args: unknown[]) => safe(ReduxSagaAll(args));

View File

@ -8,15 +8,3 @@ export const snapToGrid = (
const snappedY = Math.floor(y / rowHeight); const snappedY = Math.floor(y / rowHeight);
return [snappedX, snappedY]; return [snappedX, snappedY];
}; };
export const getRowColSizes = (
rowCount: number,
columnCount: number,
width: number,
height: number,
): { rowHeight: number; columnWidth: number } => {
return {
columnWidth: width / columnCount,
rowHeight: height / rowCount,
};
};

View File

@ -94,6 +94,9 @@ abstract class BaseWidget<
getCanvasView(): JSX.Element { getCanvasView(): JSX.Element {
const style = this.getPositionStyle(); const style = this.getPositionStyle();
if (!this.props.parentId) {
return this.getPageView();
} else {
return ( return (
<DraggableComponent <DraggableComponent
{...this.props} {...this.props}
@ -106,6 +109,7 @@ abstract class BaseWidget<
</DraggableComponent> </DraggableComponent>
); );
} }
}
abstract getWidgetType(): WidgetType; abstract getWidgetType(): WidgetType;

View File

@ -14,7 +14,7 @@ import { GridDefaults } from "../constants/WidgetConstants";
import DraggableComponent from "../editorComponents/DraggableComponent"; import DraggableComponent from "../editorComponents/DraggableComponent";
import ResizableComponent from "../editorComponents/ResizableComponent"; import ResizableComponent from "../editorComponents/ResizableComponent";
const { DEFAULT_GRID_COLUMNS, DEFAULT_GRID_ROWS } = GridDefaults; const { DEFAULT_GRID_COLUMNS, DEFAULT_GRID_ROW_HEIGHT } = GridDefaults;
class ContainerWidget extends BaseWidget< class ContainerWidget extends BaseWidget<
ContainerWidgetProps<WidgetProps>, ContainerWidgetProps<WidgetProps>,
@ -34,21 +34,14 @@ class ContainerWidget extends BaseWidget<
componentDidUpdate(previousProps: ContainerWidgetProps<WidgetProps>) { componentDidUpdate(previousProps: ContainerWidgetProps<WidgetProps>) {
super.componentDidUpdate(previousProps); super.componentDidUpdate(previousProps);
let snapColumnSpace = this.state.snapColumnSpace; let snapColumnSpace = this.state.snapColumnSpace;
let snapRowSpace = this.state.snapRowSpace;
if (this.state.componentWidth) if (this.state.componentWidth)
snapColumnSpace = snapColumnSpace =
this.state.componentWidth / this.state.componentWidth /
(this.props.snapColumns || DEFAULT_GRID_COLUMNS); (this.props.snapColumns || DEFAULT_GRID_COLUMNS);
if (this.state.componentHeight) if (this.state.snapColumnSpace !== snapColumnSpace) {
snapRowSpace =
this.state.componentHeight / (this.props.snapRows || DEFAULT_GRID_ROWS);
if (
this.state.snapColumnSpace !== snapColumnSpace ||
this.state.snapRowSpace !== snapRowSpace
) {
this.setState({ this.setState({
snapColumnSpace, snapColumnSpace,
snapRowSpace, snapRowSpace: DEFAULT_GRID_ROW_HEIGHT,
}); });
} }
} }
@ -79,9 +72,8 @@ class ContainerWidget extends BaseWidget<
); );
} }
getCanvasView() { getOccupiedSpaces(): OccupiedSpace[] | null {
const style = this.getPositionStyle(); return this.props.children
const occupiedSpaces: OccupiedSpace[] | null = this.props.children
? this.props.children.map(child => ({ ? this.props.children.map(child => ({
id: child.widgetId, id: child.widgetId,
left: child.leftColumn, left: child.leftColumn,
@ -90,15 +82,12 @@ class ContainerWidget extends BaseWidget<
right: child.rightColumn, right: child.rightColumn,
})) }))
: null; : null;
return ( }
<DropTargetComponent getCanvasView() {
{...this.props} const style = this.getPositionStyle();
{...this.state} const occupiedSpaces = this.getOccupiedSpaces();
occupiedSpaces={occupiedSpaces}
style={{ const renderDraggableComponent = (
...style,
}}
>
<DraggableComponent <DraggableComponent
style={{ ...style, xPosition: 0, yPosition: 0 }} style={{ ...style, xPosition: 0, yPosition: 0 }}
{...this.props} {...this.props}
@ -108,6 +97,18 @@ class ContainerWidget extends BaseWidget<
{this.getPageView()} {this.getPageView()}
</ResizableComponent> </ResizableComponent>
</DraggableComponent> </DraggableComponent>
);
return (
<DropTargetComponent
{...this.props}
{...this.state}
occupiedSpaces={occupiedSpaces}
style={{
...style,
}}
>
{this.props.parentId ? renderDraggableComponent : this.getPageView()}
</DropTargetComponent> </DropTargetComponent>
); );
} }