mirror of
https://bitbucket.org/wisemapping/wisemapping-frontend.git
synced 2024-12-22 19:53:49 +01:00
Merge branch 'feature/labels' into develop
This commit is contained in:
commit
67fb3bbc35
@ -90,10 +90,22 @@ class CacheDecoratorClient implements Client {
|
||||
return this.client.fetchLabels();
|
||||
}
|
||||
|
||||
createLabel(title: string, color: string): Promise<number> {
|
||||
return this.client.createLabel(title, color);
|
||||
}
|
||||
|
||||
deleteLabel(id: number): Promise<void> {
|
||||
return this.client.deleteLabel(id);
|
||||
}
|
||||
|
||||
addLabelToMap(labelId: number, mapId: number): Promise<void> {
|
||||
return this.client.addLabelToMap(labelId, mapId);
|
||||
}
|
||||
|
||||
deleteLabelFromMap(labelId: number, mapId: number): Promise<void> {
|
||||
return this.client.deleteLabelFromMap(labelId, mapId);
|
||||
}
|
||||
|
||||
fetchAccountInfo(): Promise<AccountInfo> {
|
||||
return this.client.fetchAccountInfo();
|
||||
}
|
||||
|
@ -94,8 +94,11 @@ interface Client {
|
||||
updateStarred(id: number, starred: boolean): Promise<void>;
|
||||
updateMapToPublic(id: number, isPublic: boolean): Promise<void>;
|
||||
|
||||
createLabel(title: string, color: string): Promise<number>;
|
||||
fetchLabels(): Promise<Label[]>;
|
||||
deleteLabel(id: number): Promise<void>;
|
||||
addLabelToMap(labelId: number, mapId: number): Promise<void>;
|
||||
deleteLabelFromMap(labelId: number, mapId: number): Promise<void>;
|
||||
fetchAccountInfo(): Promise<AccountInfo>;
|
||||
|
||||
registerNewUser(user: NewUser): Promise<void>;
|
||||
|
@ -327,9 +327,46 @@ class MockClient implements Client {
|
||||
}
|
||||
}
|
||||
|
||||
createLabel(title: string, color: string): Promise<number> {
|
||||
const newId = Math.max.apply(Number, this.labels.map(l => l.id)) + 1;
|
||||
this.labels.push({
|
||||
id: newId,
|
||||
title,
|
||||
color,
|
||||
});
|
||||
return newId;
|
||||
}
|
||||
|
||||
deleteLabel(id: number): Promise<void> {
|
||||
this.labels = this.labels.filter((l) => l.id != id);
|
||||
console.log('Label delete:' + this.labels);
|
||||
this.maps = this.maps.map(m => {
|
||||
return {
|
||||
...m,
|
||||
labels: m.labels.filter((l) => l.id != id)
|
||||
};
|
||||
});
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
addLabelToMap(labelId: number, mapId: number): Promise<void> {
|
||||
const labelToAdd = this.labels.find((l) => l.id === labelId);
|
||||
if (!labelToAdd) {
|
||||
return Promise.reject({ msg: `unable to find label with id ${labelId}`});
|
||||
}
|
||||
const map = this.maps.find((m) => m.id === mapId);
|
||||
if (!map) {
|
||||
return Promise.reject({ msg: `unable to find map with id ${mapId}` });
|
||||
}
|
||||
map.labels.push(labelToAdd);
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
deleteLabelFromMap(labelId: number, mapId: number): Promise<void> {
|
||||
const map = this.maps.find((m) => m.id === mapId);
|
||||
if (!map) {
|
||||
return Promise.reject({ msg: `unable to find map with id ${mapId}` });
|
||||
}
|
||||
map.labels = map.labels.filter((l) => l.id !== labelId);
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
|
@ -510,10 +510,59 @@ export default class RestClient implements Client {
|
||||
return new Promise(handler);
|
||||
}
|
||||
|
||||
createLabel(title: string, color: string): Promise<number> {
|
||||
const handler = (success: (labelId: number) => void, reject: (error: ErrorInfo) => void) => {
|
||||
axios
|
||||
.post(`${this.baseUrl}/c/restful/labels`, JSON.stringify({ title, color, iconName: 'smile' }), {
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
})
|
||||
.then((response) => {
|
||||
success(response.headers.resourceid);
|
||||
})
|
||||
.catch((error) => {
|
||||
const errorInfo = this.parseResponseOnError(error.response);
|
||||
reject(errorInfo);
|
||||
});
|
||||
};
|
||||
return new Promise(handler);
|
||||
}
|
||||
|
||||
deleteLabel(id: number): Promise<void> {
|
||||
const handler = (success: () => void, reject: (error: ErrorInfo) => void) => {
|
||||
axios
|
||||
.delete(`${this.baseUrl}/c/restful/label/${id}`)
|
||||
.delete(`${this.baseUrl}/c/restful/labels/${id}`)
|
||||
.then(() => {
|
||||
success();
|
||||
})
|
||||
.catch((error) => {
|
||||
const errorInfo = this.parseResponseOnError(error.response);
|
||||
reject(errorInfo);
|
||||
});
|
||||
};
|
||||
return new Promise(handler);
|
||||
}
|
||||
|
||||
addLabelToMap(labelId: number, mapId: number): Promise<void> {
|
||||
const handler = (success: () => void, reject: (error: ErrorInfo) => void) => {
|
||||
axios
|
||||
.post(`${this.baseUrl}/c/restful/maps/${mapId}/labels`, JSON.stringify(labelId), {
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
})
|
||||
.then(() => {
|
||||
success();
|
||||
})
|
||||
.catch((error) => {
|
||||
const errorInfo = this.parseResponseOnError(error.response);
|
||||
reject(errorInfo);
|
||||
});
|
||||
};
|
||||
return new Promise(handler);
|
||||
}
|
||||
|
||||
deleteLabelFromMap(labelId: number, mapId: number): Promise<void> {
|
||||
const handler = (success: () => void, reject: (error: ErrorInfo) => void) => {
|
||||
axios
|
||||
.delete(`${this.baseUrl}/c/restful/maps/${mapId}/labels/${labelId}`)
|
||||
.then(() => {
|
||||
success();
|
||||
})
|
||||
|
@ -12,6 +12,7 @@ import InfoDialog from './info-dialog';
|
||||
import DeleteMultiselectDialog from './delete-multiselect-dialog';
|
||||
import ExportDialog from './export-dialog';
|
||||
import ShareDialog from './share-dialog';
|
||||
import LabelDialog from './label-dialog';
|
||||
|
||||
export type BasicMapInfo = {
|
||||
name: string;
|
||||
@ -61,6 +62,7 @@ const ActionDispatcher = ({ mapsId, action, onClose, fromEditor }: ActionDialogP
|
||||
<ExportDialog onClose={handleOnClose} mapId={mapsId[0]} enableImgExport={fromEditor} />
|
||||
)}
|
||||
{action === 'share' && <ShareDialog onClose={handleOnClose} mapId={mapsId[0]} />}
|
||||
{action === 'label' && <LabelDialog onClose={handleOnClose} mapId={mapsId[0]} />}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
@ -0,0 +1,75 @@
|
||||
import React from 'react';
|
||||
import BaseDialog from '../base-dialog';
|
||||
import { SimpleDialogProps } from '..';
|
||||
import { useIntl } from 'react-intl';
|
||||
import Client, { ErrorInfo, Label, MapInfo } from '../../../../classes/client';
|
||||
import { useStyles } from './style';
|
||||
import { LabelSelector } from '../../maps-list/label-selector';
|
||||
import { useMutation, useQuery, useQueryClient } from 'react-query';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { activeInstance } from '../../../../redux/clientSlice';
|
||||
|
||||
|
||||
const LabelDialog = ({ mapId, onClose }: SimpleDialogProps): React.ReactElement => {
|
||||
const intl = useIntl();
|
||||
const classes = useStyles();
|
||||
const client: Client = useSelector(activeInstance);
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
// TODO: pass down map data instead of using query?
|
||||
const { data } = useQuery<unknown, ErrorInfo, MapInfo[]>('maps', () => {
|
||||
return client.fetchAllMaps();
|
||||
});
|
||||
|
||||
const map = data.find(m => m.id === mapId);
|
||||
|
||||
const changeLabelMutation = useMutation<void, ErrorInfo, { label: Label, checked: boolean }, number>(
|
||||
async ({ label, checked }) => {
|
||||
if (!label.id) {
|
||||
label.id = await client.createLabel(label.title, label.color);
|
||||
}
|
||||
if (checked){
|
||||
return client.addLabelToMap(label.id, mapId);
|
||||
} else {
|
||||
return client.deleteLabelFromMap(label.id, mapId);
|
||||
}
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries('maps');
|
||||
queryClient.invalidateQueries('labels');
|
||||
},
|
||||
onError: (error) => {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const handleChangesInLabels = (label: Label, checked: boolean) => {
|
||||
changeLabelMutation.mutate({
|
||||
label,
|
||||
checked
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<BaseDialog
|
||||
onClose={onClose}
|
||||
title={intl.formatMessage({
|
||||
id: 'label.title',
|
||||
defaultMessage: 'Add a label',
|
||||
})}
|
||||
description={intl.formatMessage({
|
||||
id: 'label.description',
|
||||
defaultMessage:
|
||||
'Use labels to organize your maps',
|
||||
})}
|
||||
PaperProps={{ classes: { root: classes.paper } }}
|
||||
>
|
||||
<LabelSelector onChange={handleChangesInLabels} maps={[map]} />
|
||||
</BaseDialog>
|
||||
</div>);
|
||||
};
|
||||
|
||||
export default LabelDialog;
|
@ -0,0 +1,10 @@
|
||||
import createStyles from '@mui/styles/createStyles';
|
||||
import makeStyles from '@mui/styles/makeStyles';
|
||||
|
||||
export const useStyles = makeStyles(() =>
|
||||
createStyles({
|
||||
paper: {
|
||||
maxWidth: '420px',
|
||||
},
|
||||
})
|
||||
);
|
@ -7,7 +7,7 @@ import List from '@mui/material/List';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
import { useStyles } from './style';
|
||||
import { MapsList } from './maps-list';
|
||||
import { createIntl, createIntlCache, FormattedMessage, IntlProvider, IntlShape, useIntl } from 'react-intl';
|
||||
import { createIntl, createIntlCache, FormattedMessage, IntlProvider } from 'react-intl';
|
||||
import { useQuery, useMutation, useQueryClient } from 'react-query';
|
||||
import { activeInstance } from '../../redux/clientSlice';
|
||||
import { useSelector } from 'react-redux';
|
||||
@ -76,7 +76,6 @@ const MapsPage = (): ReactElement => {
|
||||
}, cache)
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
document.title = intl.formatMessage({
|
||||
id: 'maps.page-title',
|
||||
defaultMessage: 'My Maps | WiseMapping',
|
||||
@ -84,7 +83,10 @@ const MapsPage = (): ReactElement => {
|
||||
}, []);
|
||||
|
||||
const mutation = useMutation((id: number) => client.deleteLabel(id), {
|
||||
onSuccess: () => queryClient.invalidateQueries('labels'),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries('labels');
|
||||
queryClient.invalidateQueries('maps');
|
||||
},
|
||||
onError: (error) => {
|
||||
console.error(`Unexpected error ${error}`);
|
||||
},
|
||||
|
@ -4,15 +4,15 @@ import Button from '@mui/material/Button';
|
||||
import Tooltip from '@mui/material/Tooltip';
|
||||
import LabelTwoTone from '@mui/icons-material/LabelTwoTone';
|
||||
import { FormattedMessage, useIntl } from 'react-intl';
|
||||
import { Label } from '../../../../classes/client';
|
||||
import { Label, MapInfo } from '../../../../classes/client';
|
||||
import { LabelSelector } from '../label-selector';
|
||||
|
||||
type AddLabelButtonTypes = {
|
||||
onChange?: (label: Label) => void;
|
||||
maps: MapInfo[];
|
||||
onChange: (label: Label, checked: boolean) => void;
|
||||
};
|
||||
|
||||
export function AddLabelButton({ onChange }: AddLabelButtonTypes): React.ReactElement {
|
||||
console.log(onChange);
|
||||
export function AddLabelButton({ onChange, maps }: AddLabelButtonTypes): React.ReactElement {
|
||||
const intl = useIntl();
|
||||
|
||||
const [anchorEl, setAnchorEl] = React.useState<HTMLButtonElement | null>(null);
|
||||
@ -29,14 +29,14 @@ export function AddLabelButton({ onChange }: AddLabelButtonTypes): React.ReactEl
|
||||
const id = open ? 'add-label-popover' : undefined;
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
arrow={true}
|
||||
title={intl.formatMessage({
|
||||
id: 'map.tooltip-add',
|
||||
defaultMessage: 'Add label to selected',
|
||||
})}
|
||||
>
|
||||
<>
|
||||
<>
|
||||
<Tooltip
|
||||
arrow={true}
|
||||
title={intl.formatMessage({
|
||||
id: 'map.tooltip-add',
|
||||
defaultMessage: 'Add label to selected',
|
||||
})}
|
||||
>
|
||||
<Button
|
||||
color="primary"
|
||||
size="medium"
|
||||
@ -49,23 +49,24 @@ export function AddLabelButton({ onChange }: AddLabelButtonTypes): React.ReactEl
|
||||
>
|
||||
<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>
|
||||
</Tooltip>
|
||||
|
||||
<Popover
|
||||
id={id}
|
||||
open={open}
|
||||
anchorEl={anchorEl}
|
||||
onClose={handleClose}
|
||||
anchorOrigin={{
|
||||
vertical: 'bottom',
|
||||
horizontal: 'center',
|
||||
}}
|
||||
transformOrigin={{
|
||||
vertical: 'top',
|
||||
horizontal: 'center',
|
||||
}}
|
||||
>
|
||||
<LabelSelector onChange={onChange} maps={maps} />
|
||||
</Popover>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -331,7 +331,7 @@ export const MapsList = (props: MapsListProps): React.ReactElement => {
|
||||
event.stopPropagation();
|
||||
};
|
||||
};
|
||||
9;
|
||||
|
||||
const starredMultation = useMutation<void, ErrorInfo, number>(
|
||||
(id: number) => {
|
||||
const map = mapsInfo.find((m) => m.id == id);
|
||||
@ -385,6 +385,58 @@ export const MapsList = (props: MapsListProps): React.ReactElement => {
|
||||
});
|
||||
};
|
||||
|
||||
const removeLabelMultation = useMutation<void, ErrorInfo, { mapId: number, labelId: number}, number>(
|
||||
({ mapId, labelId }) => {
|
||||
return client.deleteLabelFromMap(labelId, mapId);
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries('maps');
|
||||
},
|
||||
onError: (error) => {
|
||||
console.error(error);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
const handleRemoveLabel = (mapId: number, labelId: number) => {
|
||||
removeLabelMultation.mutate({ mapId, labelId });
|
||||
};
|
||||
|
||||
const changeLabelMutation = useMutation<void, ErrorInfo, { mapIds: number[], label: Label, checked: boolean }, number>(
|
||||
async ({ mapIds, label, checked }) => {
|
||||
const selectedMaps: MapInfo[] = mapsInfo.filter((m) => mapIds.includes(m.id));
|
||||
if (!label.id) {
|
||||
label.id = await client.createLabel(label.title, label.color);
|
||||
}
|
||||
if (checked){
|
||||
const toAdd = selectedMaps.filter((m) => !m.labels.find((l) => l.id === label.id));
|
||||
await Promise.all(toAdd.map((m) => client.addLabelToMap(label.id, m.id)));
|
||||
} else {
|
||||
const toRemove = selectedMaps.filter((m) => m.labels.find((l) => l.id === label.id));
|
||||
await Promise.all(toRemove.map((m) => client.deleteLabelFromMap(label.id, m.id)));
|
||||
}
|
||||
return Promise.resolve();
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries('maps');
|
||||
queryClient.invalidateQueries('labels');
|
||||
},
|
||||
onError: (error) => {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const handleChangesInLabels = (label: Label, checked: boolean) => {
|
||||
changeLabelMutation.mutate({
|
||||
mapIds: selected,
|
||||
label,
|
||||
checked
|
||||
});
|
||||
};
|
||||
|
||||
const isSelected = (id: number) => selected.indexOf(id) !== -1;
|
||||
return (
|
||||
<div className={classes.root}>
|
||||
@ -419,7 +471,10 @@ export const MapsList = (props: MapsListProps): React.ReactElement => {
|
||||
</Tooltip>
|
||||
)}
|
||||
|
||||
{selected.length > 0 && <AddLabelButton />}
|
||||
{selected.length > 0 && <AddLabelButton
|
||||
onChange={handleChangesInLabels}
|
||||
maps={mapsInfo.filter(m => isSelected(m.id))}
|
||||
/>}
|
||||
</div>
|
||||
|
||||
<div className={classes.toolbarListActions}>
|
||||
@ -561,7 +616,9 @@ export const MapsList = (props: MapsListProps): React.ReactElement => {
|
||||
</TableCell>
|
||||
|
||||
<TableCell className={classes.bodyCell}>
|
||||
<LabelsCell labels={row.labels} />
|
||||
<LabelsCell labels={row.labels} onDelete={(lbl) => {
|
||||
handleRemoveLabel(row.id, lbl.id);
|
||||
}} />
|
||||
</TableCell>
|
||||
|
||||
<TableCell className={classes.bodyCell}>
|
||||
|
@ -6,24 +6,60 @@ import AddIcon from '@mui/icons-material/Add';
|
||||
import Checkbox from '@mui/material/Checkbox';
|
||||
import Container from '@mui/material/Container';
|
||||
import { Label as LabelComponent } from '../label';
|
||||
import Client, { Label, ErrorInfo } from '../../../../classes/client';
|
||||
import Client, { Label, ErrorInfo, MapInfo } from '../../../../classes/client';
|
||||
import { useQuery } from 'react-query';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { activeInstance } from '../../../../redux/clientSlice';
|
||||
import { StyledButton } from './styled';
|
||||
import { StyledButton, NewLabelContainer, NewLabelColor, CreateLabel } from './styled';
|
||||
import { TextField } from '@mui/material';
|
||||
import { FormattedMessage, useIntl } from 'react-intl';
|
||||
import Typography from '@mui/material/Typography';
|
||||
|
||||
export function LabelSelector(): React.ReactElement {
|
||||
const labelColors = [
|
||||
'#00b327',
|
||||
'#0565ff',
|
||||
'#2d2dd6',
|
||||
'#6a00ba',
|
||||
'#ad1599',
|
||||
'#ff1e35',
|
||||
'#ff6600',
|
||||
'#ffff47',
|
||||
];
|
||||
|
||||
export type LabelSelectorProps = {
|
||||
maps: MapInfo[];
|
||||
onChange: (label: Label, checked: boolean) => void;
|
||||
};
|
||||
|
||||
export function LabelSelector({ onChange, maps }: LabelSelectorProps): React.ReactElement {
|
||||
const client: Client = useSelector(activeInstance);
|
||||
const intl = useIntl();
|
||||
|
||||
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 checkedLabelIds = labels.map(l => l.id).filter(labelId => maps.every(m => m.labels.find(l => l.id === labelId)));
|
||||
|
||||
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setState({ ...state, [event.target.id]: event.target.checked });
|
||||
const [createLabelColorIndex, setCreateLabelColorIndex] = React.useState(Math.floor(Math.random() * labelColors.length));
|
||||
const [newLabelTitle, setNewLabelTitle] = React.useState('');
|
||||
|
||||
const newLabelColor = labelColors[createLabelColorIndex];
|
||||
|
||||
const setNextLabelColorIndex = () => {
|
||||
const nextIndex = labelColors[createLabelColorIndex + 1] ?
|
||||
createLabelColorIndex + 1 :
|
||||
0;
|
||||
setCreateLabelColorIndex(nextIndex);
|
||||
};
|
||||
|
||||
|
||||
const handleSubmitNew = () => {
|
||||
onChange({
|
||||
title: newLabelTitle,
|
||||
color: newLabelColor,
|
||||
id: 0,
|
||||
}, true);
|
||||
setNewLabelTitle('');
|
||||
setNextLabelColorIndex();
|
||||
};
|
||||
|
||||
return (
|
||||
@ -35,8 +71,10 @@ export function LabelSelector(): React.ReactElement {
|
||||
control={
|
||||
<Checkbox
|
||||
id={`${id}`}
|
||||
checked={state[id]}
|
||||
onChange={handleChange}
|
||||
checked={checkedLabelIds.includes(id)}
|
||||
onChange={(e) => {
|
||||
onChange({ id, title, color }, e.target.checked);
|
||||
}}
|
||||
name={title}
|
||||
color="primary"
|
||||
/>
|
||||
@ -45,13 +83,42 @@ export function LabelSelector(): React.ReactElement {
|
||||
/>
|
||||
))}
|
||||
<Divider />
|
||||
<StyledButton
|
||||
color="primary"
|
||||
startIcon={<AddIcon />}
|
||||
>
|
||||
{/* i18n */}
|
||||
Add new label
|
||||
</StyledButton>
|
||||
<CreateLabel>
|
||||
<Typography variant="h4" component="h4" fontSize={14} >
|
||||
<FormattedMessage id="label.create-new" defaultMessage={
|
||||
intl.formatMessage({
|
||||
id: 'label.add-placeholder',
|
||||
defaultMessage: 'Label title',
|
||||
})
|
||||
} />
|
||||
</Typography>
|
||||
<NewLabelContainer>
|
||||
<NewLabelColor
|
||||
color={newLabelColor}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setNextLabelColorIndex();
|
||||
}}
|
||||
/>
|
||||
<TextField variant='outlined' label="Label title"
|
||||
onChange={(e) => setNewLabelTitle(e.target.value)}
|
||||
onKeyPress={(e) => {
|
||||
if (e.key === 'Enter') {
|
||||
handleSubmitNew();
|
||||
}
|
||||
}}
|
||||
value={newLabelTitle} />
|
||||
</NewLabelContainer>
|
||||
<StyledButton
|
||||
color="primary"
|
||||
startIcon={<AddIcon />}
|
||||
onClick={() => handleSubmitNew()}
|
||||
disabled={!newLabelTitle.length}
|
||||
>
|
||||
<FormattedMessage id="label.add-button" defaultMessage="Add label" />
|
||||
</StyledButton>
|
||||
|
||||
</CreateLabel>
|
||||
</FormGroup>
|
||||
</Container>
|
||||
);
|
||||
|
@ -1,6 +1,33 @@
|
||||
import styled from 'styled-components';
|
||||
import styled, { css } from 'styled-components';
|
||||
import Button from '@mui/material/Button';
|
||||
|
||||
export const StyledButton = styled(Button)`
|
||||
margin: 4px;
|
||||
`;
|
||||
|
||||
export const NewLabelContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: 15px 0 5px 0 ;
|
||||
`;
|
||||
|
||||
const SIZE = 25;
|
||||
export const NewLabelColor = 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};
|
||||
`}
|
||||
cursor: pointer;
|
||||
`;
|
||||
|
||||
export const CreateLabel = styled.div`
|
||||
padding-top: 10px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-end;
|
||||
`;
|
||||
|
@ -1,26 +1,35 @@
|
||||
import React from 'react';
|
||||
import Chip from '@mui/material/Chip';
|
||||
import { LabelContainer, LabelText } from './styled';
|
||||
|
||||
import { Label } from '../../../../classes/client';
|
||||
import LabelTwoTone from '@mui/icons-material/LabelTwoTone';
|
||||
import DeleteIcon from '@mui/icons-material/Clear';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
|
||||
type Props = {
|
||||
labels: Label[],
|
||||
onDelete: (label: Label) => void,
|
||||
};
|
||||
|
||||
export function LabelsCell({ labels }: Props): React.ReactElement<Props> {
|
||||
export function LabelsCell({ labels, onDelete }: Props): React.ReactElement<Props> {
|
||||
return (
|
||||
<>
|
||||
{labels.map(label => (
|
||||
<Chip
|
||||
<LabelContainer
|
||||
key={label.id}
|
||||
size="small"
|
||||
icon={<LabelTwoTone />}
|
||||
label={label.title}
|
||||
clickable
|
||||
color="primary"
|
||||
style={{ backgroundColor: label.color, opacity: '0.75' }}
|
||||
onDelete={() => { return 1; }}
|
||||
/>
|
||||
color={label.color}
|
||||
>
|
||||
<LabelTwoTone htmlColor={label.color} style={{ height: '0.6em', width: '0.6em' }} />
|
||||
<LabelText>{ label.title }</LabelText>
|
||||
<IconButton color="default" size='small' aria-label="delete tag" component="span"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onDelete(label);
|
||||
}}
|
||||
>
|
||||
<DeleteIcon style={{ height: '0.6em', width: '0.6em' }} />
|
||||
</IconButton>
|
||||
</LabelContainer>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
|
@ -0,0 +1,15 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const LabelContainer = styled.div`
|
||||
display: inline-flex;
|
||||
flex-direction: row;
|
||||
margin: 4px;
|
||||
padding: 4px;
|
||||
align-items: center;
|
||||
font-size: smaller;
|
||||
`;
|
||||
|
||||
export const LabelText = styled.span`
|
||||
margin-left: 4px;
|
||||
margin-right: 2px;
|
||||
`;
|
@ -7,6 +7,7 @@ const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||
module.exports = merge(common, {
|
||||
mode: 'development',
|
||||
devtool: 'source-map',
|
||||
watch: true,
|
||||
devServer: {
|
||||
contentBase: path.join(__dirname, 'dist'),
|
||||
port: 3000,
|
||||
|
@ -5661,7 +5661,7 @@ dateformat@^3.0.0:
|
||||
resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-3.0.3.tgz#a6e37499a4d9a9cf85ef5872044d62901c9889ae"
|
||||
integrity sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==
|
||||
|
||||
dayjs@^1.10.4:
|
||||
dayjs@^1.10.4, dayjs@^1.10.7:
|
||||
version "1.10.7"
|
||||
resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.10.7.tgz#2cf5f91add28116748440866a0a1d26f3a6ce468"
|
||||
integrity sha512-P6twpd70BcPK34K26uJ1KT3wlhpuOAPoMwJzpsIWUxHZ7wpmbdZL/hQqBDfz7hGurYSa5PhzdhDHtt319hL3ig==
|
||||
|
Loading…
Reference in New Issue
Block a user