Projekt

Obecné

Profil

« Předchozí | Další » 

Revize de12c6be

Přidáno uživatelem Václav Honzík před asi 2 roky(ů)

draggable

re #9741

Zobrazit rozdíly:

frontend/src/features/TrackingTool/MapMarker.tsx
1
import { LatLngTuple, Marker as MarkerPOJO } from 'leaflet'
2
import { FunctionComponent, ReactNode, useMemo, useRef, useState } from 'react'
3
import { Marker } from 'react-leaflet'
1
import { LatLngTuple, Marker as MarkerPOJO } from "leaflet"
2
import { FunctionComponent, ReactNode, useMemo, useRef, useState } from "react"
3
import { Marker } from "react-leaflet"
4 4

  
5 5
export interface MapMarkerProps {
6 6
    position: LatLngTuple
7 7
    children?: ReactNode
8
    color?: "external" | "disabled" | "localCatalog"
9
    updatePositionCallbackFn: (position: LatLngTuple) => void // Callback function to notify MapPath to rerender the path
8 10
}
9 11

  
10 12
// Custom Map Marker component
11
const MapMarker: FunctionComponent<MapMarkerProps> = ({ position, children }) => {
13
const MapMarker: FunctionComponent<MapMarkerProps> = ({
14
    position,
15
    children,
16
    updatePositionCallbackFn,
17
}) => {
12 18
    const [currentPosition, setCurrentPosition] = useState(position)
13 19
    const markerRef = useRef<MarkerPOJO | null>(null)
14 20
    const eventHandlers = useMemo(
......
21 27
                }
22 28
                const latlng = marker.getLatLng()
23 29
                setCurrentPosition([latlng.lat, latlng.lng])
30
                updatePositionCallbackFn([latlng.lat, latlng.lng])
24 31
            },
25 32
        }),
26
        []
33
        [updatePositionCallbackFn]
27 34
    )
