From 275f05a41ee092b1365314ddcaa696544cabe070 Mon Sep 17 00:00:00 2001 From: Paulo Gustavo Veiga Date: Tue, 8 Feb 2022 22:00:02 -0800 Subject: [PATCH 01/12] Define max input sizes for dialog --- packages/webapp/src/components/form/input/index.tsx | 5 +++++ .../maps-page/action-dispatcher/create-dialog/index.tsx | 2 ++ 2 files changed, 7 insertions(+) diff --git a/packages/webapp/src/components/form/input/index.tsx b/packages/webapp/src/components/form/input/index.tsx index b48d9e38..2d4b0ae2 100644 --- a/packages/webapp/src/components/form/input/index.tsx +++ b/packages/webapp/src/components/form/input/index.tsx @@ -1,4 +1,5 @@ import TextField from '@mui/material/TextField'; +import { row } from '@wisemapping/mindplot/src/components/widget/ColorPaletteHtml'; import React, { ChangeEvent } from 'react'; import { ErrorInfo } from '../../../classes/client'; @@ -13,6 +14,8 @@ type InputProps = { autoComplete?: string; fullWidth?: boolean; disabled?: boolean; + maxLength?: number, + rows?: number }; const Input = ({ @@ -26,6 +29,7 @@ const Input = ({ autoComplete, fullWidth = true, disabled = false, + maxLength = 254, }: InputProps): React.ReactElement => { const fieldError = error?.fields?.[name]; return ( @@ -43,6 +47,7 @@ const Input = ({ margin="dense" disabled={disabled} autoComplete={autoComplete} + inputProps={{ maxLength: maxLength }} /> ); }; diff --git a/packages/webapp/src/components/maps-page/action-dispatcher/create-dialog/index.tsx b/packages/webapp/src/components/maps-page/action-dispatcher/create-dialog/index.tsx index fd12616b..10119c74 100644 --- a/packages/webapp/src/components/maps-page/action-dispatcher/create-dialog/index.tsx +++ b/packages/webapp/src/components/maps-page/action-dispatcher/create-dialog/index.tsx @@ -86,6 +86,7 @@ const CreateDialog = ({ onClose }: CreateProps): React.ReactElement => { onChange={handleOnChange} error={error} fullWidth={true} + maxLength={60} /> { onChange={handleOnChange} required={false} fullWidth={true} + rows={3} /> From 0ff82735baeda340543304553e08fa22f6d409b6 Mon Sep 17 00:00:00 2001 From: Matias Arriola Date: Wed, 9 Feb 2022 14:54:48 -0300 Subject: [PATCH 02/12] Allow to set and remove labels from maps Allow to create new labels and delete labels --- .../client/cache-decorator-client/index.ts | 12 ++ packages/webapp/src/classes/client/index.ts | 3 + .../src/classes/client/mock-client/index.ts | 39 ++++++- .../src/classes/client/rest-client/index.ts | 54 ++++++++- .../maps-page/action-dispatcher/index.tsx | 2 + .../action-dispatcher/label-dialog/index.tsx | 75 +++++++++++++ .../action-dispatcher/label-dialog/style.ts | 10 ++ .../webapp/src/components/maps-page/index.tsx | 8 +- .../maps-list/add-label-button/index.tsx | 61 ++++++----- .../components/maps-page/maps-list/index.tsx | 63 ++++++++++- .../maps-list/label-selector/index.tsx | 103 +++++++++++++++--- .../maps-list/label-selector/styled.ts | 29 ++++- .../maps-page/maps-list/labels-cell/index.tsx | 31 ++++-- .../maps-page/maps-list/labels-cell/styled.ts | 15 +++ packages/webapp/webpack.dev.js | 1 + yarn.lock | 2 +- 16 files changed, 439 insertions(+), 69 deletions(-) create mode 100644 packages/webapp/src/components/maps-page/action-dispatcher/label-dialog/index.tsx create mode 100644 packages/webapp/src/components/maps-page/action-dispatcher/label-dialog/style.ts create mode 100644 packages/webapp/src/components/maps-page/maps-list/labels-cell/styled.ts diff --git a/packages/webapp/src/classes/client/cache-decorator-client/index.ts b/packages/webapp/src/classes/client/cache-decorator-client/index.ts index 44c9df4d..8c988e4e 100644 --- a/packages/webapp/src/classes/client/cache-decorator-client/index.ts +++ b/packages/webapp/src/classes/client/cache-decorator-client/index.ts @@ -90,10 +90,22 @@ class CacheDecoratorClient implements Client { return this.client.fetchLabels(); } + createLabel(title: string, color: string): Promise { + return this.client.createLabel(title, color); + } + deleteLabel(id: number): Promise { return this.client.deleteLabel(id); } + addLabelToMap(labelId: number, mapId: number): Promise { + return this.client.addLabelToMap(labelId, mapId); + } + + deleteLabelFromMap(labelId: number, mapId: number): Promise { + return this.client.deleteLabelFromMap(labelId, mapId); + } + fetchAccountInfo(): Promise { return this.client.fetchAccountInfo(); } diff --git a/packages/webapp/src/classes/client/index.ts b/packages/webapp/src/classes/client/index.ts index 6a0a3971..34517ba4 100644 --- a/packages/webapp/src/classes/client/index.ts +++ b/packages/webapp/src/classes/client/index.ts @@ -94,8 +94,11 @@ interface Client { updateStarred(id: number, starred: boolean): Promise; updateMapToPublic(id: number, isPublic: boolean): Promise; + createLabel(title: string, color: string): Promise; fetchLabels(): Promise; deleteLabel(id: number): Promise; + addLabelToMap(labelId: number, mapId: number): Promise; + deleteLabelFromMap(labelId: number, mapId: number): Promise; fetchAccountInfo(): Promise; registerNewUser(user: NewUser): Promise; diff --git a/packages/webapp/src/classes/client/mock-client/index.ts b/packages/webapp/src/classes/client/mock-client/index.ts index 1e248360..57376cea 100644 --- a/packages/webapp/src/classes/client/mock-client/index.ts +++ b/packages/webapp/src/classes/client/mock-client/index.ts @@ -327,9 +327,46 @@ class MockClient implements Client { } } + createLabel(title: string, color: string): Promise { + 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 { 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 { + 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 { + 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(); } diff --git a/packages/webapp/src/classes/client/rest-client/index.ts b/packages/webapp/src/classes/client/rest-client/index.ts index 37ab6c85..76b80301 100644 --- a/packages/webapp/src/classes/client/rest-client/index.ts +++ b/packages/webapp/src/classes/client/rest-client/index.ts @@ -510,10 +510,62 @@ export default class RestClient implements Client { return new Promise(handler); } + createLabel(title: string, color: string): Promise { + 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 { 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 { + 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); + } + + // TODO: not working (error 500: missing lid param) + deleteLabelFromMap(labelId: number, mapId: number): Promise { + const handler = (success: () => void, reject: (error: ErrorInfo) => void) => { + axios + .delete(`${this.baseUrl}/c/restful/maps/${mapId}/labels/${labelId}`, { + data: JSON.stringify(labelId) + }) .then(() => { success(); }) diff --git a/packages/webapp/src/components/maps-page/action-dispatcher/index.tsx b/packages/webapp/src/components/maps-page/action-dispatcher/index.tsx index ed3a7b48..c8359ad5 100644 --- a/packages/webapp/src/components/maps-page/action-dispatcher/index.tsx +++ b/packages/webapp/src/components/maps-page/action-dispatcher/index.tsx @@ -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 )} {action === 'share' && } + {action === 'label' && } ); }; diff --git a/packages/webapp/src/components/maps-page/action-dispatcher/label-dialog/index.tsx b/packages/webapp/src/components/maps-page/action-dispatcher/label-dialog/index.tsx new file mode 100644 index 00000000..37fb8759 --- /dev/null +++ b/packages/webapp/src/components/maps-page/action-dispatcher/label-dialog/index.tsx @@ -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('maps', () => { + return client.fetchAllMaps(); + }); + + const map = data.find(m => m.id === mapId); + + const changeLabelMutation = useMutation( + 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 ( +
+ + + +
); +}; + +export default LabelDialog; diff --git a/packages/webapp/src/components/maps-page/action-dispatcher/label-dialog/style.ts b/packages/webapp/src/components/maps-page/action-dispatcher/label-dialog/style.ts new file mode 100644 index 00000000..980cf20e --- /dev/null +++ b/packages/webapp/src/components/maps-page/action-dispatcher/label-dialog/style.ts @@ -0,0 +1,10 @@ +import createStyles from '@mui/styles/createStyles'; +import makeStyles from '@mui/styles/makeStyles'; + +export const useStyles = makeStyles(() => + createStyles({ + paper: { + maxWidth: '420px', + }, + }) +); diff --git a/packages/webapp/src/components/maps-page/index.tsx b/packages/webapp/src/components/maps-page/index.tsx index 8fc8fa73..c1cb9c20 100644 --- a/packages/webapp/src/components/maps-page/index.tsx +++ b/packages/webapp/src/components/maps-page/index.tsx @@ -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'; @@ -77,7 +77,6 @@ const MapsPage = (): ReactElement => { }, cache) useEffect(() => { - document.title = intl.formatMessage({ id: 'maps.page-title', defaultMessage: 'My Maps | WiseMapping', @@ -85,7 +84,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}`); }, diff --git a/packages/webapp/src/components/maps-page/maps-list/add-label-button/index.tsx b/packages/webapp/src/components/maps-page/maps-list/add-label-button/index.tsx index f6ae19ac..0c37dc7f 100644 --- a/packages/webapp/src/components/maps-page/maps-list/add-label-button/index.tsx +++ b/packages/webapp/src/components/maps-page/maps-list/add-label-button/index.tsx @@ -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(null); @@ -29,14 +29,14 @@ export function AddLabelButton({ onChange }: AddLabelButtonTypes): React.ReactEl const id = open ? 'add-label-popover' : undefined; return ( - - <> + <> + - - - - - + + + + + + ); } diff --git a/packages/webapp/src/components/maps-page/maps-list/index.tsx b/packages/webapp/src/components/maps-page/maps-list/index.tsx index 853c42fa..2caf2103 100644 --- a/packages/webapp/src/components/maps-page/maps-list/index.tsx +++ b/packages/webapp/src/components/maps-page/maps-list/index.tsx @@ -331,7 +331,7 @@ export const MapsList = (props: MapsListProps): React.ReactElement => { event.stopPropagation(); }; }; - 9; + const starredMultation = useMutation( (id: number) => { const map = mapsInfo.find((m) => m.id == id); @@ -385,6 +385,58 @@ export const MapsList = (props: MapsListProps): React.ReactElement => { }); }; + const removeLabelMultation = useMutation( + ({ 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( + 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 (
@@ -419,7 +471,10 @@ export const MapsList = (props: MapsListProps): React.ReactElement => { )} - {selected.length > 0 && } + {selected.length > 0 && isSelected(m.id))} + />}
@@ -561,7 +616,9 @@ export const MapsList = (props: MapsListProps): React.ReactElement => { - + { + handleRemoveLabel(row.id, lbl.id); + }} /> diff --git a/packages/webapp/src/components/maps-page/maps-list/label-selector/index.tsx b/packages/webapp/src/components/maps-page/maps-list/label-selector/index.tsx index 1f4947cc..9e2b74c7 100644 --- a/packages/webapp/src/components/maps-page/maps-list/label-selector/index.tsx +++ b/packages/webapp/src/components/maps-page/maps-list/label-selector/index.tsx @@ -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('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) => { - 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={ { + onChange({ id, title, color }, e.target.checked); + }} name={title} color="primary" /> @@ -45,13 +83,42 @@ export function LabelSelector(): React.ReactElement { /> ))} - } - > - {/* i18n */} - Add new label - + + + + + + { + e.stopPropagation(); + setNextLabelColorIndex(); + }} + /> + setNewLabelTitle(e.target.value)} + onKeyPress={(e) => { + if (e.key === 'Enter') { + handleSubmitNew(); + } + }} + value={newLabelTitle} /> + + } + onClick={() => handleSubmitNew()} + disabled={!newLabelTitle.length} + > + + + + ); diff --git a/packages/webapp/src/components/maps-page/maps-list/label-selector/styled.ts b/packages/webapp/src/components/maps-page/maps-list/label-selector/styled.ts index 9c2a4d2a..289353b3 100644 --- a/packages/webapp/src/components/maps-page/maps-list/label-selector/styled.ts +++ b/packages/webapp/src/components/maps-page/maps-list/label-selector/styled.ts @@ -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; +`; diff --git a/packages/webapp/src/components/maps-page/maps-list/labels-cell/index.tsx b/packages/webapp/src/components/maps-page/maps-list/labels-cell/index.tsx index 471a816b..9abcfb6d 100644 --- a/packages/webapp/src/components/maps-page/maps-list/labels-cell/index.tsx +++ b/packages/webapp/src/components/maps-page/maps-list/labels-cell/index.tsx @@ -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 { +export function LabelsCell({ labels, onDelete }: Props): React.ReactElement { return ( <> {labels.map(label => ( - } - label={label.title} - clickable - color="primary" - style={{ backgroundColor: label.color, opacity: '0.75' }} - onDelete={() => { return 1; }} - /> + color={label.color} + > + + { label.title } + { + e.stopPropagation(); + onDelete(label); + }} + > + + + ))} ); diff --git a/packages/webapp/src/components/maps-page/maps-list/labels-cell/styled.ts b/packages/webapp/src/components/maps-page/maps-list/labels-cell/styled.ts new file mode 100644 index 00000000..eab7c0cb --- /dev/null +++ b/packages/webapp/src/components/maps-page/maps-list/labels-cell/styled.ts @@ -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; +`; \ No newline at end of file diff --git a/packages/webapp/webpack.dev.js b/packages/webapp/webpack.dev.js index 986c42bb..006fc763 100644 --- a/packages/webapp/webpack.dev.js +++ b/packages/webapp/webpack.dev.js @@ -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, diff --git a/yarn.lock b/yarn.lock index ae6b6542..f31bcad6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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== From 0e54c6bc78a35a3fc5c500ec462887d462bcfad0 Mon Sep 17 00:00:00 2001 From: Paulo Gustavo Veiga Date: Wed, 9 Feb 2022 14:17:42 -0800 Subject: [PATCH 03/12] Fix delete map from label --- packages/webapp/src/classes/client/rest-client/index.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/webapp/src/classes/client/rest-client/index.ts b/packages/webapp/src/classes/client/rest-client/index.ts index 76b80301..3cffcdaa 100644 --- a/packages/webapp/src/classes/client/rest-client/index.ts +++ b/packages/webapp/src/classes/client/rest-client/index.ts @@ -559,13 +559,10 @@ export default class RestClient implements Client { return new Promise(handler); } - // TODO: not working (error 500: missing lid param) deleteLabelFromMap(labelId: number, mapId: number): Promise { const handler = (success: () => void, reject: (error: ErrorInfo) => void) => { axios - .delete(`${this.baseUrl}/c/restful/maps/${mapId}/labels/${labelId}`, { - data: JSON.stringify(labelId) - }) + .delete(`${this.baseUrl}/c/restful/maps/${mapId}/labels/${labelId}`) .then(() => { success(); }) From c77268868d388e9655dd34476b0ceef2a0e11e71 Mon Sep 17 00:00:00 2001 From: Paulo Gustavo Veiga Date: Wed, 9 Feb 2022 16:36:31 -0800 Subject: [PATCH 04/12] Fix i18n on default user --- packages/webapp/src/app.tsx | 3 +-- packages/webapp/src/classes/app-i18n/index.ts | 8 ++++---- packages/webapp/src/classes/client/index.ts | 2 +- packages/webapp/src/classes/client/rest-client/index.ts | 4 ++-- packages/webapp/src/components/editor-page/index.tsx | 3 +-- .../maps-page/action-dispatcher/import-dialog/index.tsx | 2 +- packages/webapp/src/components/maps-page/index.tsx | 3 +-- .../src/components/maps-page/language-menu/index.tsx | 6 +++--- 8 files changed, 14 insertions(+), 17 deletions(-) diff --git a/packages/webapp/src/app.tsx b/packages/webapp/src/app.tsx index f0f6c91b..34f84ab7 100644 --- a/packages/webapp/src/app.tsx +++ b/packages/webapp/src/app.tsx @@ -37,8 +37,7 @@ const queryClient = new QueryClient({ }); const App = (): ReactElement => { - const appi18n = new AppI18n(); - const locale = appi18n.getBrowserLocale(); + const locale = AppI18n.getBrowserLocale(); // global variables set server-side const istTryMode = global.memoryPersistence; diff --git a/packages/webapp/src/classes/app-i18n/index.ts b/packages/webapp/src/classes/app-i18n/index.ts index e4663c05..b361721a 100644 --- a/packages/webapp/src/classes/app-i18n/index.ts +++ b/packages/webapp/src/classes/app-i18n/index.ts @@ -15,13 +15,13 @@ export class Locale { } } -export default class AppI18n { - public getUserLocale(): Locale { +export default abstract class AppI18n { + public static getUserLocale(): Locale { const account = fetchAccount(); - return account ? account.locale : this.getBrowserLocale(); + return account?.locale ? account.locale : this.getBrowserLocale(); } - public getBrowserLocale(): Locale { + public static getBrowserLocale(): Locale { let localeCode = (navigator.languages && navigator.languages[0]) || navigator.language; // Just remove the variant ... diff --git a/packages/webapp/src/classes/client/index.ts b/packages/webapp/src/classes/client/index.ts index 6a0a3971..711f9e49 100644 --- a/packages/webapp/src/classes/client/index.ts +++ b/packages/webapp/src/classes/client/index.ts @@ -63,7 +63,7 @@ export type AccountInfo = { firstname: string; lastname: string; email: string; - locale: Locale; + locale?: Locale; }; export type Permission = { diff --git a/packages/webapp/src/classes/client/rest-client/index.ts b/packages/webapp/src/classes/client/rest-client/index.ts index 37ab6c85..0fefd4c8 100644 --- a/packages/webapp/src/classes/client/rest-client/index.ts +++ b/packages/webapp/src/classes/client/rest-client/index.ts @@ -184,7 +184,7 @@ export default class RestClient implements Client { `${this.baseUrl}/c/restful/maps?title=${model.title}&description=${model.description ? model.description : '' }`, model.content, - { headers: { 'Content-Type': model.contentType } } + { headers: { 'Content-Type': 'application/xml' } } ) .then((response) => { const mapId = response.headers.resourceid; @@ -214,7 +214,7 @@ export default class RestClient implements Client { lastname: account.lastname ? account.lastname : '', firstname: account.firstname ? account.firstname : '', email: account.email, - locale: locale ? localeFromStr(locale) : Locales.EN, + locale: locale ? localeFromStr(locale) : undefined, }); }) .catch((error) => { diff --git a/packages/webapp/src/components/editor-page/index.tsx b/packages/webapp/src/components/editor-page/index.tsx index 56a7082a..055ccb49 100644 --- a/packages/webapp/src/components/editor-page/index.tsx +++ b/packages/webapp/src/components/editor-page/index.tsx @@ -13,8 +13,7 @@ const EditorPage = ({ mapId, ...props }: EditorPropsType): React.ReactElement => const [activeDialog, setActiveDialog] = React.useState(null); // Load user locale ... - const appi18n = new AppI18n(); - const userLocale = appi18n.getUserLocale(); + const userLocale = AppI18n.getUserLocale(); return <> diff --git a/packages/webapp/src/components/maps-page/action-dispatcher/import-dialog/index.tsx b/packages/webapp/src/components/maps-page/action-dispatcher/import-dialog/index.tsx index 8e381495..61fd9948 100644 --- a/packages/webapp/src/components/maps-page/action-dispatcher/import-dialog/index.tsx +++ b/packages/webapp/src/components/maps-page/action-dispatcher/import-dialog/index.tsx @@ -105,7 +105,7 @@ const ImportDialog = ({ onClose }: CreateProps): React.ReactElement => { description={intl.formatMessage({ id: 'import.description', defaultMessage: - 'You can import FreeMind 1.0.1 and WiseMapping maps to your list of maps. Select the file you want to import.', + 'You can import WiseMapping maps to your list of maps. Select the file you want to import.', })} submitButton={intl.formatMessage({ id: 'import.button', defaultMessage: 'Create' })} > diff --git a/packages/webapp/src/components/maps-page/index.tsx b/packages/webapp/src/components/maps-page/index.tsx index 8fc8fa73..949ae30f 100644 --- a/packages/webapp/src/components/maps-page/index.tsx +++ b/packages/webapp/src/components/maps-page/index.tsx @@ -66,8 +66,7 @@ const MapsPage = (): ReactElement => { const [activeDialog, setActiveDialog] = React.useState(undefined); // Reload based on user preference ... - const appi18n = new AppI18n(); - const userLocale = appi18n.getUserLocale(); + const userLocale = AppI18n.getUserLocale(); const cache = createIntlCache(); const intl = createIntl({ diff --git a/packages/webapp/src/components/maps-page/language-menu/index.tsx b/packages/webapp/src/components/maps-page/language-menu/index.tsx index 0a47c94b..b89ea31b 100644 --- a/packages/webapp/src/components/maps-page/language-menu/index.tsx +++ b/packages/webapp/src/components/maps-page/language-menu/index.tsx @@ -5,7 +5,7 @@ import Client from '../../../classes/client'; import { useSelector } from 'react-redux'; import { activeInstance, fetchAccount } from '../../../redux/clientSlice'; import { FormattedMessage, useIntl } from 'react-intl'; -import { LocaleCode, Locales } from '../../../classes/app-i18n'; +import AppI18n, { LocaleCode, Locales } from '../../../classes/app-i18n'; import Tooltip from '@mui/material/Tooltip'; import Button from '@mui/material/Button'; import Menu from '@mui/material/Menu'; @@ -49,7 +49,7 @@ const LanguageMenu = (): React.ReactElement => { mutation.mutate(localeCode); }; - const accountInfo = fetchAccount(); + const userLocale = AppI18n.getUserLocale(); return ( { onClick={handleMenu} startIcon={} > - {accountInfo?.locale?.label} + {userLocale.label} Date: Wed, 9 Feb 2022 16:48:33 -0800 Subject: [PATCH 05/12] Fix default i18n load --- packages/editor/src/index.tsx | 10 ++++---- .../src/components/editor-page/index.tsx | 1 + .../components/maps-page/maps-list/index.tsx | 23 +++++++++---------- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/packages/editor/src/index.tsx b/packages/editor/src/index.tsx index f5ce16fb..7d4edc74 100644 --- a/packages/editor/src/index.tsx +++ b/packages/editor/src/index.tsx @@ -37,7 +37,7 @@ declare global { } export type EditorPropsType = { - initCallback?: () => void; + initCallback?: (locale: string) => void; mapId?: number; isTryMode: boolean; readOnlyMode: boolean; @@ -60,7 +60,7 @@ const loadLocaleData = (locale: string) => { } } -const initMindplot = () => { +const initMindplot = (locale: string) => { // Change page title ... document.title = `${global.mapTitle} | WiseMapping `; @@ -95,7 +95,7 @@ const initMindplot = () => { (global.userOptions?.zoom != undefined ? Number.parseFloat(global.userOptions.zoom as string) : 0.8), - locale: global.locale, + locale: locale, }); // Build designer ... @@ -119,11 +119,11 @@ const Editor = ({ onAction, }: EditorPropsType): React.ReactElement => { React.useEffect(() => { - initCallback(); + initCallback(locale); }, []); return ( - + // Load user locale ... const userLocale = AppI18n.getUserLocale(); + console.log("Locale:" + userLocale.code); return <> diff --git a/packages/webapp/src/components/maps-page/maps-list/index.tsx b/packages/webapp/src/components/maps-page/maps-list/index.tsx index 2caf2103..32dfb83d 100644 --- a/packages/webapp/src/components/maps-page/maps-list/index.tsx +++ b/packages/webapp/src/components/maps-page/maps-list/index.tsx @@ -2,7 +2,7 @@ import React, { useEffect, CSSProperties } from 'react'; import { useStyles } from './styled'; import { useSelector } from 'react-redux'; -import { activeInstance, fetchAccount } from '../../../redux/clientSlice'; +import { activeInstance } from '../../../redux/clientSlice'; import { useMutation, useQuery, useQueryClient } from 'react-query'; import Client, { ErrorInfo, Label, MapInfo } from '../../../classes/client'; import ActionChooser, { ActionType } from '../action-chooser'; @@ -37,6 +37,7 @@ import { AddLabelButton } from './add-label-button'; import relativeTime from 'dayjs/plugin/relativeTime'; import { LabelsCell } from './labels-cell'; import LocalizedFormat from 'dayjs/plugin/localizedFormat'; +import AppI18n from '../../../classes/app-i18n'; dayjs.extend(LocalizedFormat) dayjs.extend(relativeTime); @@ -58,9 +59,9 @@ function getComparator( order: Order, orderBy: Key ): ( - a: { [key in Key]: number | string | boolean | Label[] | undefined }, - b: { [key in Key]: number | string | Label[] | boolean } -) => number { + 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) : (a, b) => -descendingComparator(a, b, orderBy); @@ -251,10 +252,8 @@ export const MapsList = (props: MapsListProps): React.ReactElement => { const queryClient = useQueryClient(); // Configure locale ... - const account = fetchAccount(); - if (account) { - dayjs.locale(account.locale.code); - } + const userLocale = AppI18n.getUserLocale(); + dayjs.locale(userLocale.code); useEffect(() => { setSelected([]); @@ -385,7 +384,7 @@ export const MapsList = (props: MapsListProps): React.ReactElement => { }); }; - const removeLabelMultation = useMutation( + const removeLabelMultation = useMutation( ({ mapId, labelId }) => { return client.deleteLabelFromMap(labelId, mapId); }, @@ -409,7 +408,7 @@ export const MapsList = (props: MapsListProps): React.ReactElement => { if (!label.id) { label.id = await client.createLabel(label.title, label.color); } - if (checked){ + 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 { @@ -471,9 +470,9 @@ export const MapsList = (props: MapsListProps): React.ReactElement => { )} - {selected.length > 0 && 0 && isSelected(m.id))} + maps={mapsInfo.filter(m => isSelected(m.id))} />}
From 1a8ffee80120695b8012956f22d8c5bed7e48fb6 Mon Sep 17 00:00:00 2001 From: Paulo Gustavo Veiga Date: Wed, 9 Feb 2022 19:26:44 -0800 Subject: [PATCH 06/12] Fix eslint issues. --- .../mindplot/src/components/CentralTopic.ts | 1 - .../src/components/DesignerKeyboard.ts | 14 ++++++++------ packages/mindplot/src/components/MainTopic.ts | 2 +- .../src/components/ShrinkConnector.ts | 7 ++++--- packages/mindplot/src/components/Topic.ts | 19 ++++++++++++++----- .../commands/AddFeatureToTopicCommand.ts | 3 ++- .../commands/ChangeFeatureToTopicCommand.ts | 6 +++--- .../commands/MoveControlPointCommand.ts | 3 ++- .../libraries/bootstrap/BootstrapDialog.js | 4 ++-- .../src/classes/client/rest-client/index.ts | 2 +- .../src/components/form/input/index.tsx | 1 - .../maps-page/language-menu/index.tsx | 2 +- 12 files changed, 38 insertions(+), 26 deletions(-) diff --git a/packages/mindplot/src/components/CentralTopic.ts b/packages/mindplot/src/components/CentralTopic.ts index 5164b6fa..85908a28 100644 --- a/packages/mindplot/src/components/CentralTopic.ts +++ b/packages/mindplot/src/components/CentralTopic.ts @@ -35,7 +35,6 @@ class CentralTopic extends Topic { }); } - workoutIncomingConnectionPoint(): Point { return this.getPosition(); } diff --git a/packages/mindplot/src/components/DesignerKeyboard.ts b/packages/mindplot/src/components/DesignerKeyboard.ts index 45bdd562..4de8d09a 100644 --- a/packages/mindplot/src/components/DesignerKeyboard.ts +++ b/packages/mindplot/src/components/DesignerKeyboard.ts @@ -22,7 +22,8 @@ import { Designer } from '..'; import Topic from './Topic'; class DesignerKeyboard extends Keyboard { - static _instance: any; + // eslint-disable-next-line no-use-before-define + static _instance: DesignerKeyboard; constructor(designer: Designer) { super(); @@ -79,14 +80,14 @@ class DesignerKeyboard extends Keyboard { this.addShortcut( ['tab'], (eventevent: Event) => { designer.createChildForSelectedNode(); - event.preventDefault(); - event.stopPropagation(); + eventevent.preventDefault(); + eventevent.stopPropagation(); }, ); this.addShortcut( ['meta+enter'], (eventevent: Event) => { - event.preventDefault(); - event.stopPropagation(); + eventevent.preventDefault(); + eventevent.stopPropagation(); designer.createChildForSelectedNode(); }, ); @@ -244,7 +245,7 @@ class DesignerKeyboard extends Keyboard { const excludes = ['esc', 'escape', 'f1', 'f3', 'f4', 'f5', 'f6', 'f7', 'f8', 'f9', 'f10', 'f11', 'f12']; $(document).on('keypress', (event) => { - let keyCode; + let keyCode: number; // Firefox doesn't skip special keys for keypress event... if (event.key && excludes.includes(event.key.toLowerCase())) { return; @@ -256,6 +257,7 @@ class DesignerKeyboard extends Keyboard { keyCode = event.keyCode; } + // eslint-disable-next-line @typescript-eslint/no-explicit-any const jq: any = $; const specialKey = jq.hotkeys.specialKeys[keyCode]; if (['enter', 'capslock'].indexOf(specialKey) === -1 && !jq.hotkeys.shiftNums[keyCode]) { diff --git a/packages/mindplot/src/components/MainTopic.ts b/packages/mindplot/src/components/MainTopic.ts index ad0a6b43..c5db2dd8 100644 --- a/packages/mindplot/src/components/MainTopic.ts +++ b/packages/mindplot/src/components/MainTopic.ts @@ -27,6 +27,7 @@ import SizeType from './SizeType'; class MainTopic extends Topic { private INNER_RECT_ATTRIBUTES: { stroke: string; }; + /** * @extends mindplot.Topic * @constructs @@ -74,7 +75,6 @@ class MainTopic extends Topic { return group; } - updateTopicShape(targetTopic: Topic) { // Change figure based on the connected topic ... const model = this.getModel(); diff --git a/packages/mindplot/src/components/ShrinkConnector.ts b/packages/mindplot/src/components/ShrinkConnector.ts index bb8aea96..271e690c 100644 --- a/packages/mindplot/src/components/ShrinkConnector.ts +++ b/packages/mindplot/src/components/ShrinkConnector.ts @@ -20,11 +20,12 @@ import { Elipse } from '@wisemapping/web2d'; import TopicConfig from './TopicConfig'; import ActionDispatcher from './ActionDispatcher'; import Topic from './Topic'; -import IconGroup from './IconGroup'; class ShirinkConnector { private _isShrink: boolean; - private _ellipse: any; + + private _ellipse: Elipse; + constructor(topic: Topic) { this._isShrink = false; const ellipse = new Elipse(TopicConfig.INNER_RECT_ATTRIBUTES); @@ -33,7 +34,7 @@ class ShirinkConnector { ellipse.setFill('rgb(62,118,179)'); ellipse.setSize(TopicConfig.CONNECTOR_WIDTH, TopicConfig.CONNECTOR_WIDTH); - ellipse.addEvent('click', (event) => { + ellipse.addEvent('click', (event: Event) => { const model = topic.getModel(); const collapse = !model.areChildrenShrunken(); diff --git a/packages/mindplot/src/components/Topic.ts b/packages/mindplot/src/components/Topic.ts index a4249b84..3e5f6989 100644 --- a/packages/mindplot/src/components/Topic.ts +++ b/packages/mindplot/src/components/Topic.ts @@ -19,7 +19,7 @@ import $ from 'jquery'; import { $assert, $defined } from '@wisemapping/core-js'; import { - Rect, Image, Line, Text, Group, ElementClass, Point + Rect, Image, Line, Text, Group, ElementClass, Point, } from '@wisemapping/web2d'; import NodeGraph from './NodeGraph'; @@ -35,7 +35,6 @@ import NoteEditor from './widget/NoteEditor'; import ActionDispatcher from './ActionDispatcher'; import LinkEditor from './widget/LinkEditor'; - import TopicEventDispatcher, { TopicEvent } from './TopicEventDispatcher'; import { TopicShape } from './model/INodeModel'; import NodeModel from './model/NodeModel'; @@ -50,14 +49,25 @@ const ICON_SCALING_FACTOR = 1.3; abstract class Topic extends NodeGraph { private _innerShape: ElementClass; + private _relationships: Relationship[]; + private _isInWorkspace: boolean; + + // eslint-disable-next-line no-use-before-define private _children: Topic[]; + + // eslint-disable-next-line no-use-before-define private _parent: Topic | null; + private _outerShape: ElementClass; + private _text: Text | null; + private _iconsGroup: IconGroup; - private _connector: any; + + private _connector: ShirinkConnector; + private _outgoingLine: Line; constructor(model: NodeModel, options) { @@ -241,7 +251,7 @@ abstract class Topic extends NodeGraph { result.setStroke(1, 'solid', stokeColor); }; - result.getSize = function getSize() { this.size }; + result.getSize = function getSize() { return this.size; }; result.setPosition = () => { // Overwrite behaviour ... @@ -1324,7 +1334,6 @@ abstract class Topic extends NodeGraph { return result; } - isChildTopic(childTopic: Topic): boolean { let result = this.getId() === childTopic.getId(); if (!result) { diff --git a/packages/mindplot/src/components/commands/AddFeatureToTopicCommand.ts b/packages/mindplot/src/components/commands/AddFeatureToTopicCommand.ts index ea89bbb9..952e6562 100644 --- a/packages/mindplot/src/components/commands/AddFeatureToTopicCommand.ts +++ b/packages/mindplot/src/components/commands/AddFeatureToTopicCommand.ts @@ -18,6 +18,7 @@ import { $assert, $defined } from '@wisemapping/core-js'; import Command from '../Command'; import CommandContext from '../CommandContext'; +import FeatureModel from '../model/FeatureModel'; import FeatureType from '../model/FeatureType'; class AddFeatureToTopicCommand extends Command { @@ -27,7 +28,7 @@ class AddFeatureToTopicCommand extends Command { private _attributes: object; - private _featureModel: any; + private _featureModel: FeatureModel; /* * @classdesc This command class handles do/undo of adding features to topics, e.g. an diff --git a/packages/mindplot/src/components/commands/ChangeFeatureToTopicCommand.ts b/packages/mindplot/src/components/commands/ChangeFeatureToTopicCommand.ts index 98069885..07253e15 100644 --- a/packages/mindplot/src/components/commands/ChangeFeatureToTopicCommand.ts +++ b/packages/mindplot/src/components/commands/ChangeFeatureToTopicCommand.ts @@ -24,9 +24,9 @@ class ChangeFeatureToTopicCommand extends Command { private _topicId: number; - private _attributes: any; + private _attributes; - constructor(topicId: number, featureId: number, attributes: any) { + constructor(topicId: number, featureId: number, attributes) { $assert($defined(topicId), 'topicId can not be null'); $assert($defined(featureId), 'featureId can not be null'); $assert($defined(attributes), 'attributes can not be null'); @@ -53,7 +53,7 @@ class ChangeFeatureToTopicCommand extends Command { * Overrides abstract parent method * @see {@link mindplot.Command.undoExecute} */ - undoExecute(commandContext: any) { + undoExecute(commandContext: CommandContext) { this.execute(commandContext); } } diff --git a/packages/mindplot/src/components/commands/MoveControlPointCommand.ts b/packages/mindplot/src/components/commands/MoveControlPointCommand.ts index 22da44bb..85f9868a 100644 --- a/packages/mindplot/src/components/commands/MoveControlPointCommand.ts +++ b/packages/mindplot/src/components/commands/MoveControlPointCommand.ts @@ -16,13 +16,14 @@ * limitations under the License. */ import { $assert, $defined } from '@wisemapping/core-js'; +import { Line } from '@wisemapping/web2d'; import Command from '../Command'; import ControlPoint from '../ControlPoint'; class MoveControlPointCommand extends Command { private _ctrlPointControler: ControlPoint; - private _line: any; + private _line: Line; private _controlPoint: any; diff --git a/packages/mindplot/src/components/libraries/bootstrap/BootstrapDialog.js b/packages/mindplot/src/components/libraries/bootstrap/BootstrapDialog.js index f982bf11..303c5221 100644 --- a/packages/mindplot/src/components/libraries/bootstrap/BootstrapDialog.js +++ b/packages/mindplot/src/components/libraries/bootstrap/BootstrapDialog.js @@ -112,7 +112,7 @@ class BootstrapDialog extends Options { return header; } - onAcceptClick(event) { + onAcceptClick() { throw new Error('Unsupported operation'); } @@ -120,7 +120,7 @@ class BootstrapDialog extends Options { // Overwrite default behaviour ... } - onRemoveClick(event) { + onRemoveClick() { throw new Error('Unsupported operation'); } diff --git a/packages/webapp/src/classes/client/rest-client/index.ts b/packages/webapp/src/classes/client/rest-client/index.ts index 942472e7..5fdd6285 100644 --- a/packages/webapp/src/classes/client/rest-client/index.ts +++ b/packages/webapp/src/classes/client/rest-client/index.ts @@ -11,7 +11,7 @@ import Client, { ImportMapInfo, Permission, } from '..'; -import { LocaleCode, localeFromStr, Locales } from '../../app-i18n'; +import { LocaleCode, localeFromStr } from '../../app-i18n'; export default class RestClient implements Client { private baseUrl: string; diff --git a/packages/webapp/src/components/form/input/index.tsx b/packages/webapp/src/components/form/input/index.tsx index 2d4b0ae2..806a2524 100644 --- a/packages/webapp/src/components/form/input/index.tsx +++ b/packages/webapp/src/components/form/input/index.tsx @@ -1,5 +1,4 @@ import TextField from '@mui/material/TextField'; -import { row } from '@wisemapping/mindplot/src/components/widget/ColorPaletteHtml'; import React, { ChangeEvent } from 'react'; import { ErrorInfo } from '../../../classes/client'; diff --git a/packages/webapp/src/components/maps-page/language-menu/index.tsx b/packages/webapp/src/components/maps-page/language-menu/index.tsx index b89ea31b..676a0bbb 100644 --- a/packages/webapp/src/components/maps-page/language-menu/index.tsx +++ b/packages/webapp/src/components/maps-page/language-menu/index.tsx @@ -3,7 +3,7 @@ import React from 'react'; import { useMutation, useQueryClient } from 'react-query'; import Client from '../../../classes/client'; import { useSelector } from 'react-redux'; -import { activeInstance, fetchAccount } from '../../../redux/clientSlice'; +import { activeInstance } from '../../../redux/clientSlice'; import { FormattedMessage, useIntl } from 'react-intl'; import AppI18n, { LocaleCode, Locales } from '../../../classes/app-i18n'; import Tooltip from '@mui/material/Tooltip'; From 2e191a168a108fee58f9eac299bf225384382a94 Mon Sep 17 00:00:00 2001 From: Paulo Gustavo Veiga Date: Wed, 9 Feb 2022 19:44:55 -0800 Subject: [PATCH 07/12] Fix lint error --- .../{DragManager.js => DragManager.ts} | 26 +++++++++++++++---- .../mindplot/src/components/Relationship.ts | 2 +- .../mindplot/src/components/ScreenManager.ts | 5 ++-- .../components/commands/DragTopicCommand.ts | 8 +++--- .../commands/RemoveFeatureFromTopicCommand.ts | 7 ++--- 5 files changed, 33 insertions(+), 15 deletions(-) rename packages/mindplot/src/components/{DragManager.js => DragManager.ts} (86%) diff --git a/packages/mindplot/src/components/DragManager.js b/packages/mindplot/src/components/DragManager.ts similarity index 86% rename from packages/mindplot/src/components/DragManager.js rename to packages/mindplot/src/components/DragManager.ts index 02d0ce84..09403006 100644 --- a/packages/mindplot/src/components/DragManager.js +++ b/packages/mindplot/src/components/DragManager.ts @@ -17,9 +17,25 @@ */ import { $assert, $defined } from '@wisemapping/core-js'; import DragTopic from './DragTopic'; +import EventBusDispatcher from './layout/EventBusDispatcher'; +import Workspace from './Workspace'; class DragManager { - constructor(workspace, eventDispatcher) { + private _workspace: Workspace; + + private _designerModel: Workspace; + + private _isDragInProcess: boolean; + + private _eventDispatcher: EventBusDispatcher; + + private _listeners; + + private _mouseMoveListener; + + private _mouseUpListener; + + constructor(workspace: Workspace, eventDispatcher: EventBusDispatcher) { this._workspace = workspace; this._designerModel = workspace; this._listeners = {}; @@ -34,7 +50,7 @@ class DragManager { const screen = workspace.getScreenManager(); const dragManager = this; const me = this; - const mouseDownListener = function mouseDownListener(event) { + const mouseDownListener = function mouseDownListener() { if (workspace.isWorkspaceEventsEnabled()) { // Disable double drag... workspace.enableWorkspaceEvents(false); @@ -62,11 +78,11 @@ class DragManager { node.addEvent('mousedown', mouseDownListener); } - remove(node) { + remove() { throw new Error('Not implemented: DragManager.prototype.remove'); } - _buildMouseMoveListener(workspace, dragNode, dragManager) { + protected _buildMouseMoveListener(workspace: Workspace, dragNode, dragManager: DragManager) { const screen = workspace.getScreenManager(); const me = this; const result = (event) => { @@ -98,7 +114,7 @@ class DragManager { return result; } - _buildMouseUpListener(workspace, node, dragNode, dragManager) { + protected _buildMouseUpListener(workspace: Workspace, node, dragNode, dragManager: DragManager) { const screen = workspace.getScreenManager(); const me = this; const result = (event) => { diff --git a/packages/mindplot/src/components/Relationship.ts b/packages/mindplot/src/components/Relationship.ts index a7d62a0e..3e056911 100644 --- a/packages/mindplot/src/components/Relationship.ts +++ b/packages/mindplot/src/components/Relationship.ts @@ -258,7 +258,7 @@ class Relationship extends ConnectionLine { } // @typescript-eslint/ban-types - addEvent(eventType: string, listener: any) { + addEvent(eventType: string, listener) { let type = eventType; // Translate to web 2d events ... if (type === 'onfocus') { diff --git a/packages/mindplot/src/components/ScreenManager.ts b/packages/mindplot/src/components/ScreenManager.ts index ab49de12..286ec05c 100644 --- a/packages/mindplot/src/components/ScreenManager.ts +++ b/packages/mindplot/src/components/ScreenManager.ts @@ -17,6 +17,7 @@ */ import { $assert } from '@wisemapping/core-js'; import { Point } from '@wisemapping/web2d'; +import Icon from './Icon'; import Topic from './Topic'; class ScreenManager { @@ -75,7 +76,7 @@ class ScreenManager { fireEvent(type: string, event: UIEvent = null) { if (type === 'click') { - this._clickEvents.forEach((listener: (arg0: any, arg1: any) => void) => { + this._clickEvents.forEach((listener) => { listener(type, event); }); } else { @@ -101,7 +102,7 @@ class ScreenManager { return { x, y }; } - getWorkspaceIconPosition(e: { getImage: () => any; getSize: () => any; getGroup: () => any; }) { + getWorkspaceIconPosition(e: Icon) { // Retrieve current icon position. const image = e.getImage(); const elementPosition = image.getPosition(); diff --git a/packages/mindplot/src/components/commands/DragTopicCommand.ts b/packages/mindplot/src/components/commands/DragTopicCommand.ts index aad827a4..8bee475c 100644 --- a/packages/mindplot/src/components/commands/DragTopicCommand.ts +++ b/packages/mindplot/src/components/commands/DragTopicCommand.ts @@ -24,7 +24,7 @@ import Topic from '../Topic'; class DragTopicCommand extends Command { private _topicsId: number; - private _parentId: any; + private _parentId: number; private _position: Point; @@ -62,7 +62,7 @@ class DragTopicCommand extends Command { const origPosition = topic.getPosition(); // Disconnect topic .. - if ($defined(origParentTopic) && origParentTopic !== this._parentId) { + if ($defined(origParentTopic) && origParentTopic.getId() !== this._parentId) { commandContext.disconnect(topic); } @@ -76,9 +76,9 @@ class DragTopicCommand extends Command { } // Finally, connect topic ... - if (origParentTopic !== this._parentId) { + if (origParentTopic.getId() !== this._parentId) { if ($defined(this._parentId)) { - const parentTopic = commandContext.findTopics(this._parentId)[0]; + const parentTopic = commandContext.findTopics([this._parentId])[0]; commandContext.connect(topic, parentTopic); } diff --git a/packages/mindplot/src/components/commands/RemoveFeatureFromTopicCommand.ts b/packages/mindplot/src/components/commands/RemoveFeatureFromTopicCommand.ts index 414edff7..8a791cc8 100644 --- a/packages/mindplot/src/components/commands/RemoveFeatureFromTopicCommand.ts +++ b/packages/mindplot/src/components/commands/RemoveFeatureFromTopicCommand.ts @@ -18,13 +18,14 @@ import { $assert, $defined } from '@wisemapping/core-js'; import Command from '../Command'; import CommandContext from '../CommandContext'; +import FeatureModel from '../model/FeatureModel'; class RemoveFeatureFromTopicCommand extends Command { private _topicId: number; private _featureId: number; - private _oldFeature: any; + private _oldFeature: FeatureModel; /** * @classdesc This command handles do/undo of removing a feature from a topic, e.g. an icon or @@ -43,7 +44,7 @@ class RemoveFeatureFromTopicCommand extends Command { /** * Overrides abstract parent method */ - execute(commandContext:CommandContext):void { + execute(commandContext: CommandContext): void { const topic = commandContext.findTopics([this._topicId])[0]; const feature = topic.findFeatureById(this._featureId); topic.removeFeature(feature); @@ -54,7 +55,7 @@ class RemoveFeatureFromTopicCommand extends Command { * Overrides abstract parent method * @see {@link mindplot.Command.undoExecute} */ - undoExecute(commandContext:CommandContext) { + undoExecute(commandContext: CommandContext) { const topic = commandContext.findTopics([this._topicId])[0]; topic.addFeature(this._oldFeature); this._oldFeature = null; From d8b7fd81fa50bd70036cdbea75f2539a6356d573 Mon Sep 17 00:00:00 2001 From: Paulo Gustavo Veiga Date: Wed, 9 Feb 2022 20:02:57 -0800 Subject: [PATCH 08/12] Move control point to TS --- .../src/components/ActionDispatcher.ts | 3 +- .../{ControlPoint.js => ControlPoint.ts} | 58 ++++++++++++++----- 2 files changed, 44 insertions(+), 17 deletions(-) rename packages/mindplot/src/components/{ControlPoint.js => ControlPoint.ts} (86%) diff --git a/packages/mindplot/src/components/ActionDispatcher.ts b/packages/mindplot/src/components/ActionDispatcher.ts index f1975636..8130c3d8 100644 --- a/packages/mindplot/src/components/ActionDispatcher.ts +++ b/packages/mindplot/src/components/ActionDispatcher.ts @@ -21,6 +21,7 @@ import { $assert } from '@wisemapping/core-js'; import Point from '@wisemapping/web2d'; import { Mindmap } from '..'; import CommandContext from './CommandContext'; +import ControlPoint from './ControlPoint'; import Events from './Events'; import NodeModel from './model/NodeModel'; import RelationshipModel from './model/RelationshipModel'; @@ -44,7 +45,7 @@ abstract class ActionDispatcher extends Events { abstract moveTopic(topicId: number, position: Point): void; - abstract moveControlPoint(ctrlPoint: this, point: Point): void; + abstract moveControlPoint(ctrlPoint: ControlPoint, point: Point): void; abstract changeFontFamilyToTopic(topicIds: number[], fontFamily: string): void; diff --git a/packages/mindplot/src/components/ControlPoint.js b/packages/mindplot/src/components/ControlPoint.ts similarity index 86% rename from packages/mindplot/src/components/ControlPoint.js rename to packages/mindplot/src/components/ControlPoint.ts index 9dfab9a3..f50cdfe8 100644 --- a/packages/mindplot/src/components/ControlPoint.js +++ b/packages/mindplot/src/components/ControlPoint.ts @@ -20,8 +20,33 @@ import { $defined } from '@wisemapping/core-js'; import Shape from './util/Shape'; import ActionDispatcher from './ActionDispatcher'; +import Workspace from './Workspace'; class ControlPoint { + private control1: Elipse; + + private control2: Elipse; + + private _controlPointsController: Elipse[]; + + private _controlLines: Line[]; + + private _isBinded: boolean; + + _line: Line; + + private _workspace: Workspace; + + private _endPoint: any[]; + + private _orignalCtrlPoint: any; + + private _controls: any; + + private _mouseMoveFunction: (e: Event) => void; + + private _mouseUpFunction: (e: Event) => void; + constructor() { this.control1 = new Elipse({ width: 6, @@ -70,7 +95,7 @@ class ControlPoint { }); } - setLine(line) { + setLine(line: Line) { if ($defined(this._line)) { this._removeLine(); } @@ -93,7 +118,7 @@ class ControlPoint { if ($defined(this._line)) this._createControlPoint(); } - _createControlPoint() { + private _createControlPoint() { this._controls = this._line.getLine().getControlPoints(); let pos = this._line.getLine().getFrom(); this._controlPointsController[0].setPosition( @@ -117,11 +142,11 @@ class ControlPoint { ); } - _removeLine() { + private _removeLine() { // Overwrite default behaviour ... } - _mouseDown(event, point, me) { + private _mouseDown(event: Event, point, me) { if (!this._isBinded) { this._isBinded = true; this._mouseMoveFunction = (e) => { @@ -129,7 +154,7 @@ class ControlPoint { }; this._workspace.getScreenManager().addEvent('mousemove', this._mouseMoveFunction); - this._mouseUpFunction = (e) => { + this._mouseUpFunction = (e: Event) => { me._mouseUp(e, point, me); }; this._workspace.getScreenManager().addEvent('mouseup', this._mouseUpFunction); @@ -139,7 +164,7 @@ class ControlPoint { return false; } - _mouseMoveEvent(event, point) { + private _mouseMoveEvent(event: MouseEvent, point: Point) { const screen = this._workspace.getScreenManager(); const pos = screen.getWorkspaceMousePosition(event); @@ -162,7 +187,7 @@ class ControlPoint { this._line.getLine().updateLine(point); } - _mouseUp(event, point) { + private _mouseUp(event, point) { this._workspace.getScreenManager().removeEvent('mousemove', this._mouseMoveFunction); this._workspace.getScreenManager().removeEvent('mouseup', this._mouseUpFunction); @@ -177,7 +202,7 @@ class ControlPoint { return false; } - setVisibility(visible) { + setVisibility(visible: boolean) { if (visible) { this._controlLines[0].moveToFront(); this._controlLines[1].moveToFront(); @@ -190,7 +215,7 @@ class ControlPoint { this._controlLines[1].setVisibility(visible); } - addToWorkspace(workspace) { + addToWorkspace(workspace: Workspace): void { this._workspace = workspace; workspace.append(this._controlPointsController[0]); workspace.append(this._controlPointsController[1]); @@ -198,7 +223,7 @@ class ControlPoint { workspace.append(this._controlLines[1]); } - removeFromWorkspace(workspace) { + removeFromWorkspace(workspace: Workspace) { this._workspace = null; workspace.removeChild(this._controlPointsController[0]); workspace.removeChild(this._controlPointsController[1]); @@ -206,20 +231,21 @@ class ControlPoint { workspace.removeChild(this._controlLines[1]); } - getControlPoint(index) { + getControlPoint(index: number): ControlPoint { return this._controls[index]; } - getOriginalEndPoint(index) { + getOriginalEndPoint(index: number) { return this._endPoint[index]; } - getOriginalCtrlPoint(index) { + getOriginalCtrlPoint(index: number): ControlPoint { return this._orignalCtrlPoint[index]; } + + static FROM = 0; + + static TO = 1; } -ControlPoint.FROM = 0; -ControlPoint.TO = 1; - export default ControlPoint; From 3bc5b3aa86715b2a3084d27bd95b50e6abf96fc4 Mon Sep 17 00:00:00 2001 From: Paulo Gustavo Veiga Date: Wed, 9 Feb 2022 21:04:12 -0800 Subject: [PATCH 09/12] Fix drag connexion error --- packages/mindplot/src/components/Designer.ts | 4 ++-- packages/mindplot/src/components/RelationshipPivot.ts | 2 +- packages/mindplot/src/components/Workspace.ts | 8 ++++---- .../mindplot/src/components/commands/DragTopicCommand.ts | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/mindplot/src/components/Designer.ts b/packages/mindplot/src/components/Designer.ts index b5a66ef6..e85f2e14 100644 --- a/packages/mindplot/src/components/Designer.ts +++ b/packages/mindplot/src/components/Designer.ts @@ -137,8 +137,8 @@ class Designer extends Events { } private _registerWheelEvents(): void { - const zoomFactor = 1.006; - document.addEventListener('wheel', (event) => { + const zoomFactor = 1.02; + document.addEventListener('wheel', (event: WheelEvent) => { if (event.deltaX > 0 || event.deltaY > 0) { this.zoomOut(zoomFactor); } else { diff --git a/packages/mindplot/src/components/RelationshipPivot.ts b/packages/mindplot/src/components/RelationshipPivot.ts index 32153ae4..ade7a4d4 100644 --- a/packages/mindplot/src/components/RelationshipPivot.ts +++ b/packages/mindplot/src/components/RelationshipPivot.ts @@ -28,7 +28,7 @@ class RelationshipPivot { private _designer: Designer; - private _mouseMoveEvent: MouseEvent; + private _mouseMoveEvent; private _onClickEvent: (event: MouseEvent) => void; diff --git a/packages/mindplot/src/components/Workspace.ts b/packages/mindplot/src/components/Workspace.ts index f49da29d..b764c1f9 100644 --- a/packages/mindplot/src/components/Workspace.ts +++ b/packages/mindplot/src/components/Workspace.ts @@ -113,11 +113,11 @@ class Workspace { } } - addEvent(type: string, listener): void { + addEvent(type: string, listener: (event: Event) => void): void { this._workspace.addEvent(type, listener); } - removeEvent(type: string, listener): void { + removeEvent(type: string, listener: (event: Event) => void): void { $assert(type, 'type can not be null'); $assert(listener, 'listener can not be null'); this._workspace.removeEvent(type, listener); @@ -193,7 +193,7 @@ class Workspace { const workspace = this._workspace; const screenManager = this._screenManager; const mWorkspace = this; - const mouseDownListener = function mouseDownListener(event) { + const mouseDownListener = function mouseDownListener(event: MouseEvent) { if (!$defined(workspace._mouseMoveListener)) { if (mWorkspace.isWorkspaceEventsEnabled()) { mWorkspace.enableWorkspaceEvents(false); @@ -202,7 +202,7 @@ class Workspace { const originalCoordOrigin = workspace.getCoordOrigin(); let wasDragged = false; - workspace._mouseMoveListener = (mouseMoveEvent) => { + workspace._mouseMoveListener = (mouseMoveEvent: MouseEvent) => { const currentMousePosition = screenManager.getWorkspaceMousePosition(mouseMoveEvent); const offsetX = currentMousePosition.x - mouseDownPosition.x; diff --git a/packages/mindplot/src/components/commands/DragTopicCommand.ts b/packages/mindplot/src/components/commands/DragTopicCommand.ts index 8bee475c..8e3766ef 100644 --- a/packages/mindplot/src/components/commands/DragTopicCommand.ts +++ b/packages/mindplot/src/components/commands/DragTopicCommand.ts @@ -76,7 +76,7 @@ class DragTopicCommand extends Command { } // Finally, connect topic ... - if (origParentTopic.getId() !== this._parentId) { + if (!$defined(origParentTopic) || origParentTopic.getId() !== this._parentId) { if ($defined(this._parentId)) { const parentTopic = commandContext.findTopics([this._parentId])[0]; commandContext.connect(topic, parentTopic); From 047195fb2f00645d9ffd2be302c1e19aeb26a291 Mon Sep 17 00:00:00 2001 From: Paulo Gustavo Veiga Date: Wed, 9 Feb 2022 22:03:03 -0800 Subject: [PATCH 10/12] Move classes to typescript --- packages/mindplot/src/components/Command.ts | 17 ++- .../mindplot/src/components/ControlPoint.ts | 4 +- ...rUndoManager.js => DesignerUndoManager.ts} | 12 +- .../commands/GenericFunctionCommand.ts | 11 -- .../components/layout/{Node.js => Node.ts} | 49 ++++---- .../{RootedTreeSet.js => RootedTreeSet.ts} | 108 ++++++++---------- 6 files changed, 103 insertions(+), 98 deletions(-) rename packages/mindplot/src/components/{DesignerUndoManager.js => DesignerUndoManager.ts} (90%) rename packages/mindplot/src/components/layout/{Node.js => Node.ts} (84%) rename packages/mindplot/src/components/layout/{RootedTreeSet.js => RootedTreeSet.ts} (85%) diff --git a/packages/mindplot/src/components/Command.ts b/packages/mindplot/src/components/Command.ts index 8efad0be..c70584ad 100644 --- a/packages/mindplot/src/components/Command.ts +++ b/packages/mindplot/src/components/Command.ts @@ -23,15 +23,18 @@ abstract class Command { static _uuid: number; + private _discardDuplicated: string; + constructor() { this._id = Command._nextUUID(); + this._discardDuplicated = undefined; } - abstract execute(commandContext:CommandContext):void; + abstract execute(commandContext: CommandContext): void; - abstract undoExecute(commandContext:CommandContext):void; + abstract undoExecute(commandContext: CommandContext): void; - getId():number { + getId(): number { return this._id; } @@ -42,6 +45,14 @@ abstract class Command { this._uuid += 1; return this._uuid; } + + get discardDuplicated(): string { + return this._discardDuplicated; + } + + set discardDuplicated(value: string) { + this._discardDuplicated = value; + } } export default Command; diff --git a/packages/mindplot/src/components/ControlPoint.ts b/packages/mindplot/src/components/ControlPoint.ts index f50cdfe8..b9aab58b 100644 --- a/packages/mindplot/src/components/ControlPoint.ts +++ b/packages/mindplot/src/components/ControlPoint.ts @@ -187,7 +187,7 @@ class ControlPoint { this._line.getLine().updateLine(point); } - private _mouseUp(event, point) { + private _mouseUp(event: MouseEvent, point: Point) { this._workspace.getScreenManager().removeEvent('mousemove', this._mouseMoveFunction); this._workspace.getScreenManager().removeEvent('mouseup', this._mouseUpFunction); @@ -196,7 +196,7 @@ class ControlPoint { this._isBinded = false; } - _mouseClick(event) { + _mouseClick(event: MouseEvent) { event.preventDefault(); event.stopPropagation(); return false; diff --git a/packages/mindplot/src/components/DesignerUndoManager.js b/packages/mindplot/src/components/DesignerUndoManager.ts similarity index 90% rename from packages/mindplot/src/components/DesignerUndoManager.js rename to packages/mindplot/src/components/DesignerUndoManager.ts index 30347b0a..bc26b3f2 100644 --- a/packages/mindplot/src/components/DesignerUndoManager.js +++ b/packages/mindplot/src/components/DesignerUndoManager.ts @@ -16,15 +16,23 @@ * limitations under the License. */ import { $assert } from '@wisemapping/core-js'; +import Command from './Command'; +import CommandContext from './CommandContext'; class DesignerUndoManager { + private _undoQueue: Command[]; + + private _redoQueue: Command[]; + + private _baseId: number; + constructor() { this._undoQueue = []; this._redoQueue = []; this._baseId = 0; } - enqueue(command) { + enqueue(command: Command) { $assert(command, 'Command can not be null'); const { length } = this._undoQueue; if (command.discardDuplicated && length > 0) { @@ -39,7 +47,7 @@ class DesignerUndoManager { this._redoQueue = []; } - execUndo(commandContext) { + execUndo(commandContext: CommandContext) { if (this._undoQueue.length > 0) { const command = this._undoQueue.pop(); this._redoQueue.push(command); diff --git a/packages/mindplot/src/components/commands/GenericFunctionCommand.ts b/packages/mindplot/src/components/commands/GenericFunctionCommand.ts index f68a5158..9054a2ce 100644 --- a/packages/mindplot/src/components/commands/GenericFunctionCommand.ts +++ b/packages/mindplot/src/components/commands/GenericFunctionCommand.ts @@ -21,8 +21,6 @@ import CommandContext from '../CommandContext'; import Topic from '../Topic'; class GenericFunctionCommand extends Command { - private _discardDuplicated: string; - private _value: string | object | boolean | number; private _topicsId: number[]; @@ -42,7 +40,6 @@ class GenericFunctionCommand extends Command { this._topicsId = topicsIds; this._commandFunc = commandFunc; this._oldValues = []; - this.discardDuplicated = undefined; } /** @@ -79,14 +76,6 @@ class GenericFunctionCommand extends Command { throw new Error('undo can not be applied.'); } } - - public get disardDuplicated(): string { - return this._discardDuplicated; - } - - public set discardDuplicated(value: string) { - this._discardDuplicated = value; - } } export default GenericFunctionCommand; diff --git a/packages/mindplot/src/components/layout/Node.js b/packages/mindplot/src/components/layout/Node.ts similarity index 84% rename from packages/mindplot/src/components/layout/Node.js rename to packages/mindplot/src/components/layout/Node.ts index b5de69fe..e6a2d323 100644 --- a/packages/mindplot/src/components/layout/Node.js +++ b/packages/mindplot/src/components/layout/Node.ts @@ -16,9 +16,23 @@ * limitations under the License. */ import { $assert, $defined } from '@wisemapping/core-js'; +import PositionType from '../PositionType'; +import SizeType from '../SizeType'; class Node { - constructor(id, size, position, sorter) { + private _id: number; + + // eslint-disable-next-line no-use-before-define + _parent: Node; + + private _sorter: any; + + private _properties; + + // eslint-disable-next-line no-use-before-define + _children: Node[]; + + constructor(id: number, size: SizeType, position, sorter) { $assert(typeof id === 'number' && Number.isFinite(id), 'id can not be null'); $assert(size, 'size can not be null'); $assert(position, 'position can not be null'); @@ -69,7 +83,7 @@ class Node { } /** */ - setOrder(order) { + setOrder(order: number) { $assert( typeof order === 'number' && Number.isFinite(order), `Order can not be null. Value:${order}`, @@ -148,7 +162,7 @@ class Node { y: oldDisplacement.y + displacement.y, }; - this._setProperty('freeDisplacement', Object.clone(newDisplacement)); + this._setProperty('freeDisplacement', { ...newDisplacement }); } /** */ @@ -163,7 +177,7 @@ class Node { } /** */ - setPosition(position) { + setPosition(position: PositionType) { $assert($defined(position), 'Position can not be null'); $assert($defined(position.x), 'x can not be null'); $assert($defined(position.y), 'y can not be null'); @@ -172,12 +186,12 @@ class Node { const currentPos = this.getPosition(); if ( currentPos == null - || Math.abs(currentPos.x - position.x) > 2 - || Math.abs(currentPos.y - position.y) > 2 + || Math.abs(currentPos.x - position.x) > 2 + || Math.abs(currentPos.y - position.y) > 2 ) this._setProperty('position', position); } - _setProperty(key, value) { + _setProperty(key: string, value) { let prop = this._properties[key]; if (!prop) { prop = { @@ -214,20 +228,13 @@ class Node { /** @return {String} returns id, order, position, size and shrink information */ toString() { return ( - `[id:${ - this.getId() - }, order:${ - this.getOrder() - }, position: {${ - this.getPosition().x - },${ - this.getPosition().y - }}, size: {${ - this.getSize().width - },${ - this.getSize().height - }}, shrink:${ - this.areChildrenShrunken() + `[id:${this.getId() + }, order:${this.getOrder() + }, position: {${this.getPosition().x + },${this.getPosition().y + }}, size: {${this.getSize().width + },${this.getSize().height + }}, shrink:${this.areChildrenShrunken() }]` ); } diff --git a/packages/mindplot/src/components/layout/RootedTreeSet.js b/packages/mindplot/src/components/layout/RootedTreeSet.ts similarity index 85% rename from packages/mindplot/src/components/layout/RootedTreeSet.js rename to packages/mindplot/src/components/layout/RootedTreeSet.ts index b56799d9..e396e2bb 100644 --- a/packages/mindplot/src/components/layout/RootedTreeSet.js +++ b/packages/mindplot/src/components/layout/RootedTreeSet.ts @@ -16,8 +16,15 @@ * limitations under the License. */ import { $assert, $defined } from '@wisemapping/core-js'; +import PositionType from '../PositionType'; +import Node from './Node'; class RootedTreeSet { + private _rootNodes: Node[]; + + protected _children: Node[]; + + constructor() { this._rootNodes = []; } @@ -26,7 +33,7 @@ class RootedTreeSet { * @param root * @throws will throw an error if root is null or undefined */ - setRoot(root) { + setRoot(root: Node) { $assert(root, 'root can not be null'); this._rootNodes.push(this._decodate(root)); } @@ -36,8 +43,7 @@ class RootedTreeSet { return this._rootNodes; } - _decodate(node) { - // eslint-disable-next-line no-param-reassign + _decodate(node: Node) { node._children = []; return node; } @@ -48,7 +54,7 @@ class RootedTreeSet { * @throws will throw an error if node with id already exists * @throws will throw an error if node has been added already */ - add(node) { + add(node: Node) { $assert(node, 'node can not be null'); $assert( !this.find(node.getId(), false), @@ -62,7 +68,7 @@ class RootedTreeSet { * @param nodeId * @throws will throw an error if nodeId is null or undefined */ - remove(nodeId) { + remove(nodeId: number) { $assert($defined(nodeId), 'nodeId can not be null'); const node = this.find(nodeId); this._rootNodes = this._rootNodes.filter((n) => n !== node); @@ -75,7 +81,7 @@ class RootedTreeSet { * @throws will throw an error if childId is null or undefined * @throws will throw an error if node with id childId is already a child of parent */ - connect(parentId, childId) { + connect(parentId: number, childId: number) { $assert($defined(parentId), 'parent can not be null'); $assert($defined(childId), 'child can not be null'); @@ -96,7 +102,7 @@ class RootedTreeSet { * @throws will throw an error if nodeId is null or undefined * @throws will throw an error if node is not connected */ - disconnect(nodeId) { + disconnect(nodeId: number) { $assert($defined(nodeId), 'nodeId can not be null'); const node = this.find(nodeId); $assert(node._parent, 'Node is not connected'); @@ -113,7 +119,7 @@ class RootedTreeSet { * @throws will throw an error if node cannot be found * @return node */ - find(id, validate = true) { + find(id: number, validate = true): Node { $assert($defined(id), 'id can not be null'); const graphs = this._rootNodes; @@ -132,7 +138,7 @@ class RootedTreeSet { return result; } - _find(id, parent) { + private _find(id: number, parent: Node): Node { if (parent.getId() === id) { return parent; } @@ -153,7 +159,7 @@ class RootedTreeSet { * @throws will throw an error if nodeId is null or undefined * @return children */ - getChildren(node) { + getChildren(node: Node): Node[] { $assert(node, 'node cannot be null'); return node._children; } @@ -163,7 +169,7 @@ class RootedTreeSet { * @throws will throw an error if node is null or undefined * @return root node or the provided node, if it has no parent */ - getRootNode(node) { + getRootNode(node: Node) { $assert(node, 'node cannot be null'); const parent = this.getParent(node); if ($defined(parent)) { @@ -177,12 +183,12 @@ class RootedTreeSet { * @param node * @throws will throw an error if node is null or undefined * @return {Array} ancestors */ - getAncestors(node) { + getAncestors(node: Node): Node[] { $assert(node, 'node cannot be null'); return this._getAncestors(this.getParent(node), []); } - _getAncestors(node, ancestors) { + _getAncestors(node: Node, ancestors: Node[]) { const result = ancestors; if (node) { result.push(node); @@ -196,7 +202,7 @@ class RootedTreeSet { * @throws will throw an error if node is null or undefined * @return {Array} siblings */ - getSiblings(node) { + getSiblings(node: Node): Node[] { $assert(node, 'node cannot be null'); if (!$defined(node._parent)) { return []; @@ -210,12 +216,12 @@ class RootedTreeSet { * @throws will throw an error if node is null or undefined * @return {Boolean} whether the node has a single path to a single leaf (no branching) */ - hasSinglePathToSingleLeaf(node) { + hasSinglePathToSingleLeaf(node: Node): boolean { $assert(node, 'node cannot be null'); return this._hasSinglePathToSingleLeaf(node); } - _hasSinglePathToSingleLeaf(node) { + private _hasSinglePathToSingleLeaf(node: Node): boolean { const children = this.getChildren(node); if (children.length === 1) { @@ -228,7 +234,7 @@ class RootedTreeSet { /** * @param node * @return {Boolean} whether the node is the start of a subbranch */ - isStartOfSubBranch(node) { + isStartOfSubBranch(node: Node): boolean { return this.getSiblings(node).length > 0 && this.getChildren(node).length === 1; } @@ -237,7 +243,7 @@ class RootedTreeSet { * @throws will throw an error if node is null or undefined * @return {Boolean} whether the node is a leaf */ - isLeaf(node) { + isLeaf(node: Node): boolean { $assert(node, 'node cannot be null'); return this.getChildren(node).length === 0; } @@ -247,7 +253,7 @@ class RootedTreeSet { * @throws will throw an error if node is null or undefined * @return parent */ - getParent(node) { + getParent(node: Node): Node { $assert(node, 'node cannot be null'); return node._parent; } @@ -265,7 +271,7 @@ class RootedTreeSet { return result; } - _dump(node, indent) { + _dump(node: Node, indent: string) { let result = `${indent + node}\n`; const children = this.getChildren(node); for (let i = 0; i < children.length; i++) { @@ -287,7 +293,7 @@ class RootedTreeSet { } } - _plot(canvas, node, root) { + _plot(canvas, node: Node, root?) { const children = this.getChildren(node); const cx = node.getPosition().x + canvas.width / 2 - node.getSize().width / 2; const cy = node.getPosition().y + canvas.height / 2 - node.getSize().height / 2; @@ -316,43 +322,27 @@ class RootedTreeSet { const rectSize = { width: rect.attr('width'), height: rect.attr('height') }; rect.click(() => { console.log( - `[id:${ - node.getId() - }, order:${ - node.getOrder() - }, position:(${ - rectPosition.x - }, ${ - rectPosition.y - }), size:${ - rectSize.width - },${ - rectSize.height - }, freeDisplacement:(${ - node.getFreeDisplacement().x - },${ - node.getFreeDisplacement().y + `[id:${node.getId() + }, order:${node.getOrder() + }, position:(${rectPosition.x + }, ${rectPosition.y + }), size:${rectSize.width + },${rectSize.height + }, freeDisplacement:(${node.getFreeDisplacement().x + },${node.getFreeDisplacement().y })]`, ); }); text.click(() => { console.log( - `[id:${ - node.getId() - }, order:${ - node.getOrder() - }, position:(${ - rectPosition.x - },${ - rectPosition.y - }), size:${ - rectSize.width - }x${ - rectSize.height - }, freeDisplacement:(${ - node.getFreeDisplacement().x - },${ - node.getFreeDisplacement().y + `[id:${node.getId() + }, order:${node.getOrder() + }, position:(${rectPosition.x + },${rectPosition.y + }), size:${rectSize.width + }x${rectSize.height + }, freeDisplacement:(${node.getFreeDisplacement().x + },${node.getFreeDisplacement().y })]`, ); }); @@ -367,7 +357,7 @@ class RootedTreeSet { * @param node * @param position */ - updateBranchPosition(node, position) { + updateBranchPosition(node: Node, position: PositionType): void { const oldPos = node.getPosition(); node.setPosition(position); @@ -386,7 +376,7 @@ class RootedTreeSet { * @param xOffset * @param yOffset */ - shiftBranchPosition(node, xOffset, yOffset) { + shiftBranchPosition(node: Node, xOffset: number, yOffset: number): void { const position = node.getPosition(); node.setPosition({ x: position.x + xOffset, y: position.y + yOffset }); @@ -402,7 +392,7 @@ class RootedTreeSet { * @param yOffset * @return siblings in the offset (vertical) direction, i.e. with lower or higher order */ - getSiblingsInVerticalDirection(node, yOffset) { + getSiblingsInVerticalDirection(node: Node, yOffset: number): Node[] { // siblings with lower or higher order // (depending on the direction of the offset and on the same side as their parent) const parent = this.getParent(node); @@ -429,7 +419,7 @@ class RootedTreeSet { * @return branches of the root node on the same side as the given node's, in the given * vertical direction */ - getBranchesInVerticalDirection(node, yOffset) { + getBranchesInVerticalDirection(node: Node, yOffset: number): Node[] { // direct descendants of the root that do not contain the node and are on the same side // and on the direction of the offset const rootNode = this.getRootNode(node); @@ -437,7 +427,7 @@ class RootedTreeSet { .filter(((child) => this._find(node.getId(), child))); const branch = branches[0]; - const rootDescendants = this.getSiblings(branch).filter((sibling) => { + const result = this.getSiblings(branch).filter((sibling) => { const sameSide = node.getPosition().x > rootNode.getPosition().x ? sibling.getPosition().x > rootNode.getPosition().x : sibling.getPosition().x < rootNode.getPosition().x; @@ -447,7 +437,7 @@ class RootedTreeSet { return sameSide && sameDirection; }, this); - return rootDescendants; + return result; } } From 3725188f8f2819742e2fe95c6b9e8ebbed334646 Mon Sep 17 00:00:00 2001 From: Paulo Gustavo Veiga Date: Wed, 9 Feb 2022 22:07:46 -0800 Subject: [PATCH 11/12] Remove export on CVS --- .../action-dispatcher/export-dialog/index.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/webapp/src/components/maps-page/action-dispatcher/export-dialog/index.tsx b/packages/webapp/src/components/maps-page/action-dispatcher/export-dialog/index.tsx index 488f8690..57548839 100644 --- a/packages/webapp/src/components/maps-page/action-dispatcher/export-dialog/index.tsx +++ b/packages/webapp/src/components/maps-page/action-dispatcher/export-dialog/index.tsx @@ -210,15 +210,15 @@ const ExportDialog = ({ value={exportFormat} className={classes.select} > - - Microsoft Excel (XLS) - Plain Text File (TXT) Markdown (MD) + {/* + Microsoft Excel (XLS) + */} )} @@ -248,9 +248,9 @@ const ExportDialog = ({ Freemind 1.0.1 (MM) - + {/* MindManager (MMAP) - + */} )} From d4b37f4139ea04c327148fe2e3b4b08ddafaa260 Mon Sep 17 00:00:00 2001 From: Matias Arriola Date: Fri, 11 Feb 2022 21:23:06 +0000 Subject: [PATCH 12/12] Add labels support. --- .../delete-multiselect-dialog/index.tsx | 9 +- .../maps-page/action-dispatcher/index.tsx | 7 +- .../action-dispatcher/label-dialog/index.tsx | 45 +++--- .../webapp/src/components/maps-page/index.tsx | 13 +- .../maps-list/add-label-button/index.tsx | 72 --------- .../maps-list/add-label-form/index.tsx | 94 +++++++++++ .../maps-list/add-label-form/styled.ts | 25 +++ .../components/maps-page/maps-list/index.tsx | 116 ++++++++------ .../maps-list/label-delete-confirm/index.tsx | 45 ++++++ .../maps-list/label-selector/index.tsx | 146 +++++------------- .../maps-list/label-selector/styled.ts | 39 +---- .../maps-page/maps-list/label/index.tsx | 43 ++++-- .../maps-page/maps-list/label/styled.ts | 32 ++-- .../components/maps-page/maps-list/styled.ts | 7 + 14 files changed, 372 insertions(+), 321 deletions(-) delete mode 100644 packages/webapp/src/components/maps-page/maps-list/add-label-button/index.tsx create mode 100644 packages/webapp/src/components/maps-page/maps-list/add-label-form/index.tsx create mode 100644 packages/webapp/src/components/maps-page/maps-list/add-label-form/styled.ts create mode 100644 packages/webapp/src/components/maps-page/maps-list/label-delete-confirm/index.tsx diff --git a/packages/webapp/src/components/maps-page/action-dispatcher/delete-multiselect-dialog/index.tsx b/packages/webapp/src/components/maps-page/action-dispatcher/delete-multiselect-dialog/index.tsx index 6e1cb9cd..e2dd6493 100644 --- a/packages/webapp/src/components/maps-page/action-dispatcher/delete-multiselect-dialog/index.tsx +++ b/packages/webapp/src/components/maps-page/action-dispatcher/delete-multiselect-dialog/index.tsx @@ -4,20 +4,15 @@ import { useMutation, useQueryClient } from 'react-query'; import { useSelector } from 'react-redux'; import Client from '../../../../classes/client'; import { activeInstance } from '../../../../redux/clientSlice'; -import { handleOnMutationSuccess } from '..'; +import { handleOnMutationSuccess, MultiDialogProps } from '..'; import BaseDialog from '../base-dialog'; import Alert from '@mui/material/Alert'; import AlertTitle from '@mui/material/AlertTitle'; -export type DeleteMultiselectDialogProps = { - mapsId: number[]; - onClose: () => void; -}; - const DeleteMultiselectDialog = ({ onClose, mapsId, -}: DeleteMultiselectDialogProps): React.ReactElement => { +}: MultiDialogProps): React.ReactElement => { const intl = useIntl(); const client: Client = useSelector(activeInstance); const queryClient = useQueryClient(); diff --git a/packages/webapp/src/components/maps-page/action-dispatcher/index.tsx b/packages/webapp/src/components/maps-page/action-dispatcher/index.tsx index c8359ad5..02a5960e 100644 --- a/packages/webapp/src/components/maps-page/action-dispatcher/index.tsx +++ b/packages/webapp/src/components/maps-page/action-dispatcher/index.tsx @@ -62,7 +62,7 @@ const ActionDispatcher = ({ mapsId, action, onClose, fromEditor }: ActionDialogP )} {action === 'share' && } - {action === 'label' && } + {action === 'label' && } ); }; @@ -81,4 +81,9 @@ export type SimpleDialogProps = { onClose: () => void; }; +export type MultiDialogProps = { + mapsId: number[]; + onClose: () => void; +}; + export default ActionDispatcher; diff --git a/packages/webapp/src/components/maps-page/action-dispatcher/label-dialog/index.tsx b/packages/webapp/src/components/maps-page/action-dispatcher/label-dialog/index.tsx index 37fb8759..99843fbc 100644 --- a/packages/webapp/src/components/maps-page/action-dispatcher/label-dialog/index.tsx +++ b/packages/webapp/src/components/maps-page/action-dispatcher/label-dialog/index.tsx @@ -1,16 +1,19 @@ 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 { FormattedMessage, useIntl } from 'react-intl'; +import { useMutation, useQuery, useQueryClient } from 'react-query'; +import Typography from '@mui/material/Typography'; + +import { useStyles } from './style'; +import { MultiDialogProps } from '..'; +import BaseDialog from '../base-dialog'; +import Client, { ErrorInfo, Label, MapInfo } from '../../../../classes/client'; +import { LabelSelector } from '../../maps-list/label-selector'; import { activeInstance } from '../../../../redux/clientSlice'; +import { ChangeLabelMutationFunctionParam, getChangeLabelMutationFunction } from '../../maps-list'; -const LabelDialog = ({ mapId, onClose }: SimpleDialogProps): React.ReactElement => { +const LabelDialog = ({ mapsId, onClose }: MultiDialogProps): React.ReactElement => { const intl = useIntl(); const classes = useStyles(); const client: Client = useSelector(activeInstance); @@ -21,19 +24,10 @@ const LabelDialog = ({ mapId, onClose }: SimpleDialogProps): React.ReactElement return client.fetchAllMaps(); }); - const map = data.find(m => m.id === mapId); + const maps = data.filter(m => mapsId.includes(m.id)); - const changeLabelMutation = useMutation( - 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); - } - }, + const changeLabelMutation = useMutation( + getChangeLabelMutationFunction(client), { onSuccess: () => { queryClient.invalidateQueries('maps'); @@ -47,6 +41,7 @@ const LabelDialog = ({ mapId, onClose }: SimpleDialogProps): React.ReactElement const handleChangesInLabels = (label: Label, checked: boolean) => { changeLabelMutation.mutate({ + maps, label, checked }); @@ -63,11 +58,17 @@ const LabelDialog = ({ mapId, onClose }: SimpleDialogProps): React.ReactElement description={intl.formatMessage({ id: 'label.description', defaultMessage: - 'Use labels to organize your maps', + 'Use labels to organize your maps.', })} PaperProps={{ classes: { root: classes.paper } }} > - + <> + + + { maps.map(m => m.title).join(', ') } + + + ); }; diff --git a/packages/webapp/src/components/maps-page/index.tsx b/packages/webapp/src/components/maps-page/index.tsx index 9960906f..26d386cd 100644 --- a/packages/webapp/src/components/maps-page/index.tsx +++ b/packages/webapp/src/components/maps-page/index.tsx @@ -40,6 +40,7 @@ import ListItemSecondaryAction from '@mui/material/ListItemSecondaryAction'; import logoIcon from './logo-small.svg'; import poweredByIcon from './pwrdby-white.svg'; +import LabelDeleteConfirm from './maps-list/label-delete-confirm'; export type Filter = GenericFilter | LabelFilter; @@ -64,7 +65,7 @@ const MapsPage = (): ReactElement => { const client: Client = useSelector(activeInstance); const queryClient = useQueryClient(); const [activeDialog, setActiveDialog] = React.useState(undefined); - + const [labelToDelete, setLabelToDelete] = React.useState(null); // Reload based on user preference ... const userLocale = AppI18n.getUserLocale(); @@ -239,7 +240,7 @@ const MapsPage = (): ReactElement => { filter={buttonInfo.filter} active={filter} onClick={handleMenuClick} - onDelete={handleLabelDelete} + onDelete={setLabelToDelete} key={`${buttonInfo.filter.type}:${buttonInfo.label}`} /> ); @@ -260,6 +261,14 @@ const MapsPage = (): ReactElement => { + { labelToDelete && setLabelToDelete(null)} + onConfirm={() => { + handleLabelDelete(labelToDelete); + setLabelToDelete(null); + }} + label={labels.find(l => l.id === labelToDelete)} + /> } ); }; diff --git a/packages/webapp/src/components/maps-page/maps-list/add-label-button/index.tsx b/packages/webapp/src/components/maps-page/maps-list/add-label-button/index.tsx deleted file mode 100644 index 0c37dc7f..00000000 --- a/packages/webapp/src/components/maps-page/maps-list/add-label-button/index.tsx +++ /dev/null @@ -1,72 +0,0 @@ -import React from 'react'; -import Popover from '@mui/material/Popover'; -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, MapInfo } from '../../../../classes/client'; -import { LabelSelector } from '../label-selector'; - -type AddLabelButtonTypes = { - maps: MapInfo[]; - onChange: (label: Label, checked: boolean) => void; -}; - -export function AddLabelButton({ onChange, maps }: AddLabelButtonTypes): React.ReactElement { - const intl = useIntl(); - - const [anchorEl, setAnchorEl] = React.useState(null); - - const handleClick = (event: React.MouseEvent) => { - setAnchorEl(event.currentTarget); - }; - - const handleClose = () => { - setAnchorEl(null); - }; - - const open = Boolean(anchorEl); - const id = open ? 'add-label-popover' : undefined; - - return ( - <> - - - - - - - - - ); -} diff --git a/packages/webapp/src/components/maps-page/maps-list/add-label-form/index.tsx b/packages/webapp/src/components/maps-page/maps-list/add-label-form/index.tsx new file mode 100644 index 00000000..4609a2e5 --- /dev/null +++ b/packages/webapp/src/components/maps-page/maps-list/add-label-form/index.tsx @@ -0,0 +1,94 @@ +import React from 'react'; +import { useIntl } from 'react-intl'; +import AddIcon from '@mui/icons-material/Add'; +import TextField from '@mui/material/TextField'; + +import { Label } from '../../../../classes/client'; +import { StyledButton, NewLabelContainer, NewLabelColor, CreateLabel } from './styled'; +import { Tooltip } from '@mui/material'; + +const labelColors = [ + '#00b327', + '#0565ff', + '#2d2dd6', + '#6a00ba', + '#ad1599', + '#ff1e35', + '#ff6600', + '#ffff47', +]; + +type AddLabelFormProps = { + onAdd: (newLabel: Label) => void; +}; + +export default function AddLabelForm({ onAdd }: AddLabelFormProps): React.ReactElement { + const intl = useIntl(); + 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 = () => { + onAdd({ + title: newLabelTitle, + color: newLabelColor, + id: 0, + }); + setNewLabelTitle(''); + setNextLabelColorIndex(); + }; + + return ( + + + + { + e.stopPropagation(); + setNextLabelColorIndex(); + }} + /> + + setNewLabelTitle(e.target.value)} + onKeyPress={(e) => { + if (e.key === 'Enter') { + handleSubmitNew(); + } + }} + value={newLabelTitle} + /> + handleSubmitNew()} + disabled={!newLabelTitle.length} + aria-label={intl.formatMessage({ + id: 'label.add-button', + defaultMessage: 'Add label', + })} + > + + + + + ); +} diff --git a/packages/webapp/src/components/maps-page/maps-list/add-label-form/styled.ts b/packages/webapp/src/components/maps-page/maps-list/add-label-form/styled.ts new file mode 100644 index 00000000..ef1da495 --- /dev/null +++ b/packages/webapp/src/components/maps-page/maps-list/add-label-form/styled.ts @@ -0,0 +1,25 @@ +import styled from 'styled-components'; +import IconButton from '@mui/material/IconButton'; +import LabelTwoTone from '@mui/icons-material/LabelTwoTone'; + +export const StyledButton = styled(IconButton)` + margin: 4px; +`; + +export const NewLabelContainer = styled.div` + display: flex; + flex-direction: row; + align-items: center; +`; + +export const NewLabelColor = styled(LabelTwoTone)` + margin-right: 12px; + cursor: pointer; +`; + +export const CreateLabel = styled.div` + padding-top: 10px; + display: flex; + flex-direction: column; + justify-content: flex-end; +`; diff --git a/packages/webapp/src/components/maps-page/maps-list/index.tsx b/packages/webapp/src/components/maps-page/maps-list/index.tsx index 32dfb83d..8b558732 100644 --- a/packages/webapp/src/components/maps-page/maps-list/index.tsx +++ b/packages/webapp/src/components/maps-page/maps-list/index.tsx @@ -33,13 +33,13 @@ import MoreHorizIcon from '@mui/icons-material/MoreHoriz'; import StarRateRoundedIcon from '@mui/icons-material/StarRateRounded'; import SearchIcon from '@mui/icons-material/Search'; -import { AddLabelButton } from './add-label-button'; import relativeTime from 'dayjs/plugin/relativeTime'; import { LabelsCell } from './labels-cell'; import LocalizedFormat from 'dayjs/plugin/localizedFormat'; import AppI18n from '../../../classes/app-i18n'; +import LabelTwoTone from '@mui/icons-material/LabelTwoTone'; -dayjs.extend(LocalizedFormat) +dayjs.extend(LocalizedFormat); dayjs.extend(relativeTime); function descendingComparator(a: T, b: T, orderBy: keyof T) { @@ -59,9 +59,9 @@ function getComparator( order: Order, orderBy: Key ): ( - a: { [key in Key]: number | string | boolean | Label[] | undefined }, - b: { [key in Key]: number | string | Label[] | boolean } - ) => number { + 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) : (a, b) => -descendingComparator(a, b, orderBy); @@ -236,6 +236,24 @@ const mapsFilter = (filter: Filter, search: string): ((mapInfo: MapInfo) => bool }; }; +export type ChangeLabelMutationFunctionParam = { maps: MapInfo[]; label: Label; checked: boolean }; + +export const getChangeLabelMutationFunction = + (client: Client) => + async ({ maps, label, checked }: ChangeLabelMutationFunctionParam): Promise => { + if (!label.id) { + label.id = await client.createLabel(label.title, label.color); + } + if (checked) { + const toAdd = maps.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 = maps.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(); + }; + export const MapsList = (props: MapsListProps): React.ReactElement => { const classes = useStyles(); const [order, setOrder] = React.useState('desc'); @@ -384,7 +402,19 @@ export const MapsList = (props: MapsListProps): React.ReactElement => { }); }; - const removeLabelMultation = useMutation( + const handleAddLabelClick = () => { + setActiveDialog({ + actionType: 'label', + mapsId: selected, + }); + }; + + const removeLabelMultation = useMutation< + void, + ErrorInfo, + { mapId: number; labelId: number }, + number + >( ({ mapId, labelId }) => { return client.deleteLabelFromMap(labelId, mapId); }, @@ -402,40 +432,6 @@ export const MapsList = (props: MapsListProps): React.ReactElement => { removeLabelMultation.mutate({ mapId, labelId }); }; - const changeLabelMutation = useMutation( - 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 (
@@ -470,10 +466,31 @@ export const MapsList = (props: MapsListProps): React.ReactElement => { )} - {selected.length > 0 && isSelected(m.id))} - />} + {selected.length > 0 && ( + + + + )}
@@ -614,10 +631,13 @@ export const MapsList = (props: MapsListProps): React.ReactElement => { - - { - handleRemoveLabel(row.id, lbl.id); - }} /> + + { + handleRemoveLabel(row.id, lbl.id); + }} + /> diff --git a/packages/webapp/src/components/maps-page/maps-list/label-delete-confirm/index.tsx b/packages/webapp/src/components/maps-page/maps-list/label-delete-confirm/index.tsx new file mode 100644 index 00000000..4817f864 --- /dev/null +++ b/packages/webapp/src/components/maps-page/maps-list/label-delete-confirm/index.tsx @@ -0,0 +1,45 @@ +import React from 'react'; +import { FormattedMessage, useIntl } from 'react-intl'; +import Alert from '@mui/material/Alert'; +import AlertTitle from '@mui/material/AlertTitle'; +import Typography from '@mui/material/Typography'; + +import BaseDialog from '../../action-dispatcher/base-dialog'; +import { Label } from '../../../../classes/client'; + +export type LabelDeleteConfirmType = { + label: Label; + onClose: () => void; + onConfirm: () => void; +}; + +const LabelDeleteConfirm = ({ label, onClose, onConfirm }: LabelDeleteConfirmType): React.ReactElement => { + const intl = useIntl(); + + return ( +
+ + + {intl.formatMessage({ id: 'label.delete-title', defaultMessage: 'Confirm label deletion' })} + + {label.title} + + + + +
+ ); +}; + +export default LabelDeleteConfirm; diff --git a/packages/webapp/src/components/maps-page/maps-list/label-selector/index.tsx b/packages/webapp/src/components/maps-page/maps-list/label-selector/index.tsx index 9e2b74c7..02235adf 100644 --- a/packages/webapp/src/components/maps-page/maps-list/label-selector/index.tsx +++ b/packages/webapp/src/components/maps-page/maps-list/label-selector/index.tsx @@ -1,125 +1,55 @@ import React from 'react'; import FormGroup from '@mui/material/FormGroup'; import FormControlLabel from '@mui/material/FormControlLabel'; -import Divider from '@mui/material/Divider'; -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 LabelComponent from '../label'; 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, NewLabelContainer, NewLabelColor, CreateLabel } from './styled'; -import { TextField } from '@mui/material'; -import { FormattedMessage, useIntl } from 'react-intl'; -import Typography from '@mui/material/Typography'; - -const labelColors = [ - '#00b327', - '#0565ff', - '#2d2dd6', - '#6a00ba', - '#ad1599', - '#ff1e35', - '#ff6600', - '#ffff47', -]; +import AddLabelForm from '../add-label-form'; +import { LabelListContainer } from './styled'; export type LabelSelectorProps = { - maps: MapInfo[]; - onChange: (label: Label, checked: boolean) => void; + 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 client: Client = useSelector(activeInstance); + const { data: labels = [] } = useQuery('labels', async () => + client.fetchLabels() + ); - const { data: labels = [] } = useQuery('labels', async () => client.fetchLabels()); + const checkedLabelIds = labels + .map((l) => l.id) + .filter((labelId) => maps.every((m) => m.labels.find((l) => l.id === labelId))); - const checkedLabelIds = labels.map(l => l.id).filter(labelId => maps.every(m => m.labels.find(l => l.id === labelId))); - - 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 ( - - - {labels.map(({ id, title, color}) => ( - { - onChange({ id, title, color }, e.target.checked); - }} - name={title} - color="primary" - /> - } - label={} - /> - ))} - - - - - - - { - e.stopPropagation(); - setNextLabelColorIndex(); - }} - /> - setNewLabelTitle(e.target.value)} - onKeyPress={(e) => { - if (e.key === 'Enter') { - handleSubmitNew(); - } - }} - value={newLabelTitle} /> - - } - onClick={() => handleSubmitNew()} - disabled={!newLabelTitle.length} - > - - - - - - - ); + return ( + + + onChange(label, true)} /> + + + {labels.map(({ id, title, color }) => ( + { + onChange({ id, title, color }, e.target.checked); + }} + name={title} + color="primary" + /> + } + label={} + /> + ))} + + + ); } diff --git a/packages/webapp/src/components/maps-page/maps-list/label-selector/styled.ts b/packages/webapp/src/components/maps-page/maps-list/label-selector/styled.ts index 289353b3..b2e697c5 100644 --- a/packages/webapp/src/components/maps-page/maps-list/label-selector/styled.ts +++ b/packages/webapp/src/components/maps-page/maps-list/label-selector/styled.ts @@ -1,33 +1,8 @@ -import styled, { css } from 'styled-components'; -import Button from '@mui/material/Button'; +import FormGroup from '@mui/material/FormGroup'; +import styled from 'styled-components'; -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; -`; +export const LabelListContainer = styled(FormGroup)` + max-height: 400px; + flex-wrap: nowrap; + overflow-y: scroll; +`; \ No newline at end of file diff --git a/packages/webapp/src/components/maps-page/maps-list/label/index.tsx b/packages/webapp/src/components/maps-page/maps-list/label/index.tsx index 5ca36096..151ddf5f 100644 --- a/packages/webapp/src/components/maps-page/maps-list/label/index.tsx +++ b/packages/webapp/src/components/maps-page/maps-list/label/index.tsx @@ -1,13 +1,38 @@ import React from 'react'; -import { Color, StyledLabel, Name } from './styled'; +import { LabelContainer, LabelText } from './styled'; -type Props = { name: string, color: string }; +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'; -export function Label({ name, color }: Props): React.ReactElement { - return ( - - - {name} - - ); + +type LabelSize = 'small' | 'big'; +type LabelComponentProps = { label: Label; onDelete?: (label: Label) => void; size?: LabelSize }; + +export default function LabelComponent({ label, onDelete, size = 'small' }: LabelComponentProps): React.ReactElement { + const iconSize = size === 'small' ? { + height: '0.6em', width: '0.6em' + } : { height: '0.9em', width: '0.9em' }; + + return ( + + + {label.title} + {onDelete && ( + { + e.stopPropagation(); + onDelete(label); + }} + > + + + )} + + ); } diff --git a/packages/webapp/src/components/maps-page/maps-list/label/styled.ts b/packages/webapp/src/components/maps-page/maps-list/label/styled.ts index 4c48bfd9..eab7c0cb 100644 --- a/packages/webapp/src/components/maps-page/maps-list/label/styled.ts +++ b/packages/webapp/src/components/maps-page/maps-list/label/styled.ts @@ -1,23 +1,15 @@ -import styled, { css } from 'styled-components'; +import styled 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; +export const LabelContainer = styled.div` + display: inline-flex; flex-direction: row; + margin: 4px; + padding: 4px; + align-items: center; + font-size: smaller; `; - -export const Name = styled.div` - flex: 1; -`; + +export const LabelText = styled.span` + margin-left: 4px; + margin-right: 2px; +`; \ No newline at end of file diff --git a/packages/webapp/src/components/maps-page/maps-list/styled.ts b/packages/webapp/src/components/maps-page/maps-list/styled.ts index d0a4335e..55e32fee 100644 --- a/packages/webapp/src/components/maps-page/maps-list/styled.ts +++ b/packages/webapp/src/components/maps-page/maps-list/styled.ts @@ -33,6 +33,13 @@ export const useStyles = makeStyles((theme: Theme) => bodyCell: { border: '0px', }, + labelsCell: { + maxWidth: '300px', + overflow: 'hidden', + textAlign: 'right', + whiteSpace: 'nowrap', + textOverflow: 'ellipsis' + }, visuallyHidden: { border: 0, clip: 'rect(0 0 0 0)',