PromucFlow_constructor/app/client/src/pages/Editor/ApiSidebar.tsx

324 lines
8.4 KiB
TypeScript
Raw Normal View History

2019-10-18 08:16:26 +00:00
import React from "react";
2019-10-21 15:12:45 +00:00
import { connect } from "react-redux";
import { RouteComponentProps } from "react-router";
import styled from "styled-components";
2019-11-25 05:07:27 +00:00
import { AppState } from "reducers";
import { ActionDataState } from "reducers/entityReducers/actionsReducer";
2019-11-25 09:15:11 +00:00
import { API_EDITOR_URL, APIEditorRouteParams } from "constants/routes";
2019-11-25 05:07:27 +00:00
import { BaseButton } from "components/designSystems/blueprint/ButtonComponent";
import { FormIcons } from "icons/FormIcons";
import { Spinner } from "@blueprintjs/core";
2019-11-25 05:07:27 +00:00
import { ApiPaneReduxState } from "reducers/uiReducers/apiPaneReducer";
import { BaseTextInput } from "components/designSystems/appsmith/TextInputComponent";
2019-11-13 07:34:59 +00:00
import { TICK } from "@blueprintjs/icons/lib/esm/generated/iconNames";
2019-11-25 05:07:27 +00:00
import { createActionRequest } from "actions/actionActions";
2019-11-25 09:15:11 +00:00
import { changeApi, initApiPane } from "actions/apiPaneActions";
import { RestAction } from "api/ActionAPI";
2019-11-25 09:15:11 +00:00
import CenteredWrapper from "components/designSystems/appsmith/CenteredWrapper";
2019-11-14 09:01:23 +00:00
import Fuse from "fuse.js";
import { getPluginIdOfName } from "selectors/entitiesSelector";
import { PLUGIN_NAME } from "constants/ApiEditorConstants";
2019-11-25 09:15:11 +00:00
const LoadingContainer = styled(CenteredWrapper)`
2019-11-13 11:34:11 +00:00
height: 50%;
`;
const ApiSidebarWrapper = styled.div`
2019-11-13 11:34:11 +00:00
margin-top: 10px;
height: 100%;
width: 100%;
flex-direction: column;
`;
2019-11-14 09:01:23 +00:00
const SearchBar = styled(BaseTextInput)`
margin-bottom: 10px;
input {
background-color: #23292e;
border: none;
color: ${props => props.theme.colors.textOnDarkBG}
:focus {
background-color: #23292e;
}
}
.bp3-icon {
background-color: #23292e;
}
`;
const ApiItemsWrapper = styled.div`
flex: 1;
2019-11-13 11:34:11 +00:00
margin-bottom: 15px;
`;
2019-10-21 15:12:45 +00:00
const ApiItem = styled.div<{ isSelected: boolean }>`
height: 32px;
2019-10-21 15:12:45 +00:00
width: 100%;
2019-11-13 11:34:11 +00:00
padding: 8px 12px;
2019-10-21 15:12:45 +00:00
border-radius: 4px;
cursor: pointer;
font-size: 14px;
display: flex;
align-items: center;
margin-bottom: 2px;
2019-10-21 15:12:45 +00:00
background-color: ${props =>
props.isSelected ? props.theme.colors.paneCard : props.theme.colors.paneBG}
:hover {
background-color: ${props => props.theme.colors.paneCard};
}
`;
2019-11-14 09:01:23 +00:00
const HTTPMethod = styled.span<{ method?: string }>`
2019-10-21 15:12:45 +00:00
flex: 1;
font-size: 12px;
2019-10-21 15:12:45 +00:00
color: ${props => {
switch (props.method) {
case "GET":
return "#29CCA3";
case "POST":
return "#F7C75B";
case "PUT":
return "#30A5E0";
case "DELETE":
return "#CE4257";
default:
return "#333";
}
}};
`;
const ActionName = styled.span`
flex: 3;
padding: 0 2px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
`;
2019-11-25 09:15:11 +00:00
const DraftIconIndicator = styled.span<{ isHidden: boolean }>`
width: 8px;
height: 8px;
border-radius: 8px;
background-color: #f2994a;
opacity: ${({ isHidden }) => (isHidden ? 0 : 1)};
`;
const CreateNewButton = styled(BaseButton)`
&& {
border: none;
color: ${props => props.theme.colors.textOnDarkBG};
height: 32px;
2019-11-13 11:34:11 +00:00
text-align: left;
justify-content: flex-start;
&:hover {
color: ${props => props.theme.colors.paneBG};
svg {
path {
fill: ${props => props.theme.colors.paneBG};
}
}
}
svg {
2019-11-13 11:34:11 +00:00
margin-top: 4px;
height: 14px;
width: 14px;
}
}
2019-10-21 15:12:45 +00:00
`;
2019-11-13 07:34:59 +00:00
const CreateApiWrapper = styled.div`
display: grid;
2019-11-13 11:34:11 +00:00
grid-template-columns: 6fr 1fr;
2019-11-13 07:34:59 +00:00
grid-gap: 5px;
2019-11-13 11:34:11 +00:00
height: 32px;
2019-11-13 07:34:59 +00:00
`;
2019-10-21 15:12:45 +00:00
interface ReduxStateProps {
actions: ActionDataState;
2019-11-13 07:34:59 +00:00
apiPane: ApiPaneReduxState;
pluginId: string | undefined;
2019-10-21 15:12:45 +00:00
}
2019-11-13 07:34:59 +00:00
interface ReduxDispatchProps {
createAction: (name: string) => void;
2019-11-25 09:15:11 +00:00
onApiChange: (id: string) => void;
initApiPane: (urlId?: string) => void;
2019-11-13 07:34:59 +00:00
}
type Props = ReduxStateProps &
ReduxDispatchProps &
RouteComponentProps<APIEditorRouteParams>;
2019-11-13 07:34:59 +00:00
type State = {
isCreating: boolean;
name: string;
2019-11-14 09:01:23 +00:00
search: string;
};
2019-12-06 13:16:08 +00:00
const FUSE_OPTIONS = {
2019-11-14 09:01:23 +00:00
shouldSort: true,
2019-12-06 13:16:08 +00:00
threshold: 0.5,
2019-11-14 09:01:23 +00:00
location: 0,
minMatchCharLength: 3,
2019-12-06 13:16:08 +00:00
findAllMatches: true,
2019-11-14 09:01:23 +00:00
keys: ["name"],
2019-11-13 07:34:59 +00:00
};
class ApiSidebar extends React.Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = {
isCreating: false,
name: "",
2019-11-14 09:01:23 +00:00
search: "",
2019-11-13 07:34:59 +00:00
};
}
2019-11-25 09:15:11 +00:00
componentDidMount(): void {
this.props.initApiPane(this.props.match.params.apiId);
}
2019-11-13 07:34:59 +00:00
componentDidUpdate(prevProps: Readonly<Props>): void {
2019-11-25 09:15:11 +00:00
// If url has changed, hide the create input
if (!prevProps.match.params.apiId && this.props.match.params.apiId) {
2019-11-13 07:34:59 +00:00
this.setState({
isCreating: false,
name: "",
});
}
}
2019-10-21 15:12:45 +00:00
handleCreateNew = () => {
2019-11-25 09:15:11 +00:00
const { history, actions } = this.props;
const { pageId, applicationId } = this.props.match.params;
history.push(API_EDITOR_URL(applicationId, pageId));
2019-11-13 07:34:59 +00:00
this.setState({
isCreating: true,
2019-11-25 09:15:11 +00:00
name: `action${actions.data.length}`,
2019-11-13 07:34:59 +00:00
});
};
saveAction = () => {
if (this.state.name) {
this.props.createAction(this.state.name);
} else {
this.setState({
isCreating: false,
});
}
};
handleNameChange = (e: React.ChangeEvent<{ value: string }>) => {
const value = e.target.value;
this.setState({
name: value,
});
};
2019-11-14 09:01:23 +00:00
handleSearchChange = (e: React.ChangeEvent<{ value: string }>) => {
const value = e.target.value;
this.setState({
search: value,
});
};
2019-11-25 09:15:11 +00:00
handleApiChange = (actionId: string) => {
this.props.onApiChange(actionId);
};
2019-10-18 08:16:26 +00:00
render() {
2019-11-14 09:01:23 +00:00
const {
2019-11-25 09:15:11 +00:00
apiPane: { isFetching, isSaving, drafts },
2019-11-14 09:01:23 +00:00
match,
actions: { data },
pluginId,
2019-11-14 09:01:23 +00:00
} = this.props;
if (!pluginId) return null;
2019-11-14 09:01:23 +00:00
const { isCreating, search, name } = this.state;
const activeActionId = match.params.apiId;
2019-12-06 13:16:08 +00:00
const fuse = new Fuse(data, FUSE_OPTIONS);
const actions: RestAction[] = search ? fuse.search(search) : data;
2019-10-21 15:12:45 +00:00
return (
2019-11-13 11:34:11 +00:00
<React.Fragment>
2019-11-25 09:15:11 +00:00
{isFetching ? (
2019-11-13 11:34:11 +00:00
<LoadingContainer>
<Spinner size={30} />
</LoadingContainer>
2019-11-13 07:34:59 +00:00
) : (
2019-11-13 11:34:11 +00:00
<ApiSidebarWrapper>
<ApiItemsWrapper>
2019-11-14 09:01:23 +00:00
<SearchBar
icon="search"
input={{
value: search,
onChange: this.handleSearchChange,
}}
2019-11-25 09:15:11 +00:00
placeholder="Search"
2019-11-14 09:01:23 +00:00
/>
{actions.map(action => (
2019-11-13 11:34:11 +00:00
<ApiItem
key={action.id}
2019-11-25 09:15:11 +00:00
onClick={() => this.handleApiChange(action.id)}
2019-11-13 11:34:11 +00:00
isSelected={activeActionId === action.id}
>
2019-11-14 09:01:23 +00:00
{action.actionConfiguration ? (
<HTTPMethod method={action.actionConfiguration.httpMethod}>
{action.actionConfiguration.httpMethod}
</HTTPMethod>
) : (
<HTTPMethod />
)}
2019-11-13 11:34:11 +00:00
<ActionName>{action.name}</ActionName>
2019-11-25 09:15:11 +00:00
<DraftIconIndicator isHidden={!(action.id in drafts)} />
2019-11-13 11:34:11 +00:00
</ApiItem>
))}
</ApiItemsWrapper>
{isCreating ? (
<CreateApiWrapper>
<BaseTextInput
2019-11-25 09:15:11 +00:00
placeholder="API name"
2019-11-13 11:34:11 +00:00
input={{
2019-11-14 09:01:23 +00:00
value: name,
2019-11-13 11:34:11 +00:00
onChange: this.handleNameChange,
}}
/>
<BaseButton
icon={TICK}
2019-11-28 07:08:39 +00:00
accent="primary"
2019-11-13 11:34:11 +00:00
text=""
onClick={this.saveAction}
filled
2019-11-25 09:15:11 +00:00
loading={isSaving}
2019-11-13 11:34:11 +00:00
/>
</CreateApiWrapper>
) : (
<React.Fragment>
2019-11-25 09:15:11 +00:00
{!isFetching && (
2019-11-13 11:34:11 +00:00
<CreateNewButton
text="Create new API"
icon={FormIcons.ADD_NEW_ICON()}
onClick={this.handleCreateNew}
/>
)}
</React.Fragment>
)}
</ApiSidebarWrapper>
2019-11-13 07:34:59 +00:00
)}
2019-11-13 11:34:11 +00:00
</React.Fragment>
2019-10-21 15:12:45 +00:00
);
2019-10-18 08:16:26 +00:00
}
}
2019-10-21 15:12:45 +00:00
const mapStateToProps = (state: AppState): ReduxStateProps => ({
pluginId: getPluginIdOfName(state, PLUGIN_NAME),
2019-10-21 15:12:45 +00:00
actions: state.entities.actions,
2019-11-13 07:34:59 +00:00
apiPane: state.ui.apiPane,
});
const mapDispatchToProps = (dispatch: Function): ReduxDispatchProps => ({
2019-11-14 13:35:39 +00:00
createAction: (name: string) => dispatch(createActionRequest({ name })),
2019-11-25 09:15:11 +00:00
onApiChange: (actionId: string) => dispatch(changeApi(actionId)),
initApiPane: (urlId?: string) => dispatch(initApiPane(urlId)),
2019-10-21 15:12:45 +00:00
});
export default connect(mapStateToProps, mapDispatchToProps)(ApiSidebar);