28 35

  
29 36
    return (
frontend/src/features/TrackingTool/MapPath.tsx
1
import { Fragment, FunctionComponent, useEffect, useState } from 'react'
2
import { CatalogItemDto } from '../../swagger/data-contracts'
3
import { PathVariant } from './buildPathVariants'
4
import TextPath from 'react-leaflet-textpath'
5
import { Marker, Popup } from 'react-leaflet'
6
import {
7
    Checkbox,
8
    FormControlLabel,
9
    Paper,
10
    Stack,
11
    Typography,
12
} from '@mui/material'
13
import { formatHtmlStringToReactDom } from '../../utils/formatting/HtmlUtils'
14
import { DialogCatalogItemDetail as CatalogItemDetailDialog } from '../Catalog/CatalogItemDetail'
15
import { useDispatch, useSelector } from 'react-redux'
16
import { RootState } from '../redux/store'
17
import MapMarker from './MapMarker'
18

  
19
// CatalogItemDto wrapper to keep track whether the item is active or not
20
class DisplayableMapPoint {
21
    constructor(
22
        public readonly catalogItem: CatalogItemDto,
23
        public active: boolean = true
24
    ) {}
25
}
1
import { Fragment, FunctionComponent, useEffect, useState } from "react"
2
import { useDispatch, useSelector } from "react-redux"
3
import { RootState } from "../redux/store"
4
import { PathVariant, MapPoint } from "./buildPathVariants"
5
import TextPath from "react-leaflet-textpath"
6
import { setPrimaryIdx, updateMapMarkerPosition } from "./trackingToolSlice"
7
import MapMarker from "./MapMarker"
8
import { LatLngTuple } from "leaflet"
9
import { Popup } from "react-leaflet"
10
import { Checkbox, FormControlLabel, Stack, Typography } from "@mui/material"
11
import { formatHtmlStringToReactDom } from "../../utils/formatting/HtmlUtils"
12
import { DialogCatalogItemDetail as CatalogItemDetailDialog } from "../Catalog/CatalogItemDetail"
26 13

  
27 14
export interface MapPathProps {
28
    pathVariant: PathVariant // aka CatalogItemDto[]
29
    idx: number // index of path in the list
15
    idx: number // index of the path in the list
30 16
}
31 17

  
18
type EdgeElement = any
19

  
32 20
// Blue
33
export const primaryPathColor = '#346eeb'
21
export const primaryPathColor = "#346eeb"
34 22

  
35 23
// Grey
36
export const secondaryPathColor = '#878e9c'
24
export const secondaryPathColor = "#878e9c"
37 25

  
38
// Map path component
39
const MapPath: FunctionComponent<MapPathProps> = ({ idx, pathVariant }) => {
40
    // List of all map points that belong to the path
41
    const pathVariants = useSelector(
42
        (state: RootState) => state.trackingTool.pathVariants
43
    )
44
    const [mapPoints, setMapPoints] = useState<DisplayableMapPoint[]>([])
26
const MapPath: FunctionComponent<MapPathProps> = ({ idx }) => {
45 27
    const dispatch = useDispatch()
46 28

  
47
    // Set of all active paths
48
    const activePaths = useSelector(
49
        (state: RootState) => state.trackingTool.activePaths
29
    // Get list of all paths from the store
30
    // And extract path from them
31
    const paths = useSelector(
32
        (state: RootState) => state.trackingTool.pathVariants
50 33
    )
51
    // Index of the primary path
34
    const [path, setPath] = useState<PathVariant>([])
35
    useEffect(() => {
36
        // Either set the path if it exists or set it to an empty array
37
        setPath(paths && paths.length > idx ? paths[idx] : [])
38
    }, [paths])
39

  
40
    // Primary path index to set the correct color
52 41
    const primaryPathIdx = useSelector(
53 42
        (state: RootState) => state.trackingTool.primaryPathIdx
54 43
    )
55 44

  
56
    // Whether the path is active or not
57
    const [active, setActive] = useState(false)
45
    // List of all active map points
46
    const [displayableMapPoints, setDisplayableMapPoints] = useState<
47
        MapPoint[]
48
    >([])
58 49
    useEffect(() => {
59
        setActive(activePaths.has(idx))
60
    }, [activePaths, idx])
50
        // Set all displayable vertices
51
        setDisplayableMapPoints(path.filter((vertex) => vertex.displayable))
52
    }, [path])
61 53

  
62
    const getActiveMapPoints = () => mapPoints.filter((item) => item.active)
63

  
64
    // Refresh the list of map points if it has changed
54
    // List of all edges in the path
55
    const [edges, setEdges] = useState<EdgeElement[]>([])
65 56
    useEffect(() => {
66
        if (!pathVariants || pathVariants.length <= idx) {
67
            return
68
        }
69
        setMapPoints(
70
            [...pathVariants[idx]]
71
                .filter((item) => item.latitude && item.longitude)
72
                .map((item) => new DisplayableMapPoint(item))
57
        // Get all active map points
58
        const activeMapPoints = displayableMapPoints.filter(
59
            (item) => item.active
73 60
        )
74
    }, [pathVariants, idx])
75

  
76
    // Builds all edges of the path
77
    const buildEdges = () => {
78
        const activeMapPoints = getActiveMapPoints()
79 61
        if (activeMapPoints.length < 2) {
80
            return null
62
            return
81 63
        }
82 64

  
83
        // Create path edges
84
        const edges: any[] = []
65
        // Build edges
66
        const edges = []
85 67
        for (let i = 0; i < activeMapPoints.length - 1; i += 1) {
68
            const [start, end] = [
69
                activeMapPoints[i].catalogItem,
70
                activeMapPoints[i + 1].catalogItem,
71
            ]
86 72
            edges.push(
87 73
                <TextPath
74
                    // Somehow this refuses to work so let it rerender everything ...
75
                    // key={`${start.latitude},${start.longitude}-${end.latitude},${end.longitude}`}
76
                    // key={`${start.id}-${end.id}`}
88 77
                    positions={[
89
                        [
90
                            activeMapPoints[i].catalogItem.latitude,
91
                            activeMapPoints[i].catalogItem.longitude,
92
                        ],
93
                        [
94
                            activeMapPoints[i + 1].catalogItem.latitude,
95
                            activeMapPoints[i + 1].catalogItem.longitude,
96
                        ],
78
                        [start.latitude, start.longitude],
79
                        [end.latitude, end.longitude],
97 80
                    ]}
98 81
                    text="►"
99 82
                    // text=" > > > > "
100 83
                    attributes={{
101
                        'font-size': 19,
84
                        "font-size": 19,
102 85
                        // Set to primaryPathColor if primary index in the tracking tool is equal to this index
103 86
                        fill:
104 87
                            primaryPathIdx === idx
105 88
                                ? primaryPathColor
106 89
                                : secondaryPathColor,
107 90
                    }}
108
                    onClick={() => {
109
                        dispatch(setPrimaryIdx(idx))
110
                    }}
91
                    onClick={() => dispatch(setPrimaryIdx(idx))}
111 92
                    repeat
112 93
                    center
113 94
                    weight={0}
114 95
                />
115 96
            )
116 97
        }
98
        setEdges(edges)
99
    }, [dispatch, displayableMapPoints, idx, primaryPathIdx])
117 100

  
118
        // Return the path
119
        return edges
120
    }
121

  
122
    /**
123
     * Creates a list of all vertices in the path
124
     */
125
    const buildVertices = () => {
126
        return mapPoints.map((mapPoint, idx) => (
127
            <MapMarker
128
                key={idx}
129
                position={[
130
                    mapPoint.catalogItem.latitude as number,
131
                    mapPoint.catalogItem.longitude as number,
132
                ]}
133
            >
134
                <Popup>
135
                    <Fragment>
136
                        <Stack direction="column" sx={{ m: 0 }}>
137
                            <Typography
138
                                variant="h6"
139
                                fontWeight="bold"
140
                                fontSize={16}
141
                            >
142
                                {formatHtmlStringToReactDom(
143
                                    mapPoint.catalogItem.name as string
144
                                )}
145
                            </Typography>
146
                            <FormControlLabel
147
                                control={
148
                                    <Checkbox
149
                                        checked={mapPoint.active}
150
                                        onChange={() => {
151
                                            mapPoint.active = !mapPoint.active
152
                                            setMapPoints([...mapPoints])
153
                                        }}
154
                                    />
155
                                }
156
                                labelPlacement="end"
157
                                label="Active"
158
                            />
159
                            <CatalogItemDetailDialog
160
                                itemId={mapPoint.catalogItem.id ?? ''}
161
                            />
162
                        </Stack>
163
                    </Fragment>
164
                </Popup>
165
            </MapMarker>
166
        ))
167
    }
101
    // List of vertices to display
102
    const [vertices, setVertices] = useState<JSX.Element[]>([])
103
    useEffect(() => {
104
        // Iterate over all displayable map points and map them to MapMarker
105
        setVertices(
106
            displayableMapPoints.map((item) => (
107
                <MapMarker
108
                    key={`${item.catalogItem.latitude}${item.catalogItem.latitude}`}
109
                    position={[
110
                        item.catalogItem.latitude as number,
111
                        item.catalogItem.longitude as number,
112
                    ]}
113
                    updatePositionCallbackFn={(position: LatLngTuple) => {
114
                        // Update the position of the map point
115
                        dispatch(
116
                            updateMapMarkerPosition({
117
                                idx,
118
                                mapPointIdx: item.idx,
119
                                position,
120
                            })
121
                        )
122
                    }}
123
                >
124
                    <Popup>
125
                        <Fragment>
126
                            <Stack direction="column" sx={{ m: 0 }}>
127
                                <Typography
128
                                    variant="h6"
129
                                    fontWeight="bold"
130
                                    fontSize={16}
131
                                >
132
                                    {formatHtmlStringToReactDom(
133
                                        item.catalogItem.name as string
134
                                    )}
135
                                </Typography>
136
                                <FormControlLabel
137
                                    control={
138
                                        <Checkbox
139
                                            checked={item.active}
140
                                            onChange={() => {
141
                                                item.active = !item.active
142
                                                setDisplayableMapPoints([
143
                                                    ...displayableMapPoints,
144
                                                ])
145
                                            }}
146
                                        />
147
                                    }
148
                                    labelPlacement="end"
149
                                    label="Active"
150
                                />
151
                                <CatalogItemDetailDialog
152
                                    itemId={item.catalogItem.id ?? ""}
153
                                />
154
                            </Stack>
155
                        </Fragment>
156
                    </Popup>
157
                </MapMarker>
158
            ))
159
        )
160
    }, [dispatch, displayableMapPoints, idx])
168 161

  
169 162
    return (
170 163
        <Fragment>
171
            {active && (
172
                <Fragment>
173
                    {buildVertices()}
174
                    {buildEdges()}
175
                </Fragment>
176
            )}
164
            {vertices}
165
            {edges}
177 166
        </Fragment>
178 167
    )
179 168
}
180 169

  
181 170
export default MapPath
182
function setPrimaryIdx(idx: number): any {
183
    throw new Error('Function not implemented.')
184
}
frontend/src/features/TrackingTool/TrackingTool.tsx
1 1
import {
2
    Button,
3
    Card,
4
    CardContent,
5
    Grid,
6
    Stack,
7
    Typography,
8
} from '@mui/material'
9
import { Fragment, useEffect, useState } from 'react'
10
import { MapContainer, TileLayer, useMap } from 'react-leaflet'
11
import mapConfig from '../../config/mapConfig'
12
import TextPath from 'react-leaflet-textpath'
13
import PlaintextUpload from './PlaintextUpload'
14
import FileUpload from './FileUpload'
15
import L from 'leaflet'
16
import DeleteIcon from '@mui/icons-material/Delete'
17
import { PathDto } from '../../swagger/data-contracts'
18
import { formatHtmlStringToReactDom } from '../../utils/formatting/HtmlUtils'
19
import MapPath from './MapPath'
20
import EditIcon from '@mui/icons-material/Edit'
21
import { useDispatch, useSelector } from 'react-redux'
22
import { RootState } from '../redux/store'
23
import { clear, consumeErr as consumeError } from './trackingToolSlice'
24
import { showNotification } from '../Notification/notificationSlice'
25
import ClearIcon from '@mui/icons-material/Clear'
2
  Button,
