From a7b4412e47f770b5eda33443a3faca37d2a9a12d Mon Sep 17 00:00:00 2001 From: Daniel Aschwanden Date: Mon, 19 Feb 2024 21:50:57 +0100 Subject: [PATCH] feat(map): first non-perfect version of map synchronization --- ui/package.json | 42 +- ui/src/cache.tsx | 23 + ui/src/client.tsx | 8 +- .../components/map/EnrichedLayerFeatures.tsx | 14 +- ui/src/types/layer.ts | 33 +- ui/src/views/map/Map.tsx | 607 ++++++++++++------ ui/src/views/map/controls/DrawControl.tsx | 4 +- ui/src/views/map/controls/StyleController.tsx | 2 +- ui/src/views/map/graphql.ts | 76 +++ ui/src/views/map/utils.ts | 82 +++ ui/yarn.lock | 412 +++++++----- 11 files changed, 919 insertions(+), 384 deletions(-) create mode 100644 ui/src/cache.tsx create mode 100644 ui/src/views/map/graphql.ts create mode 100644 ui/src/views/map/utils.ts diff --git a/ui/package.json b/ui/package.json index 14227277..cf101261 100644 --- a/ui/package.json +++ b/ui/package.json @@ -4,12 +4,12 @@ "private": true, "type": "module", "dependencies": { - "@apollo/client": "^3.8.10", + "@apollo/client": "^3.9.5", "@fortawesome/fontawesome-svg-core": "^6.5.1", "@fortawesome/free-regular-svg-icons": "^6.5.1", "@fortawesome/free-solid-svg-icons": "^6.5.1", "@fortawesome/react-fontawesome": "^0.2.0", - "@mapbox/mapbox-gl-draw": "^1.3.0", + "@mapbox/mapbox-gl-draw": "^1.4.3", "@turf/bearing": "^6.5.0", "@turf/center": "^6.5.0", "@turf/helpers": "^6.5.0", @@ -19,7 +19,7 @@ "dayjs": "^1.11.10", "graphql": "^16.8.1", "hat": "^0.0.3", - "i18next": "^23.7.19", + "i18next": "^23.9.0", "i18next-browser-languagedetector": "^7.2.0", "lodash": "^4.17.21", "mapbox-gl-style-switcher": "^1.0.11", @@ -28,12 +28,12 @@ "react-autocomplete-hint": "^2.0.0", "react-color": "^2.19.3", "react-dom": "^18.2.0", - "react-i18next": "^14.0.1", + "react-i18next": "^14.0.5", "react-map-gl": "~7.1.7", "react-markdown": "^8.0.7", - "react-router-dom": "^6.21.3", - "usehooks-ts": "^2.10.0", - "web-vitals": "^3.5.1" + "react-router-dom": "^6.22.1", + "usehooks-ts": "^2.14.0", + "web-vitals": "^3.5.2" }, "scripts": { "analyze": "source-map-explorer 'build/static/js/*.js'", @@ -72,38 +72,38 @@ "@types/hat": "^0.0.4", "@types/jest": "^29.5.12", "@types/lodash": "^4.14.202", - "@types/mapbox-gl": "^2.7.19", - "@types/mapbox__mapbox-gl-draw": "~1.3.3", - "@types/node": "^20.11.6", - "@types/react": "^18.2.48", + "@types/mapbox-gl": "^2.7.21", + "@types/mapbox__mapbox-gl-draw": "~1.4.6", + "@types/node": "^20.11.19", + "@types/react": "^18.2.56", "@types/react-color": "^3.0.11", - "@types/react-dom": "^18.2.18", + "@types/react-dom": "^18.2.19", "@types/react-router-dom": "^5.3.3", - "@types/semver": "^7", + "@types/semver": "^7.5.7", "@typescript-eslint/eslint-plugin": "^6.21.0", "@typescript-eslint/parser": "^6.21.0", - "@vite-pwa/assets-generator": "^0.2.3", + "@vite-pwa/assets-generator": "^0.2.4", "@vitejs/plugin-react-swc": "^3.6.0", - "@vitest/coverage-v8": "^1.2.2", + "@vitest/coverage-v8": "^1.3.0", "eslint": "^8.56.0", "eslint-config-react-app": "^7.0.1", "husky": "^8.0.3", "jest": "^29.7.0", "jsdom": "^24.0.0", - "lint-staged": "^15.2.0", - "prettier": "^3.2.4", - "sass": "^1.70.0", + "lint-staged": "^15.2.2", + "prettier": "^3.2.5", + "sass": "^1.71.0", "semver": "^7.6.0", "source-map-explorer": "^2.5.3", "ts-jest": "^29.1.2", "typescript": "^4.9.5", - "vite": "^5.0.12", + "vite": "^5.1.3", "vite-plugin-checker": "^0.6.4", "vite-plugin-eslint": "^1.8.1", - "vite-plugin-pwa": "^0.17.5", + "vite-plugin-pwa": "^0.19.0", "vite-plugin-svgr": "^4.2.0", "vite-tsconfig-paths": "^4.3.1", - "vitest": "^1.2.2" + "vitest": "^1.3.0" }, "resolutions": { "json5": "^2.2.3", diff --git a/ui/src/cache.tsx b/ui/src/cache.tsx new file mode 100644 index 00000000..a67b71a6 --- /dev/null +++ b/ui/src/cache.tsx @@ -0,0 +1,23 @@ +import { InMemoryCache, makeVar } from "@apollo/client"; + +// active drawing Layer +export const activeLayerVar = makeVar(""); + +// active Incident +export const activeIncidentVar = makeVar(""); + + +export const cache: InMemoryCache = new InMemoryCache({ + typePolicies: { + Layers: { + fields: { + isActive: { + read(_, { readField }) { + const layerId = readField('id'); + return layerId === activeLayerVar() + } + } + } + } + } +}); \ No newline at end of file diff --git a/ui/src/client.tsx b/ui/src/client.tsx index 53e51f36..1622d0d0 100644 --- a/ui/src/client.tsx +++ b/ui/src/client.tsx @@ -1,6 +1,6 @@ -import { ApolloClient, InMemoryCache } from "@apollo/client"; - +import { ApolloClient } from "@apollo/client"; import { HttpLink } from "@apollo/client"; +import { cache } from 'cache'; const httpLink = new HttpLink({ uri: import.meta.env.VITE_API_URL, @@ -9,7 +9,7 @@ const httpLink = new HttpLink({ const client = new ApolloClient({ link: httpLink, - cache: new InMemoryCache(), + cache: cache, // defaultOptions: { // watchQuery: { // nextFetchPolicy: "cache-and-network", @@ -17,4 +17,6 @@ const client = new ApolloClient({ // }, }); + + export default client; diff --git a/ui/src/components/map/EnrichedLayerFeatures.tsx b/ui/src/components/map/EnrichedLayerFeatures.tsx index 0c7dc8fc..f8ba788a 100644 --- a/ui/src/components/map/EnrichedLayerFeatures.tsx +++ b/ui/src/components/map/EnrichedLayerFeatures.tsx @@ -2,6 +2,7 @@ import bearing from '@turf/bearing'; import { point } from '@turf/helpers'; import { BabsIcon, Schaeden, Others } from 'components/BabsIcons'; import { Feature, FeatureCollection, GeoJsonProperties, Geometry } from 'geojson'; +import { memo } from 'react'; import { Layer, Source } from "react-map-gl"; const enrichFeature = (f: Feature): Feature[] => { @@ -105,7 +106,7 @@ const EnrichLineStringMap: { [key: string]: EnrichLineConfig } = { const EnrichedSymbolSource = (props: EnrichedFeaturesProps) => { let enrichedFC: FeatureCollection = { "type": "FeatureCollection", "features": [] }; - enrichedFC.features = Object.assign([], props.featureCollection.features.filter(f => f.properties?.deletedAt === undefined).filter(f => f.id !== props.selectedFeature).flatMap(f => enrichFeature(f))) + enrichedFC.features = Object.assign([], props.featureCollection.features.filter(f => f.properties?.deletedAt === null).filter(f => f.id !== props.selectedFeature).flatMap(f => enrichFeature(f))) return { } -export const EnrichedFeaturesSource = (props: EnrichedFeaturesProps) => { +const EnrichedFeaturesSource = (props: EnrichedFeaturesProps) => { return <> @@ -128,7 +129,12 @@ export const EnrichedFeaturesSource = (props: EnrichedFeaturesProps) => { interface EnrichedFeaturesProps { featureCollection: FeatureCollection; - selectedFeature: string | number | undefined + selectedFeature?: string | number | undefined } -export default EnrichedFeaturesSource; \ No newline at end of file +const MemoEnrichedFeaturesSource = memo(EnrichedFeaturesSource); +export { + MemoEnrichedFeaturesSource as EnrichedFeaturesSource +} + +export default memo(EnrichedFeaturesSource); \ No newline at end of file diff --git a/ui/src/types/layer.ts b/ui/src/types/layer.ts index 663793ad..e0a057d1 100644 --- a/ui/src/types/layer.ts +++ b/ui/src/types/layer.ts @@ -3,15 +3,16 @@ import { Incident } from "./incident"; export type Layer = { id: string; + isActive: boolean; name: string; incident: Incident; - features: Features[]; + features: Feature[]; createdAt: Date; updatedAt: Date; deletedAt: Date; }; -export type Features = { +export type Feature = { id: string; name: string; layer: Layer; @@ -21,3 +22,31 @@ export type Features = { updatedAt: Date; deletedAt: Date; }; + + +export interface GetLayersData { + layers: Layer[]; +} + +export interface GetLayersVars { + incidentId: string; +} + + +export interface AddFeatureVars { + layerId: string; + geometry: Geometry; + properties: GeoJsonProperties; + id: string | number | undefined; +} + +export interface ModifyFeatureVars { + id: string | number | undefined; + geometry: Geometry; + properties: GeoJsonProperties; +} + +export interface DeleteFeatureVars { + id: string | number | undefined; + deletedAt: Date; +} \ No newline at end of file diff --git a/ui/src/views/map/Map.tsx b/ui/src/views/map/Map.tsx index 27d5f415..01f3bb4e 100644 --- a/ui/src/views/map/Map.tsx +++ b/ui/src/views/map/Map.tsx @@ -1,27 +1,30 @@ -import { useReactiveVar } from '@apollo/client'; +import { useMutation, useQuery, useReactiveVar } from '@apollo/client'; import MapboxDraw from '@mapbox/mapbox-gl-draw'; import "@mapbox/mapbox-gl-draw/dist/mapbox-gl-draw.css"; -import bbox from "@turf/bbox"; import DefaultMaker from 'assets/marker.svg'; import { AllIcons, LinePatterns, ZonePatterns } from 'components/BabsIcons'; import EnrichedLayerFeatures from 'components/map/EnrichedLayerFeatures'; -import { Feature, FeatureCollection, GeoJsonProperties, Geometry } from 'geojson'; -import hat from 'hat'; -import { isEqual, unionBy } from 'lodash'; +import { Feature, FeatureCollection } from 'geojson'; import maplibregl from 'maplibre-gl'; import 'maplibre-gl/dist/maplibre-gl.css'; -import { memo, useCallback, useEffect, useRef, useState } from 'react'; -import { FullscreenControl, Map, MapProvider, MapRef, NavigationControl, ScaleControl } from 'react-map-gl/maplibre'; +import { Dispatch, SetStateAction, useCallback, useEffect, useRef, useState } from 'react'; +import { FullscreenControl, Map, MapProvider, MapRef, NavigationControl, ScaleControl, Source, useMap } from 'react-map-gl/maplibre'; import { useParams } from 'react-router-dom'; import Notification from 'utils/Notification'; -import useLocalStorage from 'utils/useLocalStorage'; import './control-panel.css'; import { BabsIconController } from './controls/BabsIconController'; import DrawControl from './controls/DrawControl'; import ExportControl from './controls/ExportControl'; import { StyleController, selectedStyle } from './controls/StyleController'; import { drawStyle } from './style'; +import { AddFeatureToLayer, DeleteFeature, GetLayers, ModifyFeature } from './graphql'; +import { AddFeatureVars, DeleteFeatureVars, GetLayersData, GetLayersVars, Layer, ModifyFeatureVars } from 'types/layer'; +import { } from 'utils'; +import { CleanFeature, FilterActiveFeatures, LayerToFeatureCollection } from './utils'; +import { first } from 'lodash'; +import { activeLayerVar } from 'cache'; +import bbox from "@turf/bbox"; const modes = { @@ -29,18 +32,9 @@ const modes = { // 'draw_point': BabsPointMode }; - -function MapComponent() { - const [draw, setDraw] = useState(); - const [selectedFeature, setSelectedFeature] = useState(); - const [isMapLoaded, setIsMapLoaded] = useState(false); - +function MapView() { const mapRef = useRef(null); - const mapStyle = useReactiveVar(selectedStyle); - - const { incidentId } = useParams(); - const [features, setFeatures] = useLocalStorage(`map-incident-${incidentId}`, { "type": "FeatureCollection", "features": [], }); const [viewState, setViewState] = useState({ latitude: 46.87148, longitude: 8.62994, @@ -48,105 +42,6 @@ function MapComponent() { bearing: 0, }); - const onCreate = useCallback((e: any) => { - setFeatures(curFeatureCollection => { - const newFeatureCollection = { ...curFeatureCollection }; - const createdFeatures: Feature[] = e.features; - createdFeatures.forEach(f => { - if (f.properties) { - f.properties['createdAt'] = new Date(); - newFeatureCollection.features.push(f); - } - }) - newFeatureCollection.features = unionBy(newFeatureCollection.features, curFeatureCollection.features, 'id'); - return newFeatureCollection; - }); - }, [setFeatures]); - - const onUpdate = useCallback((e: any) => { - console.log("[onUpdate]", e) - - setFeatures(curFeatureCollection => { - // an update creates a deleted feature with the old properties and adds a new one with the new properties - const newFeatureCollection = { ...curFeatureCollection }; - newFeatureCollection.features = [] - - const updatedFeatures: Feature[] = e.features; - const modifiedFeatures: Feature[] = []; - updatedFeatures.forEach(f => { - if (f.properties) { - - // fetch the old element - let cur: Feature | undefined = curFeatureCollection.features.find(c => c.id === f.id) - // make sure the old element is not identical to the current - if (cur && isEqual(cur, f) && isEqual(cur?.properties, f?.properties)) { - return; - } - - // if we found the old one and it got changed, close it - if (cur && cur.properties) { - cur.properties['deletedAt'] = new Date(); - modifiedFeatures.push(cur); - } - - // generate a new ID and - f.id = hat(); - f.properties['createdAt'] = new Date(); - f.properties['achestorID'] = cur?.id; - modifiedFeatures.push(f); - } - }); - newFeatureCollection.features = [...curFeatureCollection.features, ...modifiedFeatures]; - - return newFeatureCollection; - }); - setSelectedFeature(undefined); - draw?.changeMode("simple_select") - }, [setFeatures, setSelectedFeature, draw]); - - const onDelete = useCallback((e: any) => { - console.log("[onDelete]", e); - - setFeatures(curFeatureCollection => { - const newFeatureCollection = { ...curFeatureCollection }; - const deletedFeatures: Feature[] = e.features; - deletedFeatures.forEach(f => { - if (f.properties) { - // fetch the old element and close it - let cur: Feature | undefined = curFeatureCollection.features.find(c => c.id === f.id) - if (cur && cur.properties) { - cur.properties['deletedAt'] = new Date(); - newFeatureCollection.features.push(f); - } - } - }); - newFeatureCollection.features = unionBy(newFeatureCollection.features, curFeatureCollection.features, 'id'); - - return newFeatureCollection; - }); - setSelectedFeature(undefined); - draw?.changeMode("simple_select") - }, [setFeatures, setSelectedFeature, draw]); - - const onSelectionChange = useCallback((e: { features: Feature[]; }) => { - console.log("[onSelectionChange]", e) - const features: Feature[] = e.features; - if (features.length >= 1) { - const feature = features[0]; - // always work on the parent feature - if (feature.properties?.parent) { - setSelectedFeature(feature.properties.parent); - draw?.changeMode('simple_select', { featureIds: [feature.properties.parent] }) - return - } - setSelectedFeature(feature.id); - } - else { - setSelectedFeature(undefined); - draw?.changeMode("simple_select") - } - }, [setSelectedFeature, draw]); - const onMapLoad = useCallback(() => { // Add the default marker let defaultMarker = new Image(32, 32); @@ -158,7 +53,6 @@ function MapComponent() { customIcon.onload = () => mapRef && mapRef.current && !mapRef.current.hasImage(icon.name) && mapRef.current.addImage(icon.name, customIcon) customIcon.src = icon.src; }); - setIsMapLoaded(true); mapRef && mapRef.current && mapRef.current.on('styleimagemissing', function (e) { const id = e.id; // id of the missing image console.log("missing image", id); @@ -169,29 +63,7 @@ function MapComponent() { }); }); - // set the right bounds of the map based on the feature collection - let filteredFC: FeatureCollection = { "type": "FeatureCollection", "features": [] }; - filteredFC.features = Object.assign([], features.features.filter(f => f.properties?.deletedAt === undefined)) - if (filteredFC.features.length > 0) { - let bboxArray = bbox(filteredFC); - mapRef && mapRef.current && mapRef.current.fitBounds( - [[bboxArray[0], bboxArray[1]], [bboxArray[2], bboxArray[3]]], - { - animate: true, - padding: { top: 30, bottom: 30, left: 30, right: 30, } - } - ); - } - - }, [setIsMapLoaded, mapRef, features]); - - - useEffect(() => { - let filteredFC: FeatureCollection = { "type": "FeatureCollection", "features": [] }; - filteredFC.features = Object.assign([], features.features.filter(f => f.properties?.deletedAt === undefined)) - isMapLoaded && draw && draw.set(filteredFC); - console.log("update map", filteredFC) - }, [features, isMapLoaded, draw]); + }, [mapRef]); return ( <> @@ -212,62 +84,423 @@ function MapComponent() { onMove={e => setViewState(e.viewState)} mapStyle={mapStyle.uri} > + + {/* All Map Controls */} - {/* - - */} - - {/* */} - f.id === selectedFeature).shift()} onUpdate={onUpdate} /> - - - {/* { onMapLoad(); return true } } }} />*/} - {/* f.id === selectedFeature).shift()}> - f.id === selectedFeature).shift()} /> - */} + {/* Layersprovider and Draw */} + + {/* */} - + ); } -const MemoMap = memo(MapComponent); +function Layers() { + const { incidentId } = useParams(); + const { data } = useQuery(GetLayers, { + variables: { incidentId: incidentId || "" }, + pollInterval: 1000, + fetchPolicy: "cache-first", + }); + + // setting active to first layer if none is active + if (data && data.layers?.length > 0 && data?.layers.filter(l => l.isActive).length === 0) { + let firstLayer = first(data.layers)?.id + console.log("setting active layer", firstLayer) + activeLayerVar(firstLayer) + } + + return ( + <> + {/* Active Layer */} + l.isActive))} /> + + {/* Inactive Layers */} + !l.isActive) || []} /> + + ) +} + +function ActiveLayer(props: { layer: Layer | undefined }) { + const { layer } = props; + const [initialized, setInitalized] = useState(false); + const { current: map } = useMap(); + const { incidentId } = useParams(); + const featureCollection = LayerToFeatureCollection(layer); + const [selectedFeature, setSelectedFeature] = useState(); + const [addFeature] = useMutation(AddFeatureToLayer, { + refetchQueries: [{ query: GetLayers, variables: { incidentId: incidentId } }] + }); + const [modifyFeature] = useMutation(ModifyFeature, { + refetchQueries: [{ query: GetLayers, variables: { incidentId: incidentId } }] + }); + + const [deleteFeature] = useMutation(DeleteFeature, { + refetchQueries: [{ query: GetLayers, variables: { incidentId: incidentId } }] + }); + + const onCreate = useCallback((e: any) => { + console.log("[onCreate]", e) + const createdFeatures: Feature[] = e.features; + createdFeatures.forEach(f => { + if (layer?.id === undefined) { + console.log("undefined layer, discarding edit") + return; + } + + let feature = CleanFeature(f) + console.log("adding feature", feature) + addFeature({ variables: { layerId: layer.id, geometry: feature.geometry, id: feature.id, properties: feature.properties } }) + }) + setSelectedFeature(first(createdFeatures)?.id) + }, [addFeature, layer, setSelectedFeature]); + + const onUpdate = useCallback((e: any) => { + const updatedFeatures: Feature[] = e.features; + setSelectedFeature(undefined); + updatedFeatures.forEach(f => { + let feature = CleanFeature(f) + console.log("modifying", feature) + modifyFeature({ variables: { id: feature.id, geometry: feature.geometry, properties: feature.properties } }) + }); + }, [modifyFeature, setSelectedFeature]); + + const onDelete = useCallback((e: any) => { + console.log("[onDelete]", e); + const deletedFeatures: Feature[] = e.features; + deletedFeatures.forEach(f => { + let feature = CleanFeature(f); + deleteFeature({ variables: { id: feature.id, deletedAt: new Date() } }) + }); + setSelectedFeature(undefined); + }, [deleteFeature, setSelectedFeature]); + + useEffect(() => { + + let fc = FilterActiveFeatures(featureCollection); + console.log("effect", map) + if (initialized) { + console.log("already initialized") + return + } + // only run this for the initialization as we don't want to continously + // change the map viewport on new features + if (map !== undefined && fc.features.length > 0) { + let bboxArray = bbox(fc); + + console.log("setting map bounding box for features", bboxArray); + map && map.fitBounds( + [[bboxArray[0], bboxArray[1]], [bboxArray[2], bboxArray[3]]], + { + animate: true, + padding: { top: 30, bottom: 30, left: 30, right: 30, } + } + ); + setInitalized(true); + } + }, [featureCollection, map, selectedFeature, initialized, setInitalized]); + + return ( + <> + + + f.id === selectedFeature).shift()} onUpdate={onUpdate} /> + + ) +} + +function Draw(props: + { + onCreate: (e: any) => void, + onDelete: (e: any) => void, + onUpdate: (e: any) => void, + setSelectedFeature: Dispatch>, + featuresCollection: FeatureCollection + }) { + + const [draw, setDraw] = useState(); + const { onCreate, onUpdate, onDelete, setSelectedFeature, featuresCollection } = props; + + const onSelectionChange = useCallback((e: any) => { + console.log("[onSelectionChange]", e) + const features: Feature[] = e.features; + if (features?.length > 0) { + const feature = first(features); + setSelectedFeature(feature?.id); + } + else { + setSelectedFeature(undefined); + draw && draw.changeMode("static") && console.log("changed mode to static"); + } + }, [draw, setSelectedFeature]); + + const onCreateCallback = useCallback((e: any) => { + onCreate(e); + draw && draw.changeMode("static") && console.log("changed mode to static"); + }, [draw, onCreate]); + + const onUpdateCallback = useCallback((e: any) => { + onUpdate(e); + draw && draw.changeMode("static") && console.log("changed mode to static"); + }, [draw, onUpdate]); + + const onDeleteCallback = useCallback((e: any) => { + onDelete(e); + draw && draw.changeMode("static") && console.log("changed mode to static"); + }, [draw, onDelete]); + + useEffect(() => { + draw && draw.set(FilterActiveFeatures(featuresCollection)); + }, [draw, featuresCollection]); + + return ( + <> + + + ) + +} + +function InactiveLayers(props: { layers: Layer[] }) { + const { layers } = props; + + return ( + <> + { + layers.map(l => + + ) + } + + ) +} +function InactiveLayer(props: { featureCollection: FeatureCollection, id: string }) { + const { featureCollection, id } = props; + + return ( + <> + + + + ) +} + + +// function MapComponent() { +// const [features, setFeatures] = useState(featureCollection([])); + + +// const onCreate = useCallback((e: any) => { +// setFeatures(curFeatureCollection => { +// const newFeatureCollection = { ...curFeatureCollection }; +// const createdFeatures: Feature[] = e.features; +// createdFeatures.forEach(f => { +// if (f.properties) { +// f.properties['createdAt'] = new Date(); +// newFeatureCollection.features.push(f); +// } +// }) +// newFeatureCollection.features = unionBy(newFeatureCollection.features, curFeatureCollection.features, 'id'); +// return newFeatureCollection; +// }); +// }, [setFeatures]); + +// const onUpdate = useCallback((e: any) => { +// console.log("[onUpdate]", e) + +// setFeatures(curFeatureCollection => { +// // an update creates a deleted feature with the old properties and adds a new one with the new properties +// const newFeatureCollection = { ...curFeatureCollection }; +// newFeatureCollection.features = [] + +// const updatedFeatures: Feature[] = e.features; +// const modifiedFeatures: Feature[] = []; +// updatedFeatures.forEach(f => { +// if (f.properties) { + +// // fetch the old element +// let cur: Feature | undefined = curFeatureCollection.features.find(c => c.id === f.id) +// // make sure the old element is not identical to the current +// if (cur && isEqual(cur, f) && isEqual(cur?.properties, f?.properties)) { +// return; +// } + +// // if we found the old one and it got changed, close it +// if (cur && cur.properties) { +// cur.properties['deletedAt'] = new Date(); +// modifiedFeatures.push(cur); +// } + +// // generate a new ID and +// f.id = hat(); +// f.properties['createdAt'] = new Date(); +// f.properties['achestorID'] = cur?.id; +// modifiedFeatures.push(f); +// } +// }); +// newFeatureCollection.features = [...curFeatureCollection.features, ...modifiedFeatures]; + +// return newFeatureCollection; +// }); +// setSelectedFeature(undefined); +// draw?.changeMode("simple_select") +// }, [setFeatures, setSelectedFeature, draw]); + +// const onDelete = useCallback((e: any) => { +// console.log("[onDelete]", e); + +// setFeatures(curFeatureCollection => { +// const newFeatureCollection = { ...curFeatureCollection }; +// const deletedFeatures: Feature[] = e.features; +// deletedFeatures.forEach(f => { +// if (f.properties) { +// // fetch the old element and close it +// let cur: Feature | undefined = curFeatureCollection.features.find(c => c.id === f.id) +// if (cur && cur.properties) { +// cur.properties['deletedAt'] = new Date(); +// newFeatureCollection.features.push(f); +// } +// } +// }); +// newFeatureCollection.features = unionBy(newFeatureCollection.features, curFeatureCollection.features, 'id'); + +// return newFeatureCollection; +// }); +// setSelectedFeature(undefined); +// draw?.changeMode("simple_select") +// }, [setFeatures, setSelectedFeature, draw]); + +// const onSelectionChange = useCallback((e: { features: Feature[]; }) => { +// console.log("[onSelectionChange]", e) +// const features: Feature[] = e.features; +// if (features.length >= 1) { +// const feature = features[0]; +// // always work on the parent feature +// if (feature.properties?.parent) { +// setSelectedFeature(feature.properties.parent); +// draw?.changeMode('simple_select', { featureIds: [feature.properties.parent] }) +// return +// } +// setSelectedFeature(feature.id); +// } +// else { +// setSelectedFeature(undefined); +// draw?.changeMode("simple_select") +// } +// }, [setSelectedFeature, draw]); + + +// useEffect(() => { + +// isMapLoaded && draw && draw.set(filteredFC); +// console.log("update map", filteredFC) +// }, [features, isMapLoaded, draw]); + +// return ( +// <> +//

Lage

+// +//

Das Lagebild wird nicht mit dem Server synchronisiert, aber lokal gespeichert.

+//
+//
+// +// setViewState(e.viewState)} +// mapStyle={mapStyle.uri} +// > +// +// +// {/* +// +// */} +// +// {/* */} +// f.id === selectedFeature).shift()} onUpdate={onUpdate} /> +// + + +// {/* { onMapLoad(); return true } } }} />*/} +// +// +// +// {/* f.id === selectedFeature).shift()}> +// f.id === selectedFeature).shift()} /> +// */} +// +// +//
+// +// ); +// } + +const MemoMap = MapView; export { MemoMap as Map }; diff --git a/ui/src/views/map/controls/DrawControl.tsx b/ui/src/views/map/controls/DrawControl.tsx index 13b93a5b..c3cad4d7 100644 --- a/ui/src/views/map/controls/DrawControl.tsx +++ b/ui/src/views/map/controls/DrawControl.tsx @@ -4,7 +4,6 @@ import { useControl } from "react-map-gl"; import { Dispatch, memo, SetStateAction, useEffect, useState } from "react"; import type { ControlPosition } from "react-map-gl/maplibre"; - type DrawControlProps = ConstructorParameters[0] & { position?: ControlPosition; setDraw: Dispatch> @@ -37,7 +36,6 @@ function DrawControl(props: DrawControlProps) { const draw = new MapboxDraw(props); setDraw(draw); - return draw; }, ({ map }) => { @@ -66,7 +64,7 @@ DrawControl.defaultProps = { onDelete: () => { }, // eslint-disable-next-line @typescript-eslint/no-empty-function onCombine: () => { }, - // eslint-disable-next-line @typescript-eslint/no-empty-function + // eslint-disable-nexexport default memo(DrawControl);t-line @typescript-eslint/no-empty-function onSelectionChange: () => { } }; diff --git a/ui/src/views/map/controls/StyleController.tsx b/ui/src/views/map/controls/StyleController.tsx index efdb4170..b1428a34 100644 --- a/ui/src/views/map/controls/StyleController.tsx +++ b/ui/src/views/map/controls/StyleController.tsx @@ -19,7 +19,7 @@ const MapStyles: MapStyle[] = [ ] export const selectedStyle = makeVar(MapStyles[0]); - +export const activeLayer = makeVar(""); type MapStyle = { name: string, diff --git a/ui/src/views/map/graphql.ts b/ui/src/views/map/graphql.ts new file mode 100644 index 00000000..93dd29d9 --- /dev/null +++ b/ui/src/views/map/graphql.ts @@ -0,0 +1,76 @@ +import { gql } from "@apollo/client"; + +const GET_LAYERS = gql` + query GetLayers($incidentId: uuid!) { + layers(where: {incidentId: {_eq: $incidentId}}) { + id + isActive @client + name + features { + id + geometry + properties + createdAt + updatedAt + deletedAt + } + } + } +`; + +const ADD_FEATURE = gql` + mutation AddFeature($layerId: uuid!, $id: uuid!, $geometry: jsonb, $properties: jsonb) { + insertFeaturesOne(object: {layerId: $layerId, id: $id, geometry: $geometry, properties: $properties}) { + id + geometry + properties + createdAt + updatedAt + deletedAt + } + } +`; + +const MODIFY_FEATURE = gql` + mutation UpdateFeature($id: uuid!, $geometry: jsonb, $properties: jsonb) { + updateFeaturesByPk( + pkColumns: {id: $id} + _set: { + geometry: $geometry + properties: $properties + } + ) { + id + geometry + properties + createdAt + updatedAt + deletedAt + } + } +`; + +const DELETE_FEATURE = gql` + mutation UpdateFeature($id: uuid!, $deletedAt: timestamptz) { + updateFeaturesByPk( + pkColumns: {id: $id} + _set: { + deletedAt: $deletedAt + } + ) { + id + geometry + properties + createdAt + updatedAt + deletedAt + } + } +`; + +export { + GET_LAYERS as GetLayers, + ADD_FEATURE as AddFeatureToLayer, + MODIFY_FEATURE as ModifyFeature, + DELETE_FEATURE as DeleteFeature, +} diff --git a/ui/src/views/map/utils.ts b/ui/src/views/map/utils.ts new file mode 100644 index 00000000..97689373 --- /dev/null +++ b/ui/src/views/map/utils.ts @@ -0,0 +1,82 @@ +import { FeatureCollection, Feature, Geometry, GeoJsonProperties } from "geojson"; +import { omit } from "lodash"; +import { Layer, Feature as GraphQlFeature } from "types/layer"; + + +type LayerMap = { + active: FeatureCollection; + inactive: FeatureCollection[]; +} + + +const LayersToLayerMap = (layers: Layer[], activeLayerId: string): LayerMap => { + // let featureCollections: FeatureCollection[] = [] + + let layerMap: LayerMap = { active: { features: [], type: "FeatureCollection" }, inactive: [] }; + + layers.forEach(layer => { + let fc: FeatureCollection = LayerToFeatureCollection(layer) + if (layer.id === activeLayerId) { + layerMap.active = fc; + } + else { + layerMap.inactive.push(fc) + } + }); + + return layerMap +} + +const LayerToFeatureCollection = (layer: Layer | undefined): FeatureCollection => { + let fc: FeatureCollection = { features: [], type: "FeatureCollection" }; + + layer?.features.forEach(f => + fc.features.push(ConvertFeatureToGeoJsonFeature(f, layer.id)) + ) + + return fc +} + +function ConvertFeatureToGeoJsonFeature(f: GraphQlFeature, layerId: string) { + let feature: Feature = { + type: "Feature", + id: f.id, + geometry: f.geometry, + properties: Object.assign({}, f.properties, { + createdAt: f.createdAt, + updatedAt: f.updatedAt, + deletedAt: f.deletedAt, + layerId: layerId, + }), + }; + return feature +} + +function FilterActiveFeatures(fc: FeatureCollection) { + let filteredFC: FeatureCollection = { "type": "FeatureCollection", "features": [] }; + filteredFC.features = Object.assign([], fc.features.filter(f => f.properties?.deletedAt === null)) + + return filteredFC +} + +function CleanFeature(f: Feature) { + let feature: Feature = { + type: "Feature", + id: f.id, + geometry: f.geometry, + properties: omit(f.properties, ['createdAt', 'updatedAt', 'deletedAt', 'layerId']) + + }; + + return feature; +} + + + +export { + LayersToLayerMap, + LayerToFeatureCollection, + ConvertFeatureToGeoJsonFeature, + FilterActiveFeatures, + CleanFeature +} \ No newline at end of file diff --git a/ui/yarn.lock b/ui/yarn.lock index 4396e165..7d131bc6 100644 --- a/ui/yarn.lock +++ b/ui/yarn.lock @@ -59,24 +59,26 @@ __metadata: languageName: node linkType: hard -"@apollo/client@npm:^3.8.10": - version: 3.8.10 - resolution: "@apollo/client@npm:3.8.10" +"@apollo/client@npm:^3.9.5": + version: 3.9.5 + resolution: "@apollo/client@npm:3.9.5" dependencies: "@graphql-typed-document-node/core": "npm:^3.1.1" + "@wry/caches": "npm:^1.0.0" "@wry/equality": "npm:^0.5.6" "@wry/trie": "npm:^0.5.0" graphql-tag: "npm:^2.12.6" hoist-non-react-statics: "npm:^3.3.2" optimism: "npm:^0.18.0" prop-types: "npm:^15.7.2" + rehackt: "npm:0.0.5" response-iterator: "npm:^0.2.6" symbol-observable: "npm:^4.0.0" ts-invariant: "npm:^0.10.3" tslib: "npm:^2.3.0" zen-observable-ts: "npm:^1.2.5" peerDependencies: - graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 + graphql: ^15.0.0 || ^16.0.0 graphql-ws: ^5.5.5 react: ^16.8.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 @@ -90,7 +92,7 @@ __metadata: optional: true subscriptions-transport-ws: optional: true - checksum: 10/f28454904887f2b8df7782ef790583545bef1bd4839504769cc01cbbcc5471e9c3cc9ab60a26b586b4c98d4145184a15b07f35effcbb2785a43b5a85d26993e6 + checksum: 10/359b980928fe476c14b38b81aa3713fb6ef525d902caa67e218b7393d76f88433a1dad1b515908ab10bfe7ddec68461f5fbb3dc53bdd6ce185ab57984e7c9028 languageName: node linkType: hard @@ -2762,7 +2764,7 @@ __metadata: languageName: node linkType: hard -"@babel/runtime@npm:^7.11.2": +"@babel/runtime@npm:^7.11.2, @babel/runtime@npm:^7.23.9": version: 7.23.9 resolution: "@babel/runtime@npm:7.23.9" dependencies: @@ -2771,15 +2773,6 @@ __metadata: languageName: node linkType: hard -"@babel/runtime@npm:^7.22.5": - version: 7.22.6 - resolution: "@babel/runtime@npm:7.22.6" - dependencies: - regenerator-runtime: "npm:^0.13.11" - checksum: 10/1d2f56797f548b009910bddf3dc04f980a9701193233145dc923f3ea87c8f88121a3c3ef1d449e9cb52a370d7d025a2243c748882d5546ff079ddf5ffe29f240 - languageName: node - linkType: hard - "@babel/runtime@npm:^7.23.2": version: 7.23.8 resolution: "@babel/runtime@npm:7.23.8" @@ -3870,7 +3863,7 @@ __metadata: languageName: node linkType: hard -"@mapbox/mapbox-gl-draw@npm:^1.3.0": +"@mapbox/mapbox-gl-draw@npm:^1.4.3": version: 1.4.3 resolution: "@mapbox/mapbox-gl-draw@npm:1.4.3" dependencies: @@ -4026,10 +4019,10 @@ __metadata: languageName: node linkType: hard -"@remix-run/router@npm:1.14.2": - version: 1.14.2 - resolution: "@remix-run/router@npm:1.14.2" - checksum: 10/422844e88b985f1e287301b302c6cf8169c9eea792f80d40464f97b25393bb2e697228ebd7a7b61444d5a51c5873c4a637aad20acde5886a5caf62e833c5ceee +"@remix-run/router@npm:1.15.1": + version: 1.15.1 + resolution: "@remix-run/router@npm:1.15.1" + checksum: 10/d262285d155f80779894ee1d9ef07e35421986ba2546378dfe0e3b09397ce71becb6a4677e9efcd4155e2bd3f9f7f7ecbc110cd99bacee6dd7d3e5ce51b7caa8 languageName: node linkType: hard @@ -4849,7 +4842,7 @@ __metadata: languageName: node linkType: hard -"@types/mapbox-gl@npm:>=1.0.0, @types/mapbox-gl@npm:^2.7.19": +"@types/mapbox-gl@npm:>=1.0.0": version: 2.7.19 resolution: "@types/mapbox-gl@npm:2.7.19" dependencies: @@ -4858,13 +4851,22 @@ __metadata: languageName: node linkType: hard -"@types/mapbox__mapbox-gl-draw@npm:~1.3.3": - version: 1.3.3 - resolution: "@types/mapbox__mapbox-gl-draw@npm:1.3.3" +"@types/mapbox-gl@npm:^2.7.21": + version: 2.7.21 + resolution: "@types/mapbox-gl@npm:2.7.21" + dependencies: + "@types/geojson": "npm:*" + checksum: 10/95ad2c0c135b090dfef9a5fc8e66a0f2ac70d44617c23e10984288fe268c49e9c72c3e801461a680ad60981c6b2fdd03a4f0df5ccc2bfa24f4b00c5ef6c66df4 + languageName: node + linkType: hard + +"@types/mapbox__mapbox-gl-draw@npm:~1.4.6": + version: 1.4.6 + resolution: "@types/mapbox__mapbox-gl-draw@npm:1.4.6" dependencies: "@types/geojson": "npm:*" "@types/mapbox-gl": "npm:*" - checksum: 10/7a1bbf864195d55154e54913676e557cab92276d26fc01bee0f3bd4e2674eb520c2a5a5d2e819ba00952c7f6aa1f727d261c708f8ada112de3827596ad4bfd52 + checksum: 10/1729efba8f5306d513f7799a6936689c32e58259095f1a9580643fb1d831bf7716d72e9bcc9a7ce45dd131a3025c2859b3bba3410e62ed7cdf825ca0606bc016 languageName: node linkType: hard @@ -4909,12 +4911,12 @@ __metadata: languageName: node linkType: hard -"@types/node@npm:^20.11.6": - version: 20.11.6 - resolution: "@types/node@npm:20.11.6" +"@types/node@npm:^20.11.19": + version: 20.11.19 + resolution: "@types/node@npm:20.11.19" dependencies: undici-types: "npm:~5.26.4" - checksum: 10/375dfc75f0aa8b7fb99382d84c317e5dab2969dcc3f54d8490b119151060923eae6483839552347bc78262bbcefb2951ee131e15372f4e80b755be2dda9afe80 + checksum: 10/c7f4705d6c84aa21679ad180c33c13ca9567f650e66e14bcee77c7c43d14619c7cd3b4d7b2458947143030b7b1930180efa6d12d999b45366abff9fed7a17472 languageName: node linkType: hard @@ -5022,17 +5024,24 @@ __metadata: languageName: node linkType: hard -"@types/semver@npm:^7, @types/semver@npm:^7.5.0": +"@types/semver@npm:^7.3.12": + version: 7.3.13 + resolution: "@types/semver@npm:7.3.13" + checksum: 10/0064efd7a0515a539062b71630c72ca2b058501b957326c285cdff82f42c1716d9f9f831332ccf719d5ee8cc3ef24f9ff62122d7a7140c73959a240b49b0f62d + languageName: node + linkType: hard + +"@types/semver@npm:^7.5.0": version: 7.5.6 resolution: "@types/semver@npm:7.5.6" checksum: 10/e77282b17f74354e17e771c0035cccb54b94cc53d0433fa7e9ba9d23fd5d7edcd14b6c8b7327d58bbd89e83b1c5eda71dfe408e06b929007e2b89586e9b63459 languageName: node linkType: hard -"@types/semver@npm:^7.3.12": - version: 7.3.13 - resolution: "@types/semver@npm:7.3.13" - checksum: 10/0064efd7a0515a539062b71630c72ca2b058501b957326c285cdff82f42c1716d9f9f831332ccf719d5ee8cc3ef24f9ff62122d7a7140c73959a240b49b0f62d +"@types/semver@npm:^7.5.7": + version: 7.5.7 + resolution: "@types/semver@npm:7.5.7" + checksum: 10/535d88ec577fe59e38211881f79a1e2ba391e9e1516f8fff74e7196a5ba54315bace9c67a4616c334c830c89027d70a9f473a4ceb634526086a9da39180f2f9a languageName: node linkType: hard @@ -5443,9 +5452,9 @@ __metadata: languageName: node linkType: hard -"@vite-pwa/assets-generator@npm:^0.2.3": - version: 0.2.3 - resolution: "@vite-pwa/assets-generator@npm:0.2.3" +"@vite-pwa/assets-generator@npm:^0.2.4": + version: 0.2.4 + resolution: "@vite-pwa/assets-generator@npm:0.2.4" dependencies: cac: "npm:^6.7.14" colorette: "npm:^2.0.20" @@ -5455,7 +5464,7 @@ __metadata: unconfig: "npm:^0.3.11" bin: pwa-assets-generator: bin/pwa-assets-generator.mjs - checksum: 10/a3b3b6e30c0137d8436e6105ec804ca58b647072c07b77801f9cc9ac3569e40dd3d6ce802a2fb08e034507aa5d9efd026346f58a283949632cf6967e79dc8775 + checksum: 10/2fb28ce3730af8619431c46fda65316fffb5b9c716d494a5a4470e5524d1116a4d730cd2c010310f4f080cbe24fac7702e7c3861872e32a892c2ec742bae037d languageName: node linkType: hard @@ -5470,9 +5479,9 @@ __metadata: languageName: node linkType: hard -"@vitest/coverage-v8@npm:^1.2.2": - version: 1.2.2 - resolution: "@vitest/coverage-v8@npm:1.2.2" +"@vitest/coverage-v8@npm:^1.3.0": + version: 1.3.0 + resolution: "@vitest/coverage-v8@npm:1.3.0" dependencies: "@ampproject/remapping": "npm:^2.2.1" "@bcoe/v8-coverage": "npm:^0.2.3" @@ -5488,62 +5497,62 @@ __metadata: test-exclude: "npm:^6.0.0" v8-to-istanbul: "npm:^9.2.0" peerDependencies: - vitest: ^1.0.0 - checksum: 10/006468751dc3bebdbb833691a4713dd3fa0eda94a797f2c86eaf0b6ca7fa3e4d2574af37f9fe319ba9cba736b5c3ad2a97843ad78bac112db035e94940716e37 + vitest: 1.3.0 + checksum: 10/10cce5d97add269b5b17350fc76c4d84aea9f491f05744d22ad1e639bcfa588511589134a5e4bd9b6874df515f11f889a216f920e11cba54d87745bcf6be746a languageName: node linkType: hard -"@vitest/expect@npm:1.2.2": - version: 1.2.2 - resolution: "@vitest/expect@npm:1.2.2" +"@vitest/expect@npm:1.3.0": + version: 1.3.0 + resolution: "@vitest/expect@npm:1.3.0" dependencies: - "@vitest/spy": "npm:1.2.2" - "@vitest/utils": "npm:1.2.2" + "@vitest/spy": "npm:1.3.0" + "@vitest/utils": "npm:1.3.0" chai: "npm:^4.3.10" - checksum: 10/409bf9984a2901cd13bd8644d1dcc61a3b85a122e70f842626c83995b806c6fb1ed5a81685493e88df8bf76557e599bdeed5fd5e908d84a4cb0fa4947b90b631 + checksum: 10/32bc76108a608acb614dfb46ee9b588fc1a3f1fb68ba400a49d6ab80de463a2ab8a6a5dd51960ca76417b300d7c5ce16e2e346a0b0ebe25ca3b80232a378d6dc languageName: node linkType: hard -"@vitest/runner@npm:1.2.2": - version: 1.2.2 - resolution: "@vitest/runner@npm:1.2.2" +"@vitest/runner@npm:1.3.0": + version: 1.3.0 + resolution: "@vitest/runner@npm:1.3.0" dependencies: - "@vitest/utils": "npm:1.2.2" + "@vitest/utils": "npm:1.3.0" p-limit: "npm:^5.0.0" pathe: "npm:^1.1.1" - checksum: 10/e12a758a8c9ce762af470fc5a33e42a416b1e16469b69a077bc021044c460c468ed24fa892e80cba4bfc0448df8484d1bfc43a271db09560347455aa392cc8aa + checksum: 10/7212e457fa89425c1e0b75e1817b50dc9c7d554617faf2178784a5fe0b97e6e4fe417e20886bd189f5385d01ac3f389d90ebca2a01ea4dee8a26897921db70c1 languageName: node linkType: hard -"@vitest/snapshot@npm:1.2.2": - version: 1.2.2 - resolution: "@vitest/snapshot@npm:1.2.2" +"@vitest/snapshot@npm:1.3.0": + version: 1.3.0 + resolution: "@vitest/snapshot@npm:1.3.0" dependencies: magic-string: "npm:^0.30.5" pathe: "npm:^1.1.1" pretty-format: "npm:^29.7.0" - checksum: 10/73e669efdf8ba7270a2b71b988ca93fc9cbc9f9b4ad4cb7c7f8d44dbedfef3109fc8896867b8e1f22cd95494ce18cbc1026a0f89ef4a2e7e4546cf8e613ed302 + checksum: 10/b39bfee8ba9424e672e1c3076e3ee679d530ab8d38a5ad94ceec109063925caae10d4656d9256e331d2f8ed4a3169bfdc8ae584afe171fd51bbe70892dbce1b9 languageName: node linkType: hard -"@vitest/spy@npm:1.2.2": - version: 1.2.2 - resolution: "@vitest/spy@npm:1.2.2" +"@vitest/spy@npm:1.3.0": + version: 1.3.0 + resolution: "@vitest/spy@npm:1.3.0" dependencies: tinyspy: "npm:^2.2.0" - checksum: 10/8cf453f2b0c519b27d783dafbca8a4df6945b8f4723077e7ae153ef06bcb1422af608d2a09912284c3bd7bd1e66555d82d889497780295c73a14876807755a79 + checksum: 10/780c6b678aeb3cc1ccd730ff35fb6596e1a0adb78b39934e37bc6ae712b99bcf46a9387b34f8e76265c6805aef1dff72f47a921695ae5c567d59490d097c90a0 languageName: node linkType: hard -"@vitest/utils@npm:1.2.2": - version: 1.2.2 - resolution: "@vitest/utils@npm:1.2.2" +"@vitest/utils@npm:1.3.0": + version: 1.3.0 + resolution: "@vitest/utils@npm:1.3.0" dependencies: diff-sequences: "npm:^29.6.3" estree-walker: "npm:^3.0.3" loupe: "npm:^2.3.7" pretty-format: "npm:^29.7.0" - checksum: 10/f9a62bc8cbe05475b99e1f8bd96e0ee48cf819ca2e532ba18f071bf0371f044dffa006c33a69b1b276097e6b50f91342a776c830cfac19456b24a9bdad29abe5 + checksum: 10/9d544b24e25659d9f715f43906b9e40571450bbc14cb0320487dae67e4561acb61820200f4e185e1f7b805a17225b70802ebace9fb5c313bfff82ee3841278a9 languageName: node linkType: hard @@ -5626,7 +5635,7 @@ __metadata: languageName: node linkType: hard -"acorn@npm:^8.10.0, acorn@npm:^8.11.3, acorn@npm:^8.8.2, acorn@npm:^8.9.0": +"acorn@npm:^8.11.3, acorn@npm:^8.8.2, acorn@npm:^8.9.0": version: 8.11.3 resolution: "acorn@npm:8.11.3" bin: @@ -8848,12 +8857,12 @@ __metadata: languageName: node linkType: hard -"i18next@npm:^23.7.19": - version: 23.7.19 - resolution: "i18next@npm:23.7.19" +"i18next@npm:^23.9.0": + version: 23.9.0 + resolution: "i18next@npm:23.9.0" dependencies: "@babel/runtime": "npm:^7.23.2" - checksum: 10/fbfce98aae854ae5c68b784ae36becfb0833b08329a46f9c49396f8af09f25f23611d7cd78dc3666fdead45d697173cf43d97b014cdf7042513ed922054fd515 + checksum: 10/7b91f22560a07b2e3f874de30ce3f875cd19beb4657b0f4ac2f805554f9c44bea3552b6cf933f64c83baa1e9a0d8f85110711b4e96354b3b4d79c039b6b7373f languageName: node linkType: hard @@ -10042,6 +10051,13 @@ __metadata: languageName: node linkType: hard +"js-tokens@npm:^8.0.2": + version: 8.0.3 + resolution: "js-tokens@npm:8.0.3" + checksum: 10/af5ed8ddbc446a868c026599214f4a482ab52461edb82e547949255f98910a14bd81ddab88a8d570d74bd7dc96c6d4df7f963794ec5aaf13c53918cc46b9caa6 + languageName: node + linkType: hard + "js-yaml@npm:^3.13.1": version: 3.14.1 resolution: "js-yaml@npm:3.14.1" @@ -10305,29 +10321,29 @@ __metadata: languageName: node linkType: hard -"lint-staged@npm:^15.2.0": - version: 15.2.0 - resolution: "lint-staged@npm:15.2.0" +"lint-staged@npm:^15.2.2": + version: 15.2.2 + resolution: "lint-staged@npm:15.2.2" dependencies: chalk: "npm:5.3.0" commander: "npm:11.1.0" debug: "npm:4.3.4" execa: "npm:8.0.1" lilconfig: "npm:3.0.0" - listr2: "npm:8.0.0" + listr2: "npm:8.0.1" micromatch: "npm:4.0.5" pidtree: "npm:0.6.0" string-argv: "npm:0.3.2" yaml: "npm:2.3.4" bin: lint-staged: bin/lint-staged.js - checksum: 10/2a20e9b15f7e7419e92a2014afb01feb58798341a4a56aa1b9c8570297681cf54919f645df3c221e4348fd262df38c1e711a245a645d477bf870841e27c604f2 + checksum: 10/5855ae7abf3ffdc2d66e8ad20759915e76544e7c4bcdfef78c82b5c126502284320d9fb0ecde554a6d07747311ab751d0bccbe3468aa5d5a7661774317cd7437 languageName: node linkType: hard -"listr2@npm:8.0.0": - version: 8.0.0 - resolution: "listr2@npm:8.0.0" +"listr2@npm:8.0.1": + version: 8.0.1 + resolution: "listr2@npm:8.0.1" dependencies: cli-truncate: "npm:^4.0.0" colorette: "npm:^2.0.20" @@ -10335,7 +10351,7 @@ __metadata: log-update: "npm:^6.0.0" rfdc: "npm:^1.3.0" wrap-ansi: "npm:^9.0.0" - checksum: 10/d5a53b6d5feaa3a45c3750ebf10d242d42f11741b890edf8de7d68a002c36f15d0683f25742a0eb055763f04c005210a5cd61ef6c24ebac099d597cb21b06f29 + checksum: 10/3fa83e8b709306b7efab69884ac1af08de3e18449bccf0b4d81f78dc7235dc921a32a5875b1e7deea0650f0faef2bca2d8992f16377d858158eb5a57bbb0d025 languageName: node linkType: hard @@ -11724,6 +11740,17 @@ __metadata: languageName: node linkType: hard +"postcss@npm:^8.4.35": + version: 8.4.35 + resolution: "postcss@npm:8.4.35" + dependencies: + nanoid: "npm:^3.3.7" + picocolors: "npm:^1.0.0" + source-map-js: "npm:^1.0.2" + checksum: 10/93a7ce50cd6188f5f486a9ca98950ad27c19dfed996c45c414fa242944497e4d084a8760d3537f078630226f2bd3c6ab84b813b488740f4432e7c7039cd73a20 + languageName: node + linkType: hard + "potpack@npm:^1.0.1, potpack@npm:^1.0.2": version: 1.0.2 resolution: "potpack@npm:1.0.2" @@ -11738,12 +11765,12 @@ __metadata: languageName: node linkType: hard -"prettier@npm:^3.2.4": - version: 3.2.4 - resolution: "prettier@npm:3.2.4" +"prettier@npm:^3.2.5": + version: 3.2.5 + resolution: "prettier@npm:3.2.5" bin: prettier: bin/prettier.cjs - checksum: 10/e2b735d0552501b3a7ac8bd3ba3b6de2920bb35bd4cd02d08cb9057ebe3e96d83b9a7e4b903d987b7530a50223b12c74d107c154337236ae2c68156ba1e65cd2 + checksum: 10/d509f9da0b70e8cacc561a1911c0d99ec75117faed27b95cc8534cb2349667dee6351b0ca83fa9d5703f14127faa52b798de40f5705f02d843da133fc3aa416a languageName: node linkType: hard @@ -11951,11 +11978,11 @@ __metadata: languageName: node linkType: hard -"react-i18next@npm:^14.0.1": - version: 14.0.1 - resolution: "react-i18next@npm:14.0.1" +"react-i18next@npm:^14.0.5": + version: 14.0.5 + resolution: "react-i18next@npm:14.0.5" dependencies: - "@babel/runtime": "npm:^7.22.5" + "@babel/runtime": "npm:^7.23.9" html-parse-stringify: "npm:^3.0.1" peerDependencies: i18next: ">= 23.2.3" @@ -11965,7 +11992,7 @@ __metadata: optional: true react-native: optional: true - checksum: 10/f286bac8d091153dc5e8a469c181fddc916c660b10ca496f2fac7bd41ec6dd04e3074e0ce32db5f0192690fc6dbd522fbcf8644813a86b77eeee75d10725024b + checksum: 10/4c91d4b889ab1ab05d7cda050890f7f41c4a73dadfef4778770a53b0d2f98e9d80f04da5086790706349d8a80cf09eec0c539fc1020a7c6fead562511dd2a2cf languageName: node linkType: hard @@ -12036,27 +12063,27 @@ __metadata: languageName: node linkType: hard -"react-router-dom@npm:^6.21.3": - version: 6.21.3 - resolution: "react-router-dom@npm:6.21.3" +"react-router-dom@npm:^6.22.1": + version: 6.22.1 + resolution: "react-router-dom@npm:6.22.1" dependencies: - "@remix-run/router": "npm:1.14.2" - react-router: "npm:6.21.3" + "@remix-run/router": "npm:1.15.1" + react-router: "npm:6.22.1" peerDependencies: react: ">=16.8" react-dom: ">=16.8" - checksum: 10/6e23e35d02e5c83847c8e47d7912d1f6c2c42a35f2317802031bdd993a8205468138a045ff34f67fe807fe9f7dc9d0995ee05bab25aedc0bf978e620ac132815 + checksum: 10/73ab964083bb407773a5c4ca61249ed6b0a1b47fa58c39afca08a361eb25b349be2bcbaf6d89e112b020f6e55e40e62689c9fe2beae524030ce5ccede3c7d9e3 languageName: node linkType: hard -"react-router@npm:6.21.3": - version: 6.21.3 - resolution: "react-router@npm:6.21.3" +"react-router@npm:6.22.1": + version: 6.22.1 + resolution: "react-router@npm:6.22.1" dependencies: - "@remix-run/router": "npm:1.14.2" + "@remix-run/router": "npm:1.15.1" peerDependencies: react: ">=16.8" - checksum: 10/3d5107cfdb440519d84e6ad6d95454e3bf41ec97677b95f7b2a7f281f8ddf191b765cf1b599ead951f3cd33ed4429f140590d74a01cfdf835dc2f812023a978a + checksum: 10/f6e814b8e3005f16a5fb0e831f0e4352076cde65ab25448d56dba87a43fd3e102f55f9b366bdf1fbd8136fc1dc141bcec8d6b85d45f309e89180fb50f173744d languageName: node linkType: hard @@ -12231,6 +12258,21 @@ __metadata: languageName: node linkType: hard +"rehackt@npm:0.0.5": + version: 0.0.5 + resolution: "rehackt@npm:0.0.5" + peerDependencies: + "@types/react": "*" + react: "*" + peerDependenciesMeta: + "@types/react": + optional: true + react: + optional: true + checksum: 10/a7536eaeb47ba46bc29fa050c7cbf80860809689c893c6177664efbbdca641a1996185ea6602fb76473f0e2b145c1f3e9c27a5e738054d69809b6c39046fe269 + languageName: node + linkType: hard + "remark-parse@npm:^10.0.0": version: 10.0.1 resolution: "remark-parse@npm:10.0.1" @@ -12591,16 +12633,16 @@ __metadata: languageName: node linkType: hard -"sass@npm:^1.70.0": - version: 1.70.0 - resolution: "sass@npm:1.70.0" +"sass@npm:^1.71.0": + version: 1.71.0 + resolution: "sass@npm:1.71.0" dependencies: chokidar: "npm:>=3.0.0 <4.0.0" immutable: "npm:^4.0.0" source-map-js: "npm:>=0.6.2 <2.0.0" bin: sass: sass.js - checksum: 10/f933545d72a932f4a82322dd4ca9f3ea7d3e9d08852d695f76d419939cbdf7f8db3dd894b059ed77bf76811b07319b75b3ef8bb077bf9f52f8fbdfd8cee162f6 + checksum: 10/5616f0ef0d764d0a6242155edd02773300e0d9ac1c0b542781ae934095f3b2f73312707f95edc08c34ae88bad633bb55c8cc67ad390bef40e04eb84ae9b028cd languageName: node linkType: hard @@ -12843,12 +12885,12 @@ __metadata: version: 0.0.0-use.local resolution: "sitrep@workspace:." dependencies: - "@apollo/client": "npm:^3.8.10" + "@apollo/client": "npm:^3.9.5" "@fortawesome/fontawesome-svg-core": "npm:^6.5.1" "@fortawesome/free-regular-svg-icons": "npm:^6.5.1" "@fortawesome/free-solid-svg-icons": "npm:^6.5.1" "@fortawesome/react-fontawesome": "npm:^0.2.0" - "@mapbox/mapbox-gl-draw": "npm:^1.3.0" + "@mapbox/mapbox-gl-draw": "npm:^1.4.3" "@testing-library/dom": "npm:^9.3.4" "@testing-library/jest-dom": "npm:^6.4.2" "@testing-library/react": "npm:^14.2.1" @@ -12859,19 +12901,19 @@ __metadata: "@types/hat": "npm:^0.0.4" "@types/jest": "npm:^29.5.12" "@types/lodash": "npm:^4.14.202" - "@types/mapbox-gl": "npm:^2.7.19" - "@types/mapbox__mapbox-gl-draw": "npm:~1.3.3" - "@types/node": "npm:^20.11.6" - "@types/react": "npm:^18.2.48" + "@types/mapbox-gl": "npm:^2.7.21" + "@types/mapbox__mapbox-gl-draw": "npm:~1.4.6" + "@types/node": "npm:^20.11.19" + "@types/react": "npm:^18.2.56" "@types/react-color": "npm:^3.0.11" - "@types/react-dom": "npm:^18.2.18" + "@types/react-dom": "npm:^18.2.19" "@types/react-router-dom": "npm:^5.3.3" - "@types/semver": "npm:^7" + "@types/semver": "npm:^7.5.7" "@typescript-eslint/eslint-plugin": "npm:^6.21.0" "@typescript-eslint/parser": "npm:^6.21.0" - "@vite-pwa/assets-generator": "npm:^0.2.3" + "@vite-pwa/assets-generator": "npm:^0.2.4" "@vitejs/plugin-react-swc": "npm:^3.6.0" - "@vitest/coverage-v8": "npm:^1.2.2" + "@vitest/coverage-v8": "npm:^1.3.0" "@watergis/maplibre-gl-export": "npm:~2.0.1" bulma: "npm:^0.9.4" classnames: "npm:^2.5.1" @@ -12881,37 +12923,37 @@ __metadata: graphql: "npm:^16.8.1" hat: "npm:^0.0.3" husky: "npm:^8.0.3" - i18next: "npm:^23.7.19" + i18next: "npm:^23.9.0" i18next-browser-languagedetector: "npm:^7.2.0" jest: "npm:^29.7.0" jsdom: "npm:^24.0.0" - lint-staged: "npm:^15.2.0" + lint-staged: "npm:^15.2.2" lodash: "npm:^4.17.21" mapbox-gl-style-switcher: "npm:^1.0.11" maplibre-gl: "npm:^2.4.0" - prettier: "npm:^3.2.4" + prettier: "npm:^3.2.5" react: "npm:^18.2.0" react-autocomplete-hint: "npm:^2.0.0" react-color: "npm:^2.19.3" react-dom: "npm:^18.2.0" - react-i18next: "npm:^14.0.1" + react-i18next: "npm:^14.0.5" react-map-gl: "npm:~7.1.7" react-markdown: "npm:^8.0.7" - react-router-dom: "npm:^6.21.3" - sass: "npm:^1.70.0" + react-router-dom: "npm:^6.22.1" + sass: "npm:^1.71.0" semver: "npm:^7.6.0" source-map-explorer: "npm:^2.5.3" ts-jest: "npm:^29.1.2" typescript: "npm:^4.9.5" - usehooks-ts: "npm:^2.10.0" - vite: "npm:^5.0.12" + usehooks-ts: "npm:^2.14.0" + vite: "npm:^5.1.3" vite-plugin-checker: "npm:^0.6.4" vite-plugin-eslint: "npm:^1.8.1" - vite-plugin-pwa: "npm:^0.17.5" + vite-plugin-pwa: "npm:^0.19.0" vite-plugin-svgr: "npm:^4.2.0" vite-tsconfig-paths: "npm:^4.3.1" - vitest: "npm:^1.2.2" - web-vitals: "npm:^3.5.1" + vitest: "npm:^1.3.0" + web-vitals: "npm:^3.5.2" languageName: unknown linkType: soft @@ -13373,12 +13415,12 @@ __metadata: languageName: node linkType: hard -"strip-literal@npm:^1.3.0": - version: 1.3.0 - resolution: "strip-literal@npm:1.3.0" +"strip-literal@npm:^2.0.0": + version: 2.0.0 + resolution: "strip-literal@npm:2.0.0" dependencies: - acorn: "npm:^8.10.0" - checksum: 10/f5fa7e289df8ebe82e90091fd393974faf8871be087ca50114327506519323cf15f2f8fee6ebe68b5e58bfc795269cae8bdc7cb5a83e27b02b3fe953f37b0a89 + js-tokens: "npm:^8.0.2" + checksum: 10/efb3197175a7e403d0eaaaf5382b9574be77f8fa006b57b669856a38b58ca9caf76cbc75d9f69d56324dad0b8babe1d4ea7ad1eb12106228830bcdd5d4bf12b5 languageName: node linkType: hard @@ -14172,13 +14214,14 @@ __metadata: languageName: node linkType: hard -"usehooks-ts@npm:^2.10.0": - version: 2.10.0 - resolution: "usehooks-ts@npm:2.10.0" +"usehooks-ts@npm:^2.14.0": + version: 2.14.0 + resolution: "usehooks-ts@npm:2.14.0" + dependencies: + lodash.debounce: "npm:^4.0.8" peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 - checksum: 10/35dfc898c3b593817a11eb4aa1e8b9e3d28f0250df497196a29255fbb49a8a21a4281aeeae2679ff3b462c6845a01f3c0b5d40b2cb4b4425e01860ebd5a920af + react: ^16.8.0 || ^17 || ^18 + checksum: 10/5ceaafbed357b08553ce3b97c4286b2f514a642a30a350b7715f672834654b385ea3c7d24d0a9dd82af51c7de86c003944d13b4b22542a814a2f733745cbe530 languageName: node linkType: hard @@ -14245,9 +14288,9 @@ __metadata: languageName: node linkType: hard -"vite-node@npm:1.2.2": - version: 1.2.2 - resolution: "vite-node@npm:1.2.2" +"vite-node@npm:1.3.0": + version: 1.3.0 + resolution: "vite-node@npm:1.3.0" dependencies: cac: "npm:^6.7.14" debug: "npm:^4.3.4" @@ -14256,7 +14299,7 @@ __metadata: vite: "npm:^5.0.0" bin: vite-node: vite-node.mjs - checksum: 10/a4b39361011ebf890fb2be83babd24aa29de76185f1683f57a76b6dfcfbdcd7700b83c9d0cd3b5bebc4a114427d7c2612095ab59d0d12732ce21ef816fe86b07 + checksum: 10/39a473a927547416ee72e01310929b02ee6724f1671c20cd1f1bd65494850906624b6a06b813ba2e3f742c42e5e2d2718e4f4248032cf1ad9c95c696659dd832 languageName: node linkType: hard @@ -14324,9 +14367,9 @@ __metadata: languageName: node linkType: hard -"vite-plugin-pwa@npm:^0.17.5": - version: 0.17.5 - resolution: "vite-plugin-pwa@npm:0.17.5" +"vite-plugin-pwa@npm:^0.19.0": + version: 0.19.0 + resolution: "vite-plugin-pwa@npm:0.19.0" dependencies: debug: "npm:^4.3.4" fast-glob: "npm:^3.3.2" @@ -14334,10 +14377,14 @@ __metadata: workbox-build: "npm:^7.0.0" workbox-window: "npm:^7.0.0" peerDependencies: + "@vite-pwa/assets-generator": ^0.2.4 vite: ^3.1.0 || ^4.0.0 || ^5.0.0 workbox-build: ^7.0.0 workbox-window: ^7.0.0 - checksum: 10/6f3aa5929bd2a3b2ddaca0c61b5930c37044c310fbbe03d032125480ad580f8d687f0c6c8c96ffa99b0da21e902a357e4b80e2ef666bb632c40f4fbc17af4dab + peerDependenciesMeta: + "@vite-pwa/assets-generator": + optional: true + checksum: 10/32fa45621959924ac3122d55c6774f9f2937538c15452d07794b1d4720442d8fecf931798ba51ec1469d0a3c6bc26050d9800610fae098feb458032ec45d1713 languageName: node linkType: hard @@ -14370,7 +14417,7 @@ __metadata: languageName: node linkType: hard -"vite@npm:^5.0.0, vite@npm:^5.0.12": +"vite@npm:^5.0.0": version: 5.0.12 resolution: "vite@npm:5.0.12" dependencies: @@ -14410,17 +14457,56 @@ __metadata: languageName: node linkType: hard -"vitest@npm:^1.2.2": - version: 1.2.2 - resolution: "vitest@npm:1.2.2" +"vite@npm:^5.1.3": + version: 5.1.3 + resolution: "vite@npm:5.1.3" + dependencies: + esbuild: "npm:^0.19.3" + fsevents: "npm:~2.3.3" + postcss: "npm:^8.4.35" + rollup: "npm:^4.2.0" + peerDependencies: + "@types/node": ^18.0.0 || >=20.0.0 + less: "*" + lightningcss: ^1.21.0 + sass: "*" + stylus: "*" + sugarss: "*" + terser: ^5.4.0 + dependenciesMeta: + fsevents: + optional: true + peerDependenciesMeta: + "@types/node": + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + bin: + vite: bin/vite.js + checksum: 10/6ba2223157e2cc2fa62dff9004ccba20fc409c6baf7354c64ed0f8e4bcd853092d08d06ec4dec37143e794a96e061879a870d85bad4f1eb9ee5c6d0a13cef30f + languageName: node + linkType: hard + +"vitest@npm:^1.3.0": + version: 1.3.0 + resolution: "vitest@npm:1.3.0" dependencies: - "@vitest/expect": "npm:1.2.2" - "@vitest/runner": "npm:1.2.2" - "@vitest/snapshot": "npm:1.2.2" - "@vitest/spy": "npm:1.2.2" - "@vitest/utils": "npm:1.2.2" + "@vitest/expect": "npm:1.3.0" + "@vitest/runner": "npm:1.3.0" + "@vitest/snapshot": "npm:1.3.0" + "@vitest/spy": "npm:1.3.0" + "@vitest/utils": "npm:1.3.0" acorn-walk: "npm:^8.3.2" - cac: "npm:^6.7.14" chai: "npm:^4.3.10" debug: "npm:^4.3.4" execa: "npm:^8.0.1" @@ -14429,17 +14515,17 @@ __metadata: pathe: "npm:^1.1.1" picocolors: "npm:^1.0.0" std-env: "npm:^3.5.0" - strip-literal: "npm:^1.3.0" + strip-literal: "npm:^2.0.0" tinybench: "npm:^2.5.1" tinypool: "npm:^0.8.2" vite: "npm:^5.0.0" - vite-node: "npm:1.2.2" + vite-node: "npm:1.3.0" why-is-node-running: "npm:^2.2.2" peerDependencies: "@edge-runtime/vm": "*" "@types/node": ^18.0.0 || >=20.0.0 - "@vitest/browser": ^1.0.0 - "@vitest/ui": ^1.0.0 + "@vitest/browser": 1.3.0 + "@vitest/ui": 1.3.0 happy-dom: "*" jsdom: "*" peerDependenciesMeta: @@ -14457,7 +14543,7 @@ __metadata: optional: true bin: vitest: vitest.mjs - checksum: 10/1dc90823cde249a60e955f82e67cef76c363c78a9783c4dae94a080199fa3e48a56a5c9d1f40667b4542862e183d05c444af846059477b3a66c6b952d168b9cb + checksum: 10/4aabfb972f5f2c165f1f751a063a07a5be952bc684f93ab19ea617fad633fd3db7473ebc7c59c1afdb902334364710476785f01d1cc3cc071a48fcaaf4bac2fa languageName: node linkType: hard @@ -14557,10 +14643,10 @@ __metadata: languageName: node linkType: hard -"web-vitals@npm:^3.5.1": - version: 3.5.1 - resolution: "web-vitals@npm:3.5.1" - checksum: 10/7f2b63d3ba701433af7e2eaa26a8de4ac7cf78fed1ba77bc69048d037998e4f639e4d3c533abd64f972342c09da9ee7c6c6eccc0600261642e4d8ce0ba67e671 +"web-vitals@npm:^3.5.2": + version: 3.5.2 + resolution: "web-vitals@npm:3.5.2" + checksum: 10/59c5d7775c32977eb9156d186113f4de66c235cfea7a113d2f430ef25db25a5efe1980d2ba466f7ceefc7b1a3847764933dc1567b6d9b8f340d12e75209e3f79 languageName: node linkType: hard