mirror of
https://bitbucket.org/wisemapping/wisemapping-frontend.git
synced 2024-11-25 23:54:55 +01:00
feat: labels ui
This commit is contained in:
parent
d8453bd602
commit
37dbaf676c
@ -19,7 +19,6 @@ export type Label = {
|
|||||||
id: number;
|
id: number;
|
||||||
title: string;
|
title: string;
|
||||||
color: string;
|
color: string;
|
||||||
iconName: string;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Role = 'owner' | 'editor' | 'viewer';
|
export type Role = 'owner' | 'editor' | 'viewer';
|
||||||
@ -28,7 +27,7 @@ export type MapInfo = {
|
|||||||
id: number;
|
id: number;
|
||||||
starred: boolean;
|
starred: boolean;
|
||||||
title: string;
|
title: string;
|
||||||
labels: number[];
|
labels: Label[];
|
||||||
createdBy: string;
|
createdBy: string;
|
||||||
creationTime: string;
|
creationTime: string;
|
||||||
lastModificationBy: string;
|
lastModificationBy: string;
|
||||||
|
@ -10,6 +10,24 @@ import Client, {
|
|||||||
} from '..';
|
} from '..';
|
||||||
import { LocaleCode, localeFromStr } from '../../app-i18n';
|
import { LocaleCode, localeFromStr } from '../../app-i18n';
|
||||||
|
|
||||||
|
const label1: Label = {
|
||||||
|
id: 1,
|
||||||
|
title: 'label 1',
|
||||||
|
color: 'black',
|
||||||
|
}
|
||||||
|
|
||||||
|
const label2: Label = {
|
||||||
|
id: 2,
|
||||||
|
title: 'label 2',
|
||||||
|
color: 'green',
|
||||||
|
}
|
||||||
|
|
||||||
|
const label3: Label = {
|
||||||
|
id: 3,
|
||||||
|
title: 'label 3',
|
||||||
|
color: 'red',
|
||||||
|
}
|
||||||
|
|
||||||
class MockClient implements Client {
|
class MockClient implements Client {
|
||||||
private maps: MapInfo[] = [];
|
private maps: MapInfo[] = [];
|
||||||
private labels: Label[] = [];
|
private labels: Label[] = [];
|
||||||
@ -21,7 +39,7 @@ class MockClient implements Client {
|
|||||||
id: number,
|
id: number,
|
||||||
starred: boolean,
|
starred: boolean,
|
||||||
title: string,
|
title: string,
|
||||||
labels: number[],
|
labels: Label[],
|
||||||
creator: string,
|
creator: string,
|
||||||
creationTime: string,
|
creationTime: string,
|
||||||
modifiedByUser: string,
|
modifiedByUser: string,
|
||||||
@ -63,7 +81,7 @@ class MockClient implements Client {
|
|||||||
11,
|
11,
|
||||||
false,
|
false,
|
||||||
'El Mapa3',
|
'El Mapa3',
|
||||||
[1, 2, 3],
|
[label1, label2],
|
||||||
'Paulo3',
|
'Paulo3',
|
||||||
'2008-06-02T00:00:00Z',
|
'2008-06-02T00:00:00Z',
|
||||||
'Berna',
|
'Berna',
|
||||||
@ -76,7 +94,7 @@ class MockClient implements Client {
|
|||||||
12,
|
12,
|
||||||
false,
|
false,
|
||||||
'El Mapa3',
|
'El Mapa3',
|
||||||
[1, 2, 3],
|
[label2, label3],
|
||||||
'Paulo3',
|
'Paulo3',
|
||||||
'2008-06-02T00:00:00Z',
|
'2008-06-02T00:00:00Z',
|
||||||
'Berna',
|
'Berna',
|
||||||
@ -87,10 +105,7 @@ class MockClient implements Client {
|
|||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
this.labels = [
|
this.labels = [label1, label2, label3];
|
||||||
{ id: 1, title: 'Red Label', iconName: '', color: 'red' },
|
|
||||||
{ id: 2, title: 'Blue Label', iconName: '', color: 'blue' },
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
deleteMapPermission(id: number, email: string): Promise<void> {
|
deleteMapPermission(id: number, email: string): Promise<void> {
|
||||||
let perm = this.permissionsByMap.get(id) || [];
|
let perm = this.permissionsByMap.get(id) || [];
|
||||||
|
@ -219,20 +219,18 @@ const MapsPage = (): ReactElement => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<List component="nav">
|
<List component="nav">
|
||||||
{filterButtons.map((buttonInfo) => {
|
{filterButtons.map(buttonInfo => {
|
||||||
return (
|
return (<StyleListItem
|
||||||
<StyleListItem
|
|
||||||
icon={buttonInfo.icon}
|
icon={buttonInfo.icon}
|
||||||
label={buttonInfo.label}
|
label={buttonInfo.label}
|
||||||
filter={buttonInfo.filter}
|
filter={buttonInfo.filter}
|
||||||
active={filter}
|
active={filter}
|
||||||
onClick={handleMenuClick}
|
onClick={handleMenuClick}
|
||||||
onDelete={handleLabelDelete}
|
onDelete={handleLabelDelete}
|
||||||
key={`${buttonInfo.filter.type}:${(buttonInfo.filter as LabelFilter).label
|
key={`${buttonInfo.filter.type}:${buttonInfo.label}`}
|
||||||
}`}
|
/>)
|
||||||
/>
|
}
|
||||||
);
|
)}
|
||||||
})}
|
|
||||||
</List>
|
</List>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
@ -0,0 +1,71 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import Popover from '@material-ui/core/Popover';
|
||||||
|
import Button from '@material-ui/core/Button';
|
||||||
|
import Tooltip from '@material-ui/core/Tooltip';
|
||||||
|
import LabelTwoTone from '@material-ui/icons/LabelTwoTone';
|
||||||
|
import { FormattedMessage, useIntl } from 'react-intl';
|
||||||
|
import { Label } from '../../../../classes/client';
|
||||||
|
import { LabelSelector } from '../label-selector';
|
||||||
|
|
||||||
|
type AddLabelButtonTypes = {
|
||||||
|
onChange?: (label: Label) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function AddLabelButton({ onChange }: AddLabelButtonTypes): React.ReactElement {
|
||||||
|
console.log(onChange);
|
||||||
|
const intl = useIntl();
|
||||||
|
|
||||||
|
const [anchorEl, setAnchorEl] = React.useState<HTMLButtonElement | null>(null);
|
||||||
|
|
||||||
|
const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
|
||||||
|
setAnchorEl(event.currentTarget);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
setAnchorEl(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
const open = Boolean(anchorEl);
|
||||||
|
const id = open ? 'add-label-popover' : undefined;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Tooltip
|
||||||
|
arrow={true}
|
||||||
|
title={intl.formatMessage({
|
||||||
|
id: 'map.tooltip-add',
|
||||||
|
defaultMessage: 'Add label to selected',
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<>
|
||||||
|
<Button
|
||||||
|
color="primary"
|
||||||
|
size="medium"
|
||||||
|
variant="outlined"
|
||||||
|
type="button"
|
||||||
|
style={{ marginLeft: '10px' }}
|
||||||
|
disableElevation={true}
|
||||||
|
startIcon={<LabelTwoTone />}
|
||||||
|
onClick={handleClick}
|
||||||
|
>
|
||||||
|
<FormattedMessage id="action.label" defaultMessage="Add Label" />
|
||||||
|
</Button>
|
||||||
|
<Popover
|
||||||
|
id={id}
|
||||||
|
open={open}
|
||||||
|
anchorEl={anchorEl}
|
||||||
|
onClose={handleClose}
|
||||||
|
anchorOrigin={{
|
||||||
|
vertical: 'bottom',
|
||||||
|
horizontal: 'center',
|
||||||
|
}}
|
||||||
|
transformOrigin={{
|
||||||
|
vertical: 'top',
|
||||||
|
horizontal: 'center',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<LabelSelector />
|
||||||
|
</Popover>
|
||||||
|
</>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
}
|
@ -4,7 +4,7 @@ import { useStyles } from './styled';
|
|||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { activeInstance, fetchAccount } from '../../../redux/clientSlice';
|
import { activeInstance, fetchAccount } from '../../../redux/clientSlice';
|
||||||
import { useMutation, useQuery, useQueryClient } from 'react-query';
|
import { useMutation, useQuery, useQueryClient } from 'react-query';
|
||||||
import Client, { ErrorInfo, MapInfo } from '../../../classes/client';
|
import Client, {ErrorInfo, Label, MapInfo} from '../../../classes/client';
|
||||||
import ActionChooser, { ActionType } from '../action-chooser';
|
import ActionChooser, { ActionType } from '../action-chooser';
|
||||||
import ActionDispatcher from '../action-dispatcher';
|
import ActionDispatcher from '../action-dispatcher';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
@ -28,14 +28,15 @@ import Button from '@material-ui/core/Button';
|
|||||||
import InputBase from '@material-ui/core/InputBase';
|
import InputBase from '@material-ui/core/InputBase';
|
||||||
import Link from '@material-ui/core/Link';
|
import Link from '@material-ui/core/Link';
|
||||||
|
|
||||||
import LabelTwoTone from '@material-ui/icons/LabelTwoTone';
|
|
||||||
import DeleteOutlined from '@material-ui/icons/DeleteOutlined';
|
import DeleteOutlined from '@material-ui/icons/DeleteOutlined';
|
||||||
import MoreHorizIcon from '@material-ui/icons/MoreHoriz';
|
import MoreHorizIcon from '@material-ui/icons/MoreHoriz';
|
||||||
import StarRateRoundedIcon from '@material-ui/icons/StarRateRounded';
|
import StarRateRoundedIcon from '@material-ui/icons/StarRateRounded';
|
||||||
import SearchIcon from '@material-ui/icons/Search';
|
import SearchIcon from '@material-ui/icons/Search';
|
||||||
|
|
||||||
|
import { AddLabelButton } from './add-label-button';
|
||||||
// Load fromNow pluggin
|
// Load fromNow pluggin
|
||||||
import relativeTime from 'dayjs/plugin/relativeTime';
|
import relativeTime from 'dayjs/plugin/relativeTime';
|
||||||
|
import {LabelsCell} from './labels-cell';
|
||||||
dayjs.extend(relativeTime);
|
dayjs.extend(relativeTime);
|
||||||
|
|
||||||
function descendingComparator<T>(a: T, b: T, orderBy: keyof T) {
|
function descendingComparator<T>(a: T, b: T, orderBy: keyof T) {
|
||||||
@ -55,8 +56,8 @@ function getComparator<Key extends keyof any>(
|
|||||||
order: Order,
|
order: Order,
|
||||||
orderBy: Key
|
orderBy: Key
|
||||||
): (
|
): (
|
||||||
a: { [key in Key]: number | string | boolean | number[] | undefined },
|
a: { [key in Key]: number | string | boolean | Label[] | undefined },
|
||||||
b: { [key in Key]: number | string | number[] | boolean }
|
b: { [key in Key]: number | string | Label[] | boolean }
|
||||||
) => number {
|
) => number {
|
||||||
return order === 'desc'
|
return order === 'desc'
|
||||||
? (a, b) => descendingComparator(a, b, orderBy)
|
? (a, b) => descendingComparator(a, b, orderBy)
|
||||||
@ -220,7 +221,7 @@ const mapsFilter = (filter: Filter, search: string): ((mapInfo: MapInfo) => bool
|
|||||||
break;
|
break;
|
||||||
case 'label':
|
case 'label':
|
||||||
result =
|
result =
|
||||||
!mapInfo.labels || mapInfo.labels.includes((filter as LabelFilter).label.id);
|
!mapInfo.labels || mapInfo.labels.some((label) => label.id === (filter as LabelFilter).label.id)
|
||||||
break;
|
break;
|
||||||
case 'public':
|
case 'public':
|
||||||
result = mapInfo.isPublic;
|
result = mapInfo.isPublic;
|
||||||
@ -412,24 +413,7 @@ export const MapsList = (props: MapsListProps): React.ReactElement => {
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{selected.length > 0 && (
|
{selected.length > 0 && <AddLabelButton/>}
|
||||||
<Tooltip arrow={true} title={intl.formatMessage({ id: 'map.tooltip-add', defaultMessage: 'Add label to selected' })}>
|
|
||||||
<Button
|
|
||||||
color="primary"
|
|
||||||
size="medium"
|
|
||||||
variant="outlined"
|
|
||||||
type="button"
|
|
||||||
style={{ marginLeft: '10px' }}
|
|
||||||
disableElevation={true}
|
|
||||||
startIcon={<LabelTwoTone />}
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
|
||||||
id="action.label"
|
|
||||||
defaultMessage="Add Label"
|
|
||||||
/>
|
|
||||||
</Button>
|
|
||||||
</Tooltip>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={classes.toolbarListActions}>
|
<div className={classes.toolbarListActions}>
|
||||||
@ -558,7 +542,9 @@ export const MapsList = (props: MapsListProps): React.ReactElement => {
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
|
||||||
<TableCell className={classes.bodyCell}></TableCell>
|
<TableCell className={classes.bodyCell}>
|
||||||
|
<LabelsCell labels={row.labels} />
|
||||||
|
</TableCell>
|
||||||
|
|
||||||
<TableCell className={classes.bodyCell}>
|
<TableCell className={classes.bodyCell}>
|
||||||
{row.createdBy}
|
{row.createdBy}
|
||||||
|
@ -0,0 +1,58 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import FormGroup from '@material-ui/core/FormGroup';
|
||||||
|
import FormControlLabel from '@material-ui/core/FormControlLabel';
|
||||||
|
import Divider from '@material-ui/core/Divider';
|
||||||
|
import AddIcon from '@material-ui/icons/Add';
|
||||||
|
import Checkbox from '@material-ui/core/Checkbox';
|
||||||
|
import Container from '@material-ui/core/Container';
|
||||||
|
import { Label as LabelComponent } from '../label';
|
||||||
|
import Client, { Label, ErrorInfo } from '../../../../classes/client';
|
||||||
|
import { useQuery } from 'react-query';
|
||||||
|
import { useSelector } from 'react-redux';
|
||||||
|
import { activeInstance } from '../../../../redux/clientSlice';
|
||||||
|
import { StyledButton } from './styled';
|
||||||
|
|
||||||
|
export function LabelSelector(): React.ReactElement {
|
||||||
|
const client: Client = useSelector(activeInstance);
|
||||||
|
|
||||||
|
const { data: labels = [] } = useQuery<unknown, ErrorInfo, Label[]>('labels', async () => client.fetchLabels());
|
||||||
|
|
||||||
|
const [state, setState] = React.useState(labels.reduce((acc, label) => {
|
||||||
|
acc[label.id] = false //label.checked;
|
||||||
|
return acc;
|
||||||
|
}, {}),);
|
||||||
|
|
||||||
|
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
setState({ ...state, [event.target.id]: event.target.checked });
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container>
|
||||||
|
<FormGroup>
|
||||||
|
{labels.map(({ id, title, color}) => (
|
||||||
|
<FormControlLabel
|
||||||
|
key={id}
|
||||||
|
control={
|
||||||
|
<Checkbox
|
||||||
|
id={`${id}`}
|
||||||
|
checked={state[id]}
|
||||||
|
onChange={handleChange}
|
||||||
|
name={title}
|
||||||
|
color="primary"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label={<LabelComponent name={title} color={color} />}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
<Divider />
|
||||||
|
<StyledButton
|
||||||
|
color="primary"
|
||||||
|
startIcon={<AddIcon />}
|
||||||
|
>
|
||||||
|
{/* i18n */}
|
||||||
|
Add new label
|
||||||
|
</StyledButton>
|
||||||
|
</FormGroup>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,6 @@
|
|||||||
|
import styled from 'styled-components';
|
||||||
|
import Button from '@material-ui/core/Button';
|
||||||
|
|
||||||
|
export const StyledButton = styled(Button)`
|
||||||
|
margin: 4px;
|
||||||
|
`;
|
@ -0,0 +1,13 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Color, StyledLabel, Name } from './styled';
|
||||||
|
|
||||||
|
type Props = { name: string, color: string };
|
||||||
|
|
||||||
|
export function Label({ name, color }: Props): React.ReactElement<Props> {
|
||||||
|
return (
|
||||||
|
<StyledLabel>
|
||||||
|
<Color color={color} />
|
||||||
|
<Name>{name}</Name>
|
||||||
|
</StyledLabel>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
import styled, { css } from 'styled-components';
|
||||||
|
|
||||||
|
const SIZE = 20;
|
||||||
|
|
||||||
|
export const Color = styled.div`
|
||||||
|
width: ${SIZE}px;
|
||||||
|
height: ${SIZE}px;
|
||||||
|
border-radius: ${SIZE * 0.25}px;
|
||||||
|
border: 1px solid black;
|
||||||
|
margin: 1px ${SIZE * 0.5}px 1px 0px;
|
||||||
|
${props => props.color && css`
|
||||||
|
background-color: ${props.color};
|
||||||
|
`}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const StyledLabel = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const Name = styled.div`
|
||||||
|
flex: 1;
|
||||||
|
`;
|
@ -0,0 +1,27 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import Chip from '@material-ui/core/Chip';
|
||||||
|
import LabelIcon from '@material-ui/icons/Label';
|
||||||
|
|
||||||
|
import {Label} from '../../../../classes/client';
|
||||||
|
type Props = {
|
||||||
|
labels: Label[],
|
||||||
|
};
|
||||||
|
|
||||||
|
export function LabelsCell({ labels }: Props): React.ReactElement<Props> {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{labels.map(label => (
|
||||||
|
<Chip
|
||||||
|
key={label.id}
|
||||||
|
size="small"
|
||||||
|
icon={<LabelIcon />}
|
||||||
|
label={label.title}
|
||||||
|
clickable
|
||||||
|
color="primary"
|
||||||
|
style={{ backgroundColor: label.color }}
|
||||||
|
onDelete={() => {return 1;}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
@ -69,9 +69,9 @@ const theme = createMuiTheme({
|
|||||||
contrastText: '#FFFFFF',
|
contrastText: '#FFFFFF',
|
||||||
},
|
},
|
||||||
secondary: {
|
secondary: {
|
||||||
light: '#FFFFFF',
|
light: '#a19f9f',
|
||||||
main: '#FFFFFF',
|
main: '#5a5a5a',
|
||||||
dark: '#FFFFFF',
|
dark: '#000000',
|
||||||
contrastText: '#FFFFFF',
|
contrastText: '#FFFFFF',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
Loading…
Reference in New Issue
Block a user