Revize f41a4cd3
Přidáno uživatelem Václav Honzík před téměř 3 roky(ů)
frontend/.vscode/launch.json | ||
---|---|---|
1 |
{ |
|
2 |
// Use IntelliSense to learn about possible attributes. |
|
3 |
// Hover to view descriptions of existing attributes. |
|
4 |
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 |
|
5 |
"version": "0.2.0", |
|
6 |
"configurations": [ |
|
7 |
{ |
|
8 |
"type": "pwa-chrome", |
|
9 |
"request": "launch", |
|
10 |
"name": "Launch Chrome against localhost", |
|
11 |
"url": "http://localhost:3000", |
|
12 |
"webRoot": "${workspaceFolder}" |
|
13 |
} |
|
14 |
] |
|
15 |
} |
frontend/src/features/Reusables/ButtonOpenableDialog.tsx | ||
---|---|---|
12 | 12 |
maxWidth?: 'xs' | 'sm' | 'md' | 'lg' // the max width of the dialog |
13 | 13 |
open: boolean, |
14 | 14 |
setOpen: (open: boolean) => void |
15 |
size?: 'small' | 'medium' | 'large' |
|
15 | 16 |
} |
16 | 17 |
|
17 | 18 |
|
... | ... | |
25 | 26 |
children, |
26 | 27 |
maxWidth, |
27 | 28 |
open, |
28 |
setOpen |
|
29 |
setOpen, |
|
30 |
size |
|
29 | 31 |
}) => { |
30 | 32 |
|
31 | 33 |
// Change maxWidth to large if its undefined |
... | ... | |
52 | 54 |
onClick={onOpen} |
53 | 55 |
color={buttonColor} |
54 | 56 |
variant={buttonVariant} |
57 |
size={size ?? 'medium'} |
|
55 | 58 |
> |
56 | 59 |
{buttonText} |
57 | 60 |
</Button> |
frontend/src/features/TrackingTool/DraggableList/DraggableList.tsx | ||
---|---|---|
12 | 12 |
onDragEnd: OnDragEndResponder |
13 | 13 |
} |
14 | 14 |
|
15 |
const DraggableList = memo(({ items, onDragEnd }: DraggableListProps) => { |
|
16 |
window.addEventListener('error', (e) => { |
|
17 |
if ( |
|
18 |
e.message === |
|
19 |
'ResizeObserver loop completed with undelivered notifications.' || |
|
20 |
e.message === 'ResizeObserver loop limit exceeded' |
|
21 |
) { |
|
22 |
e.stopImmediatePropagation() |
|
23 |
} |
|
24 |
}) |
|
15 |
window.addEventListener('error', (e) => { |
|
16 |
if ( |
|
17 |
e.message === |
|
18 |
'ResizeObserver loop completed with undelivered notifications.' || |
|
19 |
e.message === 'ResizeObserver loop limit exceeded' |
|
20 |
) { |
|
21 |
e.stopImmediatePropagation() |
|
22 |
} |
|
23 |
}) |
|
25 | 24 |
|
25 |
const DraggableList = memo(({ items, onDragEnd }: DraggableListProps) => { |
|
26 | 26 |
return ( |
27 | 27 |
<DragDropContext onDragEnd={onDragEnd}> |
28 | 28 |
<Droppable droppableId="droppable-list"> |
frontend/src/features/TrackingTool/DraggableList/DraggableListItem.tsx | ||
---|---|---|
1 | 1 |
import { |
2 |
Avatar, |
|
3 | 2 |
Checkbox, |
3 |
FormControlLabel, |
|
4 |
IconButton, |
|
4 | 5 |
ListItem, |
5 | 6 |
ListItemAvatar, |
6 | 7 |
ListItemText, |
7 | 8 |
} from '@mui/material' |
8 | 9 |
import { Draggable } from 'react-beautiful-dnd' |
9 | 10 |
import { MapPoint, PathVariant } from '../Map/pathUtils' |
10 |
import LocationOnIcon from '@mui/icons-material/LocationOn' |
|
11 | 11 |
import { CatalogItemDto } from '../../../swagger/data-contracts' |
12 | 12 |
import { useDispatch } from 'react-redux' |
13 |
import { updateMapMarker, updateMapMarkerWithId } from '../trackingToolSlice'
|
|
13 |
import { updateMapMarkerWithId } from '../trackingToolSlice' |
|
14 | 14 |
import { useMemo } from 'react' |
15 |
import DragHandleIcon from '@mui/icons-material/DragHandle' |
|
16 |
import VisibilityIcon from '@mui/icons-material/Visibility' |
|
17 |
import VisibilityOffIcon from '@mui/icons-material/VisibilityOff' |
|
18 |
import AddRoadIcon from '@mui/icons-material/AddRoad' |
|
19 |
import RemoveRoadIcon from '@mui/icons-material/RemoveRoad' |
|
15 | 20 |
|
16 | 21 |
export type DraggableListItemProps = { |
17 | 22 |
list: PathVariant |
... | ... | |
20 | 25 |
|
21 | 26 |
const getFormattedLocationOrEmpty = (catalogItem: CatalogItemDto) => { |
22 | 27 |
if (!catalogItem || !catalogItem.latitude || !catalogItem.longitude) { |
23 |
return 'Location Unknown'
|
|
28 |
return 'Location unavailable'
|
|
24 | 29 |
} |
25 | 30 |
|
26 |
return `${catalogItem.latitude}°, ${catalogItem.longitude}°` |
|
31 |
return `${catalogItem.latitude.toFixed( |
|
32 |
3 |
|
33 |
)}°, ${catalogItem.longitude.toFixed(3)}°` |
|
27 | 34 |
} |
28 | 35 |
|
29 | 36 |
const DraggableListItem = ({ list, idx }: DraggableListItemProps) => { |
... | ... | |
32 | 39 |
|
33 | 40 |
// useMemo to prevent unnecessary re-renders which will make the list jumpy |
34 | 41 |
return useMemo(() => { |
42 |
const toggleAddToPath = () => { |
|
43 |
dispatch( |
|
44 |
updateMapMarkerWithId({ |
|
45 |
item: { |
|
46 |
...item, |
|
47 |
addToPath: !item?.addToPath, |
|
48 |
} as MapPoint, |
|
49 |
id: item.id, |
|
50 |
}) |
|
51 |
) |
|
52 |
} |
|
53 |
|
|
35 | 54 |
const toggleHidden = () => { |
36 | 55 |
dispatch( |
37 | 56 |
updateMapMarkerWithId({ |
38 | 57 |
item: { |
39 | 58 |
...item, |
40 |
active: !item?.active,
|
|
59 |
hidden: !item?.hidden,
|
|
41 | 60 |
} as MapPoint, |
42 | 61 |
id: item.id, |
43 | 62 |
}) |
... | ... | |
55 | 74 |
ref={provided.innerRef} |
56 | 75 |
{...provided.draggableProps} |
57 | 76 |
{...provided.dragHandleProps} |
58 |
sx={{ |
|
59 |
background: snapshot.isDragging |
|
60 |
? 'rgb(235,235,235)' |
|
61 |
: 'inherit', |
|
62 |
}} |
|
63 | 77 |
> |
64 | 78 |
<ListItemAvatar> |
65 |
<Avatar> |
|
66 |
<LocationOnIcon /> |
|
67 |
</Avatar> |
|
79 |
<DragHandleIcon /> |
|
68 | 80 |
</ListItemAvatar> |
69 | 81 |
<ListItemText |
70 | 82 |
primary={item.catalogItem.name ?? 'Unknown name'} |
... | ... | |
72 | 84 |
item.catalogItem |
73 | 85 |
)} |
74 | 86 |
/> |
75 |
<Checkbox |
|
76 |
checked={item.active} |
|
77 |
onChange={toggleHidden} |
|
78 |
/> |
|
87 |
<IconButton sx={{ mr: 1 }} onClick={toggleHidden}> |
|
88 |
{item.hidden ? ( |
|
89 |
<VisibilityOffIcon /> |
|
90 |
) : ( |
|
91 |
<VisibilityIcon /> |
|
92 |
)} |
|
93 |
</IconButton> |
|
94 |
<IconButton sx={{ mr: 1 }} onClick={toggleAddToPath}> |
|
95 |
{item.addToPath ? ( |
|
96 |
<AddRoadIcon /> |
|
97 |
) : ( |
|
98 |
<RemoveRoadIcon /> |
|
99 |
)} |
|
100 |
</IconButton> |
|
101 |
{/* <FormControlLabel |
|
102 |
control={ |
|
103 |
<Checkbox |
|
104 |
checked={item.addToPath} |
|
105 |
onChange={toggleAddToPath} |
|
106 |
/> |
|
107 |
} |
|
108 |
label="Add to path" |
|
109 |
/> */} |
|
79 | 110 |
</ListItem> |
80 | 111 |
)} |
81 | 112 |
</Draggable> |
frontend/src/features/TrackingTool/Import/AddFromCoordinatesDialog.tsx | ||
---|---|---|
1 |
import { DialogContent, DialogTitle, Grid, TextField } from '@mui/material' |
|
2 |
import { FunctionComponent, useState } from 'react' |
|
3 |
import ButtonOpenableDialog from '../../Reusables/ButtonOpenableDialog' |
|
4 |
import * as yup from 'yup' |
|
5 |
import { useDispatch } from 'react-redux' |
|
6 |
import { mergeWithCurrentPath } from '../trackingToolSlice' |
|
7 |
import { MapPointType, PathVariant } from '../Map/pathUtils' |
|
8 |
import generateUuid from '../../../utils/id/uuidGenerator' |
|
9 |
import { useFormik } from 'formik' |
|
10 |
import ContextMenuDialogProps from './contextMenuDialogProps' |
|
11 |
|
|
12 |
interface AddCatalogItemFromCoords { |
|
13 |
latitude: number |
|
14 |
longitude: number |
|
15 |
name: string |
|
16 |
} |
|
17 |
|
|
18 |
const AddFromCoordinatesDialog: FunctionComponent<ContextMenuDialogProps> = ({ |
|
19 |
latLng, |
|
20 |
closeContextMenu, |
|
21 |
}) => { |
|
22 |
const [open, setOpen] = useState(false) |
|
23 |
const dispatch = useDispatch() |
|
24 |
|
|
25 |
const formik = useFormik({ |
|
26 |
initialValues: { |
|
27 |
latitude: latLng[0], |
|
28 |
longitude: latLng[1], |
|
29 |
name: '', |
|
30 |
} as AddCatalogItemFromCoords, |
|
31 |
validationSchema: yup.object().shape({ |
|
32 |
latitude: yup.number().required('Latitude is required'), |
|
33 |
longitude: yup.number().required('Longitude is required'), |
|
34 |
name: yup.string().required('Name is required'), |
|
35 |
}), |
|
36 |
onSubmit: (values: AddCatalogItemFromCoords) => { |
|
37 |
dispatch( |
|
38 |
mergeWithCurrentPath([ |
|
39 |
{ |
|
40 |
id: generateUuid(), |
|
41 |
idx: -1, |
|
42 |
addToPath: false, |
|
43 |
type: MapPointType.FromCoordinates, |
|
44 |
catalogItem: { |
|
45 |
name: values.name, |
|
46 |
latitude: values.latitude, |
|
47 |
longitude: values.longitude, |
|
48 |
}, |
|
49 |
}, |
|
50 |
] as PathVariant) |
|
51 |
) |
|
52 |
}, |
|
53 |
}) |
|
54 |
|
|
55 |
return ( |
|
56 |
<ButtonOpenableDialog |
|
57 |
buttonText="Create Location" |
|
58 |
buttonColor="primary" |
|
59 |
buttonVariant="text" |
|
60 |
onCloseCallback={closeContextMenu} |
|
61 |
maxWidth="xs" |
|
62 |
open={open} |
|
63 |
setOpen={setOpen} |
|
64 |
size="small" |
|
65 |
> |
|
66 |
<DialogTitle>Add New Location From Coordinates</DialogTitle> |
|
67 |
<DialogContent> |
|
68 |
<form onSubmit={formik.handleSubmit}> |
|
69 |
<Grid container spacing={1} sx={{ mt: 1, mb: 1 }}> |
|
70 |
<Grid item xs={12} md={6}> |
|
71 |
<TextField |
|
72 |
fullWidth |
|
73 |
label="Latitude" |
|
74 |
name="latitude" |
|
75 |
type="number" |
|
76 |
variant="outlined" |
|
77 |
value={formik.values.latitude} |
|
78 |
onChange={formik.handleChange} |
|
79 |
error={ |
|
80 |
Boolean(formik.errors.latitude) && |
|
81 |
formik.touched.latitude |
|
82 |
} |
|
83 |
helperText={ |
|
84 |
formik.errors.latitude && |
|
85 |
formik.touched.latitude |
|
86 |
} |
|
87 |
/> |
|
88 |
</Grid> |
|
89 |
<Grid item xs={12} md={6}> |
|
90 |
<TextField |
|
91 |
fullWidth |
|
92 |
label="Longitude" |
|
93 |
name="longitude" |
|
94 |
type="number" |
|
95 |
variant="outlined" |
|
96 |
value={formik.values.longitude} |
|
97 |
onChange={formik.handleChange} |
|
98 |
error={ |
|
99 |
Boolean(formik.errors.longitude) && |
|
100 |
formik.touched.longitude |
|
101 |
} |
|
102 |
helperText={ |
|
103 |
formik.errors.longitude && |
|
104 |
formik.touched.longitude |
|
105 |
} |
|
106 |
/> |
|
107 |
</Grid> |
|
108 |
</Grid> |
|
109 |
<TextField |
|
110 |
fullWidth |
|
111 |
label="Name" |
|
112 |
name="name" |
|
113 |
variant="outlined" |
|
114 |
value={formik.values.name} |
|
115 |
onChange={formik.handleChange} |
|
116 |
error={ |
|
117 |
Boolean(formik.errors.name) && formik.touched.name |
|
118 |
} |
|
119 |
helperText={formik.errors.name && formik.touched.name} |
|
120 |
/> |
|
121 |
</form> |
|
122 |
</DialogContent> |
|
123 |
</ButtonOpenableDialog> |
|
124 |
) |
|
125 |
} |
|
126 |
|
|
127 |
export default AddFromCoordinatesDialog |
frontend/src/features/TrackingTool/Import/ImportContextMenu.tsx | ||
---|---|---|
1 |
import { LeafletMouseEvent } from 'leaflet' |
|
2 |
import { Fragment, useCallback, useState } from 'react' |
|
3 |
import { Popup, useMap, useMapEvents } from 'react-leaflet' |
|
4 |
|
|
5 |
import * as React from 'react' |
|
6 |
import Divider from '@mui/material/Divider' |
|
7 |
import Paper from '@mui/material/Paper' |
|
8 |
import MenuList from '@mui/material/MenuList' |
|
9 |
import MenuItem from '@mui/material/MenuItem' |
|
10 |
import ListItemText from '@mui/material/ListItemText' |
|
11 |
import ListItemIcon from '@mui/material/ListItemIcon' |
|
12 |
import Typography from '@mui/material/Typography' |
|
13 |
import ContentCut from '@mui/icons-material/ContentCut' |
|
14 |
import ContentCopy from '@mui/icons-material/ContentCopy' |
|
15 |
import ContentPaste from '@mui/icons-material/ContentPaste' |
|
16 |
import Cloud from '@mui/icons-material/Cloud' |
|
17 |
import { Button, Stack, ThemeProvider } from '@mui/material' |
|
18 |
import AddFromCoordinatesDialog from './AddFromCoordinatesDialog' |
|
19 |
import { useSelector } from 'react-redux' |
|
20 |
import { RootState } from '../../redux/store' |
|
21 |
import { buildTheme } from '../../Theme/ThemeWrapper' |
|
22 |
import ImportLocationDialog from './ImportLocationDialog' |
|
23 |
|
|
24 |
const RightClickPopupMenu = () => { |
|
25 |
const [open, setOpen] = useState(false) |
|
26 |
const [latLng, setLatLng] = useState<[number, number]>([0, 0]) |
|
27 |
|
|
28 |
const paletteMode = useSelector( |
|
29 |
(state: RootState) => state.theme.paletteMode |
|
30 |
) |
|
31 |
|
|
32 |
useMapEvents({ |
|
33 |
contextmenu: (e: LeafletMouseEvent) => { |
|
34 |
setLatLng([e.latlng.lat, e.latlng.lng]) |
|
35 |
setOpen(true) |
|
36 |
}, |
|
37 |
}) |
|
38 |
|
|
39 |
const closeContextMenu = useCallback(() => { |
|
40 |
setOpen(false) |
|
41 |
}, [setOpen]) |
|
42 |
|
|
43 |
return ( |
|
44 |
<Fragment> |
|
45 |
{open && ( |
|
46 |
<Popup onClose={() => setOpen(false)} position={latLng}> |
|
47 |
<Stack |
|
48 |
sx={{ p: 0, mt: 0 }} |
|
49 |
direction="column" |
|
50 |
justifyItems="center" |
|
51 |
justifyContent="center" |
|
52 |
> |
|
53 |
<Typography style={{margin: 0}} sx={{ mb: 0.5 }} align="center"> |
|
54 |
{latLng[0].toFixed(5)}°{latLng[1].toFixed(5)}° |
|
55 |
</Typography> |
|
56 |
<AddFromCoordinatesDialog |
|
57 |
latLng={latLng} |
|
58 |
closeContextMenu={closeContextMenu} |
|
59 |
/> |
|
60 |
<ImportLocationDialog |
|
61 |
latLng={latLng} |
|
62 |
closeContextMenu={closeContextMenu} |
|
63 |
/> |
|
64 |
</Stack> |
|
65 |
</Popup> |
|
66 |
)} |
|
67 |
</Fragment> |
|
68 |
) |
|
69 |
} |
|
70 |
|
|
71 |
export default RightClickPopupMenu |
frontend/src/features/TrackingTool/Import/ImportLocationDialog.tsx | ||
---|---|---|
1 |
import { |
|
2 |
Button, |
|
3 |
DialogContent, |
|
4 |
DialogTitle, |
|
5 |
FormControl, |
|
6 |
Grid, |
|
7 |
InputLabel, |
|
8 |
MenuItem, |
|
9 |
Select, |
|
10 |
} from '@mui/material' |
|
11 |
import { FunctionComponent, useState } from 'react' |
|
12 |
import ButtonOpenableDialog from '../../Reusables/ButtonOpenableDialog' |
|
13 |
import ContextMenuDialogProps from './contextMenuDialogProps' |
|
14 |
|
|
15 |
const importTypes = { |
|
16 |
localCatalog: 'Local Catalog', |
|
17 |
websiteCatalogs: 'Website Catalogs', |
|
18 |
} |
|
19 |
|
|
20 |
const ImportLocationDialog: FunctionComponent<ContextMenuDialogProps> = ({ |
|
21 |
latLng, |
|
22 |
closeContextMenu, |
|
23 |
}) => { |
|
24 |
const [open, setOpen] = useState(false) |
|
25 |
const [importType, setImportType] = useState('localCatalog') |
|
26 |
const [submitFunction, setSubmitFunction] = useState<() => void>(() => {}) |
|
27 |
|
|
28 |
return ( |
|
29 |
<ButtonOpenableDialog |
|
30 |
buttonText="Import Location" |
|
31 |
buttonColor="primary" |
|
32 |
buttonVariant="text" |
|
33 |
onCloseCallback={closeContextMenu} |
|
34 |
maxWidth="xs" |
|
35 |
open={open} |
|
36 |
setOpen={setOpen} |
|
37 |
size="small" |
|
38 |
onOpenCallback={() => {}} |
|
39 |
> |
|
40 |
<DialogTitle>Import Locations</DialogTitle> |
|
41 |
<DialogContent> |
|
42 |
<Grid container sx={{mt: 0}} spacing={1}> |
|
43 |
<Grid item xs={6} md={9}> |
|
44 |
<FormControl fullWidth> |
|
45 |
<InputLabel id="importType">Import</InputLabel> |
|
46 |
<Select |
|
47 |
labelId="importType" |
|
48 |
id="importTypeSelect" |
|
49 |
value={importType} |
|
50 |
label="Import" |
|
51 |
size="small" |
|
52 |
onChange={(e) => setImportType(e.target.value)} |
|
53 |
> |
|
54 |
{Object.keys(importTypes).map((key) => ( |
|
55 |
<MenuItem key={key} value={key}> |
|
56 |
{ |
|
57 |
//@ts-ignore |
|
58 |
importTypes[key] |
|
59 |
} |
|
60 |
</MenuItem> |
|
61 |
))} |
|
62 |
</Select> |
|
63 |
</FormControl> |
|
64 |
</Grid> |
|
65 |
<Grid item xs={6} md={3} alignContent="flex-end" justifyContent="flex-end"> |
|
66 |
<Button variant="contained" color="primary" onClick={submitFunction}>Import</Button> |
|
67 |
</Grid> |
|
68 |
</Grid> |
|
69 |
</DialogContent> |
|
70 |
</ButtonOpenableDialog> |
|
71 |
) |
|
72 |
} |
|
73 |
|
|
74 |
export default ImportLocationDialog |
frontend/src/features/TrackingTool/Import/contextMenuDialogProps.ts | ||
---|---|---|
1 |
export default interface ContextMenuDialogProps { |
|
2 |
latLng: [number, number] |
|
3 |
closeContextMenu: () => void |
|
4 |
} |
frontend/src/features/TrackingTool/Map/MapPath.tsx | ||
---|---|---|
62 | 62 |
useEffect(() => { |
63 | 63 |
// Get all active map points |
64 | 64 |
const activeMapPoints = displayableMapPoints.filter( |
65 |
(item) => item.active
|
|
65 |
(item) => item.addToPath && !item.hidden
|
|
66 | 66 |
) |
67 | 67 |
if (activeMapPoints.length < 2) { |
68 | 68 |
setEdges([]) |
... | ... | |
155 | 155 |
<FormControlLabel |
156 | 156 |
control={ |
157 | 157 |
<Checkbox |
158 |
checked={item.active}
|
|
158 |
checked={item.addToPath}
|
|
159 | 159 |
onChange={() => { |
160 | 160 |
dispatch( |
161 | 161 |
updateMapMarker({ |
162 | 162 |
...item, |
163 |
active: !item.active,
|
|
163 |
addToPath: !item.addToPath,
|
|
164 | 164 |
}) |
165 | 165 |
) |
166 | 166 |
}} |
frontend/src/features/TrackingTool/Map/RightClickPopupMenu.tsx | ||
---|---|---|
1 |
import { LeafletMouseEvent } from 'leaflet' |
|
2 |
import { Fragment, useState } from 'react' |
|
3 |
import { Popup, useMap, useMapEvents } from 'react-leaflet' |
|
4 |
|
|
5 |
import * as React from 'react' |
|
6 |
import Divider from '@mui/material/Divider' |
|
7 |
import Paper from '@mui/material/Paper' |
|
8 |
import MenuList from '@mui/material/MenuList' |
|
9 |
import MenuItem from '@mui/material/MenuItem' |
|
10 |
import ListItemText from '@mui/material/ListItemText' |
|
11 |
import ListItemIcon from '@mui/material/ListItemIcon' |
|
12 |
import Typography from '@mui/material/Typography' |
|
13 |
import ContentCut from '@mui/icons-material/ContentCut' |
|
14 |
import ContentCopy from '@mui/icons-material/ContentCopy' |
|
15 |
import ContentPaste from '@mui/icons-material/ContentPaste' |
|
16 |
import Cloud from '@mui/icons-material/Cloud' |
|
17 |
|
|
18 |
export function IconMenu() { |
|
19 |
return ( |
|
20 |
// <Paper sx={{ width: 320, maxWidth: '100%' }}> |
|
21 |
<MenuList> |
|
22 |
<MenuItem> |
|
23 |
<ListItemIcon> |
|
24 |
<ContentCut fontSize="small" /> |
|
25 |
</ListItemIcon> |
|
26 |
<ListItemText>Cut</ListItemText> |
|
27 |
<Typography variant="body2" color="text.secondary"> |
|
28 |
⌘X |
|
29 |
</Typography> |
|
30 |
</MenuItem> |
|
31 |
<MenuItem> |
|
32 |
<ListItemIcon> |
|
33 |
<ContentCopy fontSize="small" /> |
|
34 |
</ListItemIcon> |
|
35 |
<ListItemText>Copy</ListItemText> |
|
36 |
<Typography variant="body2" color="text.secondary"> |
|
37 |
⌘C |
|
38 |
</Typography> |
|
39 |
</MenuItem> |
|
40 |
<MenuItem> |
|
41 |
<ListItemIcon> |
|
42 |
<ContentPaste fontSize="small" /> |
|
43 |
</ListItemIcon> |
|
44 |
<ListItemText>Paste</ListItemText> |
|
45 |
<Typography variant="body2" color="text.secondary"> |
|
46 |
⌘V |
|
47 |
</Typography> |
|
48 |
</MenuItem> |
|
49 |
<Divider /> |
|
50 |
<MenuItem> |
|
51 |
<ListItemIcon> |
|
52 |
<Cloud fontSize="small" /> |
|
53 |
</ListItemIcon> |
|
54 |
<ListItemText>Web Clipboard</ListItemText> |
|
55 |
</MenuItem> |
|
56 |
</MenuList> |
|
57 |
// </Paper> |
|
58 |
) |
|
59 |
} |
|
60 |
|
|
61 |
const RightClickPopupMenu = () => { |
|
62 |
const [open, setOpen] = useState(false) |
|
63 |
const [latLng, setLatLng] = useState<[number, number]>([0, 0]) |
|
64 |
const mapEvents = useMapEvents({ |
|
65 |
contextmenu: (e: LeafletMouseEvent) => { |
|
66 |
setLatLng([e.latlng.lat, e.latlng.lng]) |
|
67 |
setOpen(true) |
|
68 |
}, |
|
69 |
}) |
|
70 |
|
|
71 |
return ( |
|
72 |
<Fragment> |
|
73 |
{open && <Popup onClose={() => setOpen(false)} position={latLng}> |
|
74 |
<IconMenu /> |
|
75 |
</Popup>} |
|
76 |
</Fragment> |
|
77 |
) |
|
78 |
} |
|
79 |
|
|
80 |
export default RightClickPopupMenu |
frontend/src/features/TrackingTool/Map/pathUtils.ts | ||
---|---|---|
17 | 17 |
export interface MapPoint { |
18 | 18 |
id: string // unique id for react |
19 | 19 |
idx: number, |
20 |
active: boolean,
|
|
20 |
addToPath: boolean, // whether to add the point to the path
|
|
21 | 21 |
catalogItem: CatalogItemDto, |
22 | 22 |
type: MapPointType |
23 |
hidden?: boolean // if true the point will not be displayed on the map |
|
23 | 24 |
} |
24 | 25 |
|
25 | 26 |
export const isMapPointDisplayable = (mapPoint: MapPoint): boolean => |
26 |
!!mapPoint.catalogItem.latitude && !!mapPoint.catalogItem.longitude |
|
27 |
!!mapPoint.catalogItem.latitude && !!mapPoint.catalogItem.longitude && !mapPoint.hidden
|
|
27 | 28 |
|
28 | 29 |
/** |
29 | 30 |
* Cartesian product of two arrays |
... | ... | |
59 | 60 |
{ |
60 | 61 |
id: generateUuid(), |
61 | 62 |
idx, |
62 |
active: !!catalogItem.latitude && !!catalogItem.longitude,
|
|
63 |
addToPath: !!catalogItem.latitude && !!catalogItem.longitude,
|
|
63 | 64 |
catalogItem, |
64 | 65 |
type: mapPointType, |
65 |
}) |
|
66 |
} as MapPoint)
|
|
66 | 67 |
) |
67 | 68 |
) |
68 | 69 |
} |
frontend/src/features/TrackingTool/TrackingTool.tsx | ||
---|---|---|
24 | 24 |
import GeoJsonImportDialog from './Upload/GeoJsonImportDialog' |
25 | 25 |
import ProcessedTextDisplay from './ProcessedText/ProcessedTextDisplay' |
26 | 26 |
import DraggableMarkerList from './DraggableList/DraggableMarkerList' |
27 |
import RightClickPopupMenu from './Map/RightClickPopupMenu'
|
|
27 |
import RightClickPopupMenu from './Import/ImportContextMenu'
|
|
28 | 28 |
import { buildTheme, getPalette } from '../Theme/ThemeWrapper' |
29 | 29 |
|
30 | 30 |
const mapTheme = buildTheme('light') |
frontend/src/features/TrackingTool/Upload/GeoJsonExportButton.tsx | ||
---|---|---|
32 | 32 |
} |
33 | 33 |
|
34 | 34 |
const exportPath = path.filter( |
35 |
(vertex) => isMapPointDisplayable(vertex) && vertex.active
|
|
35 |
(vertex) => isMapPointDisplayable(vertex) && vertex.addToPath
|
|
36 | 36 |
) |
37 | 37 |
const exportPathString = exportAsGeoJsonString(exportPath) |
38 | 38 |
const blob = new Blob([exportPathString], { type: 'application/json' }) |
frontend/src/features/TrackingTool/Upload/GeoJsonIo.ts | ||
---|---|---|
4 | 4 |
|
5 | 5 |
export const exportAsGeoJsonString = (path: PathVariant) => JSON.stringify({ |
6 | 6 |
type: 'FeatureCollection', |
7 |
features: path.filter(item => item.active && isMapPointDisplayable(item)).map((item) => {
|
|
7 |
features: path.filter(item => item.addToPath && isMapPointDisplayable(item)).map((item) => {
|
|
8 | 8 |
const catalogItem = item.catalogItem |
9 | 9 |
return { |
10 | 10 |
type: 'Feature', |
... | ... | |
63 | 63 |
return { |
64 | 64 |
id: generateUuid(), |
65 | 65 |
idx: feature.properties.idx, |
66 |
active: true,
|
|
66 |
addToPath: true,
|
|
67 | 67 |
catalogItem: { |
68 | 68 |
id: catalogItem.id, |
69 | 69 |
name: catalogItem.name, |
frontend/src/features/TrackingTool/trackingToolSlice.ts | ||
---|---|---|
172 | 172 |
|
173 | 173 |
// Add items to the end |
174 | 174 |
itemsToAdd.forEach((item) => { |
175 |
item.active = !state.pathVariants || state.pathVariants.length === 0
|
|
175 |
item.addToPath = !state.pathVariants || state.pathVariants.length === 0
|
|
176 | 176 |
item.idx = newPath.length |
177 | 177 |
newPath.push(item) |
178 | 178 |
}) |
Také k dispozici: Unified diff
import menu start
re #9741