WIP: integrate with fetch pages and save pages APIs

This commit is contained in:
Abhinav Jha 2019-09-18 16:18:56 +05:30
parent 3317bed1d9
commit 673cd75a3f
16 changed files with 221 additions and 137 deletions

View File

@ -1,4 +1,5 @@
import { FetchPageRequest } from "../api/PageApi";
import { ResponseMeta } from "../api/ApiResponses";
import { RenderMode } from "../constants/WidgetConstants";
import {
WidgetProps,
@ -13,6 +14,7 @@ import {
SavePageErrorPayload,
SavePageSuccessPayload,
} from "../constants/ReduxActionConstants";
import { ContainerWidgetProps } from "../widgets/ContainerWidget";
export const fetchPage = (
pageId: string,
@ -27,6 +29,13 @@ export const fetchPage = (
};
};
export const fetchPageError = (payload: ResponseMeta) => {
console.log("FETCH PAGE ERROR", payload);
return {
type: ReduxActionTypes.FETCH_PAGE_ERROR,
};
};
export const addWidget = (
pageId: string,
widget: WidgetProps,
@ -62,10 +71,14 @@ export const loadCanvasWidgets = (
};
};
export const savePage = (payload: SavePagePayload) => {
export const savePage = (
pageId: string,
layoutId: string,
dsl: ContainerWidgetProps<WidgetProps>,
): ReduxAction<SavePagePayload> => {
return {
type: ReduxActionTypes.SAVE_PAGE_INIT,
payload,
payload: { pageId, layoutId, dsl },
};
};

View File

@ -12,6 +12,12 @@ const axiosInstance = axios.create({
baseURL: BASE_URL,
timeout: REQUEST_TIMEOUT_MS,
headers: REQUEST_HEADERS,
withCredentials: true,
//TODO(abhinav): remove this.
auth: {
username: "api_user",
password: "8uA@;&mB:cnvN~{#",
},
});
axiosInstance.interceptors.response.use(
@ -54,6 +60,13 @@ class Api {
);
}
static put(url: string, queryParams?: any, body?: any) {
return axiosInstance.put(
url + this.convertObjectToQueryParams(queryParams),
body,
);
}
static convertObjectToQueryParams(object: any): string {
if (!_.isNil(object)) {
const paramArray: string[] = _.map(_.keys(object), key => {

View File

@ -1,9 +1,10 @@
import { ContentType, DataType } from "../constants/ApiConstants";
export interface APIHeaders {
Accept: ContentType;
"Content-Type": ContentType;
dataType: DataType;
Accept?: ContentType;
"Content-Type"?: ContentType;
dataType?: DataType;
Origin?: string;
}
export interface APIRequest {

View File

@ -1,10 +1,26 @@
export type APIResponseCode = "SUCCESS" | "UNKNOWN";
export type APIResponseError = {
code: number;
message: string;
};
export interface ResponseMeta {
responseCode: APIResponseCode;
message?: string;
}
export type ResponseMeta = {
status: number;
success: boolean;
error?: APIResponseError;
};
export interface ApiResponse {
export type ApiResponse = {
responseMeta: ResponseMeta;
}
data: any;
};
// NO_RESOURCE_FOUND, 1000, "Unable to find {0} with id {1}"
// INVALID_PARAMTER, 4000, "Invalid parameter {0} provided in the input"
// PLUGIN_NOT_INSTALLED, 4001, "Plugin {0} not installed"
// MISSING_PLUGIN_ID, 4002, "Missing plugin id. Please input correct plugin id"
// MISSING_RESOURCE_ID, 4003, "Missing resource id. Please input correct resource id"
// MISSING_PAGE_ID, 4004, "Missing page id. Pleaes input correct page id"
// PAGE_DOES_NOT_EXIST_IN_ORG, 4006, "Page {0} does not belong to the current user {1} organization."
// UNAUTHORIZED_DOMAIN, 4001, "Invalid email domain provided. Please sign in with a valid work email ID"
// INTERNAL_SERVER_ERROR, 5000, "Internal server error while processing request"
// REPOSITORY_SAVE_FAILED, 5001, "Repository save failed."

View File

@ -11,31 +11,50 @@ export interface FetchPageRequest {
}
export interface SavePageRequest {
pageWidget: ContainerWidgetProps<WidgetProps>;
dsl: ContainerWidgetProps<WidgetProps>;
layoutId: string;
pageId: string;
}
export interface PageLayout {
id: string;
dsl: ContainerWidgetProps<any>;
actions: PageAction[];
actions?: PageAction[];
}
export interface FetchPageResponse extends ApiResponse {
layout: PageLayout;
}
export type FetchPageResponse = ApiResponse & {
data: {
id: string;
name: string;
applicationId: string;
layouts: Array<PageLayout>;
};
};
export interface SavePageResponse {
pageId: string;
}
class PageApi extends Api {
static url = "/page";
static url = "/pages";
static getLayoutUpdateURL = (pageId: string, layoutId: string) => {
return `/layouts/${layoutId}/pages/${pageId}`;
};
static fetchPage(pageRequest: FetchPageRequest): Promise<FetchPageResponse> {
return Api.get(PageApi.url + "/" + pageRequest.pageId, pageRequest);
return Api.get(PageApi.url + "/" + pageRequest.pageId);
}
static savePage(savePageRequest: SavePageRequest): Promise<SavePageResponse> {
return Api.post(PageApi.url, undefined, savePageRequest);
const body = { dsl: savePageRequest.dsl };
return Api.put(
PageApi.getLayoutUpdateURL(
savePageRequest.pageId,
savePageRequest.layoutId,
),
undefined,
body,
);
}
}

View File

@ -1,75 +1,75 @@
import { AlertType, MessageIntent } from "../widgets/AlertWidget";
export type EventType =
| "ON_CLICK"
| "ON_HOVER"
| "ON_TOGGLE"
| "ON_LOAD"
| "ON_TEXT_CHANGE"
| "ON_SUBMIT"
| "ON_CHECK_CHANGE"
| "ON_SELECT"
| "ON_DATE_SELECTED"
| "ON_DATE_RANGE_SELECTED"
export type EventType =
| "ON_CLICK"
| "ON_HOVER"
| "ON_TOGGLE"
| "ON_LOAD"
| "ON_TEXT_CHANGE"
| "ON_SUBMIT"
| "ON_CHECK_CHANGE"
| "ON_SELECT"
| "ON_DATE_SELECTED"
| "ON_DATE_RANGE_SELECTED";
export type ActionType =
export type ActionType =
| "API"
| "QUERY"
| "NAVIGATION"
| "ALERT"
| "JS_FUNCTION"
| "SET_VALUE"
| "DOWNLOAD"
| "QUERY"
| "NAVIGATION"
| "ALERT"
| "JS_FUNCTION"
| "SET_VALUE"
| "DOWNLOAD";
export interface ActionPayload {
actionType: ActionType
contextParams: Record<string, string>
actionType: ActionType;
contextParams: Record<string, string>;
}
export interface APIActionPayload extends ActionPayload {
apiId: string
apiId: string;
}
export interface QueryActionPayload extends ActionPayload {
queryId: string
queryId: string;
}
export type NavigationType = "NEW_TAB" | "INLINE"
export type NavigationType = "NEW_TAB" | "INLINE";
export interface NavigateActionPayload extends ActionPayload {
pageUrl: string
navigationType: NavigationType
pageUrl: string;
navigationType: NavigationType;
}
export interface ShowAlertActionPayload extends ActionPayload {
header: string
message: string
alertType: AlertType
intent: MessageIntent
header: string;
message: string;
alertType: AlertType;
intent: MessageIntent;
}
export interface SetValueActionPayload extends ActionPayload {
header: string
message: string
alertType: AlertType
intent: MessageIntent
header: string;
message: string;
alertType: AlertType;
intent: MessageIntent;
}
export interface ExecuteJSActionPayload extends ActionPayload {
jsFunctionId: string
jsFunctionId: string;
}
export type DownloadFiletype = "CSV" | "XLS" | "JSON" | "TXT"
export type DownloadFiletype = "CSV" | "XLS" | "JSON" | "TXT";
export interface DownloadDataActionPayload extends ActionPayload {
data: JSON
fileName: string
fileType: DownloadFiletype
data: JSON;
fileName: string;
fileType: DownloadFiletype;
}
export interface PageAction {
actionId: string
actionType: ActionType
actionName: string
dynamicBindings: string[]
}
actionId: string;
actionType: ActionType;
actionName: string;
dynamicBindings: string[];
}

View File

@ -9,14 +9,12 @@ export type EncodingType = "gzip";
export const PROD_BASE_URL = "https://mobtools.com/api/";
export const MOCK_BASE_URL =
"https://f78ff9dd-2c08-45f1-9bf9-8c670a1bb696.mock.pstmn.io";
export const STAGE_BASE_URL =
"https://14157cb0-190f-4082-a791-886a8df05930.mock.pstmn.io";
export const BASE_URL = MOCK_BASE_URL;
export const REQUEST_TIMEOUT_MS = 2000;
export const STAGE_BASE_URL = "https://appsmith-test.herokuapp.com/api/v1/";
export const BASE_URL = STAGE_BASE_URL;
export const REQUEST_TIMEOUT_MS = 5000;
export const REQUEST_HEADERS: APIHeaders = {
Accept: "application/json",
"Content-Type": "application/json",
dataType: "json",
};
export interface APIException {

View File

@ -26,6 +26,7 @@ export const ReduxActionTypes = {
SAVE_PAGE_INIT: "SAVE_PAGE_INIT",
SAVE_PAGE_SUCCESS: "SAVE_PAGE_SUCCESS",
SAVE_PAGE_ERROR: "SAVE_PAGE_ERROR",
FETCH_PAGE_ERROR: "FETCH_PAGE_ERROR",
};
export type ReduxActionType = (typeof ReduxActionTypes)[keyof typeof ReduxActionTypes];
@ -38,6 +39,7 @@ export interface ReduxAction<T> {
export interface LoadCanvasWidgetsPayload {
pageWidgetId: string;
widgets: { [widgetId: string]: WidgetProps };
layoutId: string;
}
export interface LoadWidgetConfigPayload {

View File

@ -1,5 +1,5 @@
import { normalize, schema, denormalize } from "normalizr";
import { FetchPageResponse } from "../api/PageApi";
import { WidgetProps } from "../widgets/BaseWidget";
import { ContainerWidgetProps } from "../widgets/ContainerWidget";
export const widgetSchema = new schema.Entity(
@ -11,15 +11,15 @@ widgetSchema.define({ children: [widgetSchema] });
class CanvasWidgetsNormalizer {
static normalize(
pageResponse: FetchPageResponse,
dsl: ContainerWidgetProps<WidgetProps>,
): { entities: any; result: any } {
return normalize(pageResponse.layout.dsl, widgetSchema);
return normalize(dsl, widgetSchema);
}
static denormalize(
pageWidgetId: string,
entities: any,
): ContainerWidgetProps<any> {
): ContainerWidgetProps<WidgetProps> {
return denormalize(pageWidgetId, widgetSchema, entities);
}
}

View File

@ -13,7 +13,7 @@ import WidgetCardsPane from "./WidgetCardsPane";
import EditorHeader from "./EditorHeader";
import CanvasWidgetsNormalizer from "../../normalizers/CanvasWidgetsNormalizer";
import { ContainerWidgetProps } from "../../widgets/ContainerWidget";
import { fetchPage, updateWidget } from "../../actions/pageActions";
import { fetchPage, updateWidget, savePage } from "../../actions/pageActions";
import { RenderModes } from "../../constants/WidgetConstants";
import EditorDragLayer from "./EditorDragLayer";
@ -51,12 +51,15 @@ type EditorProps = {
fetchCanvasWidgets: Function;
cards: { [id: string]: WidgetCardProps[] } | any;
updateWidgetProperty: Function;
savePageLayout: Function;
page: string;
currentPageId: string;
currentLayoutId: string;
};
class Editor extends Component<EditorProps> {
componentDidMount() {
this.props.fetchCanvasWidgets("1");
this.props.fetchCanvasWidgets(this.props.currentPageId);
}
public render() {
@ -89,6 +92,8 @@ const mapStateToProps = (state: AppState): EditorReduxState => {
cards: state.ui.widgetCardsPane.cards,
layout,
pageWidgetId: state.ui.editor.pageWidgetId,
currentPageId: state.ui.editor.currentPageId,
currentLayoutId: state.ui.editor.currentLayoutId,
};
};
@ -101,6 +106,11 @@ const mapDispatchToProps = (dispatch: any) => {
widgetProps: WidgetProps,
payload: any,
) => dispatch(updateWidget(propertyType, widgetProps, payload)),
savePageLayout: (
pageId: string,
layoutId: string,
dsl: ContainerWidgetProps<WidgetProps>,
) => dispatch(savePage(pageId, layoutId, dsl)),
};
};

View File

@ -1,5 +0,0 @@
export default function snapToGrid(cellSize: number, x: number, y: number) {
const snappedX = Math.round(x / cellSize) * cellSize
const snappedY = Math.round(y / cellSize) * cellSize
return [snappedX, snappedY]
}

View File

@ -5,7 +5,6 @@ import {
ReduxAction,
} from "../../constants/ReduxActionConstants";
import { WidgetProps } from "../../widgets/BaseWidget";
import CanvasWidgetsNormalizer from "../../normalizers/CanvasWidgetsNormalizer";
const initialState: CanvasWidgetsReduxState = {};
@ -24,18 +23,20 @@ const canvasWidgetsReducer = createReducer(initialState, {
state: CanvasWidgetsReduxState,
action: ReduxAction<{ pageId: string; widget: WidgetProps }>,
) => {
const widget = action.payload.widget;
const widgetTree = CanvasWidgetsNormalizer.denormalize("0", {
canvasWidgets: state,
});
const children = widgetTree.children || [];
children.push(widget);
widgetTree.children = children;
const newState = CanvasWidgetsNormalizer.normalize({
responseMeta: { responseCode: "SUCCESS" },
layout: { dsl: widgetTree, actions: [] },
}).entities;
return newState.canvasWidgets;
// const widget = action.payload.widget;
// const widgetTree = CanvasWidgetsNormalizer.denormalize("0", {
// canvasWidgets: state,
// });
// const children = widgetTree.children || [];
// children.push(widget);
// widgetTree.children = children;
// const newState = CanvasWidgetsNormalizer.normalize({
// responseMeta: { responseCode: "SUCCESS" },
// layout: { dsl: widgetTree, actions: [] },
// }).entities;
// return newState.canvasWidgets;
console.log(action.payload.widget);
return state;
},
});

View File

@ -10,6 +10,8 @@ import { ContainerWidgetProps } from "../../widgets/ContainerWidget";
const initialState: EditorReduxState = {
pageWidgetId: "0",
currentPageId: "5d807e76795dc6000482bc76",
currentLayoutId: "5d807e76795dc6000482bc75",
};
const editorReducer = createReducer(initialState, {
@ -36,6 +38,8 @@ export interface EditorReduxState {
[id: string]: WidgetCardProps[];
};
pageWidgetId: string;
currentPageId: string;
currentLayoutId: string;
}
export default editorReducer;

View File

@ -15,6 +15,7 @@ import ActionAPI, { ActionCreatedResponse } from "../api/ActionAPI";
import { AppState } from "../reducers";
import { JSONPath } from "jsonpath-plus";
import _ from "lodash";
import { extractCurrentDSL } from "./utils";
const getDataTree = (state: AppState) => {
return state.entities;
@ -82,7 +83,7 @@ export function* executeAction(
);
if (pageRequest.renderMode === RenderModes.CANVAS) {
const normalizedResponse = CanvasWidgetsNormalizer.normalize(
pageResponse,
extractCurrentDSL(pageResponse),
);
const payload = {
pageWidgetId: normalizedResponse.result,

View File

@ -8,6 +8,7 @@ import {
loadCanvasWidgets,
savePageError,
savePageSuccess,
fetchPageError,
} from "../actions/pageActions";
import PageApi, {
FetchPageResponse,
@ -16,64 +17,62 @@ import PageApi, {
SavePageRequest,
} from "../api/PageApi";
import { call, put, takeLatest, all } from "redux-saga/effects";
import { RenderModes } from "../constants/WidgetConstants";
import { extractCurrentDSL } from "./utils";
export function* fetchPage(pageRequestAction: ReduxAction<FetchPageRequest>) {
const pageRequest = pageRequestAction.payload;
try {
// const pageResponse: PageResponse = yield call(
// PageApi.fetchPage,
// pageRequest,
// );
if (pageRequest.renderMode === RenderModes.CANVAS) {
const pageResponse = JSON.parse(`{
"responseMeta": {},
"layout": {
"dsl": {
"widgetId": "0",
"type": "CONTAINER_WIDGET",
"snapColumns": 16,
"snapRows": 100,
"topRow": 0,
"bottomRow": 2000,
"leftColumn": 0,
"rightColumn": 1000,
"parentColumnSpace": 1,
"parentRowSpace": 1,
"backgroundColor": "#ffffff",
"renderMode": "CANVAS",
"children": [
{
"widgetId": "1",
"type": "CONTAINER_WIDGET",
"snapColumns": 10,
"snapRows": 10,
"topRow": 1,
"bottomRow": 20,
"leftColumn": 1,
"rightColumn": 16,
"backgroundColor": "#000000",
"renderMode": "CANVAS",
"children": []
}
]
}
}
}`);
const fetchPageResponse: FetchPageResponse = yield call(
PageApi.fetchPage,
pageRequest,
);
if (fetchPageResponse.responseMeta.success) {
const normalizedResponse = CanvasWidgetsNormalizer.normalize(
pageResponse,
extractCurrentDSL(fetchPageResponse),
);
const canvasWidgetsPayload: LoadCanvasWidgetsPayload = {
pageWidgetId: normalizedResponse.result,
widgets: normalizedResponse.entities.canvasWidgets,
layoutId: fetchPageResponse.data.layouts[0].id,
};
yield put(loadCanvasWidgets(canvasWidgetsPayload));
yield put({
type: ReduxActionTypes.LOAD_CANVAS_ACTIONS,
payload: pageResponse.layout.actions,
payload: fetchPageResponse.data.layouts[0].actions, // TODO: Refactor
});
} else {
yield put(fetchPageError(fetchPageResponse.responseMeta));
}
// const fetchPageResponse = JSON.parse(`{
// "responseMeta": {
// "success": true,
// "code": 200
// },
// "data": {
// "id": "5d807e76795dc6000482bc76",
// "applicationId": "5d807e45795dc6000482bc74",
// "layouts": [
// {
// "id": "5d807e76795dc6000482bc75",
// "dsl": {
// "widgetId": "0",
// "type": "CONTAINER_WIDGET",
// "snapColumns": 16,
// "snapRows": 100,
// "topRow": 0,
// "bottomRow": 2000,
// "leftColumn": 0,
// "rightColumn": 1000,
// "parentColumnSpace": 1,
// "parentRowSpace": 1,
// "backgroundColor": "#ffffff",
// "renderMode": "CANVAS",
// "children": []
// }
// }
// ]
// }
// }`);
} catch (err) {
console.log(err);
//TODO(abhinav): REFACTOR THIS
@ -82,7 +81,6 @@ export function* fetchPage(pageRequestAction: ReduxAction<FetchPageRequest>) {
export function* savePage(savePageAction: ReduxAction<SavePageRequest>) {
const savePageRequest = savePageAction.payload;
try {
const savePageResponse: SavePageResponse = yield call(
PageApi.savePage,

View File

@ -0,0 +1,13 @@
import { FetchPageResponse } from "../api/PageApi";
import { ContainerWidgetProps } from "../widgets/ContainerWidget";
import { WidgetProps } from "../widgets/BaseWidget";
export const extractCurrentDSL = (
fetchPageResponse: FetchPageResponse,
): ContainerWidgetProps<WidgetProps> => {
return fetchPageResponse.data.layouts[0].dsl;
};
export default {
extractCurrentDSL,
};