Add create dialog

This commit is contained in:
Paulo Gustavo Veiga 2021-02-02 10:54:37 -08:00
parent 67fec75d91
commit 481c1fe619
10 changed files with 199 additions and 68 deletions

View File

@ -56,6 +56,15 @@
"common.wait": { "common.wait": {
"defaultMessage": "Please wait ..." "defaultMessage": "Please wait ..."
}, },
"create.button": {
"defaultMessage": "Create"
},
"create.description": {
"defaultMessage": "Please, fill the new map name and description."
},
"create.title": {
"defaultMessage": "Create a new mindmap"
},
"duplicate.title": { "duplicate.title": {
"defaultMessage": "Duplicate" "defaultMessage": "Duplicate"
}, },

View File

@ -33,6 +33,7 @@ export type ErrorInfo = {
} }
interface Client { interface Client {
createMap(rest: { name: string; description?: string | undefined })
deleteLabel(label: string): Promise<unknown>; deleteLabel(label: string): Promise<unknown>;
registerNewUser(user: NewUser): Promise<void>; registerNewUser(user: NewUser): Promise<void>;
resetPassword(email: string): Promise<void>; resetPassword(email: string): Promise<void>;

View File

@ -43,7 +43,10 @@ class MockClient implements Client {
} }
createMap(rest: { name: string; description?: string | undefined; }) {
throw new Error("Method not implemented.");
}
s
fetchLabels(): Promise<string[]> { fetchLabels(): Promise<string[]> {
console.log("Fetching labels from server") console.log("Fetching labels from server")
return Promise.resolve(this.labels); return Promise.resolve(this.labels);

View File

@ -113,6 +113,24 @@
"value": "Please wait ..." "value": "Please wait ..."
} }
], ],
"create.button": [
{
"type": 0,
"value": "Create"
}
],
"create.description": [
{
"type": 0,
"value": "Please, fill the new map name and description."
}
],
"create.title": [
{
"type": 0,
"value": "Create a new mindmap"
}
],
"duplicate.title": [ "duplicate.title": [
{ {
"type": 0, "type": 0,

View File

@ -12,7 +12,7 @@ import ShareOutlinedIcon from '@material-ui/icons/ShareOutlined';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import { LabelOutlined } from '@material-ui/icons'; import { LabelOutlined } from '@material-ui/icons';
export type ActionType = 'open' | 'share' | 'delete' | 'info' | 'duplicate' | 'export' | 'label' | 'rename' | 'print' | 'info' | 'publish' | 'history' | undefined; export type ActionType = 'open' | 'share' | 'delete' | 'info' | 'create'| 'duplicate' | 'export' | 'label' | 'rename' | 'print' | 'info' | 'publish' | 'history' | undefined;
interface ActionProps { interface ActionProps {
onClose: (action: ActionType) => void; onClose: (action: ActionType) => void;

View File

@ -68,7 +68,7 @@ const BaseDialog = (props: DialogProps) => {
variant="contained" variant="contained"
type="submit" type="submit"
disableElevation={true}> disableElevation={true}>
{props.title} {props.submitButton}
</Button>) : null </Button>) : null
} }
</StyledDialogActions> </StyledDialogActions>

View File

@ -0,0 +1,85 @@
import React, { useEffect } from "react";
import { useIntl } from "react-intl";
import { useMutation, useQueryClient } from "react-query";
import { useSelector } from "react-redux";
import { FormControl } from "@material-ui/core";
import Client, { BasicMapInfo, ErrorInfo } from "../../../../client";
import { activeInstance } from '../../../../reducers/serviceSlice';
import Input from "../../../form/input";
import { DialogProps, fetchMapById, handleOnMutationSuccess } from "..";
import BaseDialog from "../action-dialog";
export type CreateModel = {
name: string;
description?: string;
}
export type CreateProps = {
open: boolean,
onClose: () => void
}
const defaultModel: CreateModel = { name: '', description: ''};
const CreateDialog = (props: CreateProps) => {
const client: Client = useSelector(activeInstance);
const [model, setModel] = React.useState<CreateModel>(defaultModel);
const [error, setError] = React.useState<ErrorInfo>();
const { open } = props;
const intl = useIntl();
const queryClient = useQueryClient();
const mutation = useMutation<CreateModel, ErrorInfo, CreateModel>((model: CreateModel) => {
const { ...rest } = model;
return client.createMap(rest).then(() => model);
},
{
onSuccess: () => {
handleOnMutationSuccess(props.onClose, queryClient);
},
onError: (error) => {
setError(error);
}
}
);
const handleOnClose = (): void => {
props.onClose();
setModel(defaultModel);
setError(undefined);
};
const handleOnSubmit = (event: React.FormEvent<HTMLFormElement>): void => {
event.preventDefault();
mutation.mutate(model);
};
const handleOnChange = (event: React.ChangeEvent<HTMLInputElement>): void => {
event.preventDefault();
const name = event.target.name;
const value = event.target.value;
setModel({ ...model, [name as keyof BasicMapInfo]: value });
}
return (
<div>
<BaseDialog open={open} onClose={handleOnClose} onSubmit={handleOnSubmit} error={error}
title={intl.formatMessage({ id: 'create.title', defaultMessage: 'Create a new mindmap' })}
description={intl.formatMessage({ id: 'create.description', defaultMessage: 'Please, fill the new map name and description.' })}
submitButton={intl.formatMessage({ id: 'create.button', defaultMessage: 'Create' })}>
<FormControl fullWidth={true}>
<Input name="name" type="text" label={{ id: "action.rename-name-placeholder", defaultMessage: "Name" }}
value={model.name} onChange={handleOnChange} error={error} fullWidth={true} />
<Input name="description" type="text" label={{ id: "action.rename-description-placeholder", defaultMessage: "Description" }}
value={model.description} onChange={handleOnChange} required={false} fullWidth={true} />
</FormControl>
</BaseDialog>
</div>
);
}
export default CreateDialog;

View File

@ -10,6 +10,7 @@ import { activeInstance } from '../../../reducers/serviceSlice';
import DuplicateDialog from './duplicate'; import DuplicateDialog from './duplicate';
import { useHistory } from 'react-router-dom'; import { useHistory } from 'react-router-dom';
import InfoDialog from './info'; import InfoDialog from './info';
import CreateDialog from './create';
export type BasicMapInfo = { export type BasicMapInfo = {
@ -43,6 +44,7 @@ const ActionDispatcher = (props: ActionDialogProps) => {
return ( return (
<span> <span>
<CreateDialog open={action === 'create'} onClose={handleOnClose}/>
<DeleteDialog open={action === 'delete'} onClose={handleOnClose} mapId={mapId} /> <DeleteDialog open={action === 'delete'} onClose={handleOnClose} mapId={mapId} />
<RenameDialog open={action === 'rename'} onClose={handleOnClose} mapId={mapId} /> <RenameDialog open={action === 'rename'} onClose={handleOnClose} mapId={mapId} />
<DuplicateDialog open={action === 'duplicate'} onClose={handleOnClose} mapId={mapId} /> <DuplicateDialog open={action === 'duplicate'} onClose={handleOnClose} mapId={mapId} />

View File

@ -16,11 +16,12 @@ import { useQuery, useMutation, useQueryClient } from 'react-query';
import { activeInstance } from '../../reducers/serviceSlice'; import { activeInstance } from '../../reducers/serviceSlice';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import Client from '../../client'; import Client from '../../client';
import ActionDispatcher from './action-dispatcher';
import { ActionType } from './action-chooser';
const logoIcon = require('../../images/logo-small.svg'); const logoIcon = require('../../images/logo-small.svg');
const poweredByIcon = require('../../images/pwrdby-white.svg'); const poweredByIcon = require('../../images/pwrdby-white.svg');
export type Filter = GenericFilter | LabelFilter; export type Filter = GenericFilter | LabelFilter;
export interface GenericFilter { export interface GenericFilter {
@ -42,6 +43,7 @@ const MapsPage = () => {
const [filter, setFilter] = React.useState<Filter>({ type: 'all' }); const [filter, setFilter] = React.useState<Filter>({ type: 'all' });
const client: Client = useSelector(activeInstance); const client: Client = useSelector(activeInstance);
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const [activeDialog, setActiveDialog] = React.useState<ActionType | undefined>(undefined);
useEffect(() => { useEffect(() => {
@ -103,8 +105,14 @@ const MapsPage = () => {
<Toolbar> <Toolbar>
<Tooltip title="Create a New Map"> <Tooltip title="Create a New Map">
<Button color="primary" size="medium" variant="contained" type="button" <Button color="primary"
disableElevation={true} startIcon={<AddCircleTwoTone />} className={classes.newMapButton}> size="medium"
variant="contained"
type="button"
disableElevation={true}
startIcon={<AddCircleTwoTone />}
className={classes.newMapButton}
onClick={e => setActiveDialog('create')}>
<FormattedMessage id="action.new" defaultMessage="New Map" /> <FormattedMessage id="action.new" defaultMessage="New Map" />
</Button> </Button>
</Tooltip> </Tooltip>
@ -115,6 +123,7 @@ const MapsPage = () => {
<FormattedMessage id="action.import" defaultMessage="Import" /> <FormattedMessage id="action.import" defaultMessage="Import" />
</Button> </Button>
</Tooltip> </Tooltip>
<ActionDispatcher action={activeDialog} onClose={() => setActiveDialog(undefined)} mapId={-1} />
<div className={classes.rightButtonGroup}> <div className={classes.rightButtonGroup}>
<HelpToobarButton /> <HelpToobarButton />

View File

@ -392,75 +392,79 @@ export const MapsList = (props: MapsListProps) => {
/> />
<TableBody> <TableBody>
{isLoading ? (<TableRow></TableRow>) : stableSort(mapsInfo, getComparator(order, orderBy)) {isLoading ? (
.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage) <TableRow><TableCell rowSpan={6}>Loading ...</TableCell></TableRow>) :
.map((row: MapInfo) => { (mapsInfo.length == 0 ?
const isItemSelected = isSelected(row.id); (<TableRow><TableCell rowSpan={6}>No matching records found</TableCell></TableRow>) :
const labelId = row.id; stableSort(mapsInfo, getComparator(order, orderBy))
.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage)
.map((row: MapInfo) => {
const isItemSelected = isSelected(row.id);
const labelId = row.id;
return ( return (
<TableRow <TableRow
hover hover
onClick={(event: any) => handleRowClick(event, row.id)} onClick={(event: any) => handleRowClick(event, row.id)}
role="checkbox" role="checkbox"
aria-checked={isItemSelected} aria-checked={isItemSelected}
tabIndex={-1} tabIndex={-1}
key={row.id} key={row.id}
selected={isItemSelected} selected={isItemSelected}
style={{ border: "0" }} style={{ border: "0" }}
> >
<TableCell <TableCell
padding="checkbox" padding="checkbox"
className={classes.bodyCell}> className={classes.bodyCell}>
<Checkbox <Checkbox
checked={isItemSelected} checked={isItemSelected}
inputProps={{ 'aria-labelledby': String(labelId) }} inputProps={{ 'aria-labelledby': String(labelId) }}
size="small" /> size="small" />
</TableCell> </TableCell>
<TableCell <TableCell
padding="checkbox" padding="checkbox"
className={classes.bodyCell}> className={classes.bodyCell}>
<Tooltip title="Starred"> <Tooltip title="Starred">
<IconButton aria-label="Starred" size="small" onClick={(e) => handleStarred(e, row.id)}> <IconButton aria-label="Starred" size="small" onClick={(e) => handleStarred(e, row.id)}>
<StarRateRoundedIcon color="action" style={{ color: row.starred ? 'yellow' : 'gray' }} /> <StarRateRoundedIcon color="action" style={{ color: row.starred ? 'yellow' : 'gray' }} />
</IconButton> </IconButton>
</Tooltip> </Tooltip>
</TableCell> </TableCell>
<TableCell className={classes.bodyCell}> <TableCell className={classes.bodyCell}>
<Tooltip title="Open for edition" placement="bottom-start"> <Tooltip title="Open for edition" placement="bottom-start">
<Link href={`c/maps/${row.id}/edit`} color="textPrimary" underline="always"> <Link href={`c/maps/${row.id}/edit`} color="textPrimary" underline="always">
{row.name} {row.name}
</Link> </Link>
</Tooltip> </Tooltip>
</TableCell> </TableCell>
<TableCell className={classes.bodyCell}> <TableCell className={classes.bodyCell}>
{row.labels} {row.labels}
</TableCell> </TableCell>
<TableCell className={classes.bodyCell}> <TableCell className={classes.bodyCell}>
{row.creator} {row.creator}
</TableCell> </TableCell>
<TableCell className={classes.bodyCell}> <TableCell className={classes.bodyCell}>
<Tooltip title={moment(row.modified).format("lll")} placement="bottom-start"> <Tooltip title={moment(row.modified).format("lll")} placement="bottom-start">
<span>{moment(row.modified).fromNow()}</span> <span>{moment(row.modified).fromNow()}</span>
</Tooltip> </Tooltip>
</TableCell> </TableCell>
<TableCell className={classes.bodyCell}> <TableCell className={classes.bodyCell}>
<Tooltip title="Others"> <Tooltip title="Others">
<IconButton aria-label="Others" size="small" onClick={handleActionClick(row.id)}> <IconButton aria-label="Others" size="small" onClick={handleActionClick(row.id)}>
<MoreHorizIcon color="action" /> <MoreHorizIcon color="action" />
</IconButton> </IconButton>
</Tooltip> </Tooltip>
<ActionChooser anchor={activeRowAction?.el} onClose={handleActionMenuClose} /> <ActionChooser anchor={activeRowAction?.el} onClose={handleActionMenuClose} />
</TableCell> </TableCell>
</TableRow> </TableRow>
); );
})} }))}
</TableBody> </TableBody>
</Table> </Table>
</TableContainer> </TableContainer>