«
Předchozí
|
Další
»
Revize 14588cb6
Přidáno uživatelem Václav Honzík před téměř 3 roky(ů)
- ID 14588cb653e6b7358e0e641cc2b8d45dc27b5cf8
- Rodič fc8a530f
frontend/src/features/Reusables/SingleFileSelectionForm.tsx | ||
---|---|---|
9 | 9 |
onFileSelected: (event: any) => void |
10 | 10 |
formik: any |
11 | 11 |
onClearSelectedFile: () => void |
12 |
loading?: boolean |
|
12 | 13 |
} |
13 | 14 |
|
14 | 15 |
const SingleFileSelectionForm: FunctionComponent< |
15 | 16 |
SingleFileSelectionFormProps |
16 |
> = ({ filename, onFileSelected, formik, onClearSelectedFile }) => { |
|
17 |
> = ({ filename, onFileSelected, formik, onClearSelectedFile, loading }) => {
|
|
17 | 18 |
return ( |
18 | 19 |
<form onSubmit={formik.handleSubmit}> |
19 | 20 |
{!filename ? ( |
... | ... | |
76 | 77 |
type="submit" |
77 | 78 |
variant="contained" |
78 | 79 |
startIcon={<SendIcon />} |
80 |
disabled={loading} |
|
79 | 81 |
> |
80 | 82 |
Submit |
81 | 83 |
</Button> |
frontend/src/features/TrackingTool/Controls/DragDropCtxWrapper.tsx | ||
---|---|---|
32 | 32 |
<MapPointDraggableListItem |
33 | 33 |
list={items} |
34 | 34 |
idx={index} |
35 |
key={item.id}
|
|
35 |
key={item.reactId}
|
|
36 | 36 |
/> |
37 | 37 |
))} |
38 | 38 |
{provided.placeholder} |
frontend/src/features/TrackingTool/Controls/MapPointDraggableList.tsx | ||
---|---|---|
1 | 1 |
import { Paper } from '@mui/material' |
2 |
import { useCallback } from 'react' |
|
2 |
import { Fragment, useCallback } from 'react'
|
|
3 | 3 |
import { DropResult } from 'react-beautiful-dnd' |
4 | 4 |
import { useDispatch, useSelector } from 'react-redux' |
5 | 5 |
import { RootState } from '../../redux/store' |
6 |
import { moveMarkerToDestination } from '../trackingToolSlice'
|
|
6 |
import { changeMarkerIdx } from '../trackingToolSlice'
|
|
7 | 7 |
import DragDropCtxWrapper from './DragDropCtxWrapper' |
8 | 8 |
|
9 | 9 |
const MapPointDraggableList = () => { |
... | ... | |
18 | 18 |
} |
19 | 19 |
|
20 | 20 |
dispatch( |
21 |
moveMarkerToDestination({
|
|
21 |
changeMarkerIdx({
|
|
22 | 22 |
source: source.index, |
23 | 23 |
destination: destination.index, |
24 | 24 |
}) |
... | ... | |
28 | 28 |
) |
29 | 29 |
|
30 | 30 |
return ( |
31 |
<Paper variant="outlined"> |
|
32 |
<DragDropCtxWrapper items={path ?? []} onDragEnd={onDragEnd} /> |
|
33 |
</Paper> |
|
31 |
<Fragment> |
|
32 |
{path && ( |
|
33 |
<Paper variant="outlined"> |
|
34 |
<DragDropCtxWrapper |
|
35 |
items={path ?? []} |
|
36 |
onDragEnd={onDragEnd} |
|
37 |
/> |
|
38 |
</Paper> |
|
39 |
)} |
|
40 |
</Fragment> |
|
34 | 41 |
) |
35 | 42 |
} |
36 | 43 |
|
frontend/src/features/TrackingTool/Controls/MapPointDraggableListItem.tsx | ||
---|---|---|
8 | 8 |
import { Draggable } from 'react-beautiful-dnd' |
9 | 9 |
import { |
10 | 10 |
getMapPointSemanticColor, |
11 |
isMapPointDisplayable, |
|
11 | 12 |
MapPointType, |
12 | 13 |
PathVariant, |
13 | 14 |
} from '../trackingToolUtils' |
14 | 15 |
import { CatalogItemDto } from '../../../swagger/data-contracts' |
15 | 16 |
import { useDispatch } from 'react-redux' |
16 |
import { removeMapMarker, updateMapMarker } from '../trackingToolSlice' |
|
17 |
import { |
|
18 |
removeMapMarker, |
|
19 |
updateMapPoint, |
|
20 |
updateMapPointAtIndex, |
|
21 |
} from '../trackingToolSlice' |
|
17 | 22 |
import { useMemo } from 'react' |
18 | 23 |
import DragHandleIcon from '@mui/icons-material/DragHandle' |
19 | 24 |
import VisibilityIcon from '@mui/icons-material/Visibility' |
... | ... | |
27 | 32 |
idx: number |
28 | 33 |
} |
29 | 34 |
|
35 |
// function Item({ provided, item, isDragging }) { |
|
36 |
// return ( |
|
37 |
// <div style={{ paddingBottom: '8px' }}> |
|
38 |
// <div |
|
39 |
// {...provided.draggableProps} |
|
40 |
// {...provided.dragHandleProps} |
|
41 |
// ref={provided.innerRef} |
|
42 |
// style={provided.draggableProps.style} |
|
43 |
// className={`item ${isDragging ? 'is-dragging' : ''}`} |
|
44 |
// > |
|
45 |
// {item.text} |
|
46 |
// </div> |
|
47 |
// </div> |
|
48 |
// ) |
|
49 |
// } |
|
50 |
|
|
51 |
// const HeightPreservingItem = ({ children, ...props }) => { |
|
52 |
// const [size, setSize] = useState(0) |
|
53 |
// const knownSize = props['data-known-size'] |
|
54 |
// useEffect(() => { |
|
55 |
// setSize((prevSize) => { |
|
56 |
// return knownSize == 0 ? prevSize : knownSize |
|
57 |
// }) |
|
58 |
// }, [knownSize]) |
|
59 |
// return ( |
|
60 |
// <div |
|
61 |
// {...props} |
|
62 |
// className="height-preserving-container" |
|
63 |
// // check styling in the style tag below |
|
64 |
// style={{ '--child-height': `${size}px`, }} |
|
65 |
// > |
|
66 |
// {children} |
|
67 |
// </div> |
|
68 |
// ) |
|
69 |
// } |
|
70 |
|
|
30 | 71 |
const getFormattedLocationOrEmpty = (catalogItem: CatalogItemDto) => { |
31 | 72 |
if (!catalogItem || !catalogItem.latitude || !catalogItem.longitude) { |
32 | 73 |
return 'Location unavailable' |
... | ... | |
52 | 93 |
// } as MapPoint, |
53 | 94 |
// id: item.id, |
54 | 95 |
// }) |
55 |
updateMapMarker({
|
|
96 |
updateMapPointAtIndex({
|
|
56 | 97 |
...item, |
57 | 98 |
addToPath: !item?.addToPath, |
58 | 99 |
idx, |
... | ... | |
69 | 110 |
// } as MapPoint, |
70 | 111 |
// id: item.id, |
71 | 112 |
// }) |
72 |
updateMapMarker({
|
|
113 |
updateMapPoint({
|
|
73 | 114 |
...item, |
74 | 115 |
hidden: !item?.hidden, |
75 | 116 |
idx, |
... | ... | |
89 | 130 |
return ( |
90 | 131 |
item && ( |
91 | 132 |
<Draggable |
92 |
key={`${item.id}`}
|
|
93 |
draggableId={`${item.id}`}
|
|
133 |
key={`${item.reactId}`}
|
|
134 |
draggableId={`${item.reactId}`}
|
|
94 | 135 |
index={idx} |
95 | 136 |
> |
96 | 137 |
{(provided, snapshot) => ( |
... | ... | |
124 | 165 |
<DeleteForeverIcon /> |
125 | 166 |
</IconButton> |
126 | 167 |
)} |
127 |
<IconButton sx={{ mr: 1 }} onClick={toggleHidden}> |
|
128 |
{item.hidden ? ( |
|
129 |
<VisibilityOffIcon /> |
|
130 |
) : ( |
|
131 |
<VisibilityIcon /> |
|
132 |
)} |
|
133 |
</IconButton> |
|
168 |
{isMapPointDisplayable(item) ? ( |
|
169 |
<IconButton |
|
170 |
sx={{ mr: 1 }} |
|
171 |
onClick={toggleHidden} |
|
172 |
> |
|
173 |
{item.hidden ? ( |
|
174 |
<VisibilityOffIcon /> |
|
175 |
) : ( |
|
176 |
<VisibilityIcon /> |
|
177 |
)} |
|
178 |
</IconButton> |
|
179 |
) : null} |
|
134 | 180 |
<IconButton |
135 | 181 |
sx={{ mr: 1 }} |
136 | 182 |
onClick={toggleAddToPath} |
frontend/src/features/TrackingTool/Controls/ProcessedTextDisplay.tsx | ||
---|---|---|
12 | 12 |
return ( |
13 | 13 |
<Fragment> |
14 | 14 |
{pathDto && ( |
15 |
<Card variant="outlined" sx={{ maxHeight: '50vh' }}> |
|
15 |
<Card variant="outlined" sx={{ maxHeight: '50vh', overflow: 'auto' }}>
|
|
16 | 16 |
<CardContent> |
17 | 17 |
<Stack direction="column"> |
18 | 18 |
<Typography |
frontend/src/features/TrackingTool/Map/Map.tsx | ||
---|---|---|
7 | 7 |
import { buildTheme } from '../../Theme/ThemeWrapper' |
8 | 8 |
import RightClickPopupMenu from '../Import/ImportContextMenu' |
9 | 9 |
import MapPath from './MapPath' |
10 |
import L, { Map as LeafletMap } from 'leaflet' |
|
11 |
import { } from 'leaflet.fullscreen' |
|
10 |
import { Map as LeafletMap } from 'leaflet' |
|
12 | 11 |
|
13 | 12 |
const mapTheme = buildTheme('light') |
14 | 13 |
|
frontend/src/features/TrackingTool/Map/MapPath.tsx | ||
---|---|---|
3 | 3 |
import { RootState } from '../../redux/store' |
4 | 4 |
import { MapPoint, isMapPointDisplayable } from '../trackingToolUtils' |
5 | 5 |
import TextPath from 'react-leaflet-textpath' |
6 |
import { updateMapMarker } from '../trackingToolSlice'
|
|
6 |
import { updateMapPoint } from '../trackingToolSlice'
|
|
7 | 7 |
import MapMarker from './MapMarker' |
8 | 8 |
import { LatLngTuple } from 'leaflet' |
9 | 9 |
import { Popup, Tooltip } from 'react-leaflet' |
... | ... | |
14 | 14 |
type EdgeElement = any |
15 | 15 |
const MapPath = () => { |
16 | 16 |
const dispatch = useDispatch() |
17 |
|
|
18 |
// Displayed path contains all map points |
|
17 | 19 |
const path = useSelector( |
18 | 20 |
(state: RootState) => state.trackingTool.displayedPath |
19 | 21 |
) |
... | ... | |
47 | 49 |
setEdges([]) |
48 | 50 |
return |
49 | 51 |
} |
52 |
console.log('rerender') |
|
50 | 53 |
|
51 |
// Build edges |
|
54 |
// Keep track of already built edges so we do not render excessive amount of them |
|
55 |
// We can use a simple hack where we concatenate id of both and check if it is already in the set |
|
56 |
const existingPaths = new Set<string>() |
|
52 | 57 |
const edges = [] |
53 | 58 |
for (let i = 0; i < activeMapPoints.length - 1; i += 1) { |
59 |
const edgeId = `${activeMapPoints[i].id}${ |
|
60 |
activeMapPoints[i + 1].id |
|
61 |
}` |
|
62 |
if (existingPaths.has(edgeId)) { |
|
63 |
continue |
|
64 |
} |
|
65 |
existingPaths.add(edgeId) |
|
54 | 66 |
const [start, end] = [ |
55 | 67 |
activeMapPoints[i].catalogItem, |
56 | 68 |
activeMapPoints[i + 1].catalogItem, |
57 | 69 |
] |
58 | 70 |
edges.push( |
59 | 71 |
<TextPath |
60 |
key={`${activeMapPoints[i].id}-${ |
|
61 |
activeMapPoints[i + 1].id |
|
62 |
}`} |
|
72 |
key={edgeId} |
|
63 | 73 |
positions={[ |
64 | 74 |
[start.latitude, start.longitude], |
65 | 75 |
[end.latitude, end.longitude], |
66 | 76 |
]} |
67 | 77 |
text="►" |
68 | 78 |
attributes={{ |
69 |
'font-size': 19,
|
|
79 |
'font-size': 17,
|
|
70 | 80 |
fill: pathColor, |
71 | 81 |
}} |
72 | 82 |
repeat |
... | ... | |
82 | 92 |
const [vertices, setVertices] = useState<JSX.Element[]>([]) |
83 | 93 |
useEffect(() => { |
84 | 94 |
// Iterate over all displayable map points and map them to MapMarker |
95 |
const uniqueMapPoints = new Set<string>() |
|
85 | 96 |
setVertices( |
86 |
displayableMapPoints.map((item) => ( |
|
87 |
<MapMarker |
|
88 |
key={item.id} |
|
89 |
position={[ |
|
90 |
item.catalogItem.latitude as number, |
|
91 |
item.catalogItem.longitude as number, |
|
92 |
]} |
|
93 |
mapPoint={item} |
|
94 |
updatePositionCallbackFn={(position: LatLngTuple) => { |
|
95 |
dispatch( |
|
96 |
updateMapMarker({ |
|
97 |
...item, |
|
98 |
catalogItem: { |
|
99 |
...item.catalogItem, |
|
100 |
latitude: position[0], |
|
101 |
longitude: position[1], |
|
102 |
}, |
|
103 |
}) |
|
104 |
) |
|
105 |
}} |
|
106 |
> |
|
107 |
<Fragment> |
|
108 |
<Tooltip> |
|
109 |
{/* <Typography> */} |
|
110 |
{item.catalogItem.name ?? ''} |
|
111 |
{/* </Typography> */} |
|
112 |
</Tooltip> |
|
113 |
<Popup> |
|
114 |
<Fragment> |
|
115 |
<Stack direction="column" sx={{ m: 0 }}> |
|
116 |
<Typography |
|
117 |
variant="h6" |
|
118 |
fontWeight="bold" |
|
119 |
fontSize={16} |
|
120 |
> |
|
121 |
{formatHtmlStringToReactDom( |
|
122 |
item.catalogItem.name as string |
|
123 |
)} |
|
124 |
</Typography> |
|
125 |
<FormControlLabel |
|
126 |
control={ |
|
127 |
<Checkbox |
|
128 |
checked={item.addToPath} |
|
129 |
onChange={() => { |
|
130 |
dispatch( |
|
131 |
updateMapMarker({ |
|
132 |
...item, |
|
133 |
addToPath: |
|
134 |
!item.addToPath, |
|
135 |
}) |
|
136 |
) |
|
137 |
}} |
|
138 |
/> |
|
139 |
} |
|
140 |
labelPlacement="end" |
|
141 |
label="Active" |
|
142 |
/> |
|
143 |
<CatalogItemDetailDialog |
|
144 |
itemId={item.catalogItem.id ?? ''} |
|
145 |
/> |
|
146 |
</Stack> |
|
147 |
</Fragment> |
|
148 |
</Popup> |
|
149 |
</Fragment> |
|
150 |
</MapMarker> |
|
151 |
)) |
|
97 |
displayableMapPoints |
|
98 |
.filter((mapPoint) => { |
|
99 |
if (uniqueMapPoints.has(mapPoint.id)) { |
|
100 |
return false |
|
101 |
} |
|
102 |
uniqueMapPoints.add(mapPoint.id) |
|
103 |
return true |
|
104 |
}) |
|
105 |
.map((item) => ( |
|
106 |
<MapMarker |
|
107 |
key={item.id} |
|
108 |
position={[ |
|
109 |
item.catalogItem.latitude as number, |
|
110 |
item.catalogItem.longitude as number, |
|
111 |
]} |
|
112 |
mapPoint={item} |
|
113 |
updatePositionCallbackFn={(position: LatLngTuple) => { |
|
114 |
dispatch( |
|
115 |
updateMapPoint({ |
|
116 |
...item, |
|
117 |
catalogItem: { |
|
118 |
...item.catalogItem, |
|
119 |
latitude: position[0], |
|
120 |
longitude: position[1], |
|
121 |
}, |
|
122 |
}) |
|
123 |
) |
|
124 |
}} |
|
125 |
> |
|
126 |
<Fragment> |
|
127 |
<Tooltip> |
|
128 |
{/* <Typography> */} |
|
129 |
{item.catalogItem.name ?? ''} |
|
130 |
{/* </Typography> */} |
|
131 |
</Tooltip> |
|
132 |
<Popup> |
|
133 |
<Fragment> |
|
134 |
<Stack direction="column" sx={{ m: 0 }}> |
|
135 |
<Typography |
|
136 |
variant="h6" |
|
137 |
fontWeight="bold" |
|
138 |
fontSize={16} |
|
139 |
> |
|
140 |
{formatHtmlStringToReactDom( |
|
141 |
item.catalogItem.name as string |
|
142 |
)} |
|
143 |
</Typography> |
|
144 |
<FormControlLabel |
|
145 |
control={ |
|
146 |
<Checkbox |
|
147 |
checked={item.addToPath} |
|
148 |
onChange={() => { |
|
149 |
dispatch( |
|
150 |
updateMapPoint({ |
|
151 |
...item, |
|
152 |
addToPath: |
|
153 |
!item.addToPath, |
|
154 |
}) |
|
155 |
) |
|
156 |
}} |
|
157 |
/> |
|
158 |
} |
|
159 |
labelPlacement="end" |
|
160 |
label="Active" |
|
161 |
/> |
|
162 |
<CatalogItemDetailDialog |
|
163 |
itemId={item.catalogItem.id ?? ''} |
|
164 |
/> |
|
165 |
</Stack> |
|
166 |
</Fragment> |
|
167 |
</Popup> |
|
168 |
</Fragment> |
|
169 |
</MapMarker> |
|
170 |
)) |
|
152 | 171 |
) |
153 | 172 |
}, [dispatch, displayableMapPoints]) |
154 | 173 |
|
frontend/src/features/TrackingTool/Map/mapUtils.ts | ||
---|---|---|
35 | 35 |
variant.map( |
36 | 36 |
(catalogItem, idx) => ( |
37 | 37 |
{ |
38 |
id: generateUuid(),
|
|
38 |
id: '',
|
|
39 | 39 |
idx, |
40 |
reactId: generateUuid(), |
|
40 | 41 |
addToPath: !!catalogItem.latitude && !!catalogItem.longitude, |
41 | 42 |
catalogItem, |
42 | 43 |
type: mapPointType, |
frontend/src/features/TrackingTool/TrackingTool.tsx | ||
---|---|---|
128 | 128 |
</Grid> |
129 | 129 |
<Grid container sx={{ mt: 1, mb: 20 }} spacing={1}> |
130 | 130 |
<Grid item xs={12} md={6}> |
131 |
{/* <MapPointDraggableList /> */}
|
|
131 |
<MapPointDraggableList />
|
|
132 | 132 |
</Grid> |
133 | 133 |
<Grid item xs={12} md={6}> |
134 | 134 |
<ProcessedTextDisplay /> |
frontend/src/features/TrackingTool/Upload/FileUpload.tsx | ||
---|---|---|
1 | 1 |
import { |
2 |
Button, |
|
3 | 2 |
DialogContent, |
4 | 3 |
DialogTitle, |
5 |
Link, |
|
6 |
Stack, |
|
7 |
Typography, |
|
8 | 4 |
} from '@mui/material' |
9 | 5 |
import { useFormik } from 'formik' |
10 |
import { Fragment, useState } from 'react'
|
|
6 |
import { useState } from 'react' |
|
11 | 7 |
import * as yup from 'yup' |
12 |
import axiosInstance from '../../../api/api' |
|
13 | 8 |
import ButtonOpenableDialog from '../../Reusables/ButtonOpenableDialog' |
14 |
import { useDispatch } from 'react-redux' |
|
9 |
import { useDispatch, useSelector } from 'react-redux'
|
|
15 | 10 |
import { sendTextForProcessing } from '../trackingToolThunks' |
16 | 11 |
import SingleFileSelectionForm from '../../Reusables/SingleFileSelectionForm' |
17 | 12 |
import AttachFileIcon from '@mui/icons-material/AttachFile' |
18 |
import TextSnippetIcon from '@mui/icons-material/TextSnippet'
|
|
13 |
import { RootState } from '../../redux/store'
|
|
19 | 14 |
|
20 | 15 |
interface UploadValues { |
21 | 16 |
file?: File |
... | ... | |
34 | 29 |
file: yup.mixed().required('File is required'), |
35 | 30 |
}) |
36 | 31 |
|
32 |
const loading = useSelector( |
|
33 |
(state: RootState) => state.trackingTool.isLoading |
|
34 |
) |
|
35 |
|
|
37 | 36 |
const formik = useFormik({ |
38 | 37 |
initialValues, |
39 | 38 |
validationSchema, |
... | ... | |
88 | 87 |
onClearSelectedFile={onClearSelectedFile} |
89 | 88 |
filename={filename} |
90 | 89 |
formik={formik} |
90 |
loading={loading} |
|
91 | 91 |
/> |
92 | 92 |
</DialogContent> |
93 | 93 |
</ButtonOpenableDialog> |
frontend/src/features/TrackingTool/trackingToolSlice.ts | ||
---|---|---|
1 | 1 |
import { createSlice } from '@reduxjs/toolkit' |
2 | 2 |
import mapConfig from '../../config/mapConfig' |
3 | 3 |
import { PathDto } from '../../swagger/data-contracts' |
4 |
import generateUuid from '../../utils/id/uuidGenerator' |
|
4 | 5 |
import buildPathVariants from './Map/mapUtils' |
5 | 6 |
import TrackingToolState from './trackingToolState' |
6 | 7 |
import { sendTextForProcessing } from './trackingToolThunks' |
7 |
import { calculateMapCenter, MapPoint, PathVariant } from './trackingToolUtils' |
|
8 |
import { calculateMapCenter, MapPoint, PathVariant, setMapPointIds } from './trackingToolUtils'
|
|
8 | 9 |
|
9 | 10 |
const initialState: TrackingToolState = { |
10 | 11 |
isLoading: false, |
... | ... | |
18 | 19 |
initialState, |
19 | 20 |
reducers: { |
20 | 21 |
consumeErr: (state: TrackingToolState) => ({ ...state, lastErr: undefined, }), |
21 |
setSelectedPathIdx: (state: TrackingToolState, action: { payload: number }) => ({ ...state, selectedPathIdx: action.payload, }), |
|
22 |
resetDialogApiCallSuccess: (state: TrackingToolState) => ({ ...state, dialogApiCallSuccess: false, }), |
|
22 |
setSelectedPathIdx: (state: TrackingToolState, action: { payload: number }) => |
|
23 |
({ ...state, selectedPathIdx: action.payload, }), |
|
24 |
resetDialogApiCallSuccess: (state: TrackingToolState) => |
|
25 |
({ ...state, dialogApiCallSuccess: false, }), |
|
23 | 26 |
// Updates currently displayed path |
24 |
updateDisplayedPath: (state: TrackingToolState, action: { payload: PathVariant }) => ({ ...state, displayedPath: action.payload, }),
|
|
25 |
// Updates map marker
|
|
26 |
updateMapMarker: (state: TrackingToolState, action: { payload: MapPoint }) => {
|
|
27 |
updateDisplayedPath: (state: TrackingToolState, action: { payload: PathVariant }) => |
|
28 |
({ ...state, displayedPath: action.payload, }),
|
|
29 |
updateMapPointAtIndex: (state: TrackingToolState, action: { payload: MapPoint }) => {
|
|
27 | 30 |
const newPath = [...state.displayedPath ?? []] |
28 | 31 |
if (newPath.length <= action.payload.idx) { |
29 | 32 |
return state // do nothing if there is no path |
... | ... | |
35 | 38 |
displayedPath: newPath, |
36 | 39 |
} |
37 | 40 |
}, |
41 |
// Updates map marker |
|
42 |
updateMapPoint: (state: TrackingToolState, action: { payload: MapPoint }) => { |
|
43 |
const newPath = [...state.displayedPath ?? []] |
|
44 |
return { |
|
45 |
...state, |
|
46 |
displayedPath: newPath.map((point, _) => point.id === action.payload.id ? {...action.payload, idx: point.idx} : point) |
|
47 |
} |
|
48 |
}, |
|
38 | 49 |
// Removes map marker from currently used path |
39 | 50 |
removeMapMarker: (state: TrackingToolState, action: { payload: { id: string, idx: number } }) => { |
40 | 51 |
const newPath = [...state.displayedPath ?? []] |
... | ... | |
47 | 58 |
displayedPath: [...newPath.slice(0, action.payload.idx), ...newPath.slice(action.payload.idx + 1)], |
48 | 59 |
} |
49 | 60 |
}, |
50 |
// Moves map marker in the path array
|
|
51 |
moveMarkerToDestination: (state: TrackingToolState, action: { payload: { destination: number, source: number } }) => {
|
|
61 |
// Changes marker index
|
|
62 |
changeMarkerIdx: (state: TrackingToolState, action: { payload: { destination: number, source: number } }) => {
|
|
52 | 63 |
const { destination, source } = action.payload |
53 | 64 |
const newPath = [...state.displayedPath ?? []] |
54 | 65 |
if (newPath.length <= source |
... | ... | |
74 | 85 |
|
75 | 86 |
const newPath = [...state.displayedPath ?? []] |
76 | 87 |
const existingIds = new Set(newPath.map(item => item.id ?? '')) |
77 |
|
|
78 | 88 |
const newItems: MapPoint[] = [] |
79 | 89 |
jsonPath.forEach(item => { |
80 | 90 |
if (!existingIds.has(item.id ?? '')) { |
... | ... | |
85 | 95 |
newItems.forEach(item => { |
86 | 96 |
item.addToPath = !state.pathVariants || state.pathVariants.length === 0 |
87 | 97 |
item.idx = newPath.length |
98 |
const uuid = generateUuid() |
|
99 |
item.reactId = uuid |
|
100 |
item.id = item.catalogItem.id ? item.catalogItem.id : uuid |
|
88 | 101 |
newPath.push(item) |
89 | 102 |
}) |
90 | 103 |
|
... | ... | |
106 | 119 |
|
107 | 120 |
// We will always have displayedPath defined if this function is called |
108 | 121 |
pathVariants.splice(previousIdx, 0, [...state.displayedPath ?? []]) |
109 |
const newPath = pathVariants.splice(newIdx, 1)[0]
|
|
122 |
const newPath = setMapPointIds(pathVariants.splice(newIdx, 1)[0])
|
|
110 | 123 |
return { |
111 | 124 |
...state, |
112 | 125 |
displayedPath: newPath, |
... | ... | |
114 | 127 |
pathVariants, |
115 | 128 |
} |
116 | 129 |
}, |
117 |
moveToNextPathVariant: (state: TrackingToolState) => { |
|
118 |
const currentPathIdx = state.displayedPathIdx |
|
119 |
const pathVariants = [...state.pathVariants ?? []] |
|
120 |
|
|
121 |
// Since we take out the selected path index the displayedPathIdx will serve as |
|
122 |
// index of the next element |
|
123 |
if (currentPathIdx >= pathVariants.length) { |
|
124 |
return state // do nothing if the displayed path is also the last one |
|
125 |
} |
|
126 |
|
|
127 |
// Else get the next path variant |
|
128 |
const newPath = [...pathVariants[currentPathIdx]] |
|
129 |
const previousPath = [...state.displayedPath ?? []] |
|
130 |
|
|
131 |
// And replace the displayed path with the new one |
|
132 |
return { |
|
133 |
...state, |
|
134 |
displayedPath: newPath, |
|
135 |
displayedPathIdx: currentPathIdx + 1, |
|
136 |
pathVariants: [ |
|
137 |
...pathVariants.slice(0, currentPathIdx), |
|
138 |
previousPath, |
|
139 |
...pathVariants.slice(currentPathIdx + 1), |
|
140 |
] |
|
141 |
} |
|
142 |
}, |
|
143 |
moveToPreviousPathVariant: (state: TrackingToolState) => { |
|
144 |
const currentPathIdx = state.displayedPathIdx |
|
145 |
const pathVariants = [...state.pathVariants ?? []] |
|
146 |
|
|
147 |
const previousPathIdx = currentPathIdx - 1 |
|
148 |
if (previousPathIdx < 0) { |
|
149 |
return state // do nothing if the displayed path is the first one |
|
150 |
} |
|
151 |
|
|
152 |
// Else get the previous path variant |
|
153 |
const newPath = [...pathVariants[previousPathIdx]] |
|
154 |
const previousPath = [...state.displayedPath ?? []] |
|
155 |
|
|
156 |
return { |
|
157 |
...state, |
|
158 |
displayedPath: newPath, |
|
159 |
displayedPathIdx: previousPathIdx, |
|
160 |
pathVariants: [ |
|
161 |
...pathVariants.slice(0, previousPathIdx), |
|
162 |
previousPath, |
|
163 |
...pathVariants.slice(previousPathIdx + 1), |
|
164 |
] |
|
165 |
} |
|
166 |
} |
|
167 | 130 |
}, |
168 | 131 |
extraReducers: (builder) => { |
169 | 132 |
builder.addCase(sendTextForProcessing.fulfilled, (state, action) => { |
170 | 133 |
const pathDto: PathDto = action.payload |
171 | 134 |
const pathVariants = buildPathVariants(pathDto) |
172 |
const selectedPath = pathVariants.length > 0 ? pathVariants[0] : undefined
|
|
135 |
const selectedPath = pathVariants.length > 0 ? setMapPointIds(pathVariants[0]) : undefined
|
|
173 | 136 |
pathVariants.splice(0, 1) // remove the selected path so that we do not have to update it here |
174 | 137 |
if (!selectedPath) { |
175 | 138 |
return { |
... | ... | |
220 | 183 |
setSelectedPathIdx, |
221 | 184 |
resetDialogApiCallSuccess, |
222 | 185 |
updateDisplayedPath, |
223 |
updateMapMarker,
|
|
186 |
updateMapPoint,
|
|
224 | 187 |
removeMapMarker, |
225 |
moveMarkerToDestination,
|
|
188 |
changeMarkerIdx,
|
|
226 | 189 |
clear, |
227 | 190 |
mergeWithCurrentPath, |
228 | 191 |
setDisplayedPathIdx, |
192 |
updateMapPointAtIndex |
|
229 | 193 |
} = trackingToolSlice.actions |
230 | 194 |
|
231 | 195 |
const trackingToolReducer = trackingToolSlice.reducer |
frontend/src/features/TrackingTool/trackingToolUtils.ts | ||
---|---|---|
1 | 1 |
|
2 | 2 |
import L, { LatLngTuple, PointExpression } from 'leaflet' |
3 | 3 |
import { CatalogItemDto } from '../../swagger/data-contracts' |
4 |
import generateUuid from '../../utils/id/uuidGenerator' |
|
4 | 5 |
|
5 | 6 |
// For more comprehensive code alias CatalogItemDto[] as path variant |
6 | 7 |
export type PathVariant = MapPoint[] |
... | ... | |
14 | 15 |
|
15 | 16 |
// Represents a point on the map - wrapper for CatalogItemDto to make it easier to work with |
16 | 17 |
export interface MapPoint { |
17 |
id: string // unique id for react |
|
18 |
id: string // unique id to identify the point on the map |
|
19 |
reactId: string // unique id to identify item in React - e.g. rendering in a list |
|
18 | 20 |
idx: number, |
19 | 21 |
addToPath: boolean, // whether to add the point to the path |
20 | 22 |
catalogItem: CatalogItemDto, |
... | ... | |
65 | 67 |
[MapPointType.FromCoordinates]: createMapMarkerSvg('#21972D'), |
66 | 68 |
} |
67 | 69 |
|
68 |
const iconAnchor = [25, 25] as PointExpression
|
|
69 |
const iconSize = [40, 40] as PointExpression
|
|
70 |
const iconAnchor = [22, 22] as PointExpression
|
|
71 |
const iconSize = [35, 35] as PointExpression
|
|
70 | 72 |
|
71 | 73 |
const mapMarkers = { |
72 | 74 |
[MapPointType.LocalCatalog]: L.icon({ |
... | ... | |
104 | 106 |
.map((item) => item.catalogItem.longitude ?? 0) |
105 | 107 |
.reduce((a, b) => a + b, 0) / displayableItems.length, |
106 | 108 |
] |
107 |
} |
|
109 |
} |
|
110 |
|
|
111 |
export const setMapPointIds = (pathVariant: PathVariant): PathVariant => pathVariant.map(mapPoint => { |
|
112 |
// Get identifier - either catalog item identifier is used or generate a new UUID if the point does not have uuid |
|
113 |
// - e.g. is not from the catalog |
|
114 |
mapPoint.id = mapPoint.catalogItem.id ? mapPoint.catalogItem.id : generateUuid() |
|
115 |
return mapPoint |
|
116 |
}) |
Také k dispozici: Unified diff
fixed keys and indices
re #9741