Projekt

Obecné

Profil

Stáhnout (14 KB) Statistiky
| Větev: | Tag: | Revize:
1
import { createSlice } from '@reduxjs/toolkit'
2
import { LatLngTuple } from 'leaflet'
3
import mapConfig from '../../config/mapConfig'
4
import { CatalogItemDto, ExternalCatalogItemDto, PathDto } from '../../swagger/data-contracts'
5
import { importFromExternalCatalog, importFromLocalCatalog, saveToCatalog, sendTextForProcessing } from './trackingToolThunks'
6
import { buildPath, calculateMapCenter, ExternalMapPoint, ExternalPath, getExternalPathColor, mapExternalCatalogItemToMapPoint, mapLocalCatalogItemToMapPoint, MapPoint, MapPointType, Path, updateMapPointIfCatalogItemPresent } from './trackingToolUtils'
7

    
8
// map of all toggleables
9
export interface TrackingToolToggleables {
10
    [MapPointType.LocalCatalog]: boolean
11
    [MapPointType.ExternalCatalog]: boolean
12
    [MapPointType.FileImport]: boolean
13
    [MapPointType.FromCoordinates]: boolean,
14
    [MapPointType.ProcessedText]: boolean
15
}
16

    
17
/**
18
 * Contains tracking tool state
19
 */
