Projekt

Obecné

Profil

« Předchozí | Další » 

Revize b70813cb

Přidáno uživatelem Václav Honzík před téměř 3 roky(ů)

map displays correctly

re #9629

Zobrazit rozdíly:

frontend/src/App.tsx
8 8
import { useSelector } from 'react-redux'
9 9
import { RootState } from './features/redux/store'
10 10
import Login from './features/Auth/Login'
11
import CatalogItemDetail from './features/Catalog/CatalogItemDetail'
11
import { RoutedCatalogItemDetail } from './features/Catalog/CatalogItemDetail'
12 12
import Navigation from './features/Navigation/Navigation'
13 13
import TrackingTool from './features/TrackingTool/TrackingTool'
14 14
import Logout from './features/Auth/Logout'
......
30 30
                            <Route path="/catalog" element={<Catalog />} />
31 31
                            <Route
32 32
                                path="/catalog/:itemId"
33
                                element={<CatalogItemDetail />}
33
                                element={<RoutedCatalogItemDetail />}
34 34
                            />
35 35
                            <Route path="/login" element={<Login />} />
36 36
                            <Route path="/logout" element={<Logout />} />
frontend/src/features/Catalog/CatalogItemDetail.tsx
1
import { Button, Divider, Grid, Paper, Typography } from '@mui/material'
2
import { Fragment, useEffect, useState } from 'react'
1
import {
2
    Button,
3
    Dialog,
4
    DialogContent,
5
    Divider,
6
    Grid,
7
    Paper,
8
    Stack,
9
    Typography,
10
} from '@mui/material'
11
import { Fragment, FunctionComponent, useEffect, useState } from 'react'
3 12
import { useParams } from 'react-router-dom'
4 13
import axiosInstance from '../../api/api'
5 14
import { CatalogItemDto } from '../../swagger/data-contracts'
......
8 17
import CatalogItemMap from './CatalogItemMap'
9 18
import ArrowBackIosIcon from '@mui/icons-material/ArrowBackIos'
10 19
import { Link as RouterLink } from 'react-router-dom'
11
import DOMPurify from 'dompurify'
12 20
import { formatHtmlStringToReactDom } from '../../utils/formatting/HtmlUtils'
13 21

  
14 22
const apiError =
15 23
    'Error while fetching data from the server, please try again later.'
16 24

  
17
const CatalogItemDetail = () => {
18
    // itemId from query params
19
    const { itemId } = useParams()
25
export interface CatalogItemDetailProps {
26
    itemId: string
27
    showReturnToCatalogButton?: boolean
28
}
20 29

  
30
const CatalogItemDetail: FunctionComponent<CatalogItemDetailProps> = ({
31
    itemId,
32
    showReturnToCatalogButton,
33
}) => {
21 34
    const [item, setItem] = useState<CatalogItemDto | undefined>(undefined)
22 35
    const [isItemLoading, setIsItemLoading] = useState(true)
23 36
    const [err, setErr] = useState<string | undefined>(undefined)
......
54 67
                </Grid>
55 68
                <Grid item xs={4} sx={{ ml: 'auto' }}>
56 69
                    {items.map((item) => (
57
                        <Typography key={item}>{item}</Typography>
70
                        <Typography key={item}>
71
                            {formatHtmlStringToReactDom(item)}
72
                        </Typography>
58 73
                    ))}
59 74
                </Grid>
60 75
            </Grid>
......
98 113
    ]
99 114

  
100 115
    return (
101
        // TODO remove min height
102 116
        <Fragment>
103
            <Button
104
                startIcon={<ArrowBackIosIcon />}
105
                variant="contained"
106
                component={RouterLink}
107
                to="/catalog"
108
                color="primary"
109
                sx={{ mb: 2 }}
110
            >
111
                Return To Catalog
112
            </Button>
117
            {showReturnToCatalogButton && (
118
                <Stack
119
                    direction="row"
120
                    alignItems="flex-start"
121
                    spacing={2}
122
                    sx={{ mt: 1 }}
123
                >
124
                    <Button
125
                        startIcon={<ArrowBackIosIcon />}
126
                        variant="contained"
127
                        component={RouterLink}
128
                        to="/catalog"
129
                        color="primary"
130
                        sx={{ mb: 2 }}
131
                    >
132
                        Return To Catalog
133
                    </Button>
134
                </Stack>
135
            )}
113 136
            <ShowErrorIfPresent err={err} />
114 137

  
115 138
            <Paper style={{ minHeight: '100vh' }} variant="outlined">
......
141 164
    )
142 165
}
143 166

  
167
export const RoutedCatalogItemDetail = () => {
168
    const { itemId } = useParams()
169
    return <CatalogItemDetail itemId={itemId ?? ''} />
170
}
171

  
172
export const DialogCatalogItemDetail: FunctionComponent<
173
    CatalogItemDetailProps
