diff --git a/app/client/src/api/ApplicationApi.tsx b/app/client/src/api/ApplicationApi.tsx index a5c7e197c8..7ba501fb7e 100644 --- a/app/client/src/api/ApplicationApi.tsx +++ b/app/client/src/api/ApplicationApi.tsx @@ -10,6 +10,31 @@ export interface PublishApplicationResponse extends ApiResponse { data: {}; } +export interface ApplicationPagePayload { + id: string; + name: string; + isDefault: boolean; +} + +export interface ApplicationResponsePayload { + id: string; + name: string; + organizationId: string; + pages?: ApplicationPagePayload[]; +} + +export interface FetchApplicationsResponse extends ApiResponse { + data: Array; +} + +export interface CreateApplicationResponse extends ApiResponse { + data: ApplicationResponsePayload; +} + +export interface CreateApplicationRequest { + name: string; +} + class ApplicationApi extends Api { static baseURL = "v1/applications/"; static publishURLPath = (applicationId: string) => `publish/${applicationId}`; @@ -23,6 +48,14 @@ class ApplicationApi extends Api { {}, ); } + static fetchApplications(): AxiosPromise { + return Api.get(ApplicationApi.baseURL); + } + static createApplication( + request: CreateApplicationRequest, + ): AxiosPromise { + return Api.post(ApplicationApi.baseURL, request); + } } export default ApplicationApi; diff --git a/app/client/src/assets/icons/control/view.svg b/app/client/src/assets/icons/control/view.svg new file mode 100644 index 0000000000..1c207b68b7 --- /dev/null +++ b/app/client/src/assets/icons/control/view.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/app/client/src/constants/BindingsConstants.ts b/app/client/src/constants/BindingsConstants.ts index 733ee3390f..164fa33dcd 100644 --- a/app/client/src/constants/BindingsConstants.ts +++ b/app/client/src/constants/BindingsConstants.ts @@ -1,3 +1,5 @@ +/* eslint-disable no-useless-escape */ +// TODO (hetu): Remove useless escapes and re-enable the above lint rule export type NamePathBindingMap = Record; export const DATA_BIND_REGEX = /{{(\s*[\w\.\[\]\d]+\s*)}}/g; export const DATA_PATH_REGEX = /[\w\.\[\]\d]+/; diff --git a/app/client/src/constants/DefaultTheme.tsx b/app/client/src/constants/DefaultTheme.tsx index f3e6dc17e7..7bd30f7019 100644 --- a/app/client/src/constants/DefaultTheme.tsx +++ b/app/client/src/constants/DefaultTheme.tsx @@ -45,6 +45,10 @@ export type Theme = { activeItemBGColor: Color; navItemHeight: number; }; + card: { + minWidth: number; + minHeight: number; + }; }; export const getColorWithOpacity = (color: Color, opacity: number) => { @@ -134,6 +138,10 @@ export const theme: Theme = { activeItemBGColor: Colors.SHARK, navItemHeight: 42, }, + card: { + minWidth: 300, + minHeight: 300, + }, }; export { css, createGlobalStyle, keyframes, ThemeProvider }; diff --git a/app/client/src/constants/IconConstants.tsx b/app/client/src/constants/IconConstants.tsx index ce8fc8edcf..7622d82f16 100644 --- a/app/client/src/constants/IconConstants.tsx +++ b/app/client/src/constants/IconConstants.tsx @@ -5,6 +5,7 @@ export type IconProps = { width: number; height: number; color: Color; + background: Color; }; export const IconWrapper = styled.div` @@ -17,5 +18,8 @@ export const IconWrapper = styled.div` path { fill: ${props => props.color || props.theme.colors.textOnDarkBG}; } + circle { + fill: ${props => props.background || props.theme.colors.paneBG}; + } } `; diff --git a/app/client/src/constants/ReduxActionConstants.tsx b/app/client/src/constants/ReduxActionConstants.tsx index a20f8ac37d..cadc2c18d6 100644 --- a/app/client/src/constants/ReduxActionConstants.tsx +++ b/app/client/src/constants/ReduxActionConstants.tsx @@ -66,6 +66,10 @@ export const ReduxActionTypes: { [key: string]: string } = { FETCH_PAGE_LIST_INIT: "FETCH_PAGE_LIST_INIT", FETCH_PAGE_LIST_SUCCESS: "FETCH_PAGE_LIST_SUCCESS", INITIALIZE_PAGE_VIEWER: "INITIALIZE_PAGE_VIEWER", + FETCH_APPLICATION_LIST_INIT: "FETCH_APPLICATION_LIST_INIT", + FETCH_APPLICATION_LIST_SUCCESS: "FETCH_APPLICATION_LIST_SUCCESS", + CREATE_APPLICATION_INIT: "CREATE_APPLICATION_INIT", + CREATE_APPLICATION_SUCCESS: "CREATE_APPLICATION_SUCCESS", CREATE_UPDATE_BINDINGS_MAP_INIT: "CREATE_UPDATE_BINDINGS_MAP_INIT", CREATE_UPDATE_BINDINGS_MAP_SUCCESS: "CREATE_UPDATE_BINDINGS_MAP_SUCCESS", }; @@ -97,6 +101,8 @@ export const ReduxActionErrorTypes: { [key: string]: string } = { PUBLISH_APPLICATION_ERROR: "PUBLISH_APPLICATION_ERROR", CREATE_PAGE_ERROR: "CREATE_PAGE_ERROR", FETCH_PAGE_LIST_ERROR: "FETCH_PAGE_LIST_ERROR", + FETCH_APPLICATION_LIST_ERROR: "FETCH_APPLICATION_LIST_ERROR", + CREATE_APPLICATION_ERROR: "CREATE_APPLICATION_ERROR", }; export type ReduxActionErrorType = (typeof ReduxActionErrorTypes)[keyof typeof ReduxActionErrorTypes]; @@ -134,6 +140,14 @@ export type PageListPayload = Array<{ layoutId: string; }>; +export type ApplicationPayload = { + id: string; + name: string; + organizationId: string; + pageCount: number; + defaultPageId?: string; +}; + // export interface LoadAPIResponsePayload extends ExecuteActionResponse {} // export interface LoadQueryResponsePayload extends ExecuteActionResponse {} diff --git a/app/client/src/constants/routes.ts b/app/client/src/constants/routes.ts index ed067e509a..f8cf78a6f5 100644 --- a/app/client/src/constants/routes.ts +++ b/app/client/src/constants/routes.ts @@ -6,13 +6,23 @@ export const BUILDER_URL = "/builder"; export const API_EDITOR_URL = `${BUILDER_URL}/api`; export const API_EDITOR_ID_URL = (id = ":id") => `${API_EDITOR_URL}/${id}`; export const APP_VIEW_URL = `/view/pages/:pageId`; +export const APPLICATIONS_URL = `/applications`; + +// TODO(abhinav): We probably need a utils/routes file for such functions. +export const getApplicationBuilderURL = (applicationId: string) => + `${BUILDER_URL}/${applicationId}`; + +export const getApplicationViewerURL = ( + applicationId: string, + pageId?: string, +) => `/view/application/${applicationId}/pages/${pageId}`; export const EDITOR_ROUTES = [ { icon: MenuIcons.WIDGETS_ICON, path: BUILDER_URL, title: "Widgets", - exact: true, + exact: false, }, { icon: MenuIcons.APIS_ICON, diff --git a/app/client/src/icons/ControlIcons.tsx b/app/client/src/icons/ControlIcons.tsx index 5e47a71724..30e1262f5a 100644 --- a/app/client/src/icons/ControlIcons.tsx +++ b/app/client/src/icons/ControlIcons.tsx @@ -3,6 +3,7 @@ import { IconProps, IconWrapper } from "../constants/IconConstants"; import { ReactComponent as DeleteIcon } from "../assets/icons/control/delete.svg"; import { ReactComponent as MoveIcon } from "../assets/icons/control/move.svg"; import { ReactComponent as EditIcon } from "../assets/icons/control/edit.svg"; +import { ReactComponent as ViewIcon } from "../assets/icons/control/view.svg"; /* eslint-disable react/display-name */ @@ -24,4 +25,9 @@ export const ControlIcons: { ), + VIEW_CONTROL: (props: IconProps) => ( + + + + ), }; diff --git a/app/client/src/index.tsx b/app/client/src/index.tsx index b9bcc61696..1ec7467429 100755 --- a/app/client/src/index.tsx +++ b/app/client/src/index.tsx @@ -26,7 +26,9 @@ import { BUILDER_URL, LOGIN_URL, APP_VIEW_URL, + APPLICATIONS_URL, } from "./constants/routes"; +import Applications from "./pages/Applications"; appInitializer(); const sagaMiddleware = createSagaMiddleware(); @@ -44,6 +46,7 @@ ReactDOM.render( + diff --git a/app/client/src/pages/Applications/ApplicationsHeader.tsx b/app/client/src/pages/Applications/ApplicationsHeader.tsx new file mode 100644 index 0000000000..69506fe5a7 --- /dev/null +++ b/app/client/src/pages/Applications/ApplicationsHeader.tsx @@ -0,0 +1,51 @@ +import React from "react"; +import styled from "styled-components"; +import StyledHeader from "../../components/designSystems/appsmith/StyledHeader"; +import { + Popover, + Button, + Position, + IIconProps, + PopoverInteractionKind, +} from "@blueprintjs/core"; + +const StyledAddButton = styled(Button)` + &&& { + background: ${props => props.theme.colors.primary}; + span { + color: white; + } + } +`; + +type ApplicationsHeaderProps = { + add?: { + form: JSX.Element; + title: string; + }; +}; + +export const ApplicationsHeader = (props: ApplicationsHeaderProps) => { + return ( + + {props.add && ( + + + {props.add.form} + + )} + + ); +}; + +export default ApplicationsHeader; diff --git a/app/client/src/pages/Applications/CreateApplicationForm.tsx b/app/client/src/pages/Applications/CreateApplicationForm.tsx new file mode 100644 index 0000000000..6777b569b3 --- /dev/null +++ b/app/client/src/pages/Applications/CreateApplicationForm.tsx @@ -0,0 +1,38 @@ +import React, { useRef, MutableRefObject } from "react"; +import { BaseButton } from "../../components/designSystems/blueprint/ButtonComponent"; +import { ControlGroup, Classes } from "@blueprintjs/core"; + +type CreateApplicationFormProps = { + onCreate: (name: string) => void; + creating: boolean; + error?: string; +}; + +export const CreateApplicationForm = (props: CreateApplicationFormProps) => { + const inputRef: MutableRefObject = useRef(null); + const handleCreate = () => { + if (inputRef && inputRef.current) { + props.onCreate(inputRef.current.value); + } else { + //TODO (abhinav): Add validation code. + } + }; + return ( + + + + + ); +}; + +export default CreateApplicationForm; diff --git a/app/client/src/pages/Applications/index.tsx b/app/client/src/pages/Applications/index.tsx new file mode 100644 index 0000000000..109b3167c6 --- /dev/null +++ b/app/client/src/pages/Applications/index.tsx @@ -0,0 +1,199 @@ +import React, { Component } from "react"; +import styled from "styled-components"; +import { connect } from "react-redux"; +import { withRouter } from "react-router"; +import { + getApplicationBuilderURL, + getApplicationViewerURL, +} from "../../constants/routes"; +import { AppState } from "../../reducers"; +import { + getApplicationList, + getIsFetchingApplications, + getIsCreatingApplication, +} from "../../selectors/applicationSelectors"; +import { + ReduxActionTypes, + ApplicationPayload, +} from "../../constants/ReduxActionConstants"; +import { Card, Spinner, Tooltip } from "@blueprintjs/core"; +import { ControlIcons } from "../../icons/ControlIcons"; +import { theme } from "../../constants/DefaultTheme"; +import ApplicationsHeader from "./ApplicationsHeader"; +import CreateApplicationForm from "./CreateApplicationForm"; + +const APPLICATION_CONTROL_FONTSIZE_INDEX = 7; + +const ApplicationsBody = styled.section` + width: 100vw; + min-height: calc(100vh - ${props => props.theme.headerHeight}); + display: flex; + justify-content: center; + align-items: center; + background: white; +`; + +const ApplicationCardsWrapper = styled.div` + display: flex; + flex-flow: row wrap; + justify-content: center; + align-items: space-around; + width: 80%; +`; +const ApplicationCard = styled(Card)` + display: flex; + flex-direction: column; + justify-content: center; + min-width: ${props => props.theme.card.minWidth}px; + min-height: ${props => props.theme.card.minHeight}px; + position: relative; + margin-bottom: ${props => props.theme.spaces[2]}px; + margin-right: ${props => props.theme.spaces[2]}px; + &:hover { + & div.controls { + display: flex; + } + } +`; +const ApplicationTitle = styled.h1` + font-size: ${props => props.theme.fontSizes[7]}px; + text-align: center; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + max-width: 100%; +`; +const ApplicationControls = styled.div` + display: none; + flex-flow: row nowrap; + justify-content: space-around; +`; + +const Control = styled.button<{ fixed?: boolean }>` + outline: none; + background: none; + border: none; + cursor: pointer; + position: ${props => (props.fixed ? "absolute" : "auto")}; + right: ${props => props.theme.spaces[2]}px; + top: ${props => props.theme.spaces[2]}px; +`; + +const editControlIcon = ControlIcons.EDIT_CONTROL({ + width: theme.fontSizes[APPLICATION_CONTROL_FONTSIZE_INDEX], + height: theme.fontSizes[APPLICATION_CONTROL_FONTSIZE_INDEX], +}); + +const deleteControlIcon = ControlIcons.DELETE_CONTROL({ + width: theme.fontSizes[APPLICATION_CONTROL_FONTSIZE_INDEX], + height: theme.fontSizes[APPLICATION_CONTROL_FONTSIZE_INDEX], + background: theme.colors.error, +}); + +const viewControlIcon = ControlIcons.VIEW_CONTROL({ + width: theme.fontSizes[APPLICATION_CONTROL_FONTSIZE_INDEX], + height: theme.fontSizes[APPLICATION_CONTROL_FONTSIZE_INDEX], +}); + +type ApplicationProps = { + applicationList: ApplicationPayload[]; + fetchApplications: () => void; + createApplication: (appName: string) => void; + isCreatingApplication: boolean; + history: any; +}; + +class Applications extends Component { + handleEditApplication = (applicationId: string) => () => { + this.props.history.push(getApplicationBuilderURL(applicationId)); + }; + + handleViewApplication = (applicationId: string, pageId?: string) => () => { + this.props.history.push(getApplicationViewerURL(applicationId, pageId)); + }; + + renderApplicationCard = (application: ApplicationPayload) => { + return ( + + {application.name} + + + + {deleteControlIcon} + + + + + {viewControlIcon} + + + + + {editControlIcon} + + + + + ); + }; + componentDidMount() { + this.props.fetchApplications(); + } + public render() { + return ( +
+ + ), + title: "Create Application", + }} + /> + + {this.props.applicationList ? ( + + {this.props.applicationList.map(this.renderApplicationCard)} + + ) : ( + + )} + +
+ ); + } +} + +const mapStateToProps = (state: AppState) => ({ + applicationList: getApplicationList(state), + isFetchingApplications: getIsFetchingApplications(state), + isCreatingApplication: getIsCreatingApplication(state), +}); + +const mapDispatchToProps = (dispatch: any) => ({ + fetchApplications: () => + dispatch({ type: ReduxActionTypes.FETCH_APPLICATION_LIST_INIT }), + createApplication: (appName: string) => { + dispatch({ + type: ReduxActionTypes.CREATE_APPLICATION_INIT, + payload: { + name: appName, + }, + }); + }, +}); + +export default withRouter( + connect( + mapStateToProps, + mapDispatchToProps, + )(Applications), +); diff --git a/app/client/src/reducers/index.tsx b/app/client/src/reducers/index.tsx index dc8692c837..50bd2d29ce 100644 --- a/app/client/src/reducers/index.tsx +++ b/app/client/src/reducers/index.tsx @@ -14,6 +14,7 @@ import { WidgetConfigReducerState } from "./entityReducers/widgetConfigReducer"; import { WidgetSidebarReduxState } from "./uiReducers/widgetSidebarReducer"; import { ResourceDataState } from "./entityReducers/resourcesReducer"; import { AppViewReduxState } from "./uiReducers/appViewReducer"; +import { ApplicationsReduxState } from "./uiReducers/applicationsReducer"; import { BindingsDataState } from "./entityReducers/bindingsReducer"; const appReducer = combineReducers({ @@ -30,7 +31,8 @@ export interface AppState { editor: EditorReduxState; propertyPane: PropertyPaneReduxState; errors: ErrorReduxState; - view: AppViewReduxState; + appView: AppViewReduxState; + applications: ApplicationsReduxState; }; entities: { canvasWidgets: CanvasWidgetsReduxState; diff --git a/app/client/src/reducers/uiReducers/applicationsReducer.tsx b/app/client/src/reducers/uiReducers/applicationsReducer.tsx new file mode 100644 index 0000000000..ab572f9b55 --- /dev/null +++ b/app/client/src/reducers/uiReducers/applicationsReducer.tsx @@ -0,0 +1,52 @@ +import { createReducer } from "../../utils/AppsmithUtils"; +import { + ReduxAction, + ReduxActionTypes, + ReduxActionErrorTypes, + ApplicationPayload, +} from "../../constants/ReduxActionConstants"; + +const initialState: ApplicationsReduxState = { + isFetchingApplications: false, + applicationList: [], + creatingApplication: false, +}; + +const applicationsReducer = createReducer(initialState, { + [ReduxActionTypes.FETCH_APPLICATION_LIST_INIT]: ( + state: ApplicationsReduxState, + ) => ({ ...state, isFetchingApplications: true }), + [ReduxActionTypes.FETCH_APPLICATION_LIST_SUCCESS]: ( + state: ApplicationsReduxState, + action: ReduxAction<{ applicationList: ApplicationPayload[] }>, + ) => ({ ...state, applicationList: action.payload }), + [ReduxActionTypes.CREATE_APPLICATION_INIT]: ( + state: ApplicationsReduxState, + ) => ({ ...state, creatingApplication: true }), + [ReduxActionTypes.CREATE_APPLICATION_SUCCESS]: ( + state: ApplicationsReduxState, + action: ReduxAction, + ) => { + return { + ...state, + creatingApplication: false, + applicationList: [...state.applicationList, action.payload], + }; + }, + [ReduxActionErrorTypes.CREATE_APPLICATION_ERROR]: ( + state: ApplicationsReduxState, + ) => { + return { + ...state, + creatingApplication: false, + }; + }, +}); + +export interface ApplicationsReduxState { + applicationList: ApplicationPayload[]; + isFetchingApplications: boolean; + creatingApplication: boolean; +} + +export default applicationsReducer; diff --git a/app/client/src/reducers/uiReducers/index.tsx b/app/client/src/reducers/uiReducers/index.tsx index a40c36a57d..b93e5b9f7e 100644 --- a/app/client/src/reducers/uiReducers/index.tsx +++ b/app/client/src/reducers/uiReducers/index.tsx @@ -3,6 +3,7 @@ import editorReducer from "./editorReducer"; import errorReducer from "./errorReducer"; import propertyPaneReducer from "./propertyPaneReducer"; import appViewReducer from "./appViewReducer"; +import applicationsReducer from "./applicationsReducer"; import { widgetSidebarReducer } from "./widgetSidebarReducer"; const uiReducer = combineReducers({ @@ -10,6 +11,7 @@ const uiReducer = combineReducers({ editor: editorReducer, errors: errorReducer, propertyPane: propertyPaneReducer, - view: appViewReducer, + appView: appViewReducer, + applications: applicationsReducer, }); export default uiReducer; diff --git a/app/client/src/sagas/ApplicationSagas.tsx b/app/client/src/sagas/ApplicationSagas.tsx index 6ffffe508e..51c9262a3f 100644 --- a/app/client/src/sagas/ApplicationSagas.tsx +++ b/app/client/src/sagas/ApplicationSagas.tsx @@ -2,10 +2,15 @@ import { ReduxActionTypes, ReduxActionErrorTypes, ReduxAction, + ApplicationPayload, } from "../constants/ReduxActionConstants"; import ApplicationApi, { PublishApplicationResponse, PublishApplicationRequest, + FetchApplicationsResponse, + CreateApplicationRequest, + CreateApplicationResponse, + ApplicationPagePayload, } from "../api/ApplicationApi"; import { call, put, takeLatest, all } from "redux-saga/effects"; @@ -36,11 +41,92 @@ export function* publishApplicationSaga( } } +const getDefaultPageId = ( + pages?: ApplicationPagePayload[], +): string | undefined => { + let defaultPage: ApplicationPagePayload | undefined = undefined; + if (pages) { + pages.find(page => page.isDefault); + if (!defaultPage) { + defaultPage = pages[0]; + } + } + return defaultPage ? defaultPage.id : undefined; +}; + +export function* fetchApplicationListSaga() { + try { + const response: FetchApplicationsResponse = yield call( + ApplicationApi.fetchApplications, + ); + const isValidResponse = yield validateResponse(response); + if (isValidResponse) { + const applicationListPayload: ApplicationPayload[] = response.data.map( + application => ({ + name: application.name, + organizationId: application.organizationId, + id: application.id, + pageCount: application.pages ? application.pages.length : 0, + defaultPageId: getDefaultPageId(application.pages), + }), + ); + yield put({ + type: ReduxActionTypes.FETCH_APPLICATION_LIST_SUCCESS, + payload: applicationListPayload, + }); + } + } catch (error) { + yield put({ + type: ReduxActionErrorTypes.FETCH_APPLICATION_LIST_ERROR, + payload: { + error, + }, + }); + } +} + +export function* createApplicationSaga( + request: ReduxAction, +) { + try { + const response: CreateApplicationResponse = yield call( + ApplicationApi.createApplication, + request.payload, + ); + const isValidResponse = yield validateResponse(response); + if (isValidResponse) { + const application: ApplicationPayload = { + id: response.data.id, + name: response.data.name, + organizationId: response.data.organizationId, + pageCount: response.data.pages ? response.data.pages.length : 0, + defaultPageId: getDefaultPageId(response.data.pages), + }; + yield put({ + type: ReduxActionTypes.CREATE_APPLICATION_SUCCESS, + payload: application, + }); + } + } catch (error) { + yield put({ + type: ReduxActionErrorTypes.CREATE_APPLICATION_ERROR, + payload: { + error, + }, + }); + } +} + export default function* applicationSagas() { yield all([ takeLatest( ReduxActionTypes.PUBLISH_APPLICATION_INIT, publishApplicationSaga, ), + takeLatest( + ReduxActionTypes.FETCH_APPLICATION_LIST_INIT, + fetchApplicationListSaga, + ), + takeLatest(ReduxActionTypes.CREATE_APPLICATION_INIT, createApplicationSaga), ]); } diff --git a/app/client/src/sagas/PageSagas.tsx b/app/client/src/sagas/PageSagas.tsx index 232abedeb4..34d858360b 100644 --- a/app/client/src/sagas/PageSagas.tsx +++ b/app/client/src/sagas/PageSagas.tsx @@ -196,8 +196,13 @@ export function* saveLayoutSaga( type: ReduxActionTypes.SAVE_PAGE_INIT, payload: getLayoutSavePayload(widgets, editorConfigs), }); - } catch (err) { - console.log(err); + } catch (error) { + yield put({ + type: ReduxActionErrorTypes.SAVE_PAGE_ERROR, + payload: { + error, + }, + }); } } @@ -221,7 +226,6 @@ export function* asyncSaveLayout() { throw Error("Error when saving layout"); } } catch (error) { - console.log(error); yield put({ type: ReduxActionErrorTypes.UPDATE_WIDGET_PROPERTY_ERROR, payload: { diff --git a/app/client/src/sagas/selectors.tsx b/app/client/src/sagas/selectors.tsx index 2fff269102..efd98efc4d 100644 --- a/app/client/src/sagas/selectors.tsx +++ b/app/client/src/sagas/selectors.tsx @@ -34,7 +34,7 @@ export const getDefaultWidgetConfig = ( }; export const getPageLayoutId = (state: AppState, pageId: string): string => { - const pages = state.ui.view.pages; + const pages = state.ui.appView.pages; const page = pages.find(page => page.pageId === pageId); if (!page) { throw Error("Page not found"); diff --git a/app/client/src/selectors/appViewSelectors.tsx b/app/client/src/selectors/appViewSelectors.tsx index 5f9fcb7836..26e140977c 100644 --- a/app/client/src/selectors/appViewSelectors.tsx +++ b/app/client/src/selectors/appViewSelectors.tsx @@ -3,10 +3,12 @@ import { AppState } from "../reducers"; import { AppViewReduxState } from "../reducers/uiReducers/appViewReducer"; import { AppViewerProps } from "../pages/AppViewer"; -const getAppViewState = (state: AppState) => state.ui.view; +const getAppViewState = (state: AppState) => state.ui.appView; export const getCurrentLayoutId = (state: AppState, props: AppViewerProps) => - state.ui.view.currentLayoutId || props.match.params.layoutId; + state.ui.appView.currentLayoutId || props.match.params.layoutId; +export const getCurrentPageId = (state: AppState, props: AppViewerProps) => + state.ui.appView.currentPageId || props.match.params.pageId; export const getCurrentRoutePageId = (state: AppState, props: AppViewerProps) => props.match.params.pageId; diff --git a/app/client/src/selectors/applicationSelectors.tsx b/app/client/src/selectors/applicationSelectors.tsx new file mode 100644 index 0000000000..353625497e --- /dev/null +++ b/app/client/src/selectors/applicationSelectors.tsx @@ -0,0 +1,24 @@ +import { createSelector } from "reselect"; +import { AppState } from "../reducers"; +import { ApplicationsReduxState } from "../reducers/uiReducers/applicationsReducer"; +import { ApplicationPayload } from "../constants/ReduxActionConstants"; + +const getApplicationsState = (state: AppState) => state.ui.applications; + +export const getApplicationList = createSelector( + getApplicationsState, + (applications: ApplicationsReduxState): ApplicationPayload[] => + applications.applicationList, +); + +export const getIsFetchingApplications = createSelector( + getApplicationsState, + (applications: ApplicationsReduxState): boolean => + applications.isFetchingApplications, +); + +export const getIsCreatingApplication = createSelector( + getApplicationsState, + (applications: ApplicationsReduxState): boolean => + applications.creatingApplication, +);