3
  Card,
4
  CardContent,
5
  Grid,
6
  Stack,
7
  Typography,
8
} from "@mui/material"
9
import { Fragment, useEffect, useState } from "react"
10
import { MapContainer, TileLayer, useMap } from "react-leaflet"
11
import mapConfig from "../../config/mapConfig"
12
import TextPath from "react-leaflet-textpath"
13
import PlaintextUpload from "./PlaintextUpload"
14
import FileUpload from "./FileUpload"
15
import L from "leaflet"
16
import DeleteIcon from "@mui/icons-material/Delete"
17
import { PathDto } from "../../swagger/data-contracts"
18
import { formatHtmlStringToReactDom } from "../../utils/formatting/HtmlUtils"
19
import MapPath from "./MapPath"
20
import EditIcon from "@mui/icons-material/Edit"
21
import { useDispatch, useSelector } from "react-redux"
22
import { RootState } from "../redux/store"
23
import { clear, consumeErr as consumeError } from "./trackingToolSlice"
24
import { showNotification } from "../Notification/notificationSlice"
25
import ClearIcon from "@mui/icons-material/Clear"
26 26

  
27 27
// Page with tracking tool
28 28
const TrackingTool = () => {
29
    // Path response from the API
30
    const pathDto = useSelector(
31
        (state: RootState) => state.trackingTool.pathDto
32
    )
33
    const pathVariants = useSelector(
34
        (state: RootState) => state.trackingTool.pathVariants
35
    )
36
    const mapCenter = useSelector(
37
        (state: RootState) => state.trackingTool.mapCenter
38
    )
39

  
40
    // const map = useMap()
29
  // Path response from the API
30
  const pathDto = useSelector((state: RootState) => state.trackingTool.pathDto)
31
  const pathVariants = useSelector(
32
    (state: RootState) => state.trackingTool.pathVariants
33
  )
34
  const mapCenter = useSelector(
35
    (state: RootState) => state.trackingTool.mapCenter
36
  )
41 37

  
42
    // // Set the map center
43
    // useEffect(() => {
44
    //     map.flyTo(mapCenter, mapConfig.defaultZoom)
45
    // }, [map, mapCenter])
38
  // Consume any error
39
  const err = useSelector((state: RootState) => state.trackingTool.lastError)
40
  const dispatch = useDispatch()
46 41

  
47
    // Consume any error
48
    const err = useSelector((state: RootState) => state.trackingTool.lastError)
49
    const dispatch = useDispatch()
42
  useEffect(() => {
43
    if (!err) {
44
      return
45
    }
46
    const error = `${err}`
47
    dispatch(consumeError())
48
    dispatch(
49
      showNotification({
50
        message: error,
51
        severity: "error",
52
      })
53
    )
54
  }, [err, dispatch])
50 55

  
51
    useEffect(() => {
52
        if (!err) {
53
            return
54
        }
55
        const error = `${err}`
56
        dispatch(consumeError())
57
        dispatch(
58
            showNotification({
59
                message: error,
60
                severity: 'error',
61
            })
62
        )
63
    }, [err, dispatch])
56
  return (
57
    <Fragment>
58
      <Typography variant="h3" sx={{ mb: 2 }} fontWeight="bold">
59
        Tracking Tool
60
      </Typography>
64 61

  
65
    return (
66
        <Fragment>
67
            <Typography variant="h3" sx={{ mb: 2 }} fontWeight="bold">
68
                Tracking Tool
62
      <Grid container>
63
        <Grid item xs={12}>
64
          {pathDto && pathDto?.foundCatalogItems?.length === 0 && (
65
            <Typography
66
              variant="body1"
67
              sx={{ mb: 2 }}
68
              fontWeight="500"
69
              align="center"
70
              color="error.main"
71
            >
72
              Looks like no path / catalog items match this query.
69 73
            </Typography>
74
          )}
75
          {!pathDto && (
76
            <Stack
77
              direction="row"
78
              alignItems="flex-start"
79
              spacing={2}
80
              sx={{ mt: 1 }}
81
            >
82
              <Typography variant="h5" sx={{ mb: 2 }} fontWeight="500">
83
                Upload:
84
              </Typography>
85
              <PlaintextUpload />
86
              <FileUpload />
87
            </Stack>
88
          )}
70 89

  
71
            <Grid container>
72
                <Grid item xs={12}>
73
                    {pathDto && pathDto?.foundCatalogItems?.length === 0 && (
74
                        <Typography
75
                            variant="body1"
76
                            sx={{ mb: 2 }}
77
                            fontWeight="500"
78
                            align="center"
79
                            color="error.main"
80
                        >
81
                            Looks like no path / catalog items match this query.
82
                        </Typography>
83
                    )}
84
                    {!pathDto && (
85
                        <Stack
86
                            direction="row"
87
                            alignItems="flex-start"
88
                            spacing={2}
89
                            sx={{ mt: 1 }}
90
                        >
91
                            <Typography
92
                                variant="h5"
93
                                sx={{ mb: 2 }}
94
                                fontWeight="500"
95
                            >
96
                                Upload:
97
                            </Typography>
98
                            <PlaintextUpload />
99
                            <FileUpload />
100
                        </Stack>
101
                    )}
102

  
103
                    {pathDto && (
104
                        <Stack alignItems="flex-end">
105
                            <Button
106
                                startIcon={<ClearIcon />}
107
                                sx={{ mb: 1 }}
108
                                variant="contained"
109
                                onClick={() => dispatch(clear())}
110
                            >
111
                                Clear Map
112
                            </Button>
113
                        </Stack>
114
                    )}
115
                </Grid>
90
          {pathDto && (
91
            <Stack alignItems="flex-end">
92
              <Button
93
                startIcon={<ClearIcon />}
94
                sx={{ mb: 1 }}
95
                variant="contained"
96
                onClick={() => dispatch(clear())}
97
              >
98
                Clear Map
99
              </Button>
100
            </Stack>
101
          )}
102
        </Grid>
116 103

  
117
                <Grid
118
                    item
119
                    xs={12}
120
                    md={12}
121
                    style={{
122
                        minHeight: '60vh',
123
                        maxHeight: '100vh',
124
                        width: '100%',
125
                    }}
126
                >
127
                    <MapContainer
128
                        center={[mapCenter[0], mapCenter[1]]}
129
                        zoom={mapConfig.defaultZoom}
130
                        style={{ height: '100%', minHeight: '100%' }}
131
                    >
132
                        <TileLayer
133
                            attribution={mapConfig.attribution}
134
                            url={mapConfig.url}
135
                        />
136
                        {pathVariants?.map((pathVariant, idx) => (
137
                            <MapPath pathVariant={pathVariant} idx={idx} />
138
                        ))}
139
                    </MapContainer>
140
                    {pathDto && (
141
                        <Fragment>
142
                            <Card variant="outlined" sx={{ mt: 2 }}>
143
                                <CardContent>
144
                                    <Stack direction="column">
145
                                        <Typography
146
                                            variant="h5"
147
                                            sx={{ mb: 1 }}
148
                                            fontWeight="600"
149
                                        >
150
                                            Processed Text
151
                                        </Typography>
152
                                        <Typography variant="body2">
153
                                            {formatHtmlStringToReactDom(
154
                                                pathDto.text ?? ''
155
                                            )}
156
                                        </Typography>
157
                                    </Stack>
158
                                </CardContent>
159
                            </Card>
160
                        </Fragment>
161
                    )}
162
                </Grid>
163
            </Grid>
164
        </Fragment>
165
    )
104
        <Grid
105
          item
106
          xs={12}
107
          md={12}
108
          style={{
109
            minHeight: "60vh",
110
            maxHeight: "100vh",
111
            width: "100%",
112
          }}
113
        >
114
          <MapContainer
115
            center={[mapCenter[0], mapCenter[1]]}
116
            zoom={mapConfig.defaultZoom}
117
            style={{ height: "100%", minHeight: "100%" }}
118
          >
119
            <TileLayer
120
              attribution={mapConfig.attribution}
121
              url={mapConfig.url}
122
            />
123
            {pathVariants?.map((pathVariant, idx) => (
124
              <MapPath idx={idx} />
125
            ))}
126
          </MapContainer>
127
          {pathDto && (
128
            <Fragment>
129
              <Card variant="outlined" sx={{ mt: 2 }}>
130
                <CardContent>
131
                  <Stack direction="column">
132
                    <Typography variant="h5" sx={{ mb: 1 }} fontWeight="600">
133
                      Processed Text
134
                    </Typography>
135
                    <Typography variant="body2">
136
                      {formatHtmlStringToReactDom(pathDto.text ?? "")}
137
                    </Typography>
138
                  </Stack>
139
                </CardContent>
140
              </Card>
141
            </Fragment>
142
          )}
143
        </Grid>
144
      </Grid>
145
    </Fragment>
146
  )
166 147
}
167 148

  
168 149
export default TrackingTool
frontend/src/features/TrackingTool/buildPathVariants.ts
3 3
import { CatalogItemDto, PathDto } from '../../swagger/data-contracts'
4 4

  
5 5
// For more comprehensive code alias CatalogItemDto[] as path variant
6
export type PathVariant = CatalogItemDto[]
6
export type PathVariant = MapPoint[]
7 7

  
8
export class MapPoint {
9
    constructor(
10
        public idx: number,
11
        public active: boolean,
12
        public catalogItem: CatalogItemDto
13
    ) { }
14

  
15
    /**
16
     * @returns true if the map point is displayable - i.e. it has a valid lat/lng
17
     */
18
    get displayable() {
19
        return !!this.catalogItem.latitude && !!this.catalogItem.longitude
20
    }
21
}
22

  
23
/**
24
 * Cartesian product of two arrays
25
 * @param sets
26
 * @returns
27
 */
