diff --git a/app/client/package.json b/app/client/package.json index 0920148466..8906cbfbeb 100644 --- a/app/client/package.json +++ b/app/client/package.json @@ -7,6 +7,7 @@ "@blueprintjs/datetime": "^3.6.0", "@blueprintjs/icons": "^3.5.0", "@blueprintjs/table": "^3.4.0", + "@types/axios": "^0.14.0", "@types/jest": "^23.3.13", "@types/lodash": "^4.14.120", "@types/moment-timezone": "^0.5.10", diff --git a/app/client/src/actions/CanvasActions.tsx b/app/client/src/actions/CanvasActions.tsx deleted file mode 100644 index 7628757b10..0000000000 --- a/app/client/src/actions/CanvasActions.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { IContainerWidgetProps } from "../widgets/ContainerWidget" -import CanvasWidgetsNormalizer, { widgetSchema } from "../normalizers/CanvasWidgetsNormalizer" -import { LoadCanvasPayload, ActionTypes, ReduxAction } from "../constants/ActionConstants" - -export const loadCanvas = (canvasResponse: PageResponse): ReduxAction => { - const normalizedResponse = CanvasWidgetsNormalizer.normalize(canvasResponse) - return { - type: ActionTypes.LOAD_CANVAS, - payload: { - pageWidgetId: normalizedResponse.result, - widgets: normalizedResponse.entities.canvasWidgets - } - } -} - -export interface PageResponse { - pageWidget: IContainerWidgetProps -} diff --git a/app/client/src/api/Api.tsx b/app/client/src/api/Api.tsx new file mode 100644 index 0000000000..c900a2757d --- /dev/null +++ b/app/client/src/api/Api.tsx @@ -0,0 +1,62 @@ +import _ from "lodash" +import { + BASE_URL, + REQUEST_TIMEOUT_MS, + REQUEST_HEADERS +} from "../constants/ApiConstants" + +const axios = require("axios") + +const axiosInstance = axios.create({ + baseURL: BASE_URL, + timeout: REQUEST_TIMEOUT_MS, + headers: REQUEST_HEADERS +}) + +axiosInstance.interceptors.response.use( + function(response: any) { + // Do something with response data + return response.data + }, + function(error: any) { + if (error.response) { + // The request was made and the server responded with a status code + // that falls out of the range of 2xx + console.log(error.response.data) + console.log(error.response.status) + console.log(error.response.headers) + } else if (error.request) { + // The request was made but no response was received + // `error.request` is an instance of XMLHttpRequest in the browser and an instance of + // http.ClientRequest in node.js + console.log(error.request) + } else { + // Something happened in setting up the request that triggered an Error + console.log("Error", error.message) + } + console.log(error.config) + return Promise.reject(error) + } +) + +class Api { + static get(url: string, queryParams: any) { + return axiosInstance.get(url + this.convertObjectToQueryParams(queryParams)) + } + + static post(url: string, queryParams?: any, body?: any) { + return axiosInstance.post( + url + this.convertObjectToQueryParams(queryParams), + body + ) + } + + static convertObjectToQueryParams(object: any): string { + const paramArray: string[] = _.map(_.keys(object), key => { + return encodeURIComponent(key) + "=" + encodeURIComponent(object[key]) + }) + return "?" + _.join(paramArray, "&") + } +} + +export default Api \ No newline at end of file diff --git a/app/client/src/api/ApiRequests.tsx b/app/client/src/api/ApiRequests.tsx new file mode 100644 index 0000000000..43e52b6e57 --- /dev/null +++ b/app/client/src/api/ApiRequests.tsx @@ -0,0 +1,12 @@ +import { ContentType, DataType, EncodingType } from "../constants/ApiConstants"; + +export interface ApiHeaders { + Accept: ContentType + "Content-Type": ContentType + dataType: DataType + "Accept-Encoding": EncodingType +} + +export interface ApiRequest { + +} diff --git a/app/client/src/api/ApiResponses.tsx b/app/client/src/api/ApiResponses.tsx new file mode 100644 index 0000000000..f469a637f6 --- /dev/null +++ b/app/client/src/api/ApiResponses.tsx @@ -0,0 +1,9 @@ +export type ApiErrorCodes = "INVALID_REQUEST" | "UNKNOWN" + +export interface ResponseMeta { + errorCode?: ApiErrorCodes +} + +export interface ApiResponse { + responseMeta: ResponseMeta +} diff --git a/app/client/src/api/PageApi.tsx b/app/client/src/api/PageApi.tsx new file mode 100644 index 0000000000..fbfe6dbc69 --- /dev/null +++ b/app/client/src/api/PageApi.tsx @@ -0,0 +1,21 @@ +import Api from "./Api" +import { IContainerWidgetProps } from "../widgets/ContainerWidget" +import { ApiResponse } from "./ApiResponses" + +export interface PageRequest { + pageId: string +} + +export interface PageResponse extends ApiResponse { + pageWidget: IContainerWidgetProps +} + +class PageApi extends Api { + static url: string = "/page/" + + static fetchPage(pageRequest: PageRequest): Promise { + return Api.get(PageApi.url + pageRequest.pageId, pageRequest) + } +} + +export default PageApi diff --git a/app/client/src/constants/ActionConstants.tsx b/app/client/src/constants/ActionConstants.tsx index f5208c2ee8..957966faf6 100644 --- a/app/client/src/constants/ActionConstants.tsx +++ b/app/client/src/constants/ActionConstants.tsx @@ -2,13 +2,15 @@ import ContainerWidget from "../widgets/ContainerWidget" import { IWidgetProps } from "../widgets/BaseWidget" export type ActionType = - | "LOAD_CANVAS" + | "UPDATE_CANVAS" + | "FETCH_CANVAS" | "CLEAR_CANVAS" | "DROP_WIDGET_CANVAS" | "REMOVE_WIDGET_CANVAS" | "LOAD_WIDGET_PANE" export const ActionTypes: { [id: string]: ActionType } = { - LOAD_CANVAS: "LOAD_CANVAS", + UPDATE_CANVAS: "UPDATE_CANVAS", + FETCH_CANVAS: "FETCH_CANVAS", CLEAR_CANVAS: "CLEAR_CANVAS", DROP_WIDGET_CANVAS: "DROP_WIDGET_CANVAS", REMOVE_WIDGET_CANVAS: "REMOVE_WIDGET_CANVAS", diff --git a/app/client/src/constants/ApiConstants.tsx b/app/client/src/constants/ApiConstants.tsx new file mode 100644 index 0000000000..a98ea407d7 --- /dev/null +++ b/app/client/src/constants/ApiConstants.tsx @@ -0,0 +1,16 @@ +import { ApiHeaders } from "../api/ApiRequests"; + +export type DataType = "json" | "xml" +export type ContentType = "application/json" | "application/x-www-form-urlencoded" +export type EncodingType = "gzip" + +export const PROD_BASE_URL = "https://mobtools.com/api/" +export const STAGE_BASE_URL = "https://14157cb0-190f-4082-a791-886a8df05930.mock.pstmn.io" +export const BASE_URL = STAGE_BASE_URL +export const REQUEST_TIMEOUT_MS = 2000 +export const REQUEST_HEADERS: ApiHeaders = { + Accept: "application/json", + "Content-Type": "application/json", + dataType: "json", + "Accept-Encoding": "gzip" +} diff --git a/app/client/src/index.tsx b/app/client/src/index.tsx index c1adf98e05..edb7991113 100755 --- a/app/client/src/index.tsx +++ b/app/client/src/index.tsx @@ -7,13 +7,20 @@ import Editor from "./pages/Editor"; import PageNotFound from "./pages/PageNotFound"; import * as serviceWorker from "./serviceWorker"; import { BrowserRouter, Route, Switch } from "react-router-dom"; -import { createStore } from "redux"; +import { createStore, applyMiddleware } from "redux"; import appReducer from "./reducers"; import WidgetBuilderRegistry from "./utils/WidgetRegistry"; import { ThemeProvider, theme } from "./constants/DefaultTheme"; +import createSagaMiddleware from 'redux-saga' +import { rootSaga } from "./sagas" +import { ActionType } from "./constants/ActionConstants"; WidgetBuilderRegistry.registerWidgetBuilders(); -const store = createStore(appReducer); +const sagaMiddleware = createSagaMiddleware() +const store = createStore(appReducer, applyMiddleware(sagaMiddleware)); +sagaMiddleware.run(rootSaga) +export const action = (type: ActionType) => store.dispatch({type}) + ReactDOM.render( diff --git a/app/client/src/mockResponses/WidgetPaneResponse.tsx b/app/client/src/mockResponses/WidgetPaneResponse.tsx index 865c417f42..dd2f662284 100644 --- a/app/client/src/mockResponses/WidgetPaneResponse.tsx +++ b/app/client/src/mockResponses/WidgetPaneResponse.tsx @@ -8,6 +8,7 @@ const WidgetPaneResponse: WidgetPaneReduxState = { text: "Lorem Ipsum", renderMode: RenderModes.COMPONENT_PANE, bottomRow: 50, + widgetId: "1", rightColumn: 200 }, { @@ -15,6 +16,7 @@ const WidgetPaneResponse: WidgetPaneReduxState = { text: "Lorem Ipsum", renderMode: RenderModes.COMPONENT_PANE, bottomRow: 50, + widgetId: "2", rightColumn: 200 }, { @@ -22,6 +24,7 @@ const WidgetPaneResponse: WidgetPaneReduxState = { renderMode: RenderModes.COMPONENT_PANE, backgroundColor: "#434343", bottomRow: 50, + widgetId: "3", rightColumn: 200 } ] diff --git a/app/client/src/normalizers/CanvasWidgetsNormalizer.tsx b/app/client/src/normalizers/CanvasWidgetsNormalizer.tsx index 9dcd51aaa9..25220b4d49 100644 --- a/app/client/src/normalizers/CanvasWidgetsNormalizer.tsx +++ b/app/client/src/normalizers/CanvasWidgetsNormalizer.tsx @@ -1,5 +1,5 @@ import { normalize, schema, denormalize } from 'normalizr'; -import { PageResponse } from '../actions/CanvasActions'; +import { PageResponse } from '../api/PageApi'; import { IContainerWidgetProps } from '../widgets/ContainerWidget'; export const widgetSchema = new schema.Entity('canvasWidgets', { }, { idAttribute: "widgetId" }, ); diff --git a/app/client/src/pages/Editor/Canvas.tsx b/app/client/src/pages/Editor/Canvas.tsx index 51fa9fc48e..fd7a4485b7 100644 --- a/app/client/src/pages/Editor/Canvas.tsx +++ b/app/client/src/pages/Editor/Canvas.tsx @@ -2,16 +2,15 @@ import React, { Component } from "react" import { connect } from "react-redux" import { AppState } from "../../reducers" import WidgetFactory from "../../utils/WidgetFactory" -import { loadCanvas } from "../../actions/CanvasActions"; -import CanvasResponse from "../../mockResponses/CanvasResponse"; -import { denormalize } from "normalizr"; import CanvasWidgetsNormalizer, { widgetSchema } from "../../normalizers/CanvasWidgetsNormalizer"; import { IContainerWidgetProps } from "../../widgets/ContainerWidget"; +import { action } from "../../index" +import { ActionTypes } from "../../constants/ActionConstants"; class Canvas extends Component<{ pageWidget: IContainerWidgetProps, loadCanvas: Function }> { componentDidMount() { - this.props.loadCanvas() + action(ActionTypes.FETCH_CANVAS) } render() { @@ -35,9 +34,6 @@ const mapStateToProps = (state: AppState, props: any) => { const mapDispatchToProps = (dispatch: any) => { return { - loadCanvas: () => { - dispatch(loadCanvas({ pageWidget: CanvasResponse })) - } } } diff --git a/app/client/src/reducers/entityReducers/canvasWidgetsReducers.tsx b/app/client/src/reducers/entityReducers/canvasWidgetsReducers.tsx index bc371c15cb..7d3ac084ad 100644 --- a/app/client/src/reducers/entityReducers/canvasWidgetsReducers.tsx +++ b/app/client/src/reducers/entityReducers/canvasWidgetsReducers.tsx @@ -22,7 +22,7 @@ const initialState: CanvasWidgetsReduxState = { } const canvasWidgetsReducer = createReducer(initialState, { - [ActionTypes.LOAD_CANVAS]: ( + [ActionTypes.UPDATE_CANVAS]: ( state: CanvasWidgetsReduxState, action: ReduxAction ) => { diff --git a/app/client/src/reducers/uiReducers/canvasReducer.tsx b/app/client/src/reducers/uiReducers/canvasReducer.tsx index 9dd749faa1..be0923b80f 100644 --- a/app/client/src/reducers/uiReducers/canvasReducer.tsx +++ b/app/client/src/reducers/uiReducers/canvasReducer.tsx @@ -10,7 +10,7 @@ const initialState: CanvasReduxState = { } const canvasReducer = createReducer(initialState, { - [ActionTypes.LOAD_CANVAS]: ( + [ActionTypes.UPDATE_CANVAS]: ( state: CanvasReduxState, action: ReduxAction ) => { diff --git a/app/client/src/sagas/CanvasSagas.tsx b/app/client/src/sagas/CanvasSagas.tsx new file mode 100644 index 0000000000..1505208328 --- /dev/null +++ b/app/client/src/sagas/CanvasSagas.tsx @@ -0,0 +1,27 @@ +import CanvasWidgetsNormalizer, { +} from "../normalizers/CanvasWidgetsNormalizer" +import { + ActionTypes, +} from "../constants/ActionConstants" +import PageApi, { PageResponse } from "../api/PageApi" +import { call, put, takeLeading, all, takeEvery } from "redux-saga/effects" + +export function* fetchCanvas() { + const pageResponse: PageResponse = yield call(PageApi.fetchPage, { + pageId: "123" + }) + const normalizedResponse = CanvasWidgetsNormalizer.normalize(pageResponse) + const payload = { + pageWidgetId: normalizedResponse.result, + widgets: normalizedResponse.entities.canvasWidgets + } + yield put({ type: ActionTypes.UPDATE_CANVAS, payload }) +} + +export function* watchFetchCanvas() { + yield takeEvery(ActionTypes.FETCH_CANVAS, fetchCanvas) +} + +export function* canvasSagas() { + yield all([fetchCanvas(), watchFetchCanvas()]) +} diff --git a/app/client/src/sagas/index.tsx b/app/client/src/sagas/index.tsx new file mode 100644 index 0000000000..850156abfa --- /dev/null +++ b/app/client/src/sagas/index.tsx @@ -0,0 +1,6 @@ +import { all } from "redux-saga/effects" +import { canvasSagas } from "../sagas/CanvasSagas" + +export function* rootSaga() { + yield all([canvasSagas()]) +} diff --git a/app/client/yarn.lock b/app/client/yarn.lock index b5b37fe14b..fc8e3a6339 100755 --- a/app/client/yarn.lock +++ b/app/client/yarn.lock @@ -914,6 +914,12 @@ "@svgr/core" "^2.4.1" loader-utils "^1.1.0" +"@types/axios@^0.14.0": + version "0.14.0" + resolved "https://registry.yarnpkg.com/@types/axios/-/axios-0.14.0.tgz#ec2300fbe7d7dddd7eb9d3abf87999964cafce46" + dependencies: + axios "*" + "@types/dom4@^2.0.1": version "2.0.1" resolved "https://registry.yarnpkg.com/@types/dom4/-/dom4-2.0.1.tgz#506d5781b9bcab81bd9a878b198aec7dee2a6033" @@ -1458,7 +1464,7 @@ aws4@^1.8.0: version "1.8.0" resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f" -axios@^0.18.0: +axios@*, axios@^0.18.0: version "0.18.0" resolved "https://registry.yarnpkg.com/axios/-/axios-0.18.0.tgz#32d53e4851efdc0a11993b6cd000789d70c05102" dependencies: