WIP: Share dialog with mock support

This commit is contained in:
Paulo Gustavo Veiga 2021-02-19 17:37:55 -08:00
parent c440e2426c
commit 2ae76dc08d
16 changed files with 291 additions and 74 deletions

View File

@ -22,6 +22,8 @@ export type Label = {
iconName: string; iconName: string;
} }
export type Role = 'owner' | 'editor' | 'viewer';
export type MapInfo = { export type MapInfo = {
id: number; id: number;
starred: boolean; starred: boolean;
@ -33,7 +35,7 @@ export type MapInfo = {
lastModificationTime: string; lastModificationTime: string;
description: string; description: string;
isPublic: boolean; isPublic: boolean;
role: 'owner' | 'editor' | 'viewer' role: Role;
} }
export type ChangeHistory = { export type ChangeHistory = {
@ -64,6 +66,12 @@ export type AccountInfo = {
locale: Locale; locale: Locale;
} }
export type Permission = {
name?: string;
email: string;
role: Role;
}
interface Client { interface Client {
deleteAccount(): Promise<void> deleteAccount(): Promise<void>
importMap(model: ImportMapInfo): Promise<number> importMap(model: ImportMapInfo): Promise<number>
@ -72,11 +80,14 @@ interface Client {
deleteMap(id: number): Promise<void>; deleteMap(id: number): Promise<void>;
renameMap(id: number, basicInfo: BasicMapInfo): Promise<void>; renameMap(id: number, basicInfo: BasicMapInfo): Promise<void>;
fetchAllMaps(): Promise<MapInfo[]>; fetchAllMaps(): Promise<MapInfo[]>;
fetchMapPermissions(id: number): Promise<Permission[]>;
addMapPermissions(id: number, message: string, permissions: Permission[]): Promise<void>;
duplicateMap(id: number, basicInfo: BasicMapInfo): Promise<number>; duplicateMap(id: number, basicInfo: BasicMapInfo): Promise<number>;
updateAccountLanguage(locale: LocaleCode): Promise<void>; updateAccountLanguage(locale: LocaleCode): Promise<void>;
updateAccountPassword(pasword: string): Promise<void>; updateAccountPassword(pasword: string): Promise<void>;
updateAccountInfo(firstname: string,lastname: string): Promise<void>; updateAccountInfo(firstname: string, lastname: string): Promise<void>;
updateStarred(id: number, starred: boolean): Promise<void>; updateStarred(id: number, starred: boolean): Promise<void>;
updateMapToPublic(id: number, starred: boolean): Promise<void>; updateMapToPublic(id: number, starred: boolean): Promise<void>;

View File

@ -1,8 +1,10 @@
import Client, { AccountInfo, BasicMapInfo, ChangeHistory, ImportMapInfo, Label, MapInfo, NewUser } from '..'; import Client, { AccountInfo, BasicMapInfo, ChangeHistory, ImportMapInfo, Label, MapInfo, NewUser, Permission } from '..';
import { LocaleCode, localeFromStr } from '../../app-i18n'; import { LocaleCode, localeFromStr } from '../../app-i18n';
class MockClient implements Client { class MockClient implements Client {
private maps: MapInfo[] = []; private maps: MapInfo[] = [];
private labels: Label[] = []; private labels: Label[] = [];
private permissionsByMap: Map<number, Permission[]> = new Map();
constructor() { constructor() {
@ -22,6 +24,7 @@ class MockClient implements Client {
): MapInfo { ): MapInfo {
return { id, title, labels, createdBy: creator, creationTime, lastModificationBy: modifiedByUser, lastModificationTime: modifiedTime, starred, description, isPublic, role }; return { id, title, labels, createdBy: creator, creationTime, lastModificationBy: modifiedByUser, lastModificationTime: modifiedTime, starred, description, isPublic, role };
} }
this.maps = [ this.maps = [
createMapInfo(1, true, "El Mapa", [], "Paulo", "2008-06-02T00:00:00Z", "Berna", "2008-06-02T00:00:00Z", "", true, 'owner'), createMapInfo(1, true, "El Mapa", [], "Paulo", "2008-06-02T00:00:00Z", "Berna", "2008-06-02T00:00:00Z", "", true, 'owner'),
createMapInfo(11, false, "El Mapa3", [1, 2, 3], "Paulo3", "2008-06-02T00:00:00Z", "Berna", "2008-06-02T00:00:00Z", "", false, 'editor'), createMapInfo(11, false, "El Mapa3", [1, 2, 3], "Paulo3", "2008-06-02T00:00:00Z", "Berna", "2008-06-02T00:00:00Z", "", false, 'editor'),
@ -32,7 +35,28 @@ class MockClient implements Client {
{ id: 1, title: "Red Label", iconName: "", color: 'red' }, { id: 1, title: "Red Label", iconName: "", color: 'red' },
{ id: 2, title: "Blue Label", iconName: "", color: 'blue' } { id: 2, title: "Blue Label", iconName: "", color: 'blue' }
]; ];
}
addMapPermissions(id: number, message: string, permissions: Permission[]): Promise<void> {
let perm = this.permissionsByMap.get(id) || [];
perm = perm.concat(permissions);
this.permissionsByMap.set(id, perm);
console.log(`Message ${message}`)
return Promise.resolve();
}
fetchMapPermissions(id: number): Promise<Permission[]> {
let perm = this.permissionsByMap.get(id);
if (!perm) {
perm = [{
name: 'Cosme Sharing',
email: 'pepe@gmail.com',
role: 'editor'
}];
this.permissionsByMap.set(id, perm);
}
return Promise.resolve(perm);
} }
deleteAccount(): Promise<void> { deleteAccount(): Promise<void> {

View File

@ -1,5 +1,5 @@
import axios from 'axios'; import axios from 'axios';
import Client, { ErrorInfo, MapInfo, BasicMapInfo, NewUser, Label, ChangeHistory, AccountInfo, ImportMapInfo } from '..'; import Client, { ErrorInfo, MapInfo, BasicMapInfo, NewUser, Label, ChangeHistory, AccountInfo, ImportMapInfo, Permission } from '..';
import { LocaleCode, localeFromStr, Locales } from '../../app-i18n'; import { LocaleCode, localeFromStr, Locales } from '../../app-i18n';
export default class RestClient implements Client { export default class RestClient implements Client {
@ -10,6 +10,15 @@ export default class RestClient implements Client {
this.baseUrl = baseUrl; this.baseUrl = baseUrl;
this.sessionExpired = sessionExpired; this.sessionExpired = sessionExpired;
} }
// eslint-disable-next-line @typescript-eslint/no-unused-vars
addMapPermissions(id: number, message: string, permissions: Permission[]): Promise<void> {
throw new Error('Method not implemented.');
}
fetchMapPermissions(id: number): Promise<Permission[]> {
throw new Error('Method not implemented.' + id);
}
deleteAccount(): Promise<void> { deleteAccount(): Promise<void> {
const handler = (success: () => void, reject: (error: ErrorInfo) => void) => { const handler = (success: () => void, reject: (error: ErrorInfo) => void) => {
axios.delete(this.baseUrl + `/c/restful/account`, axios.delete(this.baseUrl + `/c/restful/account`,

View File

@ -24,7 +24,7 @@ const ConfigStatusMessage = ({ enabled = false }: ConfigStatusProps): React.Reac
</p> </p>
</div>); </div>);
} }
return result ? result : null; return result || null;
} }
const LoginError = () => { const LoginError = () => {

View File

@ -32,7 +32,7 @@ const AccountMenu = (): React.ReactElement => {
const account = fetchAccount(); const account = fetchAccount();
return ( return (
<span> <span>
<Tooltip title={`${account?.firstname} ${account?.lastname} <${account?.email}>`}> <Tooltip arrow={true} title={`${account?.firstname} ${account?.lastname} <${account?.email}>`}>
<IconButton <IconButton
onClick={handleMenu}> onClick={handleMenu}>
<AccountCircle fontSize="large" style={{ color: 'black' }} /> <AccountCircle fontSize="large" style={{ color: 'black' }} />

View File

@ -5,6 +5,7 @@ import { StyledDialog, StyledDialogActions, StyledDialogContent, StyledDialogTit
import GlobalError from "../../../form/global-error"; import GlobalError from "../../../form/global-error";
import DialogContentText from "@material-ui/core/DialogContentText"; import DialogContentText from "@material-ui/core/DialogContentText";
import Button from "@material-ui/core/Button"; import Button from "@material-ui/core/Button";
import { PaperProps } from "@material-ui/core/Paper";
export type DialogProps = { export type DialogProps = {
onClose: () => void; onClose: () => void;
@ -18,10 +19,11 @@ export type DialogProps = {
submitButton?: string; submitButton?: string;
actionUrl?: string; actionUrl?: string;
maxWidth?: 'xs' | 'sm' | 'md' | 'lg' | 'xl' | false; maxWidth?: 'xs' | 'sm' | 'md' | 'lg' | 'xl' | false;
PaperProps?: Partial<PaperProps>;
} }
const BaseDialog = (props: DialogProps): React.ReactElement => { const BaseDialog = (props: DialogProps): React.ReactElement => {
const { onClose, onSubmit, maxWidth = 'sm' } = props; const { onClose, onSubmit, maxWidth = 'sm', PaperProps } = props;
const handleOnSubmit = (e: React.FormEvent<HTMLFormElement>) => { const handleOnSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault(); e.preventDefault();
@ -31,13 +33,13 @@ const BaseDialog = (props: DialogProps): React.ReactElement => {
} }
const description = props.description ? (<DialogContentText>{props.description}</DialogContentText>) : null; const description = props.description ? (<DialogContentText>{props.description}</DialogContentText>) : null;
return ( return (
<div> <div>
<StyledDialog <StyledDialog
open={true} open={true}
onClose={onClose} onClose={onClose}
maxWidth={maxWidth} maxWidth={maxWidth}
PaperProps={PaperProps}
fullWidth={true}> fullWidth={true}>
<form autoComplete="off" onSubmit={handleOnSubmit}> <form autoComplete="off" onSubmit={handleOnSubmit}>
<StyledDialogTitle> <StyledDialogTitle>

View File

@ -2,7 +2,7 @@ import React from "react";
import { FormattedMessage, useIntl } from "react-intl"; import { FormattedMessage, useIntl } from "react-intl";
import { useMutation, useQueryClient } from "react-query"; import { useMutation, useQueryClient } from "react-query";
import { useSelector } from "react-redux"; import { useSelector } from "react-redux";
import Client from "../../../../classes/client"; import Client, { ErrorInfo } from "../../../../classes/client";
import { activeInstance, fetchMapById } from '../../../../redux/clientSlice'; import { activeInstance, fetchMapById } from '../../../../redux/clientSlice';
import { SimpleDialogProps, handleOnMutationSuccess } from ".."; import { SimpleDialogProps, handleOnMutationSuccess } from "..";
import BaseDialog from "../base-dialog"; import BaseDialog from "../base-dialog";
@ -14,10 +14,14 @@ const DeleteDialog = ({ mapId, onClose }: SimpleDialogProps): React.ReactElement
const intl = useIntl(); const intl = useIntl();
const client: Client = useSelector(activeInstance); const client: Client = useSelector(activeInstance);
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const [error, setError] = React.useState<ErrorInfo>();
const mutation = useMutation((id: number) => client.deleteMap(id), const mutation = useMutation((id: number) => client.deleteMap(id),
{ {
onSuccess: () => handleOnMutationSuccess(onClose, queryClient) onSuccess: () => handleOnMutationSuccess(onClose, queryClient),
onError: (error: ErrorInfo) => {
setError(error);
}
} }
); );
@ -35,6 +39,7 @@ const DeleteDialog = ({ mapId, onClose }: SimpleDialogProps): React.ReactElement
return ( return (
<div> <div>
<BaseDialog <BaseDialog
error={error}
onClose={handleOnClose} onSubmit={handleOnSubmit} onClose={handleOnClose} onSubmit={handleOnSubmit}
title={intl.formatMessage({ id: "action.delete-title", defaultMessage: "Delete" })} title={intl.formatMessage({ id: "action.delete-title", defaultMessage: "Delete" })}
submitButton={intl.formatMessage({ id: "action.delete-title", defaultMessage: "Delete" })}> submitButton={intl.formatMessage({ id: "action.delete-title", defaultMessage: "Delete" })}>

View File

@ -21,7 +21,10 @@ const DeleteMultiselectDialog = ({ onClose, mapsId }: DeleteMultiselectDialogPro
const mutation = useMutation((ids: number[]) => client.deleteMaps(ids), const mutation = useMutation((ids: number[]) => client.deleteMaps(ids),
{ {
onSuccess: () => handleOnMutationSuccess(onClose, queryClient) onSuccess: () => handleOnMutationSuccess(onClose, queryClient),
onError: (error) => {
console.error(`Unexpected error ${error}`);
}
} }
); );

View File

@ -1,15 +1,16 @@
import React from "react"; import React from "react";
import { FormattedMessage, useIntl } from "react-intl"; import { FormattedMessage, useIntl } from "react-intl";
import { useMutation, useQueryClient } from "react-query"; import { useMutation, useQuery, useQueryClient } from "react-query";
import { useSelector } from "react-redux"; import { useSelector } from "react-redux";
import Client from "../../../../classes/client"; import Client, { ErrorInfo, Permission } from "../../../../classes/client";
import { activeInstance } from '../../../../redux/clientSlice'; import { activeInstance } from '../../../../redux/clientSlice';
import { SimpleDialogProps, handleOnMutationSuccess } from ".."; import { SimpleDialogProps } from "..";
import BaseDialog from "../base-dialog"; import BaseDialog from "../base-dialog";
import List from "@material-ui/core/List"; import List from "@material-ui/core/List";
import ListItem from "@material-ui/core/ListItem"; import ListItem from "@material-ui/core/ListItem";
import ListItemText from "@material-ui/core/ListItemText"; import ListItemText from "@material-ui/core/ListItemText";
import IconButton from "@material-ui/core/IconButton"; import IconButton from "@material-ui/core/IconButton";
import ListItemSecondaryAction from "@material-ui/core/ListItemSecondaryAction"; import ListItemSecondaryAction from "@material-ui/core/ListItemSecondaryAction";
import DeleteIcon from '@material-ui/icons/Delete'; import DeleteIcon from '@material-ui/icons/Delete';
import Paper from "@material-ui/core/Paper"; import Paper from "@material-ui/core/Paper";
@ -19,16 +20,41 @@ import Button from "@material-ui/core/Button";
import TextField from "@material-ui/core/TextField"; import TextField from "@material-ui/core/TextField";
import FormControlLabel from "@material-ui/core/FormControlLabel"; import FormControlLabel from "@material-ui/core/FormControlLabel";
import Checkbox from "@material-ui/core/Checkbox"; import Checkbox from "@material-ui/core/Checkbox";
import Typography from "@material-ui/core/Typography";
import { useStyles } from "./style";
import RoleIcon from "../../role-icon";
type ShareModel = {
emails: string,
role: 'editor' | 'viewer',
message: string
}
const defaultModel: ShareModel = { emails: '', role: 'editor', message: '' };
const ShareDialog = ({ mapId, onClose }: SimpleDialogProps): React.ReactElement => { const ShareDialog = ({ mapId, onClose }: SimpleDialogProps): React.ReactElement => {
const intl = useIntl(); const intl = useIntl();
const client: Client = useSelector(activeInstance); const client: Client = useSelector(activeInstance);
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const classes = useStyles();
const [showMessage, setShowMessage] = React.useState<boolean>(false);
const [model, setModel] = React.useState<ShareModel>(defaultModel);
const [error, setError] = React.useState<ErrorInfo>();
const mutation = useMutation((id: number) => client.deleteMap(id), const mutation = useMutation(
(model: ShareModel) => {
const emails = model.emails.split("'");
const permissions = emails.map((email) => { return { email: email, role: model.role } });
return client.addMapPermissions(mapId, model.message, permissions);
},
{ {
onSuccess: () => handleOnMutationSuccess(onClose, queryClient) onSuccess: () => {
queryClient.invalidateQueries(`perm-${mapId}`);
setModel(defaultModel);
},
onError: (error: ErrorInfo) => {
setError(error);
}
} }
); );
@ -36,65 +62,117 @@ const ShareDialog = ({ mapId, onClose }: SimpleDialogProps): React.ReactElement
onClose(); onClose();
}; };
const handleOnSubmit = (): void => { const handleOnChange = (event: React.ChangeEvent<HTMLInputElement>): void => {
mutation.mutate(mapId); event.preventDefault();
const name = event.target.name;
const value = event.target.value;
setModel({ ...model, [name as keyof ShareModel]: value });
} }
// Fetch map model to be rendered ... const handleOnClick = (event: React.MouseEvent<HTMLButtonElement, MouseEvent>): void => {
event.stopPropagation();
mutation.mutate(model);
};
const { isLoading, data: permissions = [] } = useQuery<unknown, ErrorInfo, Permission[]>(`perm-${mapId}`, () => {
return client.fetchMapPermissions(mapId);
});
return ( return (
<div> <div>
<BaseDialog <BaseDialog
onClose={handleOnClose} onSubmit={handleOnSubmit} onClose={handleOnClose}
title={intl.formatMessage({ id: "share.delete-title", defaultMessage: "Share with people" })} title={intl.formatMessage({ id: "share.delete-title", defaultMessage: "Share with people" })}
description={intl.formatMessage({ id: "share.delete-description", defaultMessage: "Collaboration " })} description={intl.formatMessage({ id: "share.delete-description", defaultMessage: "Invite people to collaborate with you on the creation of your midnmap. They will be notified by email. " })}
submitButton={intl.formatMessage({ id: "share.delete-title", defaultMessage: "Share" })} PaperProps={{ classes: { root: classes.paper } }}
maxWidth="md"> error={error}
>
<div style={{ padding: '10px 10px', background: '#f9f9f9' }}> <div className={classes.actionContainer}>
<TextField id="email" style={{ width: '300px' }} size="small" type="text" variant="outlined" placeholder="Add collaboratos's emails seperated by commas" label="Email" /> <TextField
<Select id="emails"
value='edit' name="emails"
required={true}
style={{ width: '300px' }}
size="small"
type="email"
variant="outlined" variant="outlined"
placeholder="Add collaboration email"
label="Emails"
onChange={handleOnChange}
value={model.emails}
/>
<Select
variant="outlined"
onChange={handleOnChange}
value={model.role}
name="role"
style={{ margin: '0px 10px' }} style={{ margin: '0px 10px' }}
> >
<MenuItem value='edit'>Can Edit</MenuItem> <MenuItem value='editor'><FormattedMessage id="share.can-edit" defaultMessage="Can edit" /></MenuItem>
<MenuItem value='view'>Can View</MenuItem> <MenuItem value='viewer'><FormattedMessage id="share.can-view" defaultMessage="Can view" /></MenuItem>
</Select> </Select>
<FormControlLabel <FormControlLabel
value="start" value="start"
onChange={(event, value) => { setShowMessage(value) }}
style={{ fontSize: "5px" }}
control={<Checkbox color="primary" />} control={<Checkbox color="primary" />}
label={<FormattedMessage id="share.add-message" defaultMessage="Add message" />} label={<Typography variant="subtitle2"><FormattedMessage id="share.add-message" defaultMessage="Add message" /></Typography>}
labelPlacement="end" labelPlacement="end"
/>
<Button
color="primary"
type="button"
variant="contained"
disableElevation={true}
onClick={handleOnClick}>
<FormattedMessage id="share.add-button" defaultMessage="Add " />
</Button>
{showMessage &&
<TextField
multiline
rows={3}
rowsMax={3}
className={classes.textArea}
variant="filled"
name="message"
onChange={handleOnChange}
value={model.message}
placeholder="Include a customize message to ..."
/> />
<Button }
color="primary"
variant="contained" disableElevation={true}><FormattedMessage id="share.add-button" defaultMessage="Add " /></Button>
</div> </div>
<Paper elevation={1} style={{ maxHeight: 200, overflowY: 'scroll' }} variant="outlined"> {!isLoading &&
<List> <Paper elevation={1} className={classes.listPaper} variant="outlined">
{[.4, 5, 7, 7, 8, 9, 100, 1, 2, 3].map((value) => { <List>
const labelId = `checkbox-list-label-${value}`; {permissions && permissions.map((permission) => {
return (
<ListItem key={permission.email} role={undefined} dense button>
<ListItemText id={`${permission.name}<${permission.email}>`} primary={permission.email} />
return ( <RoleIcon role={permission.role} />
<ListItem key={value} role={undefined} dense button>
<ListItemText id={labelId} primary={`Line item ${value + 1}`} /> <ListItemSecondaryAction>
<ListItemSecondaryAction> <IconButton edge="end">
<IconButton edge="end"> <DeleteIcon />
<DeleteIcon /> </IconButton>
</IconButton> </ListItemSecondaryAction>
</ListItemSecondaryAction> </ListItem>
</ListItem> );
); })}
})} </List>
</List> </Paper>
</Paper> }
</BaseDialog> </BaseDialog>
</div > </div >
); );
} }
export default ShareDialog; export default ShareDialog;

View File

@ -0,0 +1,27 @@
import createStyles from "@material-ui/core/styles/createStyles";
import makeStyles from "@material-ui/core/styles/makeStyles";
export const useStyles = makeStyles(() =>
createStyles({
actionContainer: {
padding: '10px 0px',
border: '1px solid rgba(0, 0, 0, 0.12)',
borderRadius: '8px 8px 0px 0px',
textAlign: "center"
},
textArea:
{
width: '730px',
margin: '5px 0px',
padding: '10px'
},
listPaper: {
maxHeight: 200,
overflowY: 'scroll',
},
paper: {
width: "850px",
minWidth: "850px"
}
}),
);

View File

@ -1,5 +1,5 @@
import React from "react"; import React from "react";
import { FormattedMessage } from "react-intl"; import { FormattedMessage, useIntl } from "react-intl";
import Help from "@material-ui/icons/Help"; import Help from "@material-ui/icons/Help";
import PolicyOutlined from "@material-ui/icons/PolicyOutlined"; import PolicyOutlined from "@material-ui/icons/PolicyOutlined";
@ -11,10 +11,12 @@ import Menu from "@material-ui/core/Menu";
import MenuItem from "@material-ui/core/MenuItem"; import MenuItem from "@material-ui/core/MenuItem";
import Link from "@material-ui/core/Link"; import Link from "@material-ui/core/Link";
import ListItemIcon from "@material-ui/core/ListItemIcon"; import ListItemIcon from "@material-ui/core/ListItemIcon";
import Tooltip from "@material-ui/core/Tooltip";
const HelpMenu = (): React.ReactElement => { const HelpMenu = (): React.ReactElement => {
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null); const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
const open = Boolean(anchorEl); const open = Boolean(anchorEl);
const intl = useIntl();
const handleMenu = (event: React.MouseEvent<HTMLElement>) => { const handleMenu = (event: React.MouseEvent<HTMLElement>) => {
setAnchorEl(event.currentTarget); setAnchorEl(event.currentTarget);
@ -26,11 +28,14 @@ const HelpMenu = (): React.ReactElement => {
return ( return (
<span> <span>
<IconButton <Tooltip arrow={true} title={intl.formatMessage({ id: 'help.support', defaultMessage: 'Support' })}>
aria-haspopup="true"
onClick={handleMenu}> <IconButton
<Help /> aria-haspopup="true"
</IconButton> onClick={handleMenu}>
<Help />
</IconButton>
</Tooltip>
<Menu id="appbar-profile" <Menu id="appbar-profile"
anchorEl={anchorEl} anchorEl={anchorEl}
keepMounted keepMounted

View File

@ -74,7 +74,10 @@ const MapsPage = (): ReactElement => {
const mutation = useMutation( const mutation = useMutation(
(id: number) => client.deleteLabel(id), (id: number) => client.deleteLabel(id),
{ {
onSuccess: () => queryClient.invalidateQueries('labels') onSuccess: () => queryClient.invalidateQueries('labels'),
onError: (error) => {
console.error(`Unexpected error ${error}`);
}
} }
); );
@ -87,8 +90,8 @@ const MapsPage = (): ReactElement => {
mutation.mutate(id); mutation.mutate(id);
}; };
const { data } = useQuery<unknown, ErrorInfo, Label[]>('labels', async () => { const { data } = useQuery<unknown, ErrorInfo, Label[]>('labels', () => {
return await client.fetchLabels(); return client.fetchLabels();
}); });
const labels: Label[] = data ? data : []; const labels: Label[] = data ? data : [];
@ -140,7 +143,7 @@ const MapsPage = (): ReactElement => {
elevation={0}> elevation={0}>
<Toolbar> <Toolbar>
<Tooltip title={intl.formatMessage({ id: 'maps.create-tooltip', defaultMessage: 'Create a New Map' })}> <Tooltip arrow={true} title={intl.formatMessage({ id: 'maps.create-tooltip', defaultMessage: 'Create a New Map' })}>
<Button color="primary" <Button color="primary"
size="medium" size="medium"
variant="contained" variant="contained"
@ -153,7 +156,7 @@ const MapsPage = (): ReactElement => {
</Button> </Button>
</Tooltip> </Tooltip>
<Tooltip title={intl.formatMessage({ id: 'maps.import-desc', defaultMessage: 'Import from other tools' })}> <Tooltip arrow={true} title={intl.formatMessage({ id: 'maps.import-desc', defaultMessage: 'Import from other tools' })}>
<Button <Button
color="primary" color="primary"
size="medium" size="medium"

View File

@ -32,6 +32,9 @@ const LanguageMenu = (): React.ReactElement => {
onSuccess: () => { onSuccess: () => {
queryClient.invalidateQueries('account') queryClient.invalidateQueries('account')
handleClose(); handleClose();
},
onError: (error) => {
console.error(`Unexpected error ${error}`)
} }
} }
); );
@ -52,7 +55,7 @@ const LanguageMenu = (): React.ReactElement => {
const accountInfo = fetchAccount(); const accountInfo = fetchAccount();
return ( return (
<span> <span>
<Tooltip title={intl.formatMessage({ id: 'language.change', defaultMessage: 'Change Language' })}> <Tooltip arrow={true} title={intl.formatMessage({ id: 'language.change', defaultMessage: 'Change Language' })}>
<Button <Button
size="small" size="small"
variant="outlined" variant="outlined"

View File

@ -196,7 +196,7 @@ const mapsFilter = (filter: Filter, search: string): ((mapInfo: MapInfo) => bool
} }
} }
export const MapsList = (props: MapsListProps):React.ReactElement => { export const MapsList = (props: MapsListProps): React.ReactElement => {
const classes = useStyles(); const classes = useStyles();
const [order, setOrder] = React.useState<Order>('asc'); const [order, setOrder] = React.useState<Order>('asc');
const [filter, setFilter] = React.useState<Filter>({ type: 'all' }); const [filter, setFilter] = React.useState<Filter>({ type: 'all' });
@ -225,8 +225,8 @@ export const MapsList = (props: MapsListProps):React.ReactElement => {
}, [props.filter.type, (props.filter as LabelFilter).label]); }, [props.filter.type, (props.filter as LabelFilter).label]);
const { isLoading, data } = useQuery<unknown, ErrorInfo, MapInfo[]>('maps', async () => { const { isLoading, data } = useQuery<unknown, ErrorInfo, MapInfo[]>('maps', () => {
return await client.fetchAllMaps(); return client.fetchAllMaps();
}); });
const mapsInfo: MapInfo[] = data ? data.filter(mapsFilter(filter, searchCondition)) : []; const mapsInfo: MapInfo[] = data ? data.filter(mapsFilter(filter, searchCondition)) : [];
@ -346,7 +346,7 @@ export const MapsList = (props: MapsListProps):React.ReactElement => {
<div className={classes.toolbarActions}> <div className={classes.toolbarActions}>
{selected.length > 0 && {selected.length > 0 &&
<Tooltip title="Delete selected"> <Tooltip arrow={true} title="Delete selected">
<Button <Button
color="primary" color="primary"
size="medium" size="medium"
@ -362,7 +362,7 @@ export const MapsList = (props: MapsListProps):React.ReactElement => {
} }
{selected.length > 0 && {selected.length > 0 &&
<Tooltip title="Add label to selected"> <Tooltip arrow={true} title="Add label to selected">
<Button <Button
color="primary" color="primary"
size="medium" size="medium"
@ -456,7 +456,7 @@ export const MapsList = (props: MapsListProps):React.ReactElement => {
<TableCell <TableCell
padding="checkbox" padding="checkbox"
className={classes.bodyCell}> className={classes.bodyCell}>
<Tooltip title="Starred"> <Tooltip arrow={true} 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>
@ -464,7 +464,7 @@ export const MapsList = (props: MapsListProps):React.ReactElement => {
</TableCell> </TableCell>
<TableCell className={classes.bodyCell}> <TableCell className={classes.bodyCell}>
<Tooltip title="Open for edition" placement="bottom-start"> <Tooltip arrow={true} title="Open for edition" placement="bottom-start">
<Link href={`/c/maps/${row.id}/edit`} color="textPrimary" underline="always" onClick={(e) => e.stopPropagation()}> <Link href={`/c/maps/${row.id}/edit`} color="textPrimary" underline="always" onClick={(e) => e.stopPropagation()}>
{row.title} {row.title}
</Link> </Link>
@ -480,7 +480,7 @@ export const MapsList = (props: MapsListProps):React.ReactElement => {
</TableCell> </TableCell>
<TableCell className={classes.bodyCell}> <TableCell className={classes.bodyCell}>
<Tooltip title={ <Tooltip arrow={true} title={
`Modified by ${row.lastModificationBy} on ${dayjs(row.lastModificationTime).format("lll")}` `Modified by ${row.lastModificationBy} on ${dayjs(row.lastModificationTime).format("lll")}`
} placement="bottom-start"> } placement="bottom-start">
<span>{dayjs(row.lastModificationTime).fromNow()}</span> <span>{dayjs(row.lastModificationTime).fromNow()}</span>
@ -488,7 +488,7 @@ export const MapsList = (props: MapsListProps):React.ReactElement => {
</TableCell> </TableCell>
<TableCell className={classes.bodyCell}> <TableCell className={classes.bodyCell}>
<Tooltip title={intl.formatMessage({ id: 'map.more-actions', defaultMessage: 'More Actions' })}> <Tooltip arrow={true} title={intl.formatMessage({ id: 'map.more-actions', defaultMessage: 'More Actions' })}>
<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>

View File

@ -0,0 +1,47 @@
import React from "react";
import Tooltip from "@material-ui/core/Tooltip";
import PersonSharpIcon from '@material-ui/icons/PersonSharp';
import EditSharpIcon from '@material-ui/icons/EditSharp';
import VisibilitySharpIcon from '@material-ui/icons/VisibilitySharp';
import { FormattedMessage } from "react-intl";
import { Role } from "../../../classes/client";
type RoleIconProps = {
role: Role;
}
const RoleIcon = ({ role }: RoleIconProps): React.ReactElement => {
return (
<span>
{role == 'owner' &&
<Tooltip
title={<FormattedMessage id="role.owner" defaultMessage="Onwer" />}
arrow={true}
>
<PersonSharpIcon />
</Tooltip>
}
{ role == 'editor' &&
<Tooltip
title={<FormattedMessage id="role.editor" defaultMessage="Editor" />}
arrow={true}>
<EditSharpIcon />
</Tooltip>
}
{role == 'viewer' &&
<Tooltip
title={<FormattedMessage id="role.viewer" defaultMessage="Viewer" />}
arrow={true}>
<VisibilitySharpIcon />
</Tooltip>
}
</span>)
};
export default RoleIcon;

View File

@ -76,7 +76,7 @@ export const fetchMapById = (id: number): MapLoadResult => {
}); });
const result = data?.find(m => m.id == id); const result = data?.find(m => m.id == id);
const map = result ? result : null; const map = result || null;
return { isLoading: isLoading, error: error, map: map }; return { isLoading: isLoading, error: error, map: map };
} }