174
> = ({ itemId }) => {
175
    const [open, setOpen] = useState(false)
176
    return (
177
        <Fragment>
178
            <Button variant="contained" onClick={() => setOpen(true)}>
179
                Detail
180
            </Button>
181
            <Dialog
182
                open={open}
183
                onClose={() => setOpen(false)}
184
                fullWidth
185
                maxWidth="lg"
186
            >
187
                <DialogContent>
188
                    <CatalogItemDetail itemId={itemId} />
189
                </DialogContent>
190
            </Dialog>
191
        </Fragment>
192
    )
193
}
194

  
144 195
export default CatalogItemDetail
frontend/src/features/TrackingTool/MapPath.tsx
1
import { Fragment, FunctionComponent, 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 { Checkbox, FormControlLabel, Stack, Typography } from '@mui/material'
7
import { formatHtmlStringToReactDom } from '../../utils/formatting/HtmlUtils'
8
import { DialogCatalogItemDetail as CatalogItemDetailDialog } from '../Catalog/CatalogItemDetail'
9

  
10
// CatalogItemDto wrapper to keep track whether the item is active or not
11
class DisplayableMapPoint {
12
    constructor(
13
        public readonly catalogItem: CatalogItemDto,
14
        public active: boolean = true
15
    ) {}
16
}
17

  
18
export interface MapPathProps {
19
    pathVariant: PathVariant // aka CatalogItemDto[]
20
    active: boolean // whether this component is active
21
    idx: number // index of path in the list
22
    primaryIdx: number // reference to index of path which has primary color
23
    setPrimary: (idx: number) => void // callback to set the primary path
24
}
25

  
26
// Blue
27
export const primaryPathColor = '#346eeb'
28

  
29
// Grey
30
export const secondaryPathColor = '#878e9c'
31

  
32
// Map path component 
33
const MapPath: FunctionComponent<MapPathProps> = ({
34
    pathVariant,
35
    active,
36
    primaryIdx,
37
    idx,
38
    setPrimary
39
}) => {
40
    // List of all map points that belong to the path
41
    const [mapPoints, setMapPoints] = useState<DisplayableMapPoint[]>(
42
        pathVariant
43
            .filter((item) => item.latitude && item.longitude)
44
            .map((item) => new DisplayableMapPoint(item))
45
    )
46
    
47

  
48
    const getActiveMapPoints = () => mapPoints.filter((item) => item.active)
49

  
50
    // Builds all edges of the path
51
    const buildEdges = () => {
52
        const activeMapPoints = getActiveMapPoints()
53
        if (activeMapPoints.length < 2) {
54
            return null
55
        }
56

  
57
        // Create path edges
58
        const edges: any[] = []
59
        for (let i = 0; i < activeMapPoints.length - 1; i += 1) {
60
            edges.push(
61
                <TextPath
62
                    positions={[
63
                        [
64
                            activeMapPoints[i].catalogItem.latitude,
65
                            activeMapPoints[i].catalogItem.longitude,
66
                        ],
67
                        [
68
                            activeMapPoints[i + 1].catalogItem.latitude,
69
                            activeMapPoints[i + 1].catalogItem.longitude,
70
                        ],
71
                    ]}
72
                    text="►"
73
                    attributes={{
74
                        'font-size': 25,
75
                        // Set to primaryPathColor if primary index in the tracking tool is equal to this index
76
                        fill: primaryIdx === idx ? primaryPathColor : secondaryPathColor,
77
                    }}
78
                    onClick={() => {
79
                        setPrimary(idx)
80
                        console.log('hehehe')
81
                    }
82
                    }
83
                    repeat
84
                    center
85
                    weight={9}
86
                />
87
            )
88
        }
89

  
90
        // Return the path
91
        return edges
92
    }
93

  
94
    /**
95
     * Creates a list of all vertices in the path
96
     */
97
    const buildVertices = () => {
98
        return mapPoints.map((mapPoint, idx) => (
99
            <Marker
100
                key={idx}
101
                position={[
102
                    mapPoint.catalogItem.latitude as number,
103
                    mapPoint.catalogItem.longitude as number,
104
                ]}
105
            >
106
                <Popup>
107
                    <Fragment>
108
                        <Stack direction="column" sx={{ m: 0 }}>
109
                            <Typography
110
                                variant="h6"
111
                                fontWeight="bold"
112
                                fontSize={16}
113
                            >
114
                                {formatHtmlStringToReactDom(mapPoint.catalogItem.name as string)}
115
                            </Typography>
116
                            <FormControlLabel
117
                                control={
118
                                    <Checkbox
119
                                        checked={mapPoint.active}
120
                                        onChange={() => {
121
                                            mapPoint.active = !mapPoint.active
122
                                            setMapPoints([...mapPoints])
123
                                        }}
124
                                    />
125
                                }
126
                                labelPlacement="end"
127
                                label="Active"
128
                            />
129
                            <CatalogItemDetailDialog itemId={mapPoint.catalogItem.id ?? ''} />
130
                        </Stack>
131
                    </Fragment>
132
                </Popup>
133
            </Marker>
134
        ))
135
    }
136

  
137
    return (
138
        <Fragment>
139
            {active && <Fragment>
140
                {buildVertices()}
141
                {buildEdges()}
142
                </Fragment>}
143
        </Fragment>
144
    )
145
}
146

  
147
export default MapPath
frontend/src/features/TrackingTool/PlaintextUpload.tsx
56 56
                    )