8 28
const cartesianProduct = (sets: CatalogItemDto[][]): CatalogItemDto[][] =>
9 29
    sets.reduce<CatalogItemDto[][]>(
10 30
        (results, ids) =>
......
14 34
        [[]]
15 35
    )
16 36

  
37
/**
38
 * Builds a list of all possible path variants from pathDto
39
 * @param pathDto
40
 * @returns
41
 */
17 42
export const buildPathVariants = (pathDto: PathDto): PathVariant[] => {
18 43
    if (!pathDto.foundCatalogItems) {
19 44
        return []
20 45
    }
21 46

  
22
    const catalogItems = pathDto.foundCatalogItems
23
    if (catalogItems.length === 1) {
24
        return catalogItems
25
    }
26

  
27
    return cartesianProduct(catalogItems)
47
    return (
48
        pathDto.foundCatalogItems.length === 1
49
            ? pathDto.foundCatalogItems
50
            : cartesianProduct(pathDto.foundCatalogItems)
51
    ).map((variant, _) =>
52
        variant.map(
53
            (catalogItem, idx) =>
54
                new MapPoint(
55
                    idx,
56
                    !!catalogItem.latitude && !!catalogItem.longitude,
57
                    catalogItem
58
                )
59
        )
60
    )
28 61
}
29 62

  
30 63
export default buildPathVariants
frontend/src/features/TrackingTool/trackingToolSlice.ts
1
import { createSlice } from '@reduxjs/toolkit'
2
import { LatLngTuple } from 'leaflet'
3
import { persistReducer } from 'redux-persist'
4
import mapConfig from '../../config/mapConfig'
5
import { PathDto } from '../../swagger/data-contracts'
6
import buildPathVariants, { PathVariant } from './buildPathVariants'
7
import { sendTextForProcessing } from './trackingToolThunks'
8
import storage from 'redux-persist/lib/storage'
1
import { createSlice } from "@reduxjs/toolkit"
2
import { LatLngTuple } from "leaflet"
3
import { persistReducer } from "redux-persist"
4
import mapConfig from "../../config/mapConfig"
5
import { PathDto } from "../../swagger/data-contracts"
6
import buildPathVariants, { PathVariant } from "./buildPathVariants"
7
import { sendTextForProcessing } from "./trackingToolThunks"
8
import storage from "redux-persist/lib/storage"
9 9

  
10 10
export interface TrackingToolState {
11 11
    isLoading: boolean // whether the data is being loaded
......
14 14
    lastError?: string // consumable for errors during thunks
15 15
    mapCenter: LatLngTuple // pair of latitude and longitude
16 16
    primaryPathIdx: number // index of the primary path
17
    activePaths: Set<number> // indices of the active paths
18 17
    // trigger to close the dialog when API call is finished
19 18
    dialogApiCallSuccess: boolean
20 19
    pathsPerPage: number // max number of paths to show on the map at once
......
25 24

  
26 25
const initialState: TrackingToolState = {
27 26
    isLoading: false,
28
    mapCenter: [
29
        mapConfig.defaultCoordinates[0],
30
        mapConfig.defaultCoordinates[1],
31
    ],
27
    mapCenter: [mapConfig.defaultCoordinates[0], mapConfig.defaultCoordinates[1]],
32 28
    primaryPathIdx: 0,
33
    activePaths: new Set(),
34 29
    dialogApiCallSuccess: true,
35 30
    pathsPerPage: defaultPathsPerPage,
36 31
    currentPage: 0,
......
38 33

  
39 34
// Returns tuple of average latitude and longitude
40 35
const calculateMapCenter = (pathVariant: PathVariant): LatLngTuple => [
41
    pathVariant.map((item) => item.latitude ?? 0).reduce((a, b) => a + b, 0) /
42
        pathVariant.length,
43
    pathVariant.map((item) => item.longitude ?? 0).reduce((a, b) => a + b, 0) /
44
        pathVariant.length,
36
    pathVariant
37
        .map((item) => item.catalogItem.latitude ?? 0)
38
        .reduce((a, b) => a + b, 0) / pathVariant.length,
39
    pathVariant
40
        .map((item) => item.catalogItem.longitude ?? 0)
41
        .reduce((a, b) => a + b, 0) / pathVariant.length,
45 42
]
46 43

  
47 44
const persistConfig = {
48
    key: 'auth',
45
    key: "auth",
49 46
    storage, // localStorage for browsers
50 47
}
51 48

  
52 49
export const trackingToolSlice = createSlice({
53
    name: 'trackingTool',
50
    name: "trackingTool",
54 51
    initialState,
55 52
    reducers: {
56
        consumeErr: (state: TrackingToolState) => ({ ...state, lastErr: undefined }),
53
        consumeErr: (state: TrackingToolState) => ({
54
            ...state,
55
            lastErr: undefined,
56
        }),
57 57
        setPrimaryIdx: (state: TrackingToolState, action: any) => ({
58 58
            ...state,
59 59
            primaryPathIdx: action.payload,
......
62 62
            ...state,
63 63
            dialogApiCallSuccess: false,
64 64
        }),
65
        setPage: (state: TrackingToolState, action: any) => ({
65
        setPage: (state: TrackingToolState, action: { payload: number }) => ({
66 66
            ...state,
67 67
            currentPage: action.payload,
68 68
        }),
69
        clear: () => ({...initialState})
69
        updateMapMarkerPosition: (state: TrackingToolState, action: any) => {
70
            const { idx, mapPointIdx, position } = action.payload
71
            if (!state.pathVariants || state.pathVariants.length <= idx) {
72
                return state
73
            }
74

  
75
            return {
76
                ...state,
77
                pathVariants: state.pathVariants.map((pathVariant, i) => {
78
                    if (i !== idx) {
79
                        return [...pathVariant]
80
                    }
81

  
82
                    const mapPoint = pathVariant[mapPointIdx]
83
                    mapPoint.catalogItem = {
84
                        ...mapPoint.catalogItem,
85
                        latitude: position[0],
86
                        longitude: position[1]
87
                    }
88

  
89
                    return [
90
                        ...pathVariant.slice(0, mapPointIdx),
91
                        mapPoint,
92
                        ...pathVariant.slice(mapPointIdx + 1),
93
                    ]
94
                })
95
            }
96
        },
97
        clear: () => ({ ...initialState }),
70 98
    },
71 99
    extraReducers: (builder) => {
72 100
        builder.addCase(sendTextForProcessing.fulfilled, (state, action) => {
......
76 104
                ...state,
77 105
                pathVariants,
78 106
                pathDto,
79
                // TODO map this correctly
80
                activePaths: new Set(pathVariants.map((_, idx) => idx)),
81 107
                // TODO calculate correctly
82 108
                mapCenter:
83 109
                    pathVariants.length > 0
......
105 131
    },
106 132
})
107 133

  
108
export const { consumeErr, setPrimaryIdx, resetDialogApiCallSuccess, clear } = trackingToolSlice.actions
134
export const { consumeErr, setPrimaryIdx, resetDialogApiCallSuccess, clear, updateMapMarkerPosition } =
135
    trackingToolSlice.actions
109 136
const trackingToolReducer = trackingToolSlice.reducer
110 137
export default trackingToolReducer

Také k dispozici: Unified diff