From 1f40d135f3a41e30f896b00c245aaefe099d9bc8 Mon Sep 17 00:00:00 2001 From: Nikhil Nandagopal Date: Mon, 11 Nov 2019 11:42:52 +0000 Subject: [PATCH] Release --- app/client/src/actions/actionActions.ts | 8 -- app/client/src/actions/controlActions.tsx | 4 + app/client/src/actions/initActions.ts | 4 +- .../designSystems/appsmith/TableComponent.tsx | 67 ++++++++++ .../components/editorComponents/Sidebar.tsx | 1 + app/client/src/constants/BindingsConstants.ts | 1 + app/client/src/constants/DefaultTheme.tsx | 10 ++ .../src/constants/ReduxActionConstants.tsx | 5 +- app/client/src/pages/AppViewer/index.tsx | 120 +++++++++++------- app/client/src/pages/Editor/PropertyPane.tsx | 11 +- app/client/src/pages/Editor/WidgetsEditor.tsx | 11 +- app/client/src/pages/Editor/index.tsx | 8 +- .../propertyPaneConfigReducer.tsx | 27 +--- app/client/src/sagas/ActionSagas.ts | 20 ++- app/client/src/sagas/BindingsSagas.ts | 2 +- app/client/src/sagas/InitSagas.ts | 35 ++++- app/client/src/sagas/PageSagas.tsx | 51 +++++--- app/client/src/sagas/WidgetOperationSagas.tsx | 4 + app/client/src/selectors/appViewSelectors.tsx | 8 +- app/client/src/selectors/editorSelectors.tsx | 4 +- .../src/selectors/propertyPaneSelectors.tsx | 7 +- app/client/src/utils/AppsmithUtils.tsx | 2 + app/client/src/utils/DynamicBindingUtils.ts | 31 +++-- app/client/src/widgets/BaseWidget.tsx | 10 ++ app/client/src/widgets/TableWidget.tsx | 29 +++-- 25 files changed, 330 insertions(+), 150 deletions(-) create mode 100644 app/client/src/components/designSystems/appsmith/TableComponent.tsx diff --git a/app/client/src/actions/actionActions.ts b/app/client/src/actions/actionActions.ts index d16a82796e..3ec32ee13c 100644 --- a/app/client/src/actions/actionActions.ts +++ b/app/client/src/actions/actionActions.ts @@ -22,13 +22,6 @@ export const fetchActions = () => { }; }; -export const fetchApiConfig = (payload: { id: string }) => { - return { - type: ReduxActionTypes.FETCH_ACTION, - payload, - }; -}; - export const executeAction = (payload: ActionPayload[]) => { return { type: ReduxActionTypes.EXECUTE_ACTION, @@ -67,7 +60,6 @@ export const deleteActionSuccess = (payload: { id: string }) => { export default { createAction: createActionRequest, fetchActions, - fetchApiConfig, runAction: executeAction, deleteAction, deleteActionSuccess, diff --git a/app/client/src/actions/controlActions.tsx b/app/client/src/actions/controlActions.tsx index df2720b4e1..e5df6975d0 100644 --- a/app/client/src/actions/controlActions.tsx +++ b/app/client/src/actions/controlActions.tsx @@ -2,11 +2,13 @@ import { ReduxActionTypes, ReduxAction, } from "../constants/ReduxActionConstants"; +import { RenderMode } from "../constants/WidgetConstants"; export const updateWidgetProperty = ( widgetId: string, propertyName: string, propertyValue: any, + renderMode: RenderMode, ): ReduxAction => { return { type: ReduxActionTypes.UPDATE_WIDGET_PROPERTY_REQUEST, @@ -14,6 +16,7 @@ export const updateWidgetProperty = ( widgetId, propertyName, propertyValue, + renderMode, }, }; }; @@ -22,4 +25,5 @@ export interface UpdateWidgetPropertyPayload { widgetId: string; propertyName: string; propertyValue: any; + renderMode: RenderMode; } diff --git a/app/client/src/actions/initActions.ts b/app/client/src/actions/initActions.ts index 3e837c23df..eddda079b6 100644 --- a/app/client/src/actions/initActions.ts +++ b/app/client/src/actions/initActions.ts @@ -3,6 +3,6 @@ import { ReduxActionWithoutPayload, } from "../constants/ReduxActionConstants"; -export const initAppData = (): ReduxActionWithoutPayload => ({ - type: ReduxActionTypes.INIT_APP_DATA, +export const initEditor = (): ReduxActionWithoutPayload => ({ + type: ReduxActionTypes.INIT_EDITOR, }); diff --git a/app/client/src/components/designSystems/appsmith/TableComponent.tsx b/app/client/src/components/designSystems/appsmith/TableComponent.tsx new file mode 100644 index 0000000000..67a94672e6 --- /dev/null +++ b/app/client/src/components/designSystems/appsmith/TableComponent.tsx @@ -0,0 +1,67 @@ +import BaseTable, { Column } from "react-base-table"; +import styled from "styled-components"; +import React from "react"; +import { noop } from "../../../utils/AppsmithUtils"; + +const StyledTable = styled(BaseTable)` + .row-selected { + background-color: ${props => + props.theme.widgets.tableWidget.selectHighlightColor}; + } +`; + +export interface Column { + key: string; + dataKey: string; + title: string; + width: number; +} + +export interface ReactBaseTableProps { + width: number; + height: number; + columns: Column[]; + data: object[]; + maxHeight: number; + selectedRowIndex?: number; +} + +export interface SelectableTableProps extends ReactBaseTableProps { + onRowClick: (rowData: object, rowIndex: number) => void; +} + +export default class SelectableTable extends React.PureComponent< + SelectableTableProps +> { + static defaultProps = {}; + + _onClick = ({ rowData, rowIndex }: { rowData: object; rowIndex: number }) => { + if (this.props.selectedRowIndex !== rowIndex) { + this.props.onRowClick(rowData, rowIndex); + } + }; + + _rowClassName = ({ rowIndex }: { rowIndex: number }) => { + const { selectedRowIndex } = this.props; + return selectedRowIndex === rowIndex ? "row-selected" : ""; + }; + + render() { + const { onRowClick, ...rest } = this.props; + return ( + + ); + } +} + +SelectableTable.defaultProps = { + ...BaseTable.defaultProps, + onRowSelect: noop, + onSelectedRowsChange: noop, +}; diff --git a/app/client/src/components/editorComponents/Sidebar.tsx b/app/client/src/components/editorComponents/Sidebar.tsx index b6c37baafb..9042c524c6 100644 --- a/app/client/src/components/editorComponents/Sidebar.tsx +++ b/app/client/src/components/editorComponents/Sidebar.tsx @@ -13,6 +13,7 @@ const SidebarWrapper = styled.div` background-color: ${props => props.theme.colors.paneBG}; padding: 5px 10px; color: ${props => props.theme.colors.textOnDarkBG}; + overflow-y: scroll; `; class Sidebar extends React.Component { diff --git a/app/client/src/constants/BindingsConstants.ts b/app/client/src/constants/BindingsConstants.ts index 164fa33dcd..e3fc912a00 100644 --- a/app/client/src/constants/BindingsConstants.ts +++ b/app/client/src/constants/BindingsConstants.ts @@ -3,3 +3,4 @@ export type NamePathBindingMap = Record; export const DATA_BIND_REGEX = /{{(\s*[\w\.\[\]\d]+\s*)}}/g; export const DATA_PATH_REGEX = /[\w\.\[\]\d]+/; +/* eslint-enable no-useless-escape */ diff --git a/app/client/src/constants/DefaultTheme.tsx b/app/client/src/constants/DefaultTheme.tsx index 7bd30f7019..bf7cd64c0a 100644 --- a/app/client/src/constants/DefaultTheme.tsx +++ b/app/client/src/constants/DefaultTheme.tsx @@ -49,6 +49,11 @@ export type Theme = { minWidth: number; minHeight: number; }; + widgets: { + tableWidget: { + selectHighlightColor: Color; + }; + }; }; export const getColorWithOpacity = (color: Color, opacity: number) => { @@ -142,6 +147,11 @@ export const theme: Theme = { minWidth: 300, minHeight: 300, }, + widgets: { + tableWidget: { + selectHighlightColor: Colors.GEYSER_LIGHT, + }, + }, }; export { css, createGlobalStyle, keyframes, ThemeProvider }; diff --git a/app/client/src/constants/ReduxActionConstants.tsx b/app/client/src/constants/ReduxActionConstants.tsx index c9892b58c0..1b33f6a58b 100644 --- a/app/client/src/constants/ReduxActionConstants.tsx +++ b/app/client/src/constants/ReduxActionConstants.tsx @@ -2,7 +2,7 @@ import { WidgetProps, WidgetCardProps } from "../widgets/BaseWidget"; import { RefObject } from "react"; export const ReduxActionTypes: { [key: string]: string } = { - INIT_APP_DATA: "INIT_APP_DATA", + INIT_EDITOR: "INIT_EDITOR", REPORT_ERROR: "REPORT_ERROR", FLUSH_ERRORS: "FLUSH_ERRORS", UPDATE_CANVAS: "UPDATE_CANVAS", @@ -48,7 +48,6 @@ export const ReduxActionTypes: { [key: string]: string } = { CREATE_ACTION_SUCCESS: "CREATE_ACTION_SUCCESS", FETCH_ACTIONS_INIT: "FETCH_ACTIONS_INIT", FETCH_ACTIONS_SUCCESS: "FETCH_ACTIONS_SUCCESS", - FETCH_ACTION: "FETCH_ACTION", UPDATE_ACTION_INIT: "UPDATE_ACTION_INIT", UPDATE_ACTION_SUCCESS: "UPDATE_ACTION_SUCCESS", DELETE_ACTION_INIT: "DELETE_ACTION_INIT", @@ -77,7 +76,7 @@ export const ReduxActionTypes: { [key: string]: string } = { export type ReduxActionType = (typeof ReduxActionTypes)[keyof typeof ReduxActionTypes]; export const ReduxActionErrorTypes: { [key: string]: string } = { - INIT_APP_DATA_ERROR: "INIT_APP_DATA_ERROR", + INIT_EDITOR_ERROR: "INIT_EDITOR_ERROR", API_ERROR: "API_ERROR", WIDGET_DELETE_ERROR: "WIDGET_DELETE_ERROR", WIDGET_MOVE_ERROR: "WIDGET_MOVE_ERROR", diff --git a/app/client/src/pages/AppViewer/index.tsx b/app/client/src/pages/AppViewer/index.tsx index 2cdee336a8..871ed62a24 100644 --- a/app/client/src/pages/AppViewer/index.tsx +++ b/app/client/src/pages/AppViewer/index.tsx @@ -24,6 +24,9 @@ import { Link } from "react-router-dom"; import { theme } from "../../constants/DefaultTheme"; import SideNav, { SideNavItem } from "./viewer/SideNav"; import AppViewerHeader from "./viewer/AppViewerHeader"; +import { updateWidgetProperty } from "../../actions/controlActions"; +import { RenderModes } from "../../constants/WidgetConstants"; +import { WidgetFunctionsContext } from "../Editor/WidgetsEditor"; const AppViewWrapper = styled.div` margin-top: ${props => props.theme.headerHeight}; @@ -79,7 +82,6 @@ export type AppViewerProps = { currentRoutePageId?: string; currentDSLPageId?: string; currentLayoutId?: string; - executeAction: Function; fetchPageWidgets: Function; pages?: PageListPayload; dsl?: ContainerWidgetProps; @@ -88,6 +90,12 @@ export type AppViewerProps = { match: any; location: any; history: any; + executeAction: (actionPayloads?: ActionPayload[]) => void; + updateWidgetProperty: ( + widgetId: string, + propertyName: string, + propertyValue: any, + ) => void; }; class AppViewer extends Component { @@ -124,52 +132,59 @@ class AppViewer extends Component { page => page.pageId === this.props.currentRoutePageId, ); return ( - - - - {items && ( - - + + + + {items && ( + + - - )} - {this.props.isFetching && ( - - - - )} - {!this.props.isFetching && !this.props.dsl && items && ( - - - } - title="This page seems to be blank" - description={ -

- Please add widgets to this page in the - Appsmith Editor -

- } - /> -
- )} - {this.props.dsl && } -
-
+ /> +
+ )} + {this.props.isFetching && ( + + + + )} + {!this.props.isFetching && !this.props.dsl && items && ( + + + } + title="This page seems to be blank" + description={ +

+ Please add widgets to this page in the + Appsmith Editor +

+ } + /> +
+ )} + {this.props.dsl && } +
+
+ ); } } @@ -185,6 +200,19 @@ const mapStateToProps = (state: AppState, props: AppViewerProps) => ({ const mapDispatchToProps = (dispatch: any) => ({ executeAction: (actionPayloads?: ActionPayload[]) => dispatch(executeAction(actionPayloads)), + updateWidgetProperty: ( + widgetId: string, + propertyName: string, + propertyValue: any, + ) => + dispatch( + updateWidgetProperty( + widgetId, + propertyName, + propertyValue, + RenderModes.PAGE, + ), + ), fetchPageWidgets: (pageId: string) => { dispatch({ type: ReduxActionTypes.FETCH_PUBLISHED_PAGE_INIT, diff --git a/app/client/src/pages/Editor/PropertyPane.tsx b/app/client/src/pages/Editor/PropertyPane.tsx index 28f9750b9c..56a4c7f8fc 100644 --- a/app/client/src/pages/Editor/PropertyPane.tsx +++ b/app/client/src/pages/Editor/PropertyPane.tsx @@ -16,6 +16,7 @@ import { import Popper from "./Popper"; import { ControlProps } from "../../components/propertyControls/BaseControl"; +import { RenderModes } from "../../constants/WidgetConstants"; const PropertySectionLabel = styled.div` text-transform: uppercase; @@ -143,7 +144,15 @@ const mapDispatchToProps = (dispatch: any): PropertyPaneFunctions => { widgetId: string, propertyName: string, propertyValue: any, - ) => dispatch(updateWidgetProperty(widgetId, propertyName, propertyValue)), + ) => + dispatch( + updateWidgetProperty( + widgetId, + propertyName, + propertyValue, + RenderModes.CANVAS, + ), + ), }; }; diff --git a/app/client/src/pages/Editor/WidgetsEditor.tsx b/app/client/src/pages/Editor/WidgetsEditor.tsx index 4634a9bb8c..e4b5266e7a 100644 --- a/app/client/src/pages/Editor/WidgetsEditor.tsx +++ b/app/client/src/pages/Editor/WidgetsEditor.tsx @@ -16,6 +16,7 @@ import { getDenormalizedDSL } from "../../selectors/editorSelectors"; import { ContainerWidgetProps } from "../../widgets/ContainerWidget"; import { ReduxActionTypes } from "../../constants/ReduxActionConstants"; import { updateWidgetProperty } from "../../actions/controlActions"; +import { RenderModes } from "../../constants/WidgetConstants"; const EditorWrapper = styled.div` display: flex; @@ -95,7 +96,15 @@ const mapDispatchToProps = (dispatch: any) => { widgetId: string, propertyName: string, propertyValue: any, - ) => dispatch(updateWidgetProperty(widgetId, propertyName, propertyValue)), + ) => + dispatch( + updateWidgetProperty( + widgetId, + propertyName, + propertyValue, + RenderModes.CANVAS, + ), + ), executeAction: (actionPayloads?: ActionPayload[]) => dispatch(executeAction(actionPayloads)), updateWidget: ( diff --git a/app/client/src/pages/Editor/index.tsx b/app/client/src/pages/Editor/index.tsx index 8f141b9062..557f97c13d 100644 --- a/app/client/src/pages/Editor/index.tsx +++ b/app/client/src/pages/Editor/index.tsx @@ -17,7 +17,7 @@ import { PageListPayload, } from "../../constants/ReduxActionConstants"; import { Dialog, Classes, AnchorButton } from "@blueprintjs/core"; -import { initAppData } from "../../actions/initActions"; +import { initEditor } from "../../actions/initActions"; type EditorProps = { currentPageName: string; @@ -27,7 +27,7 @@ type EditorProps = { currentPageId: string; publishApplication: Function; previewPage: Function; - initData: Function; + initEditor: Function; createPage: Function; pages: PageListPayload; switchPage: (pageId: string) => void; @@ -41,7 +41,7 @@ class Editor extends Component { }; componentDidMount() { - this.props.initData(); + this.props.initEditor(); } componentDidUpdate(currently: EditorProps) { const previously = this.props; @@ -125,7 +125,7 @@ const mapStateToProps = (state: AppState) => ({ const mapDispatchToProps = (dispatch: any) => { return { - initData: () => dispatch(initAppData()), + initEditor: () => dispatch(initEditor()), publishApplication: (applicationId: string) => { dispatch({ type: ReduxActionTypes.PUBLISH_APPLICATION_INIT, diff --git a/app/client/src/reducers/entityReducers/propertyPaneConfigReducer.tsx b/app/client/src/reducers/entityReducers/propertyPaneConfigReducer.tsx index c050d7f732..00c196ec10 100644 --- a/app/client/src/reducers/entityReducers/propertyPaneConfigReducer.tsx +++ b/app/client/src/reducers/entityReducers/propertyPaneConfigReducer.tsx @@ -3,12 +3,14 @@ import { ReduxActionTypes, ReduxAction, } from "../../constants/ReduxActionConstants"; -import PropertyPaneConfigResponse from "../../mockResponses/PropertyPaneConfigResponse"; import { InputControlProps } from "../../components/propertyControls/InputTextControl"; import { DropDownControlProps } from "../../components/propertyControls/DropDownControl"; import { ControlProps } from "../../components/propertyControls/BaseControl"; +import { WidgetType } from "../../constants/WidgetConstants"; -const initialState: PropertyPaneConfigState = PropertyPaneConfigResponse; +const initialState: PropertyPaneConfigState = { + configVersion: 0, +}; export type ControlConfig = | InputControlProps @@ -24,30 +26,13 @@ export interface PropertySection { children: (ControlConfig | PropertySection)[]; } -export interface PropertyConfig { - BUTTON_WIDGET: PropertySection[]; - TEXT_WIDGET: PropertySection[]; - IMAGE_WIDGET: PropertySection[]; - INPUT_WIDGET: PropertySection[]; - SWITCH_WIDGET: PropertySection[]; - CONTAINER_WIDGET: PropertySection[]; - SPINNER_WIDGET: PropertySection[]; - DATE_PICKER_WIDGET: PropertySection[]; - TABLE_WIDGET: PropertySection[]; - DROP_DOWN_WIDGET: PropertySection[]; - CHECKBOX_WIDGET: PropertySection[]; - FILE_PICKER_WIDGET: PropertySection[]; - RADIO_GROUP_WIDGET: PropertySection[]; -} +export type PropertyConfig = Record; export interface PropertyPaneConfigState { - config: PropertyConfig; + config?: PropertyConfig; configVersion: number; } -/** - * TODO: Remove hardcoding of config response - */ const propertyPaneConfigReducer = createReducer(initialState, { [ReduxActionTypes.FETCH_PROPERTY_PANE_CONFIGS_SUCCESS]: ( state: PropertyPaneConfigState, diff --git a/app/client/src/sagas/ActionSagas.ts b/app/client/src/sagas/ActionSagas.ts index fe5c0a8ce0..3f9789be6d 100644 --- a/app/client/src/sagas/ActionSagas.ts +++ b/app/client/src/sagas/ActionSagas.ts @@ -12,9 +12,9 @@ import { takeEvery, takeLatest, } from "redux-saga/effects"; -import { initialize } from "redux-form"; import { ActionPayload, PageAction } from "../constants/ActionConstants"; import ActionAPI, { + ActionApiResponse, ActionCreateUpdateResponse, ExecuteActionRequest, RestAction, @@ -24,7 +24,6 @@ import _ from "lodash"; import { mapToPropList } from "../utils/AppsmithUtils"; import AppToaster from "../components/editorComponents/ToastComponent"; import { GenericApiResponse } from "../api/ApiResponses"; -import { API_EDITOR_FORM_NAME } from "../constants/forms"; import { createActionSuccess, deleteActionSuccess, @@ -33,6 +32,7 @@ import { import { API_EDITOR_ID_URL, API_EDITOR_URL } from "../constants/routes"; import { getDynamicBoundValue } from "../utils/DynamicBindingUtils"; import history from "../utils/history"; +import { createUpdateBindingsMap } from "../actions/bindingActions"; const getDataTree = (state: AppState): DataTree => { return state.entities; @@ -70,7 +70,9 @@ export function* executeAPIQueryActionSaga(apiAction: { actionId: string }) { }); executeActionRequest.params = mapToPropList(dynamicBindings); } - const response = yield ActionAPI.executeAction(executeActionRequest); + const response: ActionApiResponse = yield ActionAPI.executeAction( + executeActionRequest, + ); let payload = response; if (response.responseMeta && response.responseMeta.error) { payload = { @@ -112,6 +114,7 @@ export function* createActionSaga(actionPayload: ReduxAction) { intent: Intent.SUCCESS, }); yield put(createActionSuccess(response.data)); + yield put(createUpdateBindingsMap()); history.push(API_EDITOR_ID_URL(response.data.id)); } } @@ -133,14 +136,6 @@ export function* fetchActionsSaga() { } } -export function* fetchActionSaga(actionPayload: ReduxAction<{ id: string }>) { - const response: GenericApiResponse = yield ActionAPI.fetchAPI( - actionPayload.payload.id, - ); - const data = response.data; - yield put(initialize(API_EDITOR_FORM_NAME, data)); -} - export function* updateActionSaga( actionPayload: ReduxAction<{ data: RestAction }>, ) { @@ -153,6 +148,7 @@ export function* updateActionSaga( intent: Intent.SUCCESS, }); yield put(updateActionSuccess({ data: response.data })); + yield put(createUpdateBindingsMap()); } else { AppToaster.show({ message: "Error occurred when updating action", @@ -172,6 +168,7 @@ export function* deleteActionSaga(actionPayload: ReduxAction<{ id: string }>) { intent: Intent.SUCCESS, }); yield put(deleteActionSuccess({ id })); + yield put(createUpdateBindingsMap()); history.push(API_EDITOR_URL); } else { AppToaster.show({ @@ -186,7 +183,6 @@ export function* watchActionSagas() { takeEvery(ReduxActionTypes.FETCH_ACTIONS_INIT, fetchActionsSaga), takeLatest(ReduxActionTypes.EXECUTE_ACTION, executeActionSaga), takeLatest(ReduxActionTypes.CREATE_ACTION_INIT, createActionSaga), - takeEvery(ReduxActionTypes.FETCH_ACTION, fetchActionSaga), takeLatest(ReduxActionTypes.UPDATE_ACTION_INIT, updateActionSaga), takeLatest(ReduxActionTypes.DELETE_ACTION_INIT, deleteActionSaga), ]); diff --git a/app/client/src/sagas/BindingsSagas.ts b/app/client/src/sagas/BindingsSagas.ts index b76fdaf023..7b98da3c2d 100644 --- a/app/client/src/sagas/BindingsSagas.ts +++ b/app/client/src/sagas/BindingsSagas.ts @@ -7,7 +7,7 @@ function* createUpdateBindingsMapData() { const data: AppState = yield select(); const map: Record = {}; data.entities.actions.data.forEach(action => { - map[action.name] = `$.apiData.${action.id}`; + map[action.name] = `$.apiData.${action.id}.body`; }); Object.keys(data.entities.canvasWidgets).forEach(widgetId => { const name = data.entities.canvasWidgets[widgetId].widgetName; diff --git a/app/client/src/sagas/InitSagas.ts b/app/client/src/sagas/InitSagas.ts index e29ce06de9..11560117c2 100644 --- a/app/client/src/sagas/InitSagas.ts +++ b/app/client/src/sagas/InitSagas.ts @@ -1,5 +1,8 @@ import { all, select, put, takeLatest, take } from "redux-saga/effects"; -import { ReduxActionTypes } from "../constants/ReduxActionConstants"; +import { + ReduxAction, + ReduxActionTypes, +} from "../constants/ReduxActionConstants"; import { getPropertyPaneConfigsId, getCurrentPageId, @@ -10,13 +13,13 @@ import { fetchActions } from "../actions/actionActions"; import { fetchDatasources } from "../actions/datasourcesActions"; import { createUpdateBindingsMap } from "../actions/bindingActions"; -function* fetchAppDataSaga() { +function* initializeEditorSaga() { // Step 1: Start getting all the data needed by the app const propertyPaneConfigsId = yield select(getPropertyPaneConfigsId); const currentPageId = yield select(getCurrentPageId); yield all([ put(fetchPageList()), - put(fetchEditorConfigs(propertyPaneConfigsId)), + put(fetchEditorConfigs({ propertyPaneConfigsId })), put(fetchPage(currentPageId)), put(fetchActions()), put(fetchDatasources()), @@ -32,6 +35,28 @@ function* fetchAppDataSaga() { yield put(createUpdateBindingsMap()); } -export default function* watchInitSagas() { - yield all([takeLatest(ReduxActionTypes.INIT_APP_DATA, fetchAppDataSaga)]); +export function* initializeAppViewerSaga( + action: ReduxAction<{ pageId: string }>, +) { + yield all([put(fetchPageList()), put(fetchActions())]); + yield all([ + take(ReduxActionTypes.FETCH_PAGE_LIST_SUCCESS), + take(ReduxActionTypes.FETCH_ACTIONS_SUCCESS), + ]); + yield put({ + type: ReduxActionTypes.FETCH_PUBLISHED_PAGE_INIT, + payload: action.payload, + }); + yield take(ReduxActionTypes.FETCH_PUBLISHED_PAGE_SUCCESS); + yield put(createUpdateBindingsMap()); +} + +export default function* watchInitSagas() { + yield all([ + takeLatest(ReduxActionTypes.INIT_EDITOR, initializeEditorSaga), + takeLatest( + ReduxActionTypes.INITIALIZE_PAGE_VIEWER, + initializeAppViewerSaga, + ), + ]); } diff --git a/app/client/src/sagas/PageSagas.tsx b/app/client/src/sagas/PageSagas.tsx index 34d858360b..25f651b2d7 100644 --- a/app/client/src/sagas/PageSagas.tsx +++ b/app/client/src/sagas/PageSagas.tsx @@ -32,6 +32,8 @@ import { extractCurrentDSL } from "../utils/WidgetPropsUtils"; import { getEditorConfigs, getWidgets } from "./selectors"; import { validateResponse } from "./ErrorSagas"; import { createUpdateBindingsMap } from "../actions/bindingActions"; +import { UpdateWidgetPropertyPayload } from "../actions/controlActions"; +import { RenderModes } from "../constants/WidgetConstants"; export function* fetchPageListSaga() { try { @@ -58,16 +60,7 @@ export function* fetchPageListSaga() { }); } } -//TODO(abhinav): Probably not the best place for this particular saga -export function* initializeAppViewerSaga( - action: ReduxAction<{ pageId: string }>, -) { - yield* fetchPageListSaga(); - yield put({ - type: ReduxActionTypes.FETCH_PUBLISHED_PAGE_INIT, - payload: action.payload, - }); -} + const getCanvasWidgetsPayload = ( pageResponse: FetchPageResponse, ): UpdateCanvasPayload => { @@ -84,6 +77,21 @@ const getCanvasWidgetsPayload = ( }; }; +// TODO(abhinav): Make this similar for both Render Modes +const getAppViewWidgetsPayload = (pageResponse: any) => { + const normalizedResponse = CanvasWidgetsNormalizer.normalize( + pageResponse.data.dsl, + ); + return { + pageWidgetId: normalizedResponse.result, + currentPageName: pageResponse.data.name, + currentPageId: pageResponse.data.id, + widgets: normalizedResponse.entities.canvasWidgets, + currentLayoutId: pageResponse.data.id, + currentApplicationId: pageResponse.data.applicationId, + }; +}; + export function* fetchPageSaga( pageRequestAction: ReduxAction, ) { @@ -96,10 +104,8 @@ export function* fetchPageSaga( const isValidResponse = yield validateResponse(fetchPageResponse); if (isValidResponse) { const canvasWidgetsPayload = getCanvasWidgetsPayload(fetchPageResponse); - yield all([ - put(updateCanvas(canvasWidgetsPayload)), - put(createUpdateBindingsMap()), - ]); + yield put(updateCanvas(canvasWidgetsPayload)); + yield put(createUpdateBindingsMap()); } } catch (error) { yield put({ @@ -135,6 +141,9 @@ export function* fetchPublishedPageSaga( pageId: request.pageId, }, }); + const canvasWidgetsPayload = getAppViewWidgetsPayload(response); + yield put(updateCanvas(canvasWidgetsPayload)); + yield put(createUpdateBindingsMap()); } } catch (error) { yield put({ @@ -196,6 +205,7 @@ export function* saveLayoutSaga( type: ReduxActionTypes.SAVE_PAGE_INIT, payload: getLayoutSavePayload(widgets, editorConfigs), }); + put(createUpdateBindingsMap()); } catch (error) { yield put({ type: ReduxActionErrorTypes.SAVE_PAGE_ERROR, @@ -208,7 +218,10 @@ export function* saveLayoutSaga( // TODO(abhinav): This has redundant code. The only thing different here is the lack of state update. // For now, this is fire and forget. -export function* asyncSaveLayout() { +export function* asyncSaveLayout( + actionPayload: ReduxAction, +) { + if (actionPayload.payload.renderMode === RenderModes.PAGE) return; try { const widgets = yield select(getWidgets); const editorConfigs = yield select(getEditorConfigs) as any; @@ -276,11 +289,11 @@ export default function* pageSagas() { // No need to save layout everytime a property is updated. // We save the latest request to update layout. takeLatest(ReduxActionTypes.UPDATE_WIDGET_PROPERTY, asyncSaveLayout), + takeLatest( + ReduxActionTypes.UPDATE_WIDGET_DYNAMIC_PROPERTY, + asyncSaveLayout, + ), takeLatest(ReduxActionTypes.CREATE_PAGE_INIT, createPageSaga), takeLatest(ReduxActionTypes.FETCH_PAGE_LIST_INIT, fetchPageListSaga), - takeLatest( - ReduxActionTypes.INITIALIZE_PAGE_VIEWER, - initializeAppViewerSaga, - ), ]); } diff --git a/app/client/src/sagas/WidgetOperationSagas.tsx b/app/client/src/sagas/WidgetOperationSagas.tsx index 826f97460c..d1b984a8f3 100644 --- a/app/client/src/sagas/WidgetOperationSagas.tsx +++ b/app/client/src/sagas/WidgetOperationSagas.tsx @@ -19,6 +19,7 @@ import { put, select, takeEvery, takeLatest, all } from "redux-saga/effects"; import { getNextWidgetName } from "../utils/AppsmithUtils"; import { UpdateWidgetPropertyPayload } from "../actions/controlActions"; import { DATA_BIND_REGEX } from "../constants/BindingsConstants"; +import { createUpdateBindingsMap } from "../actions/bindingActions"; export function* addChildSaga(addChildAction: ReduxAction) { try { @@ -56,6 +57,8 @@ export function* addChildSaga(addChildAction: ReduxAction) { type: ReduxActionTypes.UPDATE_LAYOUT, payload: { widgets }, }); + // TODO might be a potential performance choke point. + yield put(createUpdateBindingsMap()); } catch (error) { yield put({ type: ReduxActionErrorTypes.WIDGET_OPERATION_ERROR, @@ -81,6 +84,7 @@ export function* deleteSaga(deleteAction: ReduxAction) { type: ReduxActionTypes.UPDATE_LAYOUT, payload: { widgets }, }); + yield put(createUpdateBindingsMap()); } catch (error) { yield put({ type: ReduxActionErrorTypes.WIDGET_OPERATION_ERROR, diff --git a/app/client/src/selectors/appViewSelectors.tsx b/app/client/src/selectors/appViewSelectors.tsx index 26e140977c..3eeb2eedbd 100644 --- a/app/client/src/selectors/appViewSelectors.tsx +++ b/app/client/src/selectors/appViewSelectors.tsx @@ -1,9 +1,11 @@ import { createSelector } from "reselect"; -import { AppState } from "../reducers"; +import { AppState, DataTree } from "../reducers"; import { AppViewReduxState } from "../reducers/uiReducers/appViewReducer"; import { AppViewerProps } from "../pages/AppViewer"; +import { injectDataTreeIntoDsl } from "../utils/DynamicBindingUtils"; const getAppViewState = (state: AppState) => state.ui.appView; +const getDataTree = (state: AppState): DataTree => state.entities; export const getCurrentLayoutId = (state: AppState, props: AppViewerProps) => state.ui.appView.currentLayoutId || props.match.params.layoutId; @@ -15,7 +17,9 @@ export const getCurrentRoutePageId = (state: AppState, props: AppViewerProps) => // For the viewer, this does not need to be wrapped in createCachedSelector, as it will not change in subsequent renders. export const getCurrentPageLayoutDSL = createSelector( getAppViewState, - (view: AppViewReduxState) => view.dsl, + getDataTree, + (view: AppViewReduxState, dataTree: DataTree) => + injectDataTreeIntoDsl(dataTree, view.dsl), ); export const getPageList = createSelector( diff --git a/app/client/src/selectors/editorSelectors.tsx b/app/client/src/selectors/editorSelectors.tsx index 5f31c86900..ada2643f1f 100644 --- a/app/client/src/selectors/editorSelectors.tsx +++ b/app/client/src/selectors/editorSelectors.tsx @@ -94,7 +94,7 @@ export const getDenormalizedDSL = createCachedSelector( getPageWidgetId, getEntities, (pageWidgetId: string, entities: DataTree) => { - const injectedEntities = injectDataTreeIntoDsl(entities); - return CanvasWidgetsNormalizer.denormalize(pageWidgetId, injectedEntities); + const dsl = CanvasWidgetsNormalizer.denormalize(pageWidgetId, entities); + return injectDataTreeIntoDsl(entities, dsl); }, )((pageWidgetId, entities) => entities || 0); diff --git a/app/client/src/selectors/propertyPaneSelectors.tsx b/app/client/src/selectors/propertyPaneSelectors.tsx index 02998fb269..c951771101 100644 --- a/app/client/src/selectors/propertyPaneSelectors.tsx +++ b/app/client/src/selectors/propertyPaneSelectors.tsx @@ -43,7 +43,12 @@ export const getPropertyConfig = createSelector( pane: PropertyPaneReduxState, widgets: CanvasWidgetsReduxState, ) => { - if (pane.widgetId && configs && widgets[pane.widgetId]) { + if ( + pane.widgetId && + configs && + !!configs.config && + widgets[pane.widgetId] + ) { return configs.config[widgets[pane.widgetId].type]; } return undefined; diff --git a/app/client/src/utils/AppsmithUtils.tsx b/app/client/src/utils/AppsmithUtils.tsx index 1500e8a957..35229b87cf 100644 --- a/app/client/src/utils/AppsmithUtils.tsx +++ b/app/client/src/utils/AppsmithUtils.tsx @@ -86,3 +86,5 @@ export const getNextWidgetName = ( return prefix + (lastIndex + 1); }; + +export const noop = () => {}; diff --git a/app/client/src/utils/DynamicBindingUtils.ts b/app/client/src/utils/DynamicBindingUtils.ts index c9de21793f..e25b3b7a83 100644 --- a/app/client/src/utils/DynamicBindingUtils.ts +++ b/app/client/src/utils/DynamicBindingUtils.ts @@ -1,8 +1,8 @@ import _ from "lodash"; import { DataTree } from "../reducers"; import { JSONPath } from "jsonpath-plus"; -import { CanvasWidgetsReduxState } from "../reducers/entityReducers/canvasWidgetsReducer"; import { WidgetProps } from "../widgets/BaseWidget"; +import { ContainerWidgetProps } from "../widgets/ContainerWidget"; import { DATA_BIND_REGEX, DATA_PATH_REGEX, @@ -23,15 +23,16 @@ export const getDynamicBoundValue = ( return JSONPath({ path: fullPath, json: dataTree }); }; -export const injectDataTreeIntoDsl = (entities: DataTree) => { - const { canvasWidgets } = entities; - // Create new widgets object - const widgets: CanvasWidgetsReduxState = {}; - if (_.isEmpty(canvasWidgets)) return entities; - Object.keys(canvasWidgets).forEach((key: string) => { - // Spread all values in the widget - const widget: WidgetProps = { ...canvasWidgets[key] }; - const { dynamicBindings } = canvasWidgets[key]; +export const injectDataTreeIntoDsl = ( + entities: DataTree, + dsl?: ContainerWidgetProps, +) => { + if (!dsl) return dsl; + const traverseTree = ( + tree: ContainerWidgetProps, + ): ContainerWidgetProps => { + const { dynamicBindings } = tree; + const widget = { ...tree }; // Check for dynamic bindings if (dynamicBindings && !_.isEmpty(dynamicBindings)) { Object.keys(dynamicBindings).forEach((dKey: string) => { @@ -64,7 +65,11 @@ export const injectDataTreeIntoDsl = (entities: DataTree) => { } }); } - widgets[key] = widget; - }); - return { ...entities, canvasWidgets: widgets }; + if (tree.children) { + const children = tree.children.map(b => traverseTree(b)); + return { ...widget, children }; + } + return { ...widget }; + }; + return traverseTree(dsl); }; diff --git a/app/client/src/widgets/BaseWidget.tsx b/app/client/src/widgets/BaseWidget.tsx index aa1157fbd4..4a3d201f68 100644 --- a/app/client/src/widgets/BaseWidget.tsx +++ b/app/client/src/widgets/BaseWidget.tsx @@ -40,6 +40,16 @@ abstract class BaseWidget< executeAction && executeAction(actionPayloads); } + updateWidgetProperty( + widgetId: string, + propertyName: string, + propertyValue: any, + ): void { + const { updateWidgetProperty } = this.context; + updateWidgetProperty && + updateWidgetProperty(widgetId, propertyName, propertyValue); + } + componentDidMount(): void { this.calculateWidgetBounds( this.props.rightColumn, diff --git a/app/client/src/widgets/TableWidget.tsx b/app/client/src/widgets/TableWidget.tsx index 48d65051c8..e27779ec17 100644 --- a/app/client/src/widgets/TableWidget.tsx +++ b/app/client/src/widgets/TableWidget.tsx @@ -2,16 +2,12 @@ import React from "react"; import BaseWidget, { WidgetProps, WidgetState } from "./BaseWidget"; import { WidgetType } from "../constants/WidgetConstants"; import { ActionPayload } from "../constants/ActionConstants"; -import BaseTable, { AutoResizer } from "react-base-table"; +import { AutoResizer } from "react-base-table"; import "react-base-table/styles.css"; import { forIn } from "lodash"; - -interface Column { - key: string; - dataKey: string; - title: string; - width: number; -} +import SelectableTable, { + Column, +} from "../components/designSystems/appsmith/TableComponent"; function constructColumns(data: object[]): Column[] { const cols: Column[] = []; @@ -54,12 +50,23 @@ class TableWidget extends BaseWidget { return ( {({ width, height }: { width: number; height: number }) => ( - { + const { widgetId, onRowSelected } = this.props; + super.updateWidgetProperty(widgetId, "selectedRow", { + data: rowData, + index: index, + }); + super.executeAction(onRowSelected); + }} /> )} @@ -85,6 +92,10 @@ export interface TableWidgetProps extends WidgetProps { recordActions?: TableAction[]; onPageChange?: ActionPayload[]; onRowSelected?: ActionPayload[]; + selectedRow?: { + data: object; + index: number; + }; } export default TableWidget;