mirror of
https://bitbucket.org/wisemapping/wisemapping-frontend.git
synced 2024-11-15 03:27:57 +01:00
feat: labels ui
This commit is contained in:
parent
b4fd0fb40e
commit
1a3e299c71
@ -19,7 +19,6 @@ export type Label = {
|
||||
id: number;
|
||||
title: string;
|
||||
color: string;
|
||||
iconName: string;
|
||||
};
|
||||
|
||||
export type Role = 'owner' | 'editor' | 'viewer';
|
||||
@ -28,7 +27,7 @@ export type MapInfo = {
|
||||
id: number;
|
||||
starred: boolean;
|
||||
title: string;
|
||||
labels: number[];
|
||||
labels: Label[];
|
||||
createdBy: string;
|
||||
creationTime: string;
|
||||
lastModificationBy: string;
|
||||
|
@ -10,6 +10,24 @@ import Client, {
|
||||
} from '..';
|
||||
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 {
|
||||
private maps: MapInfo[] = [];
|
||||
private labels: Label[] = [];
|
||||
@ -21,7 +39,7 @@ class MockClient implements Client {
|
||||
id: number,
|
||||
starred: boolean,
|
||||
title: string,
|
||||
labels: number[],
|
||||
labels: Label[],
|
||||
creator: string,
|
||||
creationTime: string,
|
||||
modifiedByUser: string,
|
||||
@ -63,7 +81,7 @@ class MockClient implements Client {
|
||||
11,
|
||||
false,
|
||||
'El Mapa3',
|
||||
[1, 2, 3],
|
||||
[label1, label2],
|
||||
'Paulo3',
|
||||
'2008-06-02T00:00:00Z',
|
||||
'Berna',
|
||||
@ -76,7 +94,7 @@ class MockClient implements Client {
|
||||
12,
|
||||
false,
|
||||
'El Mapa3',
|
||||
[1, 2, 3],
|
||||
[label2, label3],
|
||||
'Paulo3',
|
||||
'2008-06-02T00:00:00Z',
|
||||
'Berna',
|
||||
@ -87,10 +105,7 @@ class MockClient implements Client {
|
||||
),
|
||||
];
|
||||
|
||||
this.labels = [
|
||||
{ id: 1, title: 'Red Label', iconName: '', color: 'red' },
|
||||
{ id: 2, title: 'Blue Label', iconName: '', color: 'blue' },
|
||||
];
|
||||
this.labels = [label1, label2, label3];
|
||||
}
|
||||
deleteMapPermission(id: number, email: string): Promise<void> {
|
||||
let perm = this.permissionsByMap.get(id) || [];
|
||||
|
@ -219,20 +219,18 @@ const MapsPage = (): ReactElement => {
|
||||
</div>
|
||||
|
||||
<List component="nav">
|
||||
{filterButtons.map((buttonInfo) => {
|
||||
return (
|
||||
<StyleListItem
|
||||
{filterButtons.map(buttonInfo => {
|
||||
return (<StyleListItem
|
||||
icon={buttonInfo.icon}
|
||||
label={buttonInfo.label}
|
||||
filter={buttonInfo.filter}
|
||||
active={filter}
|
||||
onClick={handleMenuClick}
|
||||
onDelete={handleLabelDelete}
|
||||
key={`${buttonInfo.filter.type}:${(buttonInfo.filter as LabelFilter).label
|
||||
}`}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
key={`${buttonInfo.filter.type}:${buttonInfo.label}`}
|
||||
/>)
|
||||
}
|
||||
)}
|
||||
</List>
|
||||
|
||||
<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 { activeInstance, fetchAccount } from '../../../redux/clientSlice';
|
||||
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 ActionDispatcher from '../action-dispatcher';
|
||||
import dayjs from 'dayjs';
|
||||
@ -28,14 +28,15 @@ import Button from '@material-ui/core/Button';
|
||||
import InputBase from '@material-ui/core/InputBase';
|
||||
import Link from '@material-ui/core/Link';
|
||||
|
||||
import LabelTwoTone from '@material-ui/icons/LabelTwoTone';
|
||||
import DeleteOutlined from '@material-ui/icons/DeleteOutlined';
|
||||
import MoreHorizIcon from '@material-ui/icons/MoreHoriz';
|
||||
import StarRateRoundedIcon from '@material-ui/icons/StarRateRounded';
|
||||
import SearchIcon from '@material-ui/icons/Search';
|
||||
|
||||
import { AddLabelButton } from './add-label-button';
|
||||
// Load fromNow pluggin
|
||||
import relativeTime from 'dayjs/plugin/relativeTime';
|
||||
import {LabelsCell} from './labels-cell';
|
||||
dayjs.extend(relativeTime);
|
||||
|
||||
function descendingComparator<T>(a: T, b: T, orderBy: keyof T) {
|
||||
@ -55,8 +56,8 @@ function getComparator<Key extends keyof any>(
|
||||
order: Order,
|
||||
orderBy: Key
|
||||
): (
|
||||
a: { [key in Key]: number | string | boolean | number[] | undefined },
|
||||
b: { [key in Key]: number | string | number[] | boolean }
|
||||
a: { [key in Key]: number | string | boolean | Label[] | undefined },
|
||||
b: { [key in Key]: number | string | Label[] | boolean }
|
||||
) => number {
|
||||
return order === 'desc'
|
||||
? (a, b) => descendingComparator(a, b, orderBy)
|
||||
@ -220,7 +221,7 @@ const mapsFilter = (filter: Filter, search: string): ((mapInfo: MapInfo) => bool
|
||||
break;
|
||||
case 'label':
|
||||
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;
|
||||
case 'public':
|
||||
result = mapInfo.isPublic;
|
||||
@ -412,24 +413,7 @@ export const MapsList = (props: MapsListProps): React.ReactElement => {
|
||||
</Tooltip>
|
||||
)}
|
||||
|
||||
{selected.length > 0 && (
|
||||
<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>
|
||||
)}
|
||||
{selected.length > 0 && <AddLabelButton/>}
|
||||
</div>
|
||||
|
||||
<div className={classes.toolbarListActions}>
|
||||
@ -558,7 +542,9 @@ export const MapsList = (props: MapsListProps): React.ReactElement => {
|
||||
</Tooltip>
|
||||
</TableCell>
|
||||
|
||||
<TableCell className={classes.bodyCell}></TableCell>
|
||||
<TableCell className={classes.bodyCell}>
|
||||
<LabelsCell labels={row.labels} />
|
||||
</TableCell>
|
||||
|
||||
<TableCell className={classes.bodyCell}>
|
||||
{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',
|
||||
},
|
||||
secondary: {
|
||||
light: '#FFFFFF',
|
||||
main: '#FFFFFF',
|
||||
dark: '#FFFFFF',
|
||||
light: '#a19f9f',
|
||||
main: '#5a5a5a',
|
||||
dark: '#000000',
|
||||
contrastText: '#FFFFFF',
|
||||
},
|
||||
},
|
||||
|
Loading…
Reference in New Issue
Block a user