20
export interface TrackingToolState {
21
    isLoading: boolean // whether the pathDto is loading
22
    pathDto?: PathDto // response object from the text
23
    displayedPath?: Path // processed text path
24
    lastError?: string // last error to have occurred
25
    mapCenter: LatLngTuple // center of the map
26
    dialogApiCallSuccess: boolean, // api call flag for dialog
27
    toggleablesOpen: boolean, // whether the filter with toggleables is open
28
    importLoading: boolean // whether the import is loading
29
    importedPaths: ExternalPath[], // list of all imported paths
30
    localCatalogMapPoints: Path, // list of all local catalog map points
31
    externalCatalogMapPoints: ExternalMapPoint[], // list of all external catalog map points
32
    coordinatesMapPoints: Path, // list of all coordinates map points
33
    // Toggleables control what feature is displayed on the map
34
    // I.e. we can disable some map points 
35
    toggleables: TrackingToolToggleables
36
    savingToCatalog?: boolean // whether saving to the catalog
37
    savedToCatalogSuccess?: boolean // whether the save to catalog was successful
38
}
39

    
40
const initialState: TrackingToolState = {
41
    isLoading: false,
42
    mapCenter: [mapConfig.defaultCoordinates[0], mapConfig.defaultCoordinates[1]],
43
    dialogApiCallSuccess: true,
44
    toggleablesOpen: false,
45
    importLoading: false,
46
    importedPaths: [],
47
    localCatalogMapPoints: [],
48
    externalCatalogMapPoints: [],
49
    coordinatesMapPoints: [],
50
    toggleables: {
51
        [MapPointType.LocalCatalog]: true,
52
        [MapPointType.ExternalCatalog]: true,
53
        [MapPointType.FileImport]: true,
54
        [MapPointType.FromCoordinates]: true,
55
        [MapPointType.ProcessedText]: true
56
    }
57
}
58

    
59
export const trackingToolSlice = createSlice({
60
    name: 'trackingTool',
61
    initialState,
62
    reducers: {
63
        /**
64
         * Consumes generic error. This should be called by component that displayed the error
65
         */
66
        consumeErr: (state: TrackingToolState) => ({ ...state, lastErr: undefined, }),
67

    
68
        /**
69
         * Resets state to the initial state
70
         */
71
        clear: () => ({ ...initialState, }),
72

    
73
        /**
74
         * Resets dialog api call success flag
75
         */
76
        resetDialogApiCallSuccess: (state: TrackingToolState) => ({ ...state, dialogApiCallSuccess: false, }),
77

    
78
        /**
79
         * Flips toggleables filter on/off
80
         */
81
        toggleToggleables: (state: TrackingToolState) => ({ ...state, toggleablesOpen: !state.toggleablesOpen, }),
82

    
83
        /**
84
         * Removes map point from the path - by passing its reactId 
85
         */
86
        removeMapPointFromPath: (state: TrackingToolState, action: {payload: string}) => ({
87
            ...state,
88
           displayedPath: state.displayedPath ? state.displayedPath.filter(mapPoint => mapPoint.reactId !== action.payload) : undefined
89
        }),
90

    
91
        /**
92
         * Changes map point variant to another index
93
         */
94
        changeMapPointVariant: (state: TrackingToolState, action: { payload: { mapPoint: MapPoint, variantIdx: number, } }) => {
95
            const path = [...state.displayedPath ?? []]
96
            const { mapPoint, variantIdx } = action.payload
97
            if (mapPoint.idx >= path.length) {
98
                // Ignore if map point is out of bounds
99
                return state
100
            }
101

    
102
            // Copy the map point and its variants array
103
            const pathMapPoint = { ...path[mapPoint.idx] }
104
            const pathMapPointVariants = [...pathMapPoint.variants ?? []]
105
            if (pathMapPointVariants.length <= variantIdx || !mapPoint.variants) {
106
                // Ignore if variant is out of bounds
107
                return state
108
            }
109
            const newVariant = pathMapPointVariants[variantIdx]
110
            const previousItemVariant = { ...pathMapPoint.catalogItem }
111
            const previousItemVariantIdx = mapPoint.variantIdx
112

    
113
            return {
114
                ...state,
115
                displayedPath: path.map((pathMapPoint, idx) => {
116
                    if (pathMapPoint.firstVariantCatalogItemId !== mapPoint.firstVariantCatalogItemId) {
117
                        return pathMapPoint
118
                    }
119

    
120
                    // Copy current state of the map point to variants array of the item
121
                    const modifiedPathMapPoint = {
122
                        ...pathMapPoint,
123
                        variants: !pathMapPoint.variants ? pathMapPoint.variants : [
124
                            ...pathMapPoint.variants.slice(0, previousItemVariantIdx),
125
                            previousItemVariant,
126
                            ...pathMapPoint.variants.slice(previousItemVariantIdx + 1)
127
                        ]
128
                    }
129

    
130
                    if (modifiedPathMapPoint.reactId !== mapPoint.reactId) {
131
                        return modifiedPathMapPoint
132
                    }
133

    
134
                    modifiedPathMapPoint.id = newVariant.id as string
135
                    modifiedPathMapPoint.catalogItem = newVariant
136
                    modifiedPathMapPoint.variantIdx = variantIdx
137
                    return modifiedPathMapPoint
138
                })
139
            }
140
        },
141

    
142
        /**
143
         * Updates map point only at its specific index - this should be used when we are e.g. removing
144
         * the map point from the path
145
         */
146
        updateMapPointAtIdx: (state: TrackingToolState, action: { payload: MapPoint }) => {
147
            const newPath = [...state.displayedPath ?? []]
148
            if (newPath.length <= action.payload.idx) {
149
                return state // do nothing if there is no path
150
            }
151

    
152
            newPath[action.payload.idx] = { ...action.payload }
153
            return {
154
                ...state,
155
                displayedPath: newPath,
156
            }
157
        },
158

    
159
        /**
160
         * Updates all occurrences of map point with its specific id - this should be used when we are e.g
161
         * toggling visibility of the map point
162
         */
163
        updateMapPoint: (state: TrackingToolState, action: { payload: MapPoint }) => {
164
            const newPath = [...state.displayedPath ?? []]
165
            return {
166
                ...state,
167
                displayedPath: newPath.map((point, _) => point.id === action.payload.id ? {
168
                    ...action.payload, idx: point.idx,
169
                    reactId: point.reactId, id: point.id
170
                } : point)
171
            }
172
        },
173

    
174
        /**
175
         * Changes position of map point in the path
176
         */
177
        changeMapPointIdx: (state: TrackingToolState, action: { payload: { destination: number, source: number } }) => {
178
            const { destination, source } = action.payload
179
            const newPath = [...state.displayedPath ?? []]
180
            // JS array dark magic
181
            const removed = { ...newPath[source] }
182
            newPath.splice(source, 1)
183
            newPath.splice(destination, 0, removed)
184
            const result = newPath.map((point, idx) => ({ ...point, idx }))
185
            return {
186
                ...state,
187
                displayedPath: result,
188
            }
189
        },
190

    
191
        /**
192
         * Adds new map point from coordinates to the coordinatesMapPoints array 
193
         */
194
        addCoordinatesMapPoint: (state: TrackingToolState, action: { payload: MapPoint }) => ({
195
            ...state,
196
            coordinatesMapPoints: [...state.coordinatesMapPoints, action.payload]
197
        }),
198

    
199
        /**
200
         * Updates toggleables - i.e. what is displayed on the map
201
         */
202
        updateToggleables: (state: TrackingToolState, action: { payload: TrackingToolToggleables }) => ({
203
            ...state,
204
            toggleables: action.payload
205
        }),
206

    
207
        /**
208
         * Appends new path to file import paths
209
         */
210
        appendFileImportPath: (state: TrackingToolState,
211
            action: { payload: { path: Path, filename: string } }) => ({
212
                ...state,
213
                importedPaths: [...state.importedPaths, {
214
                    idx: state.importedPaths.length,
215
                    color: getExternalPathColor(state.importedPaths.length),
216
                    path: action.payload.path,
217
                    visible: true,
218
                    filename: action.payload.filename,
219
                }]
220
            }),
221

    
222
        /**
223
         * Removes saved to catalog successfully flag
224
         */
225
        consumeSavedToCatalogSuccessfully: (state: TrackingToolState) => ({
226
            ...state,
227
            savedToCatalogSuccessfully: undefined
228
        }),
229

    
230
        /**
231
         * Updates catalog item id of the map point 
232
         */
233
        updateCatalogItem: (state: TrackingToolState, action: { payload: CatalogItemDto }) => ({
234
            ...state,
235
            displayedPath: state.displayedPath?.map(mapPoint => updateMapPointIfCatalogItemPresent({ ...mapPoint }, action.payload))
236
        }),
237

    
238
        /**
239
         * Setter for file path visibility
240
         */
241
        toggleFilePathVisibility: (state: TrackingToolState, action: { payload: { idx: number, visible: boolean } }) => ({
242
            ...state,
243
            importedPaths: state.importedPaths.map((path, idx) => idx === action.payload.idx ? { ...path, visible: action.payload.visible } : path)
244
        }),
245

    
246
        /**
247
         * Removes file path from the importedPaths list
248
         */
249
        removeFilePath: (state: TrackingToolState, action: { payload: { idx: number } }) => ({
250
            ...state,
251
            importedPaths: state.importedPaths.filter((_, idx) => idx !== action.payload.idx)
252
        }),
253
    },
254
    // Thunks
255
    extraReducers: (builder) => {
256
        builder.addCase(sendTextForProcessing.fulfilled, (state, action) => {
257
            const pathDto: PathDto = action.payload
258
            const displayedPath = buildPath(pathDto)
259
            return {
260
                ...state,
261
                pathDto,
262
                mapCenter: calculateMapCenter(displayedPath) ?? state.mapCenter,
263
                isLoading: false,
264
                // dialogApiCallSuccess: true,
265
                displayedPath,
266
            } as TrackingToolState
267
        })
268
        builder.addCase(sendTextForProcessing.rejected, (_, action: any) => ({
269
            ...initialState,
270
            lastError: action.error.message,
271
            isLoading: false,
272
            // dialogApiCallSuccess: false,
273
            currentPage: 0,
274
            displayedPath: undefined,
275
            displayedPathIdx: 0,
276
            pathDto: undefined,
277
            pathVariants: undefined,
278
        }))
279
        builder.addCase(sendTextForProcessing.pending, (state: TrackingToolState) => ({
280
            ...state,
281
            isLoading: true,
282
            // dialogApiCallSuccess: false,
283
        }))
284
        builder.addCase(importFromExternalCatalog.fulfilled, (state: TrackingToolState, action: { payload: ExternalCatalogItemDto[] }) => ({
285
            ...state,
286
            externalCatalogMapPoints: [...new Map(
287
                [...state.externalCatalogMapPoints, ...action.payload.map(item => mapExternalCatalogItemToMapPoint(item))]
288
                    .map((item, _) => [item.id, item]))
289
                .values()],
290
            dialogApiCallSuccess: true,
291
        }))
292
        builder.addCase(importFromExternalCatalog.rejected, (state: TrackingToolState, action: any) => ({
293
            ...state,
294
            lastError: action.error.message,
295
        }))
296
        builder.addCase(importFromExternalCatalog.pending, (state: TrackingToolState) => ({
297
            ...state,
298
            isLoading: true,
299
        }))
300
        builder.addCase(importFromLocalCatalog.fulfilled, (state: TrackingToolState, action: { payload: CatalogItemDto[] }) => ({
301
            ...state,
302
            localCatalogMapPoints: [...new Map(
303
                [...state.localCatalogMapPoints, ...action.payload.map(item => mapLocalCatalogItemToMapPoint(item))]
304
                    .map((item, _) => [item.id, item]))
305
                .values()],
306
            dialogApiCallSuccess: true,
307
        }))
308
        builder.addCase(importFromLocalCatalog.rejected, (state: TrackingToolState, action: any) => ({
309
            ...state,
310
            lastError: action.error.message,
311
        }))
312
        builder.addCase(importFromLocalCatalog.pending, (state: TrackingToolState) => ({
313
            ...state,
314
            importLoading: true,
315
        }))
316
        builder.addCase(saveToCatalog.fulfilled, (state: TrackingToolState) => ({
317
            ...state,
318
            savedToCatalogSuccess: true,
319
            savingToCatalog: false,
320
        }))
321
        builder.addCase(saveToCatalog.pending, (state: TrackingToolState) => ({
322
            ...state,
323
            savingToCatalog: true
324
        }))
325
        builder.addCase(saveToCatalog.rejected, (state: TrackingToolState, action: any) => ({
326
            ...state,
327
            lastError: action.error.message,
328
            savingToCatalog: false
329
        }))
330
    }
331
})
332

    
333
export const {
334
    consumeErr,
335
    clear,
336
    resetDialogApiCallSuccess,
337
    toggleToggleables,
338
    changeMapPointVariant,
339
    updateMapPointAtIdx,
340
    updateMapPoint,
341
    changeMapPointIdx,
342
    addCoordinatesMapPoint,
343
    updateToggleables,
344
    appendFileImportPath,
345
    consumeSavedToCatalogSuccessfully,
346
    updateCatalogItem,
347
    toggleFilePathVisibility,
348
    removeFilePath,
349
    removeMapPointFromPath
350
} = trackingToolSlice.actions
351

    
352
const trackingToolReducer = trackingToolSlice.reducer
353
export default trackingToolReducer
(2-2/4)