Feature - Applications Scaffold Page - Create Application
This commit is contained in:
parent
b244c4d784
commit
9ba528830d
|
|
@ -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<ApplicationResponsePayload & { pages: ApplicationPagePayload[] }>;
|
||||
}
|
||||
|
||||
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<FetchApplicationsResponse> {
|
||||
return Api.get(ApplicationApi.baseURL);
|
||||
}
|
||||
static createApplication(
|
||||
request: CreateApplicationRequest,
|
||||
): AxiosPromise<CreateApplicationResponse> {
|
||||
return Api.post(ApplicationApi.baseURL, request);
|
||||
}
|
||||
}
|
||||
|
||||
export default ApplicationApi;
|
||||
|
|
|
|||
5
app/client/src/assets/icons/control/view.svg
Normal file
5
app/client/src/assets/icons/control/view.svg
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="10" cy="10" r="10" fill="#21282C"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M10 10.9513C9.47555 10.9513 9.04876 10.5245 9.04876 10C9.04876 9.47556 9.47555 9.04876 10 9.04876C10.5245 9.04876 10.9513 9.47556 10.9513 10C10.9513 10.5245 10.5245 10.9513 10 10.9513ZM10 7.78042C8.77606 7.78042 7.78041 8.77607 7.78041 10C7.78041 11.224 8.77606 12.2196 10 12.2196C11.224 12.2196 12.2196 11.224 12.2196 10C12.2196 8.77607 11.224 7.78042 10 7.78042ZM10.1393 13.1694C7.4086 13.2328 5.62721 10.8971 5.03616 9.99723C5.68682 8.97938 7.32552 6.89549 9.86094 6.83081C12.5809 6.76168 14.3724 9.10304 14.9635 10.0029C14.3135 11.0208 12.6741 13.1047 10.1393 13.1694ZM16.2578 9.68458C15.8532 8.97938 13.6184 5.44451 9.8286 5.5631C6.3229 5.65188 4.28403 8.7403 3.74245 9.68458C3.6302 9.8799 3.6302 10.1203 3.74245 10.3156C4.14134 11.0113 6.29753 14.439 10.0157 14.439C10.0677 14.439 10.1197 14.4383 10.1717 14.4371C13.6768 14.3476 15.7163 11.2599 16.2578 10.3156C16.3694 10.1203 16.3694 9.8799 16.2578 9.68458Z" fill="#BCCCD9"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M10 10.9513C9.47555 10.9513 9.04876 10.5245 9.04876 10C9.04876 9.47556 9.47555 9.04876 10 9.04876C10.5245 9.04876 10.9513 9.47556 10.9513 10C10.9513 10.5245 10.5245 10.9513 10 10.9513ZM10 7.78042C8.77606 7.78042 7.78041 8.77607 7.78041 10C7.78041 11.224 8.77606 12.2196 10 12.2196C11.224 12.2196 12.2196 11.224 12.2196 10C12.2196 8.77607 11.224 7.78042 10 7.78042ZM10.1393 13.1694C7.4086 13.2328 5.62721 10.8971 5.03616 9.99723C5.68682 8.97938 7.32552 6.89549 9.86094 6.83081C12.5809 6.76168 14.3724 9.10304 14.9635 10.0029C14.3135 11.0208 12.6741 13.1047 10.1393 13.1694ZM16.2578 9.68458C15.8532 8.97938 13.6184 5.44451 9.8286 5.5631C6.3229 5.65188 4.28403 8.7403 3.74245 9.68458C3.6302 9.8799 3.6302 10.1203 3.74245 10.3156C4.14134 11.0113 6.29753 14.439 10.0157 14.439C10.0677 14.439 10.1197 14.4383 10.1717 14.4371C13.6768 14.3476 15.7163 11.2599 16.2578 10.3156C16.3694 10.1203 16.3694 9.8799 16.2578 9.68458Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.1 KiB |
|
|
@ -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<string, string>;
|
||||
export const DATA_BIND_REGEX = /{{(\s*[\w\.\[\]\d]+\s*)}}/g;
|
||||
export const DATA_PATH_REGEX = /[\w\.\[\]\d]+/;
|
||||
|
|
|
|||
|
|
@ -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 };
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ export type IconProps = {
|
|||
width: number;
|
||||
height: number;
|
||||
color: Color;
|
||||
background: Color;
|
||||
};
|
||||
|
||||
export const IconWrapper = styled.div<IconProps>`
|
||||
|
|
@ -17,5 +18,8 @@ export const IconWrapper = styled.div<IconProps>`
|
|||
path {
|
||||
fill: ${props => props.color || props.theme.colors.textOnDarkBG};
|
||||
}
|
||||
circle {
|
||||
fill: ${props => props.background || props.theme.colors.paneBG};
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
|
|
|||
|
|
@ -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 {}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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: {
|
|||
<EditIcon />
|
||||
</IconWrapper>
|
||||
),
|
||||
VIEW_CONTROL: (props: IconProps) => (
|
||||
<IconWrapper {...props}>
|
||||
<ViewIcon />
|
||||
</IconWrapper>
|
||||
),
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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(
|
|||
<Route exact path={BASE_URL} component={App} />
|
||||
<ProtectedRoute path={BUILDER_URL} component={Editor} />
|
||||
<ProtectedRoute path={APP_VIEW_URL} component={AppViewer} />
|
||||
<ProtectedRoute path={APPLICATIONS_URL} component={Applications} />
|
||||
<Route exact path={LOGIN_URL} component={LoginPage} />
|
||||
<Route component={PageNotFound} />
|
||||
</Switch>
|
||||
|
|
|
|||
51
app/client/src/pages/Applications/ApplicationsHeader.tsx
Normal file
51
app/client/src/pages/Applications/ApplicationsHeader.tsx
Normal file
|
|
@ -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)<IIconProps>`
|
||||
&&& {
|
||||
background: ${props => props.theme.colors.primary};
|
||||
span {
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
type ApplicationsHeaderProps = {
|
||||
add?: {
|
||||
form: JSX.Element;
|
||||
title: string;
|
||||
};
|
||||
};
|
||||
|
||||
export const ApplicationsHeader = (props: ApplicationsHeaderProps) => {
|
||||
return (
|
||||
<StyledHeader>
|
||||
{props.add && (
|
||||
<Popover
|
||||
interactionKind={PopoverInteractionKind.CLICK}
|
||||
popoverClassName="bp3-popover-content-sizing"
|
||||
position={Position.BOTTOM}
|
||||
>
|
||||
<StyledAddButton
|
||||
text={props.add.title}
|
||||
icon="plus"
|
||||
title={props.add.title}
|
||||
minimal
|
||||
large
|
||||
/>
|
||||
{props.add.form}
|
||||
</Popover>
|
||||
)}
|
||||
</StyledHeader>
|
||||
);
|
||||
};
|
||||
|
||||
export default ApplicationsHeader;
|
||||
38
app/client/src/pages/Applications/CreateApplicationForm.tsx
Normal file
38
app/client/src/pages/Applications/CreateApplicationForm.tsx
Normal file
|
|
@ -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<HTMLInputElement | null> = useRef(null);
|
||||
const handleCreate = () => {
|
||||
if (inputRef && inputRef.current) {
|
||||
props.onCreate(inputRef.current.value);
|
||||
} else {
|
||||
//TODO (abhinav): Add validation code.
|
||||
}
|
||||
};
|
||||
return (
|
||||
<ControlGroup fill vertical>
|
||||
<input
|
||||
type="text"
|
||||
className={Classes.INPUT}
|
||||
ref={inputRef}
|
||||
placeholder="Application Name"
|
||||
/>
|
||||
<BaseButton
|
||||
text="Create Application"
|
||||
onClick={handleCreate}
|
||||
styleName="secondary"
|
||||
loading={props.creating}
|
||||
></BaseButton>
|
||||
</ControlGroup>
|
||||
);
|
||||
};
|
||||
|
||||
export default CreateApplicationForm;
|
||||
199
app/client/src/pages/Applications/index.tsx
Normal file
199
app/client/src/pages/Applications/index.tsx
Normal file
|
|
@ -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<ApplicationProps> {
|
||||
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 (
|
||||
<ApplicationCard interactive key={application.id}>
|
||||
<ApplicationTitle>{application.name}</ApplicationTitle>
|
||||
<ApplicationControls className="controls">
|
||||
<Control fixed>
|
||||
<Tooltip content="Delete Application" hoverOpenDelay={500}>
|
||||
{deleteControlIcon}
|
||||
</Tooltip>
|
||||
</Control>
|
||||
<Control
|
||||
onClick={this.handleViewApplication(
|
||||
application.id,
|
||||
application.defaultPageId,
|
||||
)}
|
||||
>
|
||||
<Tooltip content="View Application" hoverOpenDelay={500}>
|
||||
{viewControlIcon}
|
||||
</Tooltip>
|
||||
</Control>
|
||||
<Control onClick={this.handleEditApplication(application.id)}>
|
||||
<Tooltip content="Edit Application" hoverOpenDelay={500}>
|
||||
{editControlIcon}
|
||||
</Tooltip>
|
||||
</Control>
|
||||
</ApplicationControls>
|
||||
</ApplicationCard>
|
||||
);
|
||||
};
|
||||
componentDidMount() {
|
||||
this.props.fetchApplications();
|
||||
}
|
||||
public render() {
|
||||
return (
|
||||
<div>
|
||||
<ApplicationsHeader
|
||||
add={{
|
||||
form: (
|
||||
<CreateApplicationForm
|
||||
onCreate={this.props.createApplication}
|
||||
creating={this.props.isCreatingApplication}
|
||||
/>
|
||||
),
|
||||
title: "Create Application",
|
||||
}}
|
||||
/>
|
||||
<ApplicationsBody>
|
||||
{this.props.applicationList ? (
|
||||
<ApplicationCardsWrapper>
|
||||
{this.props.applicationList.map(this.renderApplicationCard)}
|
||||
</ApplicationCardsWrapper>
|
||||
) : (
|
||||
<Spinner />
|
||||
)}
|
||||
</ApplicationsBody>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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),
|
||||
);
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
52
app/client/src/reducers/uiReducers/applicationsReducer.tsx
Normal file
52
app/client/src/reducers/uiReducers/applicationsReducer.tsx
Normal file
|
|
@ -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<ApplicationPayload>,
|
||||
) => {
|
||||
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;
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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<CreateApplicationRequest>,
|
||||
) {
|
||||
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),
|
||||
]);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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: {
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
24
app/client/src/selectors/applicationSelectors.tsx
Normal file
24
app/client/src/selectors/applicationSelectors.tsx
Normal file
|
|
@ -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,
|
||||
);
|
||||
Loading…
Reference in New Issue
Block a user