57 57
                    setPaths(data)
58 58
                    closeDialog = true
59
                    console.log(data)
60 59
                }
61 60
            } catch (err: any) {
62 61
                dispatch(showNotification)
frontend/src/features/TrackingTool/TrackingTool.tsx
1
import {
2
    Button,
3
    Card,
4
    CardContent,
5
    Divider,
6
    Grid,
7
    Paper,
8
    Stack,
9
    Typography,
10
} from '@mui/material'
1
import { Card, CardContent, Grid, Stack, Typography } from '@mui/material'
11 2
import { Fragment, useEffect, useState } from 'react'
12
import AddIcon from '@mui/icons-material/Add'
13
import { MapContainer, Marker, Polyline, Popup, TileLayer } from 'react-leaflet'
3
import { MapContainer, TileLayer } from 'react-leaflet'
14 4
import mapConfig from '../../config/mapConfig'
15 5
import TextPath from 'react-leaflet-textpath'
16 6
import PlaintextUpload from './PlaintextUpload'
......
20 10
import { PathDto } from '../../swagger/data-contracts'
21 11
import { formatHtmlStringToReactDom } from '../../utils/formatting/HtmlUtils'
22 12
import buildPathVariants, { PathVariant } from './buildPathVariants'
13
import MapPath from './MapPath'
23 14

  
24 15
// Page with tracking tool
25 16
const TrackingTool = () => {
......
35 26
        undefined
36 27
    )
37 28

  
38
    // List of all rendered paths to display
39
    const [renderedPaths, setRenderedPaths] = useState<any[]>([])
40

  
29
    // latitude longitude pair of where the map is to be centered
41 30
    const [mapCenter, setMapCenter] = useState<number[]>([
42 31
        mapConfig.defaultCoordinates[0],
43 32
        mapConfig.defaultCoordinates[1],
44 33
    ])
45 34

  
35
    const [primaryPathIdx, setActivePathIdx] = useState(0)
36

  
37
    // TODO make some sort of selection list to select active paths?
38
    const [activePaths, setActivePaths] = useState<Set<number>>(new Set())
39

  
46 40
    // Returns tuple of average latitude and longitude
47 41
    const calculateMapCenter = (pathVariant: PathVariant) => [
48 42
        pathVariant
......
53 47
            .reduce((a, b) => a + b, 0) / pathVariant.length,
54 48
    ]
55 49

  
50
    useEffect(() => {
51
        if (!pathVariants || pathVariants.length === 0) {
52
            return
53
        }
54
        // TODO calculate only for path that has some non-null coordinates
55
        setMapCenter(calculateMapCenter(pathVariants[0]))
56
    }, [pathVariants])
57

  
56 58
    useEffect(() => {
57 59
        if (!pathDto) {
58 60
            return
......
60 62

  
61 63
        setBuildingPathVariants(true)
62 64
        buildPathVariants(pathDto).then((pathVariants) => {
65
            setActivePaths(new Set(pathVariants.map((_, idx) => idx)))
63 66
            setPathVariants(pathVariants)
64 67
            setBuildingPathVariants(false)
65
            const paths: any[] = []
66
            let first = true
67
            pathVariants?.forEach((pathVariant) => {
68
                if (first) {
69
                    setMapCenter(calculateMapCenter(pathVariant))
70
                }
71
                for (let i = 0; i < pathVariant.length - 1; i++) {
72
                    paths.push(
73
                        <TextPath
74
                            positions={[
75
                                [
76
                                    pathVariant[i].latitude,
77
                                    pathVariant[i].longitude,
78
                                ],
79
                                [
80
                                    pathVariant[i + 1].latitude,
81
                                    pathVariant[i + 1].longitude,
82
                                ],
83
                            ]}
84
                            text="►"
85
                            attributes={{
86
                                'font-size': 25,
87
                                fill: first ? 'blue' : 'gray',
88
                            }}
89
                            repeat
90
                            center
91
                            weight={10}
92
                        />
93
                    )
94
                }
95
                first = false
96
            })
97
            console.log(paths)
98
            setRenderedPaths(paths)
99 68
        })
100 69
    }, [pathDto])
101 70

  
......
115 84
                                    sx={{ mb: 1 }}
116 85
                                    fontWeight="600"
117 86
                                >
118
                                    Text
87
                                    Processed Text
119 88
                                </Typography>
120 89
                                <Typography variant="body2">
121 90
                                    {formatHtmlStringToReactDom(
......
178 147
                            attribution={mapConfig.attribution}
179 148
                            url={mapConfig.url}
180 149
                        />
181
                        {renderedPaths}
150
                        {pathVariants &&
151
                            pathVariants.map((pathVariant, idx) => (
152
                                <MapPath
153
                                    key={idx}
154
                                    pathVariant={pathVariant}
155
                                    idx={idx}
156
                                    setPrimary={setActivePathIdx}
157
                                    primaryIdx={primaryPathIdx}
158
                                    active={activePaths.has(idx)}
159
                                />
160
                            ))}
182 161
                    </MapContainer>
183 162
                </Grid>
184 163
            </Grid>

Také k dispozici: Unified diff