added transformation for requests

wired button onclick to execute action saga
split widget props into widget data and widget functions
This commit is contained in:
Nikhil Nandagopal 2019-09-17 15:41:50 +05:30
parent a8adc4e5f3
commit 751ea339d1
15 changed files with 189 additions and 85 deletions

View File

@ -0,0 +1,14 @@
import {
ReduxActionTypes,
ReduxAction,
} from "../constants/ReduxActionConstants";
import { ActionPayload } from "../constants/ActionConstants";
export const executeAction = (
actionPayloads?: ActionPayload[],
): ReduxAction<ActionPayload[] | undefined> => {
return {
type: ReduxActionTypes.EXECUTE_ACTION,
payload: actionPayloads,
};
};

View File

@ -1,9 +1,11 @@
import Api, { HttpMethod } from "./Api";
import API, { HttpMethod } from "./Api";
import { ApiResponse } from "./ApiResponses";
import { APIRequest } from "./ApiRequests";
import _ from "lodash";
export interface CreateActionRequest<T> extends APIRequest {
resourceId: string;
actionName: string;
actionConfiguration: T;
}
@ -12,12 +14,27 @@ export interface UpdateActionRequest<T> extends CreateActionRequest<T> {
}
export interface APIConfig {
resourceId: string;
actionName: string;
requestHeaders: Record<string, string>;
method: HttpMethod;
path: string;
APIName: string;
body: JSON;
queryParams: Record<string, string>;
actionId: string;
}
export interface Property {
key: string;
value: string;
}
export interface APIConfigRequest {
headers: Property[];
httpMethod: HttpMethod;
path: string;
body: JSON;
queryParameters: Property[];
}
export interface QueryConfig {
@ -39,37 +56,64 @@ export interface ExecuteActionResponse extends ApiResponse {
data: any;
}
class ActionAPI extends Api {
static url = "/actions";
class ActionAPI extends API {
static url = "v1/actions";
static createAPI(
createAPI: CreateActionRequest<APIConfig>,
): Promise<ActionCreateUpdateResponse> {
return Api.post(ActionAPI.url, createAPI);
static createAPI(apiConfig: APIConfig): Promise<ActionCreateUpdateResponse> {
const createAPI: CreateActionRequest<APIConfigRequest> = {
resourceId: apiConfig.resourceId,
actionName: apiConfig.actionName,
actionConfiguration: {
httpMethod: apiConfig.method,
path: apiConfig.path,
body: apiConfig.body,
headers: _.map(apiConfig.requestHeaders, (value, key) => {
return { key: key, value: value };
}),
queryParameters: _.map(apiConfig.queryParams, (value, key) => {
return { key: key, value: value };
}),
},
};
return API.post(ActionAPI.url, createAPI);
}
static updateAPI(
updateAPI: UpdateActionRequest<APIConfig>,
): Promise<ActionCreateUpdateResponse> {
return Api.post(ActionAPI.url, updateAPI);
static updateAPI(apiConfig: APIConfig): Promise<ActionCreateUpdateResponse> {
const updateAPI: UpdateActionRequest<APIConfigRequest> = {
resourceId: apiConfig.resourceId,
actionName: apiConfig.actionName,
actionId: apiConfig.actionId,
actionConfiguration: {
httpMethod: apiConfig.method,
path: apiConfig.path,
body: apiConfig.body,
headers: _.map(apiConfig.requestHeaders, (value, key) => {
return { key: key, value: value };
}),
queryParameters: _.map(apiConfig.queryParams, (value, key) => {
return { key: key, value: value };
}),
},
};
return API.post(ActionAPI.url, updateAPI);
}
static createQuery(
createQuery: CreateActionRequest<QueryConfig>,
): Promise<ActionCreateUpdateResponse> {
return Api.post(ActionAPI.url, createQuery);
return API.post(ActionAPI.url, createQuery);
}
static updateQuery(
updateQuery: UpdateActionRequest<QueryConfig>,
): Promise<ActionCreateUpdateResponse> {
return Api.post(ActionAPI.url, updateQuery);
return API.post(ActionAPI.url, updateQuery);
}
static executeAction(
executeAction: ExecuteActionRequest,
): Promise<ActionCreateUpdateResponse> {
return Api.post(ActionAPI.url, executeAction);
return API.post(ActionAPI.url, executeAction);
}
}

View File

@ -1,6 +1,5 @@
// import ContainerWidget from "../widgets/ContainerWidget"
import { WidgetProps, WidgetCardProps } from "../widgets/BaseWidget";
import { ExecuteActionResponse } from "../api/ActionAPI";
export type ReduxActionType =
| "LOAD_CANVAS_WIDGETS"

View File

@ -7,7 +7,9 @@ class ButtonComponent extends React.Component<ButtonComponentProps> {
render() {
return (
<Container {...this.props}>
<Button icon={this.props.icon}>{this.props.text}</Button>
<Button icon={this.props.icon} onClick={this.props.onClick}>
{this.props.text}
</Button>
</Container>
);
}
@ -15,6 +17,7 @@ class ButtonComponent extends React.Component<ButtonComponentProps> {
interface ButtonComponentProps extends TextComponentProps {
icon?: MaybeElement;
onClick?: (event: React.MouseEvent<HTMLElement>) => void;
}
export default ButtonComponent;

View File

@ -1,8 +1,8 @@
import React, { MutableRefObject, useLayoutEffect } from "react";
import styled from "styled-components";
import WidgetFactory from "../../utils/WidgetFactory";
import { WidgetTypes } from "../../constants/WidgetConstants";
import { DraggableWidget } from "../../widgets/BaseWidget";
import { WidgetTypes, RenderModes } from "../../constants/WidgetConstants";
import { DraggableWidget, WidgetFunctions } from "../../widgets/BaseWidget";
import { useDrop } from "react-dnd";
import { ContainerWidgetProps } from "../../widgets/ContainerWidget";
import EditorDragLayer from "./EditorDragLayer";
@ -46,6 +46,7 @@ const ArtBoard = styled.div<ArtBoardProps>`
interface CanvasProps {
pageWidget: ContainerWidgetProps<any>;
addWidget: Function;
widgetFunctions: WidgetFunctions;
}
interface ArtBoardProps {
@ -78,7 +79,12 @@ const Canvas = (props: CanvasProps) => {
<EditorDragLayer />
<ArtBoard ref={drop} cellSize={Math.floor(width / 16) - 1 + "px"}>
<ArtBoardBackgroundMask ref={artBoardMask}></ArtBoardBackgroundMask>
{props.pageWidget && WidgetFactory.createWidget(props.pageWidget)}
{props.pageWidget &&
WidgetFactory.createWidget(
props.pageWidget,
props.widgetFunctions,
RenderModes.CANVAS,
)}
</ArtBoard>
</React.Fragment>
);

View File

@ -4,6 +4,7 @@ import { XYCoord, useDragLayer } from "react-dnd";
import snapToGrid from "./snapToGrid";
import WidgetFactory from "../../utils/WidgetFactory";
import { RenderModes, WidgetType } from "../../constants/WidgetConstants";
import { ActionPayload } from "../../constants/ActionConstants";
const WrappedDragLayer = styled.div`
position: absolute;
@ -57,18 +58,21 @@ const EditorDragLayer: React.FC = () => {
}));
function renderItem() {
return WidgetFactory.createWidget({
widgetType: itemType as WidgetType,
widgetName: "",
widgetId: item.key,
topRow: 10,
leftColumn: 10,
bottomRow: 14,
rightColumn: 20,
parentColumnSpace: 1,
parentRowSpace: 1,
renderMode: RenderModes.CANVAS,
});
return WidgetFactory.createWidget(
{
widgetType: itemType as WidgetType,
widgetName: "",
widgetId: item.key,
topRow: 10,
leftColumn: 10,
bottomRow: 14,
rightColumn: 20,
parentColumnSpace: 1,
parentRowSpace: 1,
},
{ executeAction: (actionPayload?: ActionPayload[]) => {} },
RenderModes.CANVAS,
);
}
if (!isDragging) {

View File

@ -11,7 +11,9 @@ import { WidgetType } from "../../constants/WidgetConstants";
import CanvasWidgetsNormalizer from "../../normalizers/CanvasWidgetsNormalizer";
import { ContainerWidgetProps } from "../../widgets/ContainerWidget";
import { fetchPage, addWidget } from "../../actions/pageActions";
import { executeAction } from "../../actions/widgetActions";
import { RenderModes } from "../../constants/WidgetConstants";
import { ActionPayload } from "../../constants/ActionConstants";
// import EditorDragLayer from "./EditorDragLayer"
const CanvasContainer = styled.section`
@ -46,6 +48,7 @@ const EditorWrapper = styled.div`
type EditorProps = {
pageWidget: ContainerWidgetProps<any> | any;
fetchCanvasWidgets: Function;
executeAction: (actionPayloads?: ActionPayload[]) => void;
cards: { [id: string]: WidgetCardProps[] } | any;
addPageWidget: Function;
page: string;
@ -84,6 +87,7 @@ class Editor extends Component<EditorProps> {
<Canvas
pageWidget={this.props.pageWidget}
addWidget={this.addWidgetToCanvas}
widgetFunctions={{ executeAction: this.props.executeAction }}
/>
</CanvasContainer>
</EditorWrapper>
@ -105,6 +109,8 @@ const mapStateToProps = (state: AppState): EditorReduxState => {
const mapDispatchToProps = (dispatch: any) => {
return {
executeAction: (actionPayloads?: ActionPayload[]) =>
dispatch(executeAction(actionPayloads)),
fetchCanvasWidgets: (pageId: string) =>
dispatch(fetchPage(pageId, RenderModes.CANVAS)),
addPageWidget: (pageId: string, widgetProps: WidgetProps) =>

View File

@ -1,15 +1,13 @@
import CanvasWidgetsNormalizer from "../normalizers/CanvasWidgetsNormalizer";
import {
ReduxActionTypes,
ReduxAction,
} from "../constants/ReduxActionConstants";
import PageApi, { PageResponse, PageRequest } from "../api/PageApi";
import { call, put, takeEvery, select, all } from "redux-saga/effects";
import { RenderModes } from "../constants/WidgetConstants";
import { call, takeEvery, select, all } from "redux-saga/effects";
import {
APIActionPayload,
QueryActionPayload,
PageAction,
ActionPayload,
} from "../constants/ActionConstants";
import ActionAPI, { ActionCreateUpdateResponse } from "../api/ActionAPI";
import { AppState } from "../reducers";
@ -27,17 +25,17 @@ const getAction = (
return state.entities.actions[actionId];
};
export function* evaluateJSONPath(jsonPath: string): any {
export function* evaluateJSONPathSaga(jsonPath: string): any {
const dataTree = yield select(getDataTree);
const result = JSONPath({ path: jsonPath, json: dataTree });
return result;
}
export function* executeAPIAction(apiAction: APIActionPayload) {
export function* executeAPIActionSaga(apiAction: APIActionPayload) {
const api: PageAction = yield select(getAction, apiAction.apiId);
const responses: any = yield all(
api.dynamicBindings.map((jsonPath: string) => {
return call(evaluateJSONPath, jsonPath);
return call(evaluateJSONPathSaga, jsonPath);
}),
);
const dynamicBindingMap: Record<string, any> = _.keyBy(
@ -52,11 +50,11 @@ export function* executeAPIAction(apiAction: APIActionPayload) {
});
}
export function* executeQueryAction(queryAction: QueryActionPayload) {
export function* executeQueryActionSaga(queryAction: QueryActionPayload) {
const query: PageAction = yield select(getAction, queryAction.queryId);
const responses: any = yield all(
query.dynamicBindings.map((jsonPath: string) => {
return call(evaluateJSONPath, jsonPath);
return call(evaluateJSONPathSaga, jsonPath);
}),
);
const dynamicBindingMap: Record<string, any> = _.keyBy(
@ -71,28 +69,21 @@ export function* executeQueryAction(queryAction: QueryActionPayload) {
});
}
export function* executeAction(pageRequestAction: ReduxAction<PageRequest>) {
const pageRequest = pageRequestAction.payload;
try {
const pageResponse: PageResponse = yield call(
PageApi.fetchPage,
pageRequest,
export function* executeActionSaga(action: ReduxAction<ActionPayload[]>) {
if (!_.isNil(action.payload)) {
yield all(
action.payload.map((actionPayload: ActionPayload) => {
switch (actionPayload.actionType) {
case "API":
const apiActionPaylod: APIActionPayload = actionPayload as APIActionPayload;
return call(executeAPIActionSaga, apiActionPaylod);
}
return undefined;
}),
);
if (pageRequest.renderMode === RenderModes.CANVAS) {
const normalizedResponse = CanvasWidgetsNormalizer.normalize(
pageResponse,
);
const payload = {
pageWidgetId: normalizedResponse.result,
widgets: normalizedResponse.entities.canvasWidgets,
};
yield put({ type: ReduxActionTypes.UPDATE_CANVAS, payload });
}
} catch (err) {
//TODO(abhinav): REFACTOR THIS
}
}
export function* watchExecuteAction() {
yield takeEvery(ReduxActionTypes.EXECUTE_ACTION, executeAction);
export function* watchExecuteActionSaga() {
yield takeEvery(ReduxActionTypes.EXECUTE_ACTION, executeActionSaga);
}

View File

@ -36,6 +36,6 @@ export function* fetchPageSaga(pageRequestAction: ReduxAction<PageRequest>) {
}
}
export function* watchFetchPage() {
export function* watchFetchPageSaga() {
yield takeEvery(ReduxActionTypes.FETCH_PAGE, fetchPageSaga);
}

View File

@ -1,8 +1,5 @@
// import CanvasWidgetsNormalizer from "../normalizers/CanvasWidgetsNormalizer"
import {
ReduxActionTypes,
ReduxAction,
} from "../constants/ReduxActionConstants";
import { ReduxActionTypes } from "../constants/ReduxActionConstants";
import WidgetCardsPaneApi, {
WidgetCardsPaneResponse,
} from "../api/WidgetCardsPaneApi";

View File

@ -1,12 +1,12 @@
import { all, fork, spawn } from "redux-saga/effects";
import { watchFetchPage } from "../sagas/PageSagas";
import { all, spawn } from "redux-saga/effects";
import { watchFetchPageSaga } from "../sagas/PageSagas";
import { fetchWidgetCardsSaga } from "./WidgetCardsPaneSagas";
import { watchExecuteAction } from "./ActionSagas";
import { watchExecuteActionSaga } from "./ActionSagas";
export function* rootSaga() {
yield all([
spawn(watchFetchPage),
spawn(watchFetchPageSaga),
spawn(fetchWidgetCardsSaga),
spawn(watchExecuteAction),
spawn(watchExecuteActionSaga),
]);
}

View File

@ -1,5 +1,10 @@
import { WidgetType } from "../constants/WidgetConstants";
import { WidgetBuilder, WidgetProps } from "../widgets/BaseWidget";
import { WidgetType, RenderMode } from "../constants/WidgetConstants";
import {
WidgetBuilder,
WidgetProps,
WidgetFunctions,
WidgetDataProps,
} from "../widgets/BaseWidget";
class WidgetFactory {
static widgetMap: Map<WidgetType, WidgetBuilder<WidgetProps>> = new Map();
@ -11,11 +16,22 @@ class WidgetFactory {
this.widgetMap.set(widgetType, widgetBuilder);
}
static createWidget(widgetData: WidgetProps): JSX.Element {
widgetData.key = widgetData.widgetId;
static createWidget(
widgetData: WidgetDataProps,
widgetFunctions: WidgetFunctions,
renderMode: RenderMode,
): JSX.Element {
const widgetProps: WidgetProps = {
key: widgetData.widgetId,
renderMode: renderMode,
...widgetData,
...widgetFunctions,
};
const widgetBuilder = this.widgetMap.get(widgetData.widgetType);
if (widgetBuilder) return widgetBuilder.buildWidget(widgetData);
else {
if (widgetBuilder) {
const widget = widgetBuilder.buildWidget(widgetProps);
return widget;
} else {
const ex: WidgetCreationException = {
message:
"Widget Builder not registered for widget type" +

View File

@ -14,9 +14,10 @@ import { BaseStyle } from "../editorComponents/BaseComponent";
import _ from "lodash";
import React from "react";
import DraggableComponent from "../editorComponents/DraggableComponent";
import { ActionPayload } from "../constants/ActionConstants";
abstract class BaseWidget<
T extends WidgetProps,
T extends WidgetProps & WidgetFunctions,
K extends WidgetState
> extends Component<T, K> {
constructor(props: T) {
@ -147,24 +148,31 @@ export interface DraggableWidget {
}
export interface WidgetBuilder<T extends WidgetProps> {
buildWidget(data: T): JSX.Element;
buildWidget(widgetProps: T): JSX.Element;
}
export interface WidgetProps {
export interface WidgetProps extends WidgetFunctions, WidgetDataProps {
key?: string;
renderMode: RenderMode;
}
export interface WidgetDataProps {
widgetId: string;
widgetType: WidgetType;
widgetName: string;
key?: string;
topRow: number;
leftColumn: number;
bottomRow: number;
rightColumn: number;
parentColumnSpace: number;
parentRowSpace: number;
renderMode: RenderMode;
isVisible?: boolean;
}
export interface WidgetFunctions {
executeAction: (actionPayloads?: ActionPayload[]) => void;
}
export interface WidgetCardProps {
widgetType: WidgetType;
key?: string;

View File

@ -5,13 +5,20 @@ import ButtonComponent from "../editorComponents/ButtonComponent";
import { ActionPayload } from "../constants/ActionConstants";
class ButtonWidget extends BaseWidget<ButtonWidgetProps, WidgetState> {
onButtonClick() {
this.props.executeAction(this.props.onClick);
}
getPageView() {
return (
<ButtonComponent
style={this.getPositionStyle()}
widgetId={this.props.widgetId}
key={this.props.widgetId}
text={this.props.text || "Button"}
text={this.props.text}
onClick={() => {
this.onButtonClick();
}}
/>
);
}

View File

@ -1,5 +1,9 @@
import React from "react";
import BaseWidget, { WidgetProps, WidgetState } from "./BaseWidget";
import BaseWidget, {
WidgetProps,
WidgetState,
WidgetFunctions,
} from "./BaseWidget";
import ContainerComponent from "../editorComponents/ContainerComponent";
import { ContainerOrientation, WidgetType } from "../constants/WidgetConstants";
import WidgetFactory from "../utils/WidgetFactory";
@ -49,7 +53,12 @@ class ContainerWidget extends BaseWidget<
renderChildWidget(childWidgetData: WidgetProps) {
childWidgetData.parentColumnSpace = this.state.snapColumnSpace;
childWidgetData.parentRowSpace = this.state.snapRowSpace;
return WidgetFactory.createWidget(childWidgetData);
const widgetFunctions: WidgetFunctions = this.props as WidgetFunctions;
return WidgetFactory.createWidget(
childWidgetData,
widgetFunctions,
this.props.renderMode,
);
}
getPageView() {