From 12ac97072e878881f9d65e71f50f6fda23a74d48 Mon Sep 17 00:00:00 2001 From: Emil Balitzki Date: Sun, 7 Jul 2024 15:28:46 +0200 Subject: [PATCH 01/23] Fixed the styling of polygons Signed-off-by: Emil Balitzki --- frontend/src/components/MapView/LeafletMap.css | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/frontend/src/components/MapView/LeafletMap.css b/frontend/src/components/MapView/LeafletMap.css index e337a3fe..6165ccdb 100644 --- a/frontend/src/components/MapView/LeafletMap.css +++ b/frontend/src/components/MapView/LeafletMap.css @@ -16,3 +16,8 @@ background-color: #ff0000 !important; border-radius: 1rem; } + +.leaflet-interactive { + stroke-opacity: 1 !important; + stroke-width: 1.5 !important; +} From b1d1df8c7e92695a164e24484663edaab9f18916 Mon Sep 17 00:00:00 2001 From: Emil Balitzki Date: Wed, 10 Jul 2024 14:31:27 +0200 Subject: [PATCH 02/23] Changed the location data output Signed-off-by: Emil Balitzki --- frontend/src/types/LocationDataTypes.tsx | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/frontend/src/types/LocationDataTypes.tsx b/frontend/src/types/LocationDataTypes.tsx index ec9471da..92ec0306 100644 --- a/frontend/src/types/LocationDataTypes.tsx +++ b/frontend/src/types/LocationDataTypes.tsx @@ -1,11 +1,18 @@ +import { LatLng } from "leaflet"; + export interface LocationDataResponse { - currentDatasetData: DatasetItem[]; - generalData: DatasetItem[]; - extraRows: DatasetItem[]; + individualData: DatasetItem[]; + selectionData: DatasetItem[]; } -export interface DatasetItem { +export interface SubdataItem { key: string; value: string; - mapId: string; +} + +export interface DatasetItem { + displayName: string; + datasetID: string; + coordinate: LatLng; + subdata: SubdataItem[]; } From 3955c55b9ecd4bcbbdbd3be0cd9f46020562921d Mon Sep 17 00:00:00 2001 From: Emil Balitzki Date: Wed, 10 Jul 2024 14:39:32 +0200 Subject: [PATCH 03/23] Fixed FE issues after chaning location response Signed-off-by: Emil Balitzki --- .../src/components/DataView/DataPanel.tsx | 25 ++++++++++--------- frontend/src/components/DataView/DataView.tsx | 8 +++--- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/frontend/src/components/DataView/DataPanel.tsx b/frontend/src/components/DataView/DataPanel.tsx index 560037fc..1c5d0ab9 100644 --- a/frontend/src/components/DataView/DataPanel.tsx +++ b/frontend/src/components/DataView/DataPanel.tsx @@ -37,8 +37,9 @@ const DataPanel: React.FC = ({ genericRows, }) => { // Keep track of if tabs are hidden - const [ifMapDataTabHidden, toggleMapDataHidden] = useState(false); - const [ifGeneralDataTabHidden, toggleGeneralDataHidden] = + const [ifSelectionDataTabHidden, toggleSelectionDataHidden] = + useState(false); + const [ifIndividualDataTabHidden, toggleIndividualDataHidden] = useState(false); useState(false); const { currentAlertCache, setCurrentAlertCache } = useContext(AlertContext); @@ -85,7 +86,7 @@ const DataPanel: React.FC = ({ // Returns a button if the "button" value is set to 1 const renderDetailsButton = (params: GridRenderCellParams) => { const dataObject = params.row as DatasetItem; - if (dataObject.mapId !== "") { + if (dataObject.datasetID !== "") { return ( @@ -93,7 +94,7 @@ const DataPanel: React.FC = ({ aria-label="open as map" size="small" onClick={() => { - openDatasetFromMapIcon(dataObject.mapId); + openDatasetFromMapIcon(dataObject.datasetID); }} > @@ -130,12 +131,12 @@ const DataPanel: React.FC = ({
{ - toggleMapDataHidden(!ifMapDataTabHidden); + toggleSelectionDataHidden(!ifSelectionDataTabHidden); }} > {listTitle} @@ -146,12 +147,12 @@ const DataPanel: React.FC = ({ item style={{ width: "100%" }} className={`data-panel-grid ${ - ifMapDataTabHidden ? "data-panel-grid-hidden" : "" + ifSelectionDataTabHidden ? "data-panel-grid-hidden" : "" }`} > { - return row.key + row.value; + return row.displayName + row.coordinate; }} hideFooter={true} disableColumnMenu @@ -195,12 +196,12 @@ const DataPanel: React.FC = ({
{ - toggleGeneralDataHidden(!ifGeneralDataTabHidden); + toggleIndividualDataHidden(!ifIndividualDataTabHidden); }} > Individual Data @@ -211,12 +212,12 @@ const DataPanel: React.FC = ({ container spacing={2} className={`data-panel-grid ${ - ifGeneralDataTabHidden ? "data-panel-grid-hidden" : "" + ifIndividualDataTabHidden ? "data-panel-grid-hidden" : "" }`} > { - return row.key + row.value; + return row.displayName + row.coordinate; }} hideFooter={true} disableColumnMenu diff --git a/frontend/src/components/DataView/DataView.tsx b/frontend/src/components/DataView/DataView.tsx index f1f8029f..c556b5f7 100644 --- a/frontend/src/components/DataView/DataView.tsx +++ b/frontend/src/components/DataView/DataView.tsx @@ -100,8 +100,8 @@ function DataView() { } else { console.log("Currently selected coordinates are null."); } - - setIsLoading(false); // Set loading to false after the fetch request completes + // Set loading to false after the fetch request completes + setIsLoading(false); }; return ( @@ -156,9 +156,9 @@ function DataView() {
) : ( )} From 36e49b60f63e4dd0a0a0297623103895f1130350 Mon Sep 17 00:00:00 2001 From: Emil Balitzki Date: Wed, 10 Jul 2024 16:01:42 +0200 Subject: [PATCH 04/23] Added random ID for rows Signed-off-by: Emil Balitzki --- frontend/package-lock.json | 22 ++++++++++++++++++- frontend/package.json | 4 +++- .../src/components/DataView/DataPanel.tsx | 21 +++++++++++++----- 3 files changed, 39 insertions(+), 8 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 7eeb9bd5..a5dd5c2e 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -31,13 +31,15 @@ "proj4leaflet": "^1.0.2", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-leaflet": "^4.2.1" + "react-leaflet": "^4.2.1", + "uuid": "^10.0.0" }, "devDependencies": { "@types/leaflet": "^1.9.12", "@types/node": "^20.12.12", "@types/react": "^18.2.66", "@types/react-dom": "^18.2.22", + "@types/uuid": "^10.0.0", "@typescript-eslint/eslint-plugin": "^7.2.0", "@typescript-eslint/parser": "^7.2.0", "@vitejs/plugin-react": "^4.2.1", @@ -1887,6 +1889,12 @@ "@types/react": "*" } }, + "node_modules/@types/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==", + "dev": true + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "7.11.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.11.0.tgz", @@ -4161,6 +4169,18 @@ "punycode": "^2.1.0" } }, + "node_modules/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/vite": { "version": "5.2.12", "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.12.tgz", diff --git a/frontend/package.json b/frontend/package.json index 023b86b8..704f3887 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -33,13 +33,15 @@ "proj4leaflet": "^1.0.2", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-leaflet": "^4.2.1" + "react-leaflet": "^4.2.1", + "uuid": "^10.0.0" }, "devDependencies": { "@types/leaflet": "^1.9.12", "@types/node": "^20.12.12", "@types/react": "^18.2.66", "@types/react-dom": "^18.2.22", + "@types/uuid": "^10.0.0", "@typescript-eslint/eslint-plugin": "^7.2.0", "@typescript-eslint/parser": "^7.2.0", "@vitejs/plugin-react": "^4.2.1", diff --git a/frontend/src/components/DataView/DataPanel.tsx b/frontend/src/components/DataView/DataPanel.tsx index 1c5d0ab9..c5064f89 100644 --- a/frontend/src/components/DataView/DataPanel.tsx +++ b/frontend/src/components/DataView/DataPanel.tsx @@ -14,6 +14,7 @@ import { Dataset } from "../../types/DatasetTypes"; import L from "leaflet"; import CustomSvgIcon from "../DatasetsList/CustomSvgIcon"; import { svgIconDefault } from "../DatasetsList/DatasetsList"; +import { v4 } from "uuid"; function MyCustomToolbar(props: GridToolbarProps) { return ; @@ -151,8 +152,8 @@ const DataPanel: React.FC = ({ }`} > { - return row.displayName + row.coordinate; + getRowId={() => { + return v4(); }} hideFooter={true} disableColumnMenu @@ -183,7 +184,11 @@ const DataPanel: React.FC = ({ }} filterModel={{ items: [ - { field: "key", operator: "contains", value: filterValue }, + { + field: "displayName", + operator: "contains", + value: filterValue, + }, ], }} density="compact" @@ -216,8 +221,8 @@ const DataPanel: React.FC = ({ }`} > { - return row.displayName + row.coordinate; + getRowId={() => { + return v4(); }} hideFooter={true} disableColumnMenu @@ -248,7 +253,11 @@ const DataPanel: React.FC = ({ }} filterModel={{ items: [ - { field: "key", operator: "contains", value: filterValue }, + { + field: "displayName", + operator: "contains", + value: filterValue, + }, ], }} density="compact" From 81d8fe445d77ad5eb17967705c0431399663b18b Mon Sep 17 00:00:00 2001 From: Emil Balitzki Date: Wed, 10 Jul 2024 16:25:50 +0200 Subject: [PATCH 05/23] Reimplemented 3D view Signed-off-by: Emil Balitzki --- frontend/package-lock.json | 50 ++++++ frontend/package.json | 2 + .../src/components/MapView/MapOptions.css | 23 ++- .../src/components/MapView/MapOptions.tsx | 16 +- frontend/src/components/MapView/MapView.tsx | 18 +- .../src/components/ThreeDView/ThreeDView.tsx | 154 ++++++++++++++++++ 6 files changed, 258 insertions(+), 5 deletions(-) create mode 100644 frontend/src/components/ThreeDView/ThreeDView.tsx diff --git a/frontend/package-lock.json b/frontend/package-lock.json index a5dd5c2e..b4a04118 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -32,6 +32,7 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "react-leaflet": "^4.2.1", + "three": "^0.166.1", "uuid": "^10.0.0" }, "devDependencies": { @@ -39,6 +40,7 @@ "@types/node": "^20.12.12", "@types/react": "^18.2.66", "@types/react-dom": "^18.2.22", + "@types/three": "^0.166.0", "@types/uuid": "^10.0.0", "@typescript-eslint/eslint-plugin": "^7.2.0", "@typescript-eslint/parser": "^7.2.0", @@ -1748,6 +1750,12 @@ "win32" ] }, + "node_modules/@tweenjs/tween.js": { + "version": "23.1.2", + "resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-23.1.2.tgz", + "integrity": "sha512-kMCNaZCJugWI86xiEHaY338CU5JpD0B97p1j1IKNn/Zto8PgACjQx0UxbHjmOcLl/dDOBnItwD07KmCs75pxtQ==", + "dev": true + }, "node_modules/@types/autosuggest-highlight": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/@types/autosuggest-highlight/-/autosuggest-highlight-3.2.3.tgz", @@ -1889,12 +1897,37 @@ "@types/react": "*" } }, + "node_modules/@types/stats.js": { + "version": "0.17.3", + "resolved": "https://registry.npmjs.org/@types/stats.js/-/stats.js-0.17.3.tgz", + "integrity": "sha512-pXNfAD3KHOdif9EQXZ9deK82HVNaXP5ZIF5RP2QG6OQFNTaY2YIetfrE9t528vEreGQvEPRDDc8muaoYeK0SxQ==", + "dev": true + }, + "node_modules/@types/three": { + "version": "0.166.0", + "resolved": "https://registry.npmjs.org/@types/three/-/three-0.166.0.tgz", + "integrity": "sha512-FHMnpcdhdbdOOIYbfkTkUVpYMW53odxbTRwd0/xJpYnTzEsjnVnondGAvHZb4z06UW0vo6WPVuvH0/9qrxKx7g==", + "dev": true, + "dependencies": { + "@tweenjs/tween.js": "~23.1.2", + "@types/stats.js": "*", + "@types/webxr": "*", + "fflate": "~0.8.2", + "meshoptimizer": "~0.18.1" + } + }, "node_modules/@types/uuid": { "version": "10.0.0", "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz", "integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==", "dev": true }, + "node_modules/@types/webxr": { + "version": "0.5.19", + "resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.19.tgz", + "integrity": "sha512-4hxA+NwohSgImdTSlPXEqDqqFktNgmTXQ05ff1uWam05tNGroCMp4G+4XVl6qWm1p7GQ/9oD41kAYsSssF6Mzw==", + "dev": true + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "7.11.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.11.0.tgz", @@ -2860,6 +2893,12 @@ "reusify": "^1.0.4" } }, + "node_modules/fflate": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", + "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==", + "dev": true + }, "node_modules/file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", @@ -3403,6 +3442,12 @@ "node": ">= 8" } }, + "node_modules/meshoptimizer": { + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/meshoptimizer/-/meshoptimizer-0.18.1.tgz", + "integrity": "sha512-ZhoIoL7TNV4s5B6+rx5mC//fw8/POGyNxS/DZyCJeiZ12ScLfVwRE/GfsxwiTkMYYD5DmK2/JXnEVXqL4rF+Sw==", + "dev": true + }, "node_modules/mgrs": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/mgrs/-/mgrs-1.0.0.tgz", @@ -4055,6 +4100,11 @@ "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "dev": true }, + "node_modules/three": { + "version": "0.166.1", + "resolved": "https://registry.npmjs.org/three/-/three-0.166.1.tgz", + "integrity": "sha512-LtuafkKHHzm61AQA1be2MAYIw1IjmhOUxhBa0prrLpEMWbV7ijvxCRHjSgHPGp2493wLBzwKV46tA9nivLEgKg==" + }, "node_modules/to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index 704f3887..98b4f8be 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -34,6 +34,7 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "react-leaflet": "^4.2.1", + "three": "^0.166.1", "uuid": "^10.0.0" }, "devDependencies": { @@ -41,6 +42,7 @@ "@types/node": "^20.12.12", "@types/react": "^18.2.66", "@types/react-dom": "^18.2.22", + "@types/three": "^0.166.0", "@types/uuid": "^10.0.0", "@typescript-eslint/eslint-plugin": "^7.2.0", "@typescript-eslint/parser": "^7.2.0", diff --git a/frontend/src/components/MapView/MapOptions.css b/frontend/src/components/MapView/MapOptions.css index 620e3629..9eaa2e5a 100644 --- a/frontend/src/components/MapView/MapOptions.css +++ b/frontend/src/components/MapView/MapOptions.css @@ -68,7 +68,7 @@ border-color: blue; } -.draw-polygon-icon-container { +.threed-map-icon-container { width: 2.1rem; height: 2.1rem; position: absolute; @@ -84,6 +84,27 @@ border: 2px solid rgba(0, 0, 0, 0.27); box-shadow: none; } +.threed-map-icon-container:hover { + background-color: #f4f4f4; + color: #535bf2; +} + +.draw-polygon-icon-container { + width: 2.1rem; + height: 2.1rem; + position: absolute; + top: 10rem; + right: 0.6rem; + cursor: pointer; + z-index: 2; + display: flex; + flex-direction: column; + background-color: white; + justify-content: center; + align-items: center; + border: 2px solid rgba(0, 0, 0, 0.27); + box-shadow: none; +} .draw-polygon-icon-container:hover { background-color: #f4f4f4; color: #535bf2; diff --git a/frontend/src/components/MapView/MapOptions.tsx b/frontend/src/components/MapView/MapOptions.tsx index 8db0839a..61cbd1e3 100644 --- a/frontend/src/components/MapView/MapOptions.tsx +++ b/frontend/src/components/MapView/MapOptions.tsx @@ -1,15 +1,19 @@ import React, { useContext, useState } from "react"; import { Paper, Popover, Grid, Typography, Box, Tooltip } from "@mui/material"; import "./MapOptions.css"; -import { Polygon, StackSimple } from "@phosphor-icons/react"; +import { Polygon, StackSimple, ThreeD } from "@phosphor-icons/react"; import SearchBar from "../SearchBar/SearchBar"; import { MapContext } from "../../contexts/MapContext"; interface MapOptionsProps { onMapTypeChange: (type: "normal" | "satellite" | "parcel" | "aerial") => void; + toggle3D: () => void; } -const MapOptions: React.FC = ({ onMapTypeChange }) => { +const MapOptions: React.FC = ({ + onMapTypeChange, + toggle3D, +}) => { const [anchorEl, setAnchorEl] = useState(null); const { currentMapCache, setCurrentMapCache } = useContext(MapContext); @@ -44,6 +48,14 @@ const MapOptions: React.FC = ({ onMapTypeChange }) => {
+ +
+ +
+
{ diff --git a/frontend/src/components/MapView/MapView.tsx b/frontend/src/components/MapView/MapView.tsx index 5f0e1981..40c64a95 100644 --- a/frontend/src/components/MapView/MapView.tsx +++ b/frontend/src/components/MapView/MapView.tsx @@ -9,6 +9,7 @@ import icon from "leaflet/dist/images/marker-icon.png"; import iconShadow from "leaflet/dist/images/marker-shadow.png"; import MapOptions from "./MapOptions"; import LeafletMap from "./LeafletMap"; +import ThreeDView from "../ThreeDView/ThreeDView"; const DefaultIcon = L.icon({ iconUrl: icon, @@ -23,6 +24,7 @@ interface MapViewProps { } const MapView: React.FC = ({ datasetId }) => { + const [if3D, setIf3D] = useState(false); const [mapType, setMapType] = useState< "normal" | "satellite" | "parcel" | "aerial" >("normal"); @@ -37,10 +39,22 @@ const MapView: React.FC = ({ datasetId }) => { setMapType(type); }; + /** + * Toggles the 3D View + */ + const toggle3D = () => { + console.log("3D"); + setIf3D(!if3D); + }; + return (
- - + {if3D ? ( + + ) : ( + + )} +
); }; diff --git a/frontend/src/components/ThreeDView/ThreeDView.tsx b/frontend/src/components/ThreeDView/ThreeDView.tsx new file mode 100644 index 00000000..8eb3268d --- /dev/null +++ b/frontend/src/components/ThreeDView/ThreeDView.tsx @@ -0,0 +1,154 @@ +import React, { useRef, useEffect } from "react"; +import * as THREE from "three"; +import { + CSS3DObject, + CSS3DRenderer, +} from "three/examples/jsm/renderers/CSS3DRenderer.js"; +import { MapControls } from "three/examples/jsm/controls/MapControls.js"; +import "leaflet/dist/leaflet.css"; +import LeafletMap from "../MapView/LeafletMap"; + +interface ThreeDViewProps { + datasetId: string; + mapType: string; +} + +const ThreeDView: React.FC = ({ datasetId, mapType }) => { + const mountRef = useRef(null); + const threedMapRef = useRef(null); + + useEffect(() => { + const mount = mountRef.current!; + const mapElement = threedMapRef.current!; + + // Scene setup + const scene = new THREE.Scene(); + scene.background = new THREE.Color(0x87ceeb); // Light blue sky + const camera = new THREE.PerspectiveCamera( + 75, + mount.clientWidth / mount.clientHeight, + 0.1, + 1000 + ); + camera.position.set(0, 400, 10); + camera.rotateX(-1); + + const renderer = new THREE.WebGLRenderer({ antialias: true }); + renderer.setSize(mount.clientWidth, mount.clientHeight); + mount.appendChild(renderer.domElement); + + const cssRenderer = new CSS3DRenderer(); + cssRenderer.setSize(mount.clientWidth, mount.clientHeight); + cssRenderer.domElement.style.position = "absolute"; + cssRenderer.domElement.style.top = "0"; + cssRenderer.domElement.style.pointerEvents = "none"; // Allow mouse events to pass through + mount.appendChild(cssRenderer.domElement); + + // Ensure the map container is displayed and has dimensions before initializing Leaflet + mapElement.style.display = "block"; // Ensure the element is visible + mapElement.style.pointerEvents = "auto"; // Ensure the element can consume events + + const cssObject = new CSS3DObject(mapElement); + cssObject.rotation.x = -Math.PI / 2; // Rotate to lie flat + cssObject.position.set(0, 0, 0); // Position at ground level + scene.add(cssObject); + + // Light + const light = new THREE.DirectionalLight(0xffffff, 1); + light.position.set(10, 10, 10).normalize(); + scene.add(light); + + // Set up MapControls + const controls = new MapControls(camera, cssRenderer.domElement); + controls.enableDamping = true; // Enable damping (inertia) + controls.dampingFactor = 0.05; // Damping factor + controls.screenSpacePanning = false; // Prevent camera from moving vertically in screen space + controls.minDistance = 10; // Minimum zoom distance + controls.maxDistance = 500; // Maximum zoom distance + controls.maxPolarAngle = Math.PI / 2; // Limit angle from the top + + // Configure controls to respond only to right-click + controls.mouseButtons = { + LEFT: null, + MIDDLE: null, + RIGHT: THREE.MOUSE.ROTATE, + }; + controls.enableZoom = false; + + // Function to forward events to Leaflet map + const forwardEventToMap = (event: Event) => { + if (event instanceof WheelEvent) { + if (event.deltaY < 0) { + // Simulate zoom in + const simulatedEvent = new WheelEvent(event.type, { + ...event, + deltaY: -1, + }); + mapElement.dispatchEvent(simulatedEvent); + } else { + // Simulate zoom out + const simulatedEvent = new WheelEvent(event.type, { + ...event, + deltaY: 1, + }); + mapElement.dispatchEvent(simulatedEvent); + } + } else if (event instanceof MouseEvent && event.button !== 2) { + // Prevent right-click events from being forwarded + const simulatedEvent = new MouseEvent(event.type, { + bubbles: true, + cancelable: true, + clientX: event.clientX - 1, + clientY: event.clientY - 1, + }); + mapElement.dispatchEvent(simulatedEvent); + } + }; + + // Add event listeners to forward events + const events: (keyof HTMLElementEventMap)[] = ["mousedown", "wheel"]; + events.forEach((eventName) => { + renderer.domElement.addEventListener( + eventName, + forwardEventToMap as EventListener + ); + }); + + // Animate + const animate = () => { + requestAnimationFrame(animate); + controls.update(); // Update controls + renderer.render(scene, camera); + cssRenderer.render(scene, camera); + }; + animate(); + + // Cleanup on component unmount + return () => { + mount.removeChild(renderer.domElement); + mount.removeChild(cssRenderer.domElement); + }; + }, []); + + return ( + <> +
+
+ +
+ + ); +}; + +export default ThreeDView; From e869936a5c8c230367fdcf09388f5c641739acbe Mon Sep 17 00:00:00 2001 From: Emil Balitzki Date: Wed, 10 Jul 2024 16:33:12 +0200 Subject: [PATCH 06/23] Fixed closing issues Signed-off-by: Emil Balitzki --- frontend/src/components/ThreeDView/ThreeDView.tsx | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/frontend/src/components/ThreeDView/ThreeDView.tsx b/frontend/src/components/ThreeDView/ThreeDView.tsx index 8eb3268d..28c6a080 100644 --- a/frontend/src/components/ThreeDView/ThreeDView.tsx +++ b/frontend/src/components/ThreeDView/ThreeDView.tsx @@ -131,11 +131,7 @@ const ThreeDView: React.FC = ({ datasetId, mapType }) => { }, []); return ( - <> -
+
= ({ datasetId, mapType }) => { >
- +
); }; From 0970781d7b8679d8d38713a14fcd70063417bb24 Mon Sep 17 00:00:00 2001 From: Emil Balitzki Date: Wed, 10 Jul 2024 16:46:45 +0200 Subject: [PATCH 07/23] Added fake zoom buttons for 3D Signed-off-by: Emil Balitzki --- .../src/components/MapView/MapOptions.css | 38 +++++++++++++++++++ .../src/components/MapView/MapOptions.tsx | 16 +++++++- frontend/src/components/MapView/MapView.tsx | 6 ++- 3 files changed, 58 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/MapView/MapOptions.css b/frontend/src/components/MapView/MapOptions.css index 9eaa2e5a..174b979e 100644 --- a/frontend/src/components/MapView/MapOptions.css +++ b/frontend/src/components/MapView/MapOptions.css @@ -109,3 +109,41 @@ background-color: #f4f4f4; color: #535bf2; } + +.zoom-in-icon-container { + width: 2.1rem; + height: 2.1rem; + position: absolute; + top: 0.6rem; + right: 0.6rem; + cursor: pointer; + z-index: 2; + display: flex; + flex-direction: column; + background-color: rgb(215, 215, 215); + justify-content: center; + align-items: center; + border: 2px solid rgba(0, 0, 0, 0.27); + box-shadow: none; + text-align: center; + font-size: 1.6rem; +} + +.zoom-out-icon-container { + width: 2.1rem; + height: 2.1rem; + position: absolute; + top: 2.5rem; + right: 0.6rem; + cursor: pointer; + z-index: 2; + display: flex; + flex-direction: column; + background-color: rgb(215, 215, 215); + justify-content: center; + align-items: center; + border: 2px solid rgba(0, 0, 0, 0.27); + box-shadow: none; + text-align: center; + font-size: 1.6rem; +} diff --git a/frontend/src/components/MapView/MapOptions.tsx b/frontend/src/components/MapView/MapOptions.tsx index 61cbd1e3..cd31dd75 100644 --- a/frontend/src/components/MapView/MapOptions.tsx +++ b/frontend/src/components/MapView/MapOptions.tsx @@ -1,4 +1,4 @@ -import React, { useContext, useState } from "react"; +import React, { Fragment, useContext, useState } from "react"; import { Paper, Popover, Grid, Typography, Box, Tooltip } from "@mui/material"; import "./MapOptions.css"; import { Polygon, StackSimple, ThreeD } from "@phosphor-icons/react"; @@ -7,11 +7,13 @@ import { MapContext } from "../../contexts/MapContext"; interface MapOptionsProps { onMapTypeChange: (type: "normal" | "satellite" | "parcel" | "aerial") => void; + if3D: boolean; toggle3D: () => void; } const MapOptions: React.FC = ({ onMapTypeChange, + if3D, toggle3D, }) => { const [anchorEl, setAnchorEl] = useState(null); @@ -69,6 +71,18 @@ const MapOptions: React.FC = ({
+ {if3D ? ( + +
+ + +
+
+ - +
+
+ ) : ( + + )} = ({ datasetId }) => { ) : ( )} - +
); }; From b49c30db408aa91585f5b1f5cbba2d9b20c8142f Mon Sep 17 00:00:00 2001 From: Emil Balitzki Date: Wed, 10 Jul 2024 17:02:06 +0200 Subject: [PATCH 08/23] Fixed resizing of 3D view Signed-off-by: Emil Balitzki --- .../src/components/MapView/LeafletMap.tsx | 9 +++++-- .../src/components/MapView/MapOptions.css | 4 +-- frontend/src/components/MapView/MapView.tsx | 2 +- .../src/components/ThreeDView/ThreeDView.tsx | 27 ++++++++++++++++--- 4 files changed, 33 insertions(+), 9 deletions(-) diff --git a/frontend/src/components/MapView/LeafletMap.tsx b/frontend/src/components/MapView/LeafletMap.tsx index a41dd0a2..050608e5 100644 --- a/frontend/src/components/MapView/LeafletMap.tsx +++ b/frontend/src/components/MapView/LeafletMap.tsx @@ -35,9 +35,14 @@ import { interface LeafletMapProps { datasetId: string; mapType: string; + if3D: boolean; } -const LeafletMap: React.FC = ({ datasetId, mapType }) => { +const LeafletMap: React.FC = ({ + datasetId, + mapType, + if3D, +}) => { const { currentTabsCache, getCurrentTab, getOrFetchMetadata } = useContext(TabsContext); const [map, setMap] = useState(null); @@ -209,7 +214,7 @@ const LeafletMap: React.FC = ({ datasetId, mapType }) => { maxBounds={L.latLngBounds([47.1512, 5.6259], [54.967, 15.4446])} minZoom={6} > - + {!if3D && } {isGrayscale ? ( ) : ( diff --git a/frontend/src/components/MapView/MapOptions.css b/frontend/src/components/MapView/MapOptions.css index 174b979e..0e99a0a8 100644 --- a/frontend/src/components/MapView/MapOptions.css +++ b/frontend/src/components/MapView/MapOptions.css @@ -126,7 +126,7 @@ border: 2px solid rgba(0, 0, 0, 0.27); box-shadow: none; text-align: center; - font-size: 1.6rem; + font-size: 1.65rem; } .zoom-out-icon-container { @@ -145,5 +145,5 @@ border: 2px solid rgba(0, 0, 0, 0.27); box-shadow: none; text-align: center; - font-size: 1.6rem; + font-size: 2rem; } diff --git a/frontend/src/components/MapView/MapView.tsx b/frontend/src/components/MapView/MapView.tsx index 366c72c9..f8477fde 100644 --- a/frontend/src/components/MapView/MapView.tsx +++ b/frontend/src/components/MapView/MapView.tsx @@ -52,7 +52,7 @@ const MapView: React.FC = ({ datasetId }) => { {if3D ? ( ) : ( - + )} = ({ datasetId, mapType }) => { const renderer = new THREE.WebGLRenderer({ antialias: true }); renderer.setSize(mount.clientWidth, mount.clientHeight); + renderer.setPixelRatio(window.devicePixelRatio); mount.appendChild(renderer.domElement); const cssRenderer = new CSS3DRenderer(); @@ -114,6 +115,18 @@ const ThreeDView: React.FC = ({ datasetId, mapType }) => { ); }); + // Resize handler + const handleResize = () => { + camera.aspect = mount.clientWidth / mount.clientHeight; + camera.updateProjectionMatrix(); + renderer.setSize(mount.clientWidth, mount.clientHeight); + cssRenderer.setSize(mount.clientWidth, mount.clientHeight); + }; + + // Use ResizeObserver to handle parent element resize + const resizeObserver = new ResizeObserver(handleResize); + resizeObserver.observe(mount); + // Animate const animate = () => { requestAnimationFrame(animate); @@ -125,23 +138,29 @@ const ThreeDView: React.FC = ({ datasetId, mapType }) => { // Cleanup on component unmount return () => { + resizeObserver.unobserve(mount); + window.removeEventListener("resize", handleResize); mount.removeChild(renderer.domElement); mount.removeChild(cssRenderer.domElement); }; }, []); return ( -
+
- +
); From 7e5d1d3348d361ac8cad960555ff02effad9943b Mon Sep 17 00:00:00 2001 From: Emil Balitzki Date: Wed, 10 Jul 2024 17:17:48 +0200 Subject: [PATCH 09/23] 3D component cleanup Signed-off-by: Emil Balitzki --- frontend/src/components/MapView/MapView.tsx | 1 - .../src/components/ThreeDView/ThreeDView.tsx | 27 +++++++++---------- 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/frontend/src/components/MapView/MapView.tsx b/frontend/src/components/MapView/MapView.tsx index f8477fde..d6dd806a 100644 --- a/frontend/src/components/MapView/MapView.tsx +++ b/frontend/src/components/MapView/MapView.tsx @@ -43,7 +43,6 @@ const MapView: React.FC = ({ datasetId }) => { * Toggles the 3D View */ const toggle3D = () => { - console.log("3D"); setIf3D(!if3D); }; diff --git a/frontend/src/components/ThreeDView/ThreeDView.tsx b/frontend/src/components/ThreeDView/ThreeDView.tsx index 46871caa..2e346f0a 100644 --- a/frontend/src/components/ThreeDView/ThreeDView.tsx +++ b/frontend/src/components/ThreeDView/ThreeDView.tsx @@ -50,8 +50,8 @@ const ThreeDView: React.FC = ({ datasetId, mapType }) => { mapElement.style.pointerEvents = "auto"; // Ensure the element can consume events const cssObject = new CSS3DObject(mapElement); - cssObject.rotation.x = -Math.PI / 2; // Rotate to lie flat - cssObject.position.set(0, 0, 0); // Position at ground level + cssObject.rotation.x = -Math.PI / 2; + cssObject.position.set(0, 0, 0); scene.add(cssObject); // Light @@ -68,6 +68,15 @@ const ThreeDView: React.FC = ({ datasetId, mapType }) => { controls.maxDistance = 500; // Maximum zoom distance controls.maxPolarAngle = Math.PI / 2; // Limit angle from the top + // Prevent camera from going below a certain height + const minCameraY = 50; + + controls.addEventListener("change", () => { + if (camera.position.y < minCameraY) { + camera.position.y = minCameraY; + } + }); + // Configure controls to respond only to right-click controls.mouseButtons = { LEFT: null, @@ -94,20 +103,11 @@ const ThreeDView: React.FC = ({ datasetId, mapType }) => { }); mapElement.dispatchEvent(simulatedEvent); } - } else if (event instanceof MouseEvent && event.button !== 2) { - // Prevent right-click events from being forwarded - const simulatedEvent = new MouseEvent(event.type, { - bubbles: true, - cancelable: true, - clientX: event.clientX - 1, - clientY: event.clientY - 1, - }); - mapElement.dispatchEvent(simulatedEvent); } }; // Add event listeners to forward events - const events: (keyof HTMLElementEventMap)[] = ["mousedown", "wheel"]; + const events: (keyof HTMLElementEventMap)[] = ["wheel"]; events.forEach((eventName) => { renderer.domElement.addEventListener( eventName, @@ -130,7 +130,7 @@ const ThreeDView: React.FC = ({ datasetId, mapType }) => { // Animate const animate = () => { requestAnimationFrame(animate); - controls.update(); // Update controls + controls.update(); renderer.render(scene, camera); cssRenderer.render(scene, camera); }; @@ -139,7 +139,6 @@ const ThreeDView: React.FC = ({ datasetId, mapType }) => { // Cleanup on component unmount return () => { resizeObserver.unobserve(mount); - window.removeEventListener("resize", handleResize); mount.removeChild(renderer.domElement); mount.removeChild(cssRenderer.domElement); }; From df4749d8ceecde825db37d54514b096e7b3e3012 Mon Sep 17 00:00:00 2001 From: Emil Balitzki Date: Wed, 10 Jul 2024 17:36:57 +0200 Subject: [PATCH 10/23] Cleanup Signed-off-by: Emil Balitzki --- .../src/components/ThreeDView/ThreeDView.tsx | 30 ------------------- 1 file changed, 30 deletions(-) diff --git a/frontend/src/components/ThreeDView/ThreeDView.tsx b/frontend/src/components/ThreeDView/ThreeDView.tsx index 2e346f0a..7b028c4b 100644 --- a/frontend/src/components/ThreeDView/ThreeDView.tsx +++ b/frontend/src/components/ThreeDView/ThreeDView.tsx @@ -85,36 +85,6 @@ const ThreeDView: React.FC = ({ datasetId, mapType }) => { }; controls.enableZoom = false; - // Function to forward events to Leaflet map - const forwardEventToMap = (event: Event) => { - if (event instanceof WheelEvent) { - if (event.deltaY < 0) { - // Simulate zoom in - const simulatedEvent = new WheelEvent(event.type, { - ...event, - deltaY: -1, - }); - mapElement.dispatchEvent(simulatedEvent); - } else { - // Simulate zoom out - const simulatedEvent = new WheelEvent(event.type, { - ...event, - deltaY: 1, - }); - mapElement.dispatchEvent(simulatedEvent); - } - } - }; - - // Add event listeners to forward events - const events: (keyof HTMLElementEventMap)[] = ["wheel"]; - events.forEach((eventName) => { - renderer.domElement.addEventListener( - eventName, - forwardEventToMap as EventListener - ); - }); - // Resize handler const handleResize = () => { camera.aspect = mount.clientWidth / mount.clientHeight; From f5feffaeb134aa2657f93f408f0d53b3909dff83 Mon Sep 17 00:00:00 2001 From: Emil Balitzki Date: Wed, 10 Jul 2024 18:42:51 +0200 Subject: [PATCH 11/23] Final 3D cleanup Signed-off-by: Emil Balitzki --- .../src/components/MapView/MapOptions.css | 4 ++- .../src/components/ThreeDView/ThreeDView.tsx | 29 ++++++++++++++----- 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/frontend/src/components/MapView/MapOptions.css b/frontend/src/components/MapView/MapOptions.css index 0e99a0a8..b34f7f65 100644 --- a/frontend/src/components/MapView/MapOptions.css +++ b/frontend/src/components/MapView/MapOptions.css @@ -112,7 +112,7 @@ .zoom-in-icon-container { width: 2.1rem; - height: 2.1rem; + height: 2rem; position: absolute; top: 0.6rem; right: 0.6rem; @@ -127,6 +127,7 @@ box-shadow: none; text-align: center; font-size: 1.65rem; + border-bottom-width: 1px; } .zoom-out-icon-container { @@ -146,4 +147,5 @@ box-shadow: none; text-align: center; font-size: 2rem; + border-top-width: 1px; } diff --git a/frontend/src/components/ThreeDView/ThreeDView.tsx b/frontend/src/components/ThreeDView/ThreeDView.tsx index 7b028c4b..160d0bb6 100644 --- a/frontend/src/components/ThreeDView/ThreeDView.tsx +++ b/frontend/src/components/ThreeDView/ThreeDView.tsx @@ -61,6 +61,7 @@ const ThreeDView: React.FC = ({ datasetId, mapType }) => { // Set up MapControls const controls = new MapControls(camera, cssRenderer.domElement); + controls.enableDamping = true; // Enable damping (inertia) controls.dampingFactor = 0.05; // Damping factor controls.screenSpacePanning = false; // Prevent camera from moving vertically in screen space @@ -85,6 +86,13 @@ const ThreeDView: React.FC = ({ datasetId, mapType }) => { }; controls.enableZoom = false; + // Disable right-click context menu + const disableContextMenu = (event: MouseEvent) => { + event.preventDefault(); + }; + + mount.addEventListener("contextmenu", disableContextMenu); + // Resize handler const handleResize = () => { camera.aspect = mount.clientWidth / mount.clientHeight; @@ -109,24 +117,31 @@ const ThreeDView: React.FC = ({ datasetId, mapType }) => { // Cleanup on component unmount return () => { resizeObserver.unobserve(mount); + controls.dispose(); + scene.clear(); + renderer.dispose(); + renderer.forceContextLoss(); mount.removeChild(renderer.domElement); mount.removeChild(cssRenderer.domElement); + cssObject.position.set(0, 0, 0); + + // Additional cleanup for CSS3DObject + if (cssObject.element.parentNode) { + cssObject.element.parentNode.removeChild(cssObject.element); + } + + // Remove the context menu event listener + mount.removeEventListener("contextmenu", disableContextMenu); }; }, []); return ( -
+
From e20bf7bc3402a74c7b480d58a064647b956375e2 Mon Sep 17 00:00:00 2001 From: Emil Balitzki Date: Wed, 10 Jul 2024 18:46:18 +0200 Subject: [PATCH 12/23] Disabling draw polygon in 3D Signed-off-by: Emil Balitzki --- frontend/src/components/MapView/MapOptions.css | 9 +++++++++ frontend/src/components/MapView/MapOptions.tsx | 14 +++++++++----- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/frontend/src/components/MapView/MapOptions.css b/frontend/src/components/MapView/MapOptions.css index b34f7f65..ee1bd4b5 100644 --- a/frontend/src/components/MapView/MapOptions.css +++ b/frontend/src/components/MapView/MapOptions.css @@ -110,6 +110,15 @@ color: #535bf2; } +.draw-polygon-icon-disabled { + background-color: rgb(215, 215, 215) !important; +} + +.draw-polygon-icon-disabled:hover { + background-color: rgb(215, 215, 215) !important; + color: black !important; +} + .zoom-in-icon-container { width: 2.1rem; height: 2rem; diff --git a/frontend/src/components/MapView/MapOptions.tsx b/frontend/src/components/MapView/MapOptions.tsx index cd31dd75..ef15c58f 100644 --- a/frontend/src/components/MapView/MapOptions.tsx +++ b/frontend/src/components/MapView/MapOptions.tsx @@ -61,12 +61,16 @@ const MapOptions: React.FC = ({
{ - setCurrentMapCache({ - ...currentMapCache, - isDrawing: !currentMapCache.isDrawing, - }); + if (!if3D) { + setCurrentMapCache({ + ...currentMapCache, + isDrawing: !currentMapCache.isDrawing, + }); + } }} - className="draw-polygon-icon-container leaflet-touch leaflet-bar leaflet-control leaflet-control-custom" + className={`draw-polygon-icon-container ${ + if3D ? "draw-polygon-icon-disabled" : "" + } leaflet-touch leaflet-bar leaflet-control leaflet-control-custom`} >
From fca2053b5eb68d72003e78bfec97c5033e96f325 Mon Sep 17 00:00:00 2001 From: Emil Balitzki Date: Wed, 10 Jul 2024 18:50:04 +0200 Subject: [PATCH 13/23] Fixed search bar bug Signed-off-by: Emil Balitzki --- frontend/src/components/SearchBar/SearchBar.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/components/SearchBar/SearchBar.tsx b/frontend/src/components/SearchBar/SearchBar.tsx index 40ca0c6a..13c06d49 100644 --- a/frontend/src/components/SearchBar/SearchBar.tsx +++ b/frontend/src/components/SearchBar/SearchBar.tsx @@ -174,7 +174,7 @@ const SearchBar: React.FC = () => { 0 ? 400 : 150 }} getOptionLabel={(option) => typeof option === "string" ? option : option.displayName } From f35e607c5df77b98e0a60d321224238d0c7c3f9f Mon Sep 17 00:00:00 2001 From: Emil Balitzki Date: Wed, 10 Jul 2024 18:58:11 +0200 Subject: [PATCH 14/23] Map type now stays on tab switch Signed-off-by: Emil Balitzki --- frontend/src/components/MapView/LeafletMap.tsx | 15 +++++---------- frontend/src/components/MapView/MapOptions.tsx | 13 ++++++------- frontend/src/components/MapView/MapView.tsx | 18 ++++++++---------- .../src/components/ThreeDView/ThreeDView.tsx | 5 ++--- frontend/src/contexts/MapContext.tsx | 3 +++ frontend/src/types/MapTypes.tsx | 7 +++++++ 6 files changed, 31 insertions(+), 30 deletions(-) create mode 100644 frontend/src/types/MapTypes.tsx diff --git a/frontend/src/components/MapView/LeafletMap.tsx b/frontend/src/components/MapView/LeafletMap.tsx index 050608e5..7bb98605 100644 --- a/frontend/src/components/MapView/LeafletMap.tsx +++ b/frontend/src/components/MapView/LeafletMap.tsx @@ -34,15 +34,10 @@ import { interface LeafletMapProps { datasetId: string; - mapType: string; if3D: boolean; } -const LeafletMap: React.FC = ({ - datasetId, - mapType, - if3D, -}) => { +const LeafletMap: React.FC = ({ datasetId, if3D }) => { const { currentTabsCache, getCurrentTab, getOrFetchMetadata } = useContext(TabsContext); const [map, setMap] = useState(null); @@ -228,10 +223,10 @@ const LeafletMap: React.FC = ({
)} - {mapType === "satellite" && } - {mapType === "aerial" && } - {mapType === "normal" && } - {mapType === "parcel" && } + {currentMapCache.mapType === "satellite" && } + {currentMapCache.mapType === "aerial" && } + {currentMapCache.mapType === "normal" && } + {currentMapCache.mapType === "parcel" && }
diff --git a/frontend/src/components/MapView/MapOptions.tsx b/frontend/src/components/MapView/MapOptions.tsx index ef15c58f..c660a933 100644 --- a/frontend/src/components/MapView/MapOptions.tsx +++ b/frontend/src/components/MapView/MapOptions.tsx @@ -4,9 +4,10 @@ import "./MapOptions.css"; import { Polygon, StackSimple, ThreeD } from "@phosphor-icons/react"; import SearchBar from "../SearchBar/SearchBar"; import { MapContext } from "../../contexts/MapContext"; +import { MapTypes } from "../../types/MapTypes"; interface MapOptionsProps { - onMapTypeChange: (type: "normal" | "satellite" | "parcel" | "aerial") => void; + onMapTypeChange: (type: MapTypes) => void; if3D: boolean; toggle3D: () => void; } @@ -27,9 +28,7 @@ const MapOptions: React.FC = ({ setAnchorEl(null); }; - const handleMapTypeChange = ( - type: "normal" | "satellite" | "parcel" | "aerial" - ) => { + const handleMapTypeChange = (type: MapTypes) => { onMapTypeChange(type); handleClose(); }; @@ -126,7 +125,7 @@ const MapOptions: React.FC = ({ width="50" height="50" onClick={() => { - handleMapTypeChange("normal"); + handleMapTypeChange(MapTypes.Normal); handleClose(); }} /> @@ -144,7 +143,7 @@ const MapOptions: React.FC = ({ width="50" height="50" onClick={() => { - handleMapTypeChange("satellite"); + handleMapTypeChange(MapTypes.Satellite); handleClose(); }} /> @@ -162,7 +161,7 @@ const MapOptions: React.FC = ({ width="50" height="50" onClick={() => { - handleMapTypeChange("aerial"); + handleMapTypeChange(MapTypes.Aerial); handleClose(); }} /> diff --git a/frontend/src/components/MapView/MapView.tsx b/frontend/src/components/MapView/MapView.tsx index d6dd806a..c2414603 100644 --- a/frontend/src/components/MapView/MapView.tsx +++ b/frontend/src/components/MapView/MapView.tsx @@ -1,4 +1,4 @@ -import { useState } from "react"; +import { useContext, useState } from "react"; import "leaflet/dist/leaflet.css"; import "leaflet.markercluster/dist/MarkerCluster.css"; import "leaflet.markercluster/dist/MarkerCluster.Default.css"; @@ -10,6 +10,8 @@ import iconShadow from "leaflet/dist/images/marker-shadow.png"; import MapOptions from "./MapOptions"; import LeafletMap from "./LeafletMap"; import ThreeDView from "../ThreeDView/ThreeDView"; +import { MapTypes } from "../../types/MapTypes"; +import { MapContext } from "../../contexts/MapContext"; const DefaultIcon = L.icon({ iconUrl: icon, @@ -25,18 +27,14 @@ interface MapViewProps { const MapView: React.FC = ({ datasetId }) => { const [if3D, setIf3D] = useState(false); - const [mapType, setMapType] = useState< - "normal" | "satellite" | "parcel" | "aerial" - >("normal"); + const { currentMapCache, setCurrentMapCache } = useContext(MapContext); /** * Changes the layer type * @param type type of the layer */ - const handleMapTypeChange = ( - type: "normal" | "satellite" | "parcel" | "aerial" - ) => { - setMapType(type); + const handleMapTypeChange = (type: MapTypes) => { + setCurrentMapCache({ ...currentMapCache, mapType: type }); }; /** @@ -49,9 +47,9 @@ const MapView: React.FC = ({ datasetId }) => { return (
{if3D ? ( - + ) : ( - + )} = ({ datasetId, mapType }) => { +const ThreeDView: React.FC = ({ datasetId }) => { const mountRef = useRef(null); const threedMapRef = useRef(null); @@ -144,7 +143,7 @@ const ThreeDView: React.FC = ({ datasetId, mapType }) => { height: "100%", }} > - +
); diff --git a/frontend/src/contexts/MapContext.tsx b/frontend/src/contexts/MapContext.tsx index cf6effb6..2c832ba0 100644 --- a/frontend/src/contexts/MapContext.tsx +++ b/frontend/src/contexts/MapContext.tsx @@ -1,6 +1,7 @@ import L, { LatLng, LatLngBounds } from "leaflet"; import React, { createContext, useState, ReactNode } from "react"; import { MarkerSelection, PolygonSelection } from "../types/MapSelectionTypes"; +import { MapTypes } from "../types/MapTypes"; //// TYPES //// @@ -12,6 +13,7 @@ export type MapCacheProps = { currentTabID: null | string; mapCenter: LatLng; mapBounds: LatLngBounds; + mapType: MapTypes; zoom: number; isDrawing: boolean; drawnItems: L.FeatureGroup | null; @@ -38,6 +40,7 @@ const defaultMapCache: MapCacheProps = { currentTabID: null, // The currently loaded tab ID mapCenter: L.latLng([49.5732, 11.0288]), mapBounds: L.latLngBounds([49.5732, 11.0288], [49.5732, 11.0288]), + mapType: MapTypes.Normal, zoom: 13, isDrawing: false, drawnItems: null, diff --git a/frontend/src/types/MapTypes.tsx b/frontend/src/types/MapTypes.tsx new file mode 100644 index 00000000..64098087 --- /dev/null +++ b/frontend/src/types/MapTypes.tsx @@ -0,0 +1,7 @@ +// Enum for types of maps +export enum MapTypes { + Normal = "normal", + Satellite = "satellite", + Parcel = "parcel", + Aerial = "aerial", +} From 600b9962c9cf2c4fbf942a3b94937af7d7c38a80 Mon Sep 17 00:00:00 2001 From: Emil Balitzki Date: Wed, 10 Jul 2024 19:16:33 +0200 Subject: [PATCH 15/23] Fixed polygon reseting on tab switch Signed-off-by: Emil Balitzki --- .../src/components/MapView/LeafletMap.tsx | 65 +++++++++---------- frontend/src/contexts/MapContext.tsx | 4 +- 2 files changed, 32 insertions(+), 37 deletions(-) diff --git a/frontend/src/components/MapView/LeafletMap.tsx b/frontend/src/components/MapView/LeafletMap.tsx index 7bb98605..dd03b6d5 100644 --- a/frontend/src/components/MapView/LeafletMap.tsx +++ b/frontend/src/components/MapView/LeafletMap.tsx @@ -102,7 +102,6 @@ const LeafletMap: React.FC = ({ datasetId, if3D }) => { const initialBounds = map.getBounds(); const initialCenter = map.getCenter(); const initialZoom = map.getZoom(); - const drawnItems = new L.FeatureGroup(); setCurrentMapCache((prevCache) => ({ ...prevCache, @@ -110,10 +109,9 @@ const LeafletMap: React.FC = ({ datasetId, if3D }) => { mapCenter: initialCenter, mapBounds: initialBounds, zoom: initialZoom, - drawnItems: drawnItems, })); // Allow for drawing polygons - map.addLayer(drawnItems); + map.addLayer(currentMapCache.drawnItems); // Define the options for the polygon drawer const polygonOptions = { shapeOptions: { @@ -127,39 +125,36 @@ const LeafletMap: React.FC = ({ datasetId, if3D }) => { map.on(L.Draw.Event.CREATED, (event: LeafletEvent) => { const drawnObject = (event as L.DrawEvents.Created).layer; if (drawnObject instanceof L.Polygon) { - if (drawnItems) { - drawnItems.addLayer(drawnObject); - const geoJsonObject = drawnObject.toGeoJSON() as Feature< - Geometry, - GeoJsonProperties - >; - let multiPolygon: MultiPolygon; - - // we will probably always encounter only polygons but in a istant future it may be interesting to have multi polygon selection - if (geoJsonObject.geometry.type === "Polygon") { - const polygon = geoJsonObject.geometry - .coordinates as Position[][]; - multiPolygon = { - type: "MultiPolygon", - coordinates: [polygon], - }; - } else if (geoJsonObject.geometry.type === "MultiPolygon") { - multiPolygon = geoJsonObject.geometry as MultiPolygon; - } else { - throw new Error("Unsupported geometry type"); - } - const polygonSelection = new PolygonSelection( - multiPolygon, - "Custom Polygon", - true - ); - - setCurrentMapCache({ - ...currentMapCacheRef.current, - selectedCoordinates: polygonSelection, - isDrawing: false, - }); + currentMapCache.drawnItems.addLayer(drawnObject); + const geoJsonObject = drawnObject.toGeoJSON() as Feature< + Geometry, + GeoJsonProperties + >; + let multiPolygon: MultiPolygon; + + // we will probably always encounter only polygons but in a istant future it may be interesting to have multi polygon selection + if (geoJsonObject.geometry.type === "Polygon") { + const polygon = geoJsonObject.geometry.coordinates as Position[][]; + multiPolygon = { + type: "MultiPolygon", + coordinates: [polygon], + }; + } else if (geoJsonObject.geometry.type === "MultiPolygon") { + multiPolygon = geoJsonObject.geometry as MultiPolygon; + } else { + throw new Error("Unsupported geometry type"); } + const polygonSelection = new PolygonSelection( + multiPolygon, + "Custom Polygon", + true + ); + + setCurrentMapCache({ + ...currentMapCacheRef.current, + selectedCoordinates: polygonSelection, + isDrawing: false, + }); } }); } diff --git a/frontend/src/contexts/MapContext.tsx b/frontend/src/contexts/MapContext.tsx index 2c832ba0..6668cc3e 100644 --- a/frontend/src/contexts/MapContext.tsx +++ b/frontend/src/contexts/MapContext.tsx @@ -16,7 +16,7 @@ export type MapCacheProps = { mapType: MapTypes; zoom: number; isDrawing: boolean; - drawnItems: L.FeatureGroup | null; + drawnItems: L.FeatureGroup; }; // Map Context Type @@ -43,7 +43,7 @@ const defaultMapCache: MapCacheProps = { mapType: MapTypes.Normal, zoom: 13, isDrawing: false, - drawnItems: null, + drawnItems: new L.FeatureGroup(), }; // Actual value of the context From efd01a176064127ddccc14b746a05c8efd6a7473 Mon Sep 17 00:00:00 2001 From: Emil Balitzki Date: Wed, 10 Jul 2024 19:31:31 +0200 Subject: [PATCH 16/23] Fixed padding issues Signed-off-by: Emil Balitzki --- frontend/src/App.css | 44 +++++++++---------- frontend/src/App.tsx | 33 ++++++++------ frontend/src/components/MultiMap/MultiMap.css | 5 +-- frontend/src/components/MultiMap/MultiMap.tsx | 2 +- 4 files changed, 43 insertions(+), 41 deletions(-) diff --git a/frontend/src/App.css b/frontend/src/App.css index c3c7000e..61662613 100644 --- a/frontend/src/App.css +++ b/frontend/src/App.css @@ -2,7 +2,7 @@ position: fixed; top: 0; left: 0; - width: 100%; + width: 100%; height: 100%; overflow: hidden; } @@ -12,30 +12,35 @@ overflow: hidden; height: 100%; position: relative; + padding: 0.6rem; } .multi-map { position: relative; width: 50%; - transition: width 0.5s; - padding-top: 10px; - padding-left: 0px; - padding-bottom: 10px; + transition: width 0.5s; +} + +.multimap-container { + height: 100%; + width: 100%; + max-width: 100%; + display: flex; + flex-direction: column; + padding-right: 10px; + position: relative; } .data-view { width: 50%; position: relative; - transition: width 0.5s; + transition: width 0.5s; overflow: hidden; box-sizing: border-box; - padding-top: 10px; - padding-bottom: 10px; - padding-right:10px; } .hidden { - width: 0; + width: 0; overflow: hidden; padding: 0px; } @@ -44,21 +49,14 @@ width: 100%; } -.multimap-container { - display: flex; - flex-direction: column; - position: relative; - height: 100%; -} - -.toggle-button { +.toggle-data-view-button { position: absolute; z-index: 5; - top: 13px; - right: 0px; - align-self: end; + top: 7px; + right: 0px; + align-self: end; padding: 0; - padding-top:3px; + padding-top: 3px; align-items: center; justify-content: center; min-width: 2rem; @@ -82,4 +80,4 @@ .toggle-button:focus { color: black; border-color: black; -} \ No newline at end of file +} diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index f6a19255..5140d977 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -11,13 +11,12 @@ import { TabsContext } from "./contexts/TabsContext"; import MainMenu from "./components/MainMenu/MainMenu"; import ErrorAlert from "./components/Alerts/ErrorAlert"; -import Tooltip from '@mui/material/Tooltip'; +import Tooltip from "@mui/material/Tooltip"; import { CaretDoubleLeft, CaretDoubleRight } from "@phosphor-icons/react"; import { MapContext } from "./contexts/MapContext"; function App() { - const { currentMapCache } = useContext(MapContext); const { currentTabsCache } = useContext(TabsContext); @@ -27,7 +26,9 @@ function App() { const toggleDataView = () => { setDataViewVisible((prevVisible) => !prevVisible); const { mapInstance } = currentMapCache; - setTimeout(function(){ mapInstance?.invalidateSize()}, 400); + setTimeout(function () { + mapInstance?.invalidateSize(); + }, 400); }; return ( @@ -37,24 +38,30 @@ function App() { ) : (
-
-
- - - - - -
+ +
- +
diff --git a/frontend/src/components/MultiMap/MultiMap.css b/frontend/src/components/MultiMap/MultiMap.css index 08af6286..ed93d12b 100644 --- a/frontend/src/components/MultiMap/MultiMap.css +++ b/frontend/src/components/MultiMap/MultiMap.css @@ -1,12 +1,9 @@ -.multimap-container { +.multimap-inner-container { height: 100%; width: 100%; max-width: 100%; display: flex; flex-direction: column; - padding-left: 20px; - padding-right: 10px; - padding-top: 3px; } .tab-list-container { diff --git a/frontend/src/components/MultiMap/MultiMap.tsx b/frontend/src/components/MultiMap/MultiMap.tsx index 70882142..84e9e791 100644 --- a/frontend/src/components/MultiMap/MultiMap.tsx +++ b/frontend/src/components/MultiMap/MultiMap.tsx @@ -41,7 +41,7 @@ const MultiMap = () => { }; return ( -
+
Date: Wed, 10 Jul 2024 20:58:04 +0200 Subject: [PATCH 17/23] Started working on the multi row data view Signed-off-by: Emil Balitzki --- .../src/components/DataView/DataPanel.css | 58 ---- .../src/components/DataView/DataPanel.tsx | 273 ------------------ frontend/src/components/DataView/DataRow.css | 7 + frontend/src/components/DataView/DataRow.tsx | 73 +++++ frontend/src/components/DataView/DataView.css | 20 ++ frontend/src/components/DataView/DataView.tsx | 94 ++++-- frontend/src/services/locationDataService.ts | 57 ++-- .../src/services/location_data_response.json | 90 ++++++ frontend/src/types/LocationDataTypes.tsx | 4 +- 9 files changed, 295 insertions(+), 381 deletions(-) delete mode 100644 frontend/src/components/DataView/DataPanel.css delete mode 100644 frontend/src/components/DataView/DataPanel.tsx create mode 100644 frontend/src/components/DataView/DataRow.css create mode 100644 frontend/src/components/DataView/DataRow.tsx create mode 100644 frontend/src/services/location_data_response.json diff --git a/frontend/src/components/DataView/DataPanel.css b/frontend/src/components/DataView/DataPanel.css deleted file mode 100644 index 15cc6023..00000000 --- a/frontend/src/components/DataView/DataPanel.css +++ /dev/null @@ -1,58 +0,0 @@ -.datapanels-container { - width: 100%; - height: 100%; - max-height: 100%; - display: flex; - flex-direction: column; - overflow-y: scroll; - margin-bottom: 1rem; -} - -.data-panel-container { - display: flex; - flex-direction: column; -} - -.data-panel-title { - display: flex; - padding: 0.5rem; - gap: 0.5rem; - align-items: center; - cursor: pointer; - font-weight: bold; -} - -.data-panel-grid { - margin: 0 !important; - padding: 0 !important; - max-height: 3000px; - transition: all 0.2s ease-in-out; - overflow: visible; -} - -.data-panel-grid-hidden { - max-height: 0px; - overflow: hidden; - transition: all 0.2s ease-in-out; -} - -.MuiGrid-root { - padding: 0 !important; -} - -.search-box-label { - display: flex; - justify-content: center; - align-items: center; - gap: 0.3rem; -} - -.data-panel-toggle-icon { - rotate: 0deg; - transition: all 0.2s ease-in-out; -} - -.data-panel-toggle-icon-hidden { - transition: all 0.2s ease-in-out; - rotate: -90deg; -} diff --git a/frontend/src/components/DataView/DataPanel.tsx b/frontend/src/components/DataView/DataPanel.tsx deleted file mode 100644 index c5064f89..00000000 --- a/frontend/src/components/DataView/DataPanel.tsx +++ /dev/null @@ -1,273 +0,0 @@ -import { DataGrid, GridColDef, GridRenderCellParams } from "@mui/x-data-grid"; -import Grid from "@mui/material/Grid"; -import IconButton from "@mui/material/IconButton"; -import { CaretDown, MapTrifold } from "@phosphor-icons/react"; -import "./DataPanel.css"; -import { Tooltip } from "@mui/material"; -import { GridToolbar, GridToolbarProps } from "@mui/x-data-grid"; -import { useContext, useState } from "react"; -import { DatasetItem } from "../../types/LocationDataTypes"; -import { TabsContext } from "../../contexts/TabsContext"; -import { fetchDatasets } from "../../services/datasetsService"; -import { AlertContext } from "../../contexts/AlertContext"; -import { Dataset } from "../../types/DatasetTypes"; -import L from "leaflet"; -import CustomSvgIcon from "../DatasetsList/CustomSvgIcon"; -import { svgIconDefault } from "../DatasetsList/DatasetsList"; -import { v4 } from "uuid"; - -function MyCustomToolbar(props: GridToolbarProps) { - return ; -} - -interface DataPanelProps { - listTitle: string; - filterValue: string; - mapRows: DatasetItem[]; - genericRows: DatasetItem[]; -} - -/* - This component displays a mui DataGrid. - Depending on the value of the "button" column, a map icon with the hover "open as map" is shown -*/ -const DataPanel: React.FC = ({ - listTitle, - filterValue, - mapRows, - genericRows, -}) => { - // Keep track of if tabs are hidden - const [ifSelectionDataTabHidden, toggleSelectionDataHidden] = - useState(false); - const [ifIndividualDataTabHidden, toggleIndividualDataHidden] = - useState(false); - useState(false); - const { currentAlertCache, setCurrentAlertCache } = useContext(AlertContext); - - const { openNewTab } = useContext(TabsContext); - - const openDatasetFromMapIcon = async (mapId: string) => { - const datasetsData = await fetchDatasets(); - if (datasetsData) { - const datasetToOpen = datasetsData.find( - (dataset) => dataset.datasetId === mapId - ); - if (datasetToOpen) { - const datasetToOpenTransformed: Dataset = { - id: datasetToOpen.datasetId, - displayName: datasetToOpen.name, - shortDescription: datasetToOpen.shortDescription, - datasetIcon: datasetToOpen.icon ? ( - - ) : ( - - ), - metaData: undefined, - data: { - type: "FeatureCollection", - features: [], - }, - lastDataRequestBounds: L.latLngBounds(L.latLng(0, 0), L.latLng(0, 0)), - }; - - openNewTab(datasetToOpenTransformed); - } else { - // Display alert - setCurrentAlertCache({ - ...currentAlertCache, - isAlertOpened: true, - text: "Dataset with provided ID does not exist.", - }); - console.error("Dataset with provided ID does not exist."); - } - } - }; - - // Returns a button if the "button" value is set to 1 - const renderDetailsButton = (params: GridRenderCellParams) => { - const dataObject = params.row as DatasetItem; - if (dataObject.datasetID !== "") { - return ( - - - { - openDatasetFromMapIcon(dataObject.datasetID); - }} - > - - - - - ); - } else { - return null; - } - }; - - // Defines the columns of the Datagrid - const columns: GridColDef[] = [ - { - field: "button", - headerName: "button", - width: 60, - renderCell: renderDetailsButton, - }, - { field: "key", headerName: "key", width: 250 }, - { - field: "value", - headerName: "value", - type: "number", - width: 250, - getApplyQuickFilterFn: undefined, - }, - ]; - - return ( -
-
-
{ - toggleSelectionDataHidden(!ifSelectionDataTabHidden); - }} - > - - {listTitle} -
- - { - return v4(); - }} - hideFooter={true} - disableColumnMenu - columnHeaderHeight={0} - rows={mapRows} - columns={columns} - slots={{ - toolbar: MyCustomToolbar, - }} - slotProps={{ - toolbar: { - printOptions: { disableToolbarButton: true }, - csvOptions: { disableToolbarButton: true }, - }, - }} - disableDensitySelector - disableColumnFilter - disableColumnSelector - disableColumnSorting - initialState={{ - filter: { - filterModel: { - items: [], - quickFilterValues: [filterValue], - quickFilterExcludeHiddenColumns: true, - }, - }, - }} - filterModel={{ - items: [ - { - field: "displayName", - operator: "contains", - value: filterValue, - }, - ], - }} - density="compact" - disableRowSelectionOnClick - autoHeight - /> - -
-
-
{ - toggleIndividualDataHidden(!ifIndividualDataTabHidden); - }} - > - - Individual Data -
- - { - return v4(); - }} - hideFooter={true} - disableColumnMenu - columnHeaderHeight={0} - rows={genericRows} - columns={columns} - slots={{ - toolbar: MyCustomToolbar, - }} - slotProps={{ - toolbar: { - printOptions: { disableToolbarButton: true }, - csvOptions: { disableToolbarButton: true }, - }, - }} - disableDensitySelector - disableColumnFilter - disableColumnSelector - disableColumnSorting - initialState={{ - filter: { - filterModel: { - items: [], - quickFilterValues: [filterValue], - quickFilterExcludeHiddenColumns: true, - }, - }, - }} - filterModel={{ - items: [ - { - field: "displayName", - operator: "contains", - value: filterValue, - }, - ], - }} - density="compact" - disableRowSelectionOnClick - autoHeight - /> - -
-
- ); -}; - -export default DataPanel; diff --git a/frontend/src/components/DataView/DataRow.css b/frontend/src/components/DataView/DataRow.css new file mode 100644 index 00000000..28e5982a --- /dev/null +++ b/frontend/src/components/DataView/DataRow.css @@ -0,0 +1,7 @@ +.toggle-column { + width: 2rem; +} + +.data-row { + height: 40px; +} diff --git a/frontend/src/components/DataView/DataRow.tsx b/frontend/src/components/DataView/DataRow.tsx new file mode 100644 index 00000000..c19cda8d --- /dev/null +++ b/frontend/src/components/DataView/DataRow.tsx @@ -0,0 +1,73 @@ +import { Fragment } from "react/jsx-runtime"; +import { DatasetItem } from "../../types/LocationDataTypes"; +import { useState } from "react"; +import { + Box, + Collapse, + IconButton, + Table, + TableBody, + TableCell, + TableRow, +} from "@mui/material"; +import { CaretDown, CaretUp } from "@phosphor-icons/react"; +import "./DataRow.css"; + +interface RowProps { + row: DatasetItem; +} + +const DataRow: React.FC = ({ row }) => { + const [open, setOpen] = useState(false); + + return ( + + + {row.subdata.length > 0 ? ( + + setOpen(!open)} + > + {open ? : } + + + ) : ( + + )} + + + {row.displayName} + + + + + + + + + {row.subdata.map((subItem) => ( + + + {subItem.key} + + {subItem.value} + + ))} + +
+
+
+
+
+
+ ); +}; + +export default DataRow; diff --git a/frontend/src/components/DataView/DataView.css b/frontend/src/components/DataView/DataView.css index e75e3dd8..4ad92e28 100644 --- a/frontend/src/components/DataView/DataView.css +++ b/frontend/src/components/DataView/DataView.css @@ -103,3 +103,23 @@ left: 0; z-index: 10; /* Ensure the spinner is on top */ } + +.data-panel-title { + display: flex; + padding: 1rem 0.5rem 1rem 0.5rem; + gap: 0.5rem; + align-items: center; + cursor: pointer; + font-weight: bold; + margin: 0; +} + +.datapanels-container { + width: 100%; + height: 100%; + max-height: 100%; + display: flex; + flex-direction: column; + overflow-y: scroll; + margin-bottom: 1rem; +} diff --git a/frontend/src/components/DataView/DataView.tsx b/frontend/src/components/DataView/DataView.tsx index c556b5f7..0e7ef471 100644 --- a/frontend/src/components/DataView/DataView.tsx +++ b/frontend/src/components/DataView/DataView.tsx @@ -1,19 +1,36 @@ -import DataPanel from "./DataPanel"; import "./DataView.css"; -import { Fragment, useContext, useEffect, useState } from "react"; +import React, { Fragment, useContext, useEffect, useState } from "react"; +import { + Box, + TextField, + Tooltip, + CircularProgress, + Table, + TableBody, + TableContainer, + Collapse, +} from "@mui/material"; +import { + CaretDown, + CaretUp, + Funnel, + MapPin, + MapPinLine, +} from "@phosphor-icons/react"; import { TabsContext } from "../../contexts/TabsContext"; -import { Box, TextField, Tooltip } from "@mui/material"; -import { Funnel, MapPin, MapPinLine } from "@phosphor-icons/react"; import { MapContext } from "../../contexts/MapContext"; import LoadDataButton from "./LoadDataButton"; -import { LocationDataResponse } from "../../types/LocationDataTypes"; +import { + LocationDataResponse, + DatasetItem, +} from "../../types/LocationDataTypes"; import { fetchLocationData } from "../../services/locationDataService"; import { MarkerSelection, PolygonSelection, } from "../../types/MapSelectionTypes"; import { MultiPolygon, Position } from "geojson"; -import { CircularProgress } from "@mui/material"; +import DataRow from "./DataRow"; // Function to filter and return an array of outer polygons function getOuterPolygons(multiPolygon: MultiPolygon): Position[][] { @@ -21,13 +38,15 @@ function getOuterPolygons(multiPolygon: MultiPolygon): Position[][] { return multiPolygon.coordinates.map((polygon) => polygon[0]); } -function DataView() { +const DataView = () => { const [isLoading, setIsLoading] = useState(false); const { currentTabsCache, getCurrentTab } = useContext(TabsContext); const { currentMapCache, setCurrentMapCache } = useContext(MapContext); const [filterValue, setFilterValue] = useState(""); const [ifNeedsReloading, setIfNeedsReloading] = useState(false); const [data, setData] = useState(); + const [showSelectionData, setShowSelectionData] = useState(true); + const [showIndividualData, setShowIndividualData] = useState(true); const handleFilterChange = (event: React.ChangeEvent) => { setFilterValue(event.target.value); @@ -104,10 +123,15 @@ function DataView() { setIsLoading(false); }; + const filterData = (items: DatasetItem[]) => + items.filter((item) => + item.displayName.toLowerCase().includes(filterValue.toLowerCase()) + ); + return (
{currentMapCache.loadedCoordinates ? ( - +
@@ -155,14 +179,46 @@ function DataView() {
) : ( - + +

setShowSelectionData(!showSelectionData)} + className="data-panel-title" + > + {showSelectionData ? : } + Selection Data +

+ + + + + {filterData(data?.selectionData ?? []).map((row) => ( + + ))} + +
+
+
+

setShowIndividualData(!showIndividualData)} + className="data-panel-title" + > + {showIndividualData ? : } + Individual Data +

+ + + + + {filterData(data?.individualData ?? []).map((row) => ( + + ))} + +
+
+
+
)} - {ifNeedsReloading ? ( + {ifNeedsReloading && (
- ) : ( - )} - +
) : (

No coordinates selected

- Click on the map, to select a new location + Click on the map to select a new location
); -} +}; export default DataView; diff --git a/frontend/src/services/locationDataService.ts b/frontend/src/services/locationDataService.ts index f4438ac4..8300cff6 100644 --- a/frontend/src/services/locationDataService.ts +++ b/frontend/src/services/locationDataService.ts @@ -2,6 +2,7 @@ import axios from "axios"; import { LocationDataResponse } from "../types/LocationDataTypes"; import { getAPIGatewayURL } from "../utils/apiGatewayURL"; import { Position } from "geojson"; +import locationData from "./location_data_response.json"; /** * Fetches the data from a specific location @@ -18,32 +19,34 @@ export const fetchLocationData = async ( datasetId: datasetId, location: location, }; - try { - console.log(requestBody); - const response = await axios.put( - getAPIGatewayURL() + "/api/loadLocationData", - requestBody - ); + const locData: LocationDataResponse = locationData; + return locData; + // try { + // console.log(requestBody); + // const response = await axios.put( + // getAPIGatewayURL() + "/api/loadLocationData", + // requestBody + // ); - if (response.status === 200) { - return response.data; - } else { - console.error( - `Error loading location data, status code: ${response.status}, message: ${response.statusText}` - ); - return undefined; - } - } catch (error) { - if (axios.isAxiosError(error) && error.response) { - console.error( - `Error loading location data, status code: ${error.response.status}, message: ${error.response.statusText}`, - error.response.data - ); - } else if (axios.isAxiosError(error)) { - console.error("Axios error loading location data:", error.message); - } else { - console.error("Unknown error loading location data", error); - } - return undefined; - } + // if (response.status === 200) { + // return response.data; + // } else { + // console.error( + // `Error loading location data, status code: ${response.status}, message: ${response.statusText}` + // ); + // return undefined; + // } + // } catch (error) { + // if (axios.isAxiosError(error) && error.response) { + // console.error( + // `Error loading location data, status code: ${error.response.status}, message: ${error.response.statusText}`, + // error.response.data + // ); + // } else if (axios.isAxiosError(error)) { + // console.error("Axios error loading location data:", error.message); + // } else { + // console.error("Unknown error loading location data", error); + // } + // return undefined; + // } }; diff --git a/frontend/src/services/location_data_response.json b/frontend/src/services/location_data_response.json new file mode 100644 index 00000000..1c6b1dd8 --- /dev/null +++ b/frontend/src/services/location_data_response.json @@ -0,0 +1,90 @@ +{ + "individualData": [ + { + "displayName": "Location_22", + "datasetID": "ID_24", + "coordinate": [67.47326087656165, 68.66789203109832], + "subdata": [] + }, + { + "displayName": "Location_79", + "datasetID": "ID_54", + "coordinate": [-153.8015366197477, 60.72947223636345], + "subdata": [ + { + "key": "key_42", + "value": "value_71" + }, + { + "key": "key_10", + "value": "value_18" + }, + { + "key": "key_35", + "value": "value_11" + } + ] + }, + { + "displayName": "Location_41", + "datasetID": "ID_95", + "coordinate": [-27.664057470945153, -60.83479865708518], + "subdata": [] + } + ], + "selectionData": [ + { + "displayName": "Location_14", + "datasetID": "ID_29", + "coordinate": [-11.636436692793325, -26.03179483172533], + "subdata": [ + { + "key": "key_99", + "value": "value_66" + }, + { + "key": "key_84", + "value": "value_97" + }, + { + "key": "key_89", + "value": "value_11" + } + ] + }, + { + "displayName": "Location_22", + "datasetID": "ID_13", + "coordinate": [124.75995564607109, -58.085713596555834], + "subdata": [ + { + "key": "key_50", + "value": "value_42" + }, + { + "key": "key_68", + "value": "value_70" + } + ] + }, + { + "displayName": "Location_91", + "datasetID": "ID_14", + "coordinate": [-155.22762742587116, -78.55297532214685], + "subdata": [ + { + "key": "key_69", + "value": "value_8" + }, + { + "key": "key_8", + "value": "value_90" + }, + { + "key": "key_97", + "value": "value_90" + } + ] + } + ] +} diff --git a/frontend/src/types/LocationDataTypes.tsx b/frontend/src/types/LocationDataTypes.tsx index 92ec0306..4508b75a 100644 --- a/frontend/src/types/LocationDataTypes.tsx +++ b/frontend/src/types/LocationDataTypes.tsx @@ -1,5 +1,3 @@ -import { LatLng } from "leaflet"; - export interface LocationDataResponse { individualData: DatasetItem[]; selectionData: DatasetItem[]; @@ -13,6 +11,6 @@ export interface SubdataItem { export interface DatasetItem { displayName: string; datasetID: string; - coordinate: LatLng; + coordinate: number[]; subdata: SubdataItem[]; } From e1907281f801dee531e702fc3179d237357baaaa Mon Sep 17 00:00:00 2001 From: Emil Balitzki Date: Thu, 11 Jul 2024 18:22:26 +0200 Subject: [PATCH 18/23] Added fly to button Signed-off-by: Emil Balitzki --- frontend/src/components/DataView/DataRow.css | 4 + frontend/src/components/DataView/DataRow.tsx | 98 ++++++++++++++++++- frontend/src/contexts/TabsContext.tsx | 28 ++++++ .../src/services/location_data_response.json | 10 +- frontend/src/types/LocationDataTypes.tsx | 5 +- 5 files changed, 135 insertions(+), 10 deletions(-) diff --git a/frontend/src/components/DataView/DataRow.css b/frontend/src/components/DataView/DataRow.css index 28e5982a..0f5783c8 100644 --- a/frontend/src/components/DataView/DataRow.css +++ b/frontend/src/components/DataView/DataRow.css @@ -5,3 +5,7 @@ .data-row { height: 40px; } + +.data-row-value { + text-align: end !important; +} diff --git a/frontend/src/components/DataView/DataRow.tsx b/frontend/src/components/DataView/DataRow.tsx index c19cda8d..4fed8419 100644 --- a/frontend/src/components/DataView/DataRow.tsx +++ b/frontend/src/components/DataView/DataRow.tsx @@ -1,6 +1,6 @@ import { Fragment } from "react/jsx-runtime"; import { DatasetItem } from "../../types/LocationDataTypes"; -import { useState } from "react"; +import { useContext, useState } from "react"; import { Box, Collapse, @@ -9,9 +9,18 @@ import { TableBody, TableCell, TableRow, + Tooltip, } from "@mui/material"; -import { CaretDown, CaretUp } from "@phosphor-icons/react"; +import { CaretDown, CaretUp, MapPin } from "@phosphor-icons/react"; import "./DataRow.css"; +import { fetchDatasets } from "../../services/datasetsService"; +import { Dataset } from "../../types/DatasetTypes"; +import CustomSvgIcon from "../DatasetsList/CustomSvgIcon"; +import { svgIconDefault } from "../DatasetsList/DatasetsList"; +import { AlertContext } from "../../contexts/AlertContext"; +import L, { LatLng } from "leaflet"; +import { TabsContext } from "../../contexts/TabsContext"; +import { MapContext } from "../../contexts/MapContext"; interface RowProps { row: DatasetItem; @@ -19,6 +28,59 @@ interface RowProps { const DataRow: React.FC = ({ row }) => { const [open, setOpen] = useState(false); + const { currentAlertCache, setCurrentAlertCache } = useContext(AlertContext); + const { changeToOrOpenNewTab } = useContext(TabsContext); + const { currentMapCache } = useContext(MapContext); + + const openDatasetFromMapIcon = async ( + mapId: string | null, + coordinates: number[] | null + ) => { + const datasetsData = await fetchDatasets(); + if (datasetsData) { + const datasetToOpen = datasetsData.find( + (dataset) => dataset.datasetId === mapId + ); + if (datasetToOpen) { + const datasetToOpenTransformed: Dataset = { + id: datasetToOpen.datasetId, + displayName: datasetToOpen.name, + shortDescription: datasetToOpen.shortDescription, + datasetIcon: datasetToOpen.icon ? ( + + ) : ( + + ), + metaData: undefined, + data: { + type: "FeatureCollection", + features: [], + }, + lastDataRequestBounds: L.latLngBounds(L.latLng(0, 0), L.latLng(0, 0)), + }; + // Open the map + const ifSwitched = changeToOrOpenNewTab(datasetToOpenTransformed); + // If provided fly to the coordinates + if ( + ifSwitched && + coordinates && + coordinates.length === 2 && + currentMapCache.mapInstance + ) { + const latLng = new LatLng(coordinates[0], coordinates[1]); + currentMapCache.mapInstance.flyTo(latLng); + } + } else { + // Display alert + setCurrentAlertCache({ + ...currentAlertCache, + isAlertOpened: true, + text: "Dataset with provided ID does not exist.", + }); + console.error("Dataset with provided ID does not exist."); + } + } + }; return ( @@ -34,9 +96,8 @@ const DataRow: React.FC = ({ row }) => { ) : ( - + )} - = ({ row }) => { > {row.displayName} + {row.value && row.value !== "" ? ( + + {row.value} + + ) : ( + + )} + {row.datasetID && row.datasetID != "" ? ( + + + { + openDatasetFromMapIcon(row.datasetID, row.coordinate); + }} + > + + + + + ) : ( + + )} diff --git a/frontend/src/contexts/TabsContext.tsx b/frontend/src/contexts/TabsContext.tsx index 84cc9469..268aabb5 100644 --- a/frontend/src/contexts/TabsContext.tsx +++ b/frontend/src/contexts/TabsContext.tsx @@ -27,6 +27,7 @@ type TabsContextValue = { datasetID: string ) => Promise; openNewTab: (datasetID: Dataset) => boolean; + changeToOrOpenNewTab: (datasetID: Dataset) => boolean; }; // Provider component props type @@ -49,6 +50,7 @@ export const TabsContext = createContext({ getCurrentTab: () => undefined, getOrFetchMetadata: async () => undefined, openNewTab: () => false, + changeToOrOpenNewTab: () => false, }); // Provider component @@ -148,12 +150,38 @@ export const TabsContextProvider: React.FC = ({ return true; }; + /** + * Opens a new tab if necessary and/or switched to already existing one. + * @param dataset dataset to change to or open + */ + const changeToOrOpenNewTab = (dataset: Dataset) => { + // Open the tab if it does not exist + if ( + !currentTabsCache.openedTabs.some((tab) => tab.dataset.id === dataset.id) + ) { + openNewTab(dataset); + } else { + // Switch to that tab + const tabID = currentTabsCache.openedTabs.find((tab) => { + tab.dataset.id === dataset.id; + }); + if (tabID) { + setCurrentTabsCache({ + ...currentTabsCache, + currentTabID: tabID.id, + }); + } + } + return true; + }; + const value = { currentTabsCache, setCurrentTabsCache, getCurrentTab, getOrFetchMetadata, openNewTab, + changeToOrOpenNewTab, }; return {children}; diff --git a/frontend/src/services/location_data_response.json b/frontend/src/services/location_data_response.json index 1c6b1dd8..f75e2737 100644 --- a/frontend/src/services/location_data_response.json +++ b/frontend/src/services/location_data_response.json @@ -2,7 +2,8 @@ "individualData": [ { "displayName": "Location_22", - "datasetID": "ID_24", + "datasetID": "EV_charging_stations", + "value": "Test", "coordinate": [67.47326087656165, 68.66789203109832], "subdata": [] }, @@ -10,6 +11,7 @@ "displayName": "Location_79", "datasetID": "ID_54", "coordinate": [-153.8015366197477, 60.72947223636345], + "value": "Test2", "subdata": [ { "key": "key_42", @@ -35,7 +37,7 @@ "selectionData": [ { "displayName": "Location_14", - "datasetID": "ID_29", + "datasetID": "EV_charging_stations", "coordinate": [-11.636436692793325, -26.03179483172533], "subdata": [ { @@ -54,7 +56,7 @@ }, { "displayName": "Location_22", - "datasetID": "ID_13", + "datasetID": "house_footprints", "coordinate": [124.75995564607109, -58.085713596555834], "subdata": [ { @@ -69,7 +71,7 @@ }, { "displayName": "Location_91", - "datasetID": "ID_14", + "datasetID": "actual_use", "coordinate": [-155.22762742587116, -78.55297532214685], "subdata": [ { diff --git a/frontend/src/types/LocationDataTypes.tsx b/frontend/src/types/LocationDataTypes.tsx index 4508b75a..66233b38 100644 --- a/frontend/src/types/LocationDataTypes.tsx +++ b/frontend/src/types/LocationDataTypes.tsx @@ -10,7 +10,8 @@ export interface SubdataItem { export interface DatasetItem { displayName: string; - datasetID: string; - coordinate: number[]; + value: string | null; + datasetID: string | null; + coordinate: number[] | null; subdata: SubdataItem[]; } From c40ce249bc493277f8fa4ffeb8f5976276b93ef9 Mon Sep 17 00:00:00 2001 From: Emil Balitzki Date: Thu, 11 Jul 2024 18:52:00 +0200 Subject: [PATCH 19/23] Fixed some fly to bugs Signed-off-by: Emil Balitzki --- frontend/src/components/DataView/DataRow.tsx | 31 ++++++++++++++------ frontend/src/contexts/TabsContext.tsx | 6 ++-- 2 files changed, 26 insertions(+), 11 deletions(-) diff --git a/frontend/src/components/DataView/DataRow.tsx b/frontend/src/components/DataView/DataRow.tsx index 4fed8419..ff403a45 100644 --- a/frontend/src/components/DataView/DataRow.tsx +++ b/frontend/src/components/DataView/DataRow.tsx @@ -1,6 +1,6 @@ -import { Fragment } from "react/jsx-runtime"; +import { Fragment, useState, useEffect } from "react"; import { DatasetItem } from "../../types/LocationDataTypes"; -import { useContext, useState } from "react"; +import { useContext } from "react"; import { Box, Collapse, @@ -28,10 +28,22 @@ interface RowProps { const DataRow: React.FC = ({ row }) => { const [open, setOpen] = useState(false); + const [shouldFlyTo, setShouldFlyTo] = useState(null); const { currentAlertCache, setCurrentAlertCache } = useContext(AlertContext); const { changeToOrOpenNewTab } = useContext(TabsContext); const { currentMapCache } = useContext(MapContext); + /** + * Triggers fly to on the next map change + */ + useEffect(() => { + if (shouldFlyTo && currentMapCache.mapInstance) { + currentMapCache.mapInstance.flyTo(shouldFlyTo); + setShouldFlyTo(null); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [currentMapCache.mapInstance]); + const openDatasetFromMapIcon = async ( mapId: string | null, coordinates: number[] | null @@ -61,14 +73,15 @@ const DataRow: React.FC = ({ row }) => { // Open the map const ifSwitched = changeToOrOpenNewTab(datasetToOpenTransformed); // If provided fly to the coordinates - if ( - ifSwitched && - coordinates && - coordinates.length === 2 && - currentMapCache.mapInstance - ) { + if (coordinates && coordinates.length === 2) { const latLng = new LatLng(coordinates[0], coordinates[1]); - currentMapCache.mapInstance.flyTo(latLng); + if (ifSwitched) { + setShouldFlyTo(latLng); + } else { + if (currentMapCache.mapInstance) { + currentMapCache.mapInstance.flyTo(latLng); + } + } } } else { // Display alert diff --git a/frontend/src/contexts/TabsContext.tsx b/frontend/src/contexts/TabsContext.tsx index 268aabb5..fb859a68 100644 --- a/frontend/src/contexts/TabsContext.tsx +++ b/frontend/src/contexts/TabsContext.tsx @@ -153,6 +153,7 @@ export const TabsContextProvider: React.FC = ({ /** * Opens a new tab if necessary and/or switched to already existing one. * @param dataset dataset to change to or open + * @return if switched */ const changeToOrOpenNewTab = (dataset: Dataset) => { // Open the tab if it does not exist @@ -160,10 +161,11 @@ export const TabsContextProvider: React.FC = ({ !currentTabsCache.openedTabs.some((tab) => tab.dataset.id === dataset.id) ) { openNewTab(dataset); + return true; } else { // Switch to that tab const tabID = currentTabsCache.openedTabs.find((tab) => { - tab.dataset.id === dataset.id; + return tab.dataset.id === dataset.id; }); if (tabID) { setCurrentTabsCache({ @@ -171,8 +173,8 @@ export const TabsContextProvider: React.FC = ({ currentTabID: tabID.id, }); } + return false; } - return true; }; const value = { From 06b87af58fe96a92facc744a76e1c969199ae029 Mon Sep 17 00:00:00 2001 From: Emil Balitzki Date: Fri, 12 Jul 2024 15:01:25 +0200 Subject: [PATCH 20/23] Optimized datasets fetching Signed-off-by: Emil Balitzki --- frontend/src/components/DataView/DataRow.tsx | 15 +++++----- frontend/src/components/DataView/DataView.tsx | 30 +++++++++++++++++-- 2 files changed, 35 insertions(+), 10 deletions(-) diff --git a/frontend/src/components/DataView/DataRow.tsx b/frontend/src/components/DataView/DataRow.tsx index ff403a45..77d18dd7 100644 --- a/frontend/src/components/DataView/DataRow.tsx +++ b/frontend/src/components/DataView/DataRow.tsx @@ -13,8 +13,7 @@ import { } from "@mui/material"; import { CaretDown, CaretUp, MapPin } from "@phosphor-icons/react"; import "./DataRow.css"; -import { fetchDatasets } from "../../services/datasetsService"; -import { Dataset } from "../../types/DatasetTypes"; +import { Dataset, DatasetBasicData } from "../../types/DatasetTypes"; import CustomSvgIcon from "../DatasetsList/CustomSvgIcon"; import { svgIconDefault } from "../DatasetsList/DatasetsList"; import { AlertContext } from "../../contexts/AlertContext"; @@ -24,9 +23,10 @@ import { MapContext } from "../../contexts/MapContext"; interface RowProps { row: DatasetItem; + currentDatasets: DatasetBasicData[]; } -const DataRow: React.FC = ({ row }) => { +const DataRow: React.FC = ({ row, currentDatasets }) => { const [open, setOpen] = useState(false); const [shouldFlyTo, setShouldFlyTo] = useState(null); const { currentAlertCache, setCurrentAlertCache } = useContext(AlertContext); @@ -38,7 +38,7 @@ const DataRow: React.FC = ({ row }) => { */ useEffect(() => { if (shouldFlyTo && currentMapCache.mapInstance) { - currentMapCache.mapInstance.flyTo(shouldFlyTo); + currentMapCache.mapInstance.flyTo(shouldFlyTo, currentMapCache.zoom); setShouldFlyTo(null); } // eslint-disable-next-line react-hooks/exhaustive-deps @@ -48,9 +48,8 @@ const DataRow: React.FC = ({ row }) => { mapId: string | null, coordinates: number[] | null ) => { - const datasetsData = await fetchDatasets(); - if (datasetsData) { - const datasetToOpen = datasetsData.find( + if (currentDatasets) { + const datasetToOpen = currentDatasets.find( (dataset) => dataset.datasetId === mapId ); if (datasetToOpen) { @@ -79,7 +78,7 @@ const DataRow: React.FC = ({ row }) => { setShouldFlyTo(latLng); } else { if (currentMapCache.mapInstance) { - currentMapCache.mapInstance.flyTo(latLng); + currentMapCache.mapInstance.flyTo(latLng, currentMapCache.zoom); } } } diff --git a/frontend/src/components/DataView/DataView.tsx b/frontend/src/components/DataView/DataView.tsx index 0e7ef471..f112b54c 100644 --- a/frontend/src/components/DataView/DataView.tsx +++ b/frontend/src/components/DataView/DataView.tsx @@ -31,6 +31,8 @@ import { } from "../../types/MapSelectionTypes"; import { MultiPolygon, Position } from "geojson"; import DataRow from "./DataRow"; +import { fetchDatasets } from "../../services/datasetsService"; +import { DatasetBasicData } from "../../types/DatasetTypes"; // Function to filter and return an array of outer polygons function getOuterPolygons(multiPolygon: MultiPolygon): Position[][] { @@ -47,6 +49,22 @@ const DataView = () => { const [data, setData] = useState(); const [showSelectionData, setShowSelectionData] = useState(true); const [showIndividualData, setShowIndividualData] = useState(true); + const [currentDatasets, setCurrentDatasets] = useState( + [] + ); + + /** + * Fetch the current datasets + */ + useEffect(() => { + const fetchCurrentDatasets = async () => { + const datasets = await fetchDatasets(); + if (datasets) { + setCurrentDatasets(datasets); + } + }; + fetchCurrentDatasets(); + }); const handleFilterChange = (event: React.ChangeEvent) => { setFilterValue(event.target.value); @@ -192,7 +210,11 @@ const DataView = () => { {filterData(data?.selectionData ?? []).map((row) => ( - + ))}
@@ -210,7 +232,11 @@ const DataView = () => { {filterData(data?.individualData ?? []).map((row) => ( - + ))}
From 62ad4da6cd34bb6716988ad56cfeab5bdee0a9e3 Mon Sep 17 00:00:00 2001 From: Emil Balitzki Date: Fri, 12 Jul 2024 15:35:08 +0200 Subject: [PATCH 21/23] Final data view styling Signed-off-by: Emil Balitzki --- frontend/src/components/DataView/DataRow.css | 9 ++++++ frontend/src/components/DataView/DataRow.tsx | 31 +++++++++---------- frontend/src/components/DataView/DataView.css | 6 +++- frontend/src/components/DataView/DataView.tsx | 7 ++++- 4 files changed, 34 insertions(+), 19 deletions(-) diff --git a/frontend/src/components/DataView/DataRow.css b/frontend/src/components/DataView/DataRow.css index 0f5783c8..e6138b19 100644 --- a/frontend/src/components/DataView/DataRow.css +++ b/frontend/src/components/DataView/DataRow.css @@ -4,8 +4,17 @@ .data-row { height: 40px; + background-color: rgb(251, 251, 251); +} + +.data-row-title-container { + padding-left: 0 !important; } .data-row-value { text-align: end !important; } + +.subdata-rows-container > *:last-child > * { + border-bottom: none; +} diff --git a/frontend/src/components/DataView/DataRow.tsx b/frontend/src/components/DataView/DataRow.tsx index 77d18dd7..6be0649e 100644 --- a/frontend/src/components/DataView/DataRow.tsx +++ b/frontend/src/components/DataView/DataRow.tsx @@ -2,7 +2,6 @@ import { Fragment, useState, useEffect } from "react"; import { DatasetItem } from "../../types/LocationDataTypes"; import { useContext } from "react"; import { - Box, Collapse, IconButton, Table, @@ -113,8 +112,8 @@ const DataRow: React.FC = ({ row, currentDatasets }) => { {row.displayName} @@ -130,7 +129,7 @@ const DataRow: React.FC = ({ row, currentDatasets }) => { ) : ( )} - {row.datasetID && row.datasetID != "" ? ( + {row.datasetID && row.datasetID !== "" ? ( = ({ row, currentDatasets }) => { - - - - {row.subdata.map((subItem) => ( - - - {subItem.key} - - {subItem.value} - - ))} - -
-
+ + + {row.subdata.map((subItem) => ( + + + {subItem.key} + + {subItem.value} + + ))} + +
diff --git a/frontend/src/components/DataView/DataView.css b/frontend/src/components/DataView/DataView.css index 4ad92e28..98b80c9e 100644 --- a/frontend/src/components/DataView/DataView.css +++ b/frontend/src/components/DataView/DataView.css @@ -120,6 +120,10 @@ max-height: 100%; display: flex; flex-direction: column; - overflow-y: scroll; + overflow-y: auto; margin-bottom: 1rem; } + +.data-collapse-table { + min-height: auto !important; +} diff --git a/frontend/src/components/DataView/DataView.tsx b/frontend/src/components/DataView/DataView.tsx index f112b54c..2805fffe 100644 --- a/frontend/src/components/DataView/DataView.tsx +++ b/frontend/src/components/DataView/DataView.tsx @@ -205,7 +205,12 @@ const DataView = () => { {showSelectionData ? : } Selection Data

- + From 319f1f808db788492c3d63217f20465397a6b8c2 Mon Sep 17 00:00:00 2001 From: Emil Balitzki Date: Fri, 12 Jul 2024 15:48:26 +0200 Subject: [PATCH 22/23] Fixed fly to with tabs that are already opened Signed-off-by: Emil Balitzki --- frontend/src/contexts/TabsContext.tsx | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/frontend/src/contexts/TabsContext.tsx b/frontend/src/contexts/TabsContext.tsx index fb859a68..6bdc320c 100644 --- a/frontend/src/contexts/TabsContext.tsx +++ b/frontend/src/contexts/TabsContext.tsx @@ -153,15 +153,19 @@ export const TabsContextProvider: React.FC = ({ /** * Opens a new tab if necessary and/or switched to already existing one. * @param dataset dataset to change to or open - * @return if switched + * @return if switched to a different tab */ const changeToOrOpenNewTab = (dataset: Dataset) => { + // Check if the provided tab is the current one + const currentTab = getCurrentTab(); + if (currentTab?.dataset.id === dataset.id) { + return false; + } // Open the tab if it does not exist if ( !currentTabsCache.openedTabs.some((tab) => tab.dataset.id === dataset.id) ) { openNewTab(dataset); - return true; } else { // Switch to that tab const tabID = currentTabsCache.openedTabs.find((tab) => { @@ -173,8 +177,8 @@ export const TabsContextProvider: React.FC = ({ currentTabID: tabID.id, }); } - return false; } + return true; }; const value = { From 4c6e5e931d88080908fa1d063539ee9eaf6b40e4 Mon Sep 17 00:00:00 2001 From: Emil Balitzki Date: Fri, 12 Jul 2024 15:49:20 +0200 Subject: [PATCH 23/23] Reverted mock data Signed-off-by: Emil Balitzki --- frontend/src/services/locationDataService.ts | 57 ++++++------ .../src/services/location_data_response.json | 92 ------------------- 2 files changed, 27 insertions(+), 122 deletions(-) delete mode 100644 frontend/src/services/location_data_response.json diff --git a/frontend/src/services/locationDataService.ts b/frontend/src/services/locationDataService.ts index 8300cff6..f4438ac4 100644 --- a/frontend/src/services/locationDataService.ts +++ b/frontend/src/services/locationDataService.ts @@ -2,7 +2,6 @@ import axios from "axios"; import { LocationDataResponse } from "../types/LocationDataTypes"; import { getAPIGatewayURL } from "../utils/apiGatewayURL"; import { Position } from "geojson"; -import locationData from "./location_data_response.json"; /** * Fetches the data from a specific location @@ -19,34 +18,32 @@ export const fetchLocationData = async ( datasetId: datasetId, location: location, }; - const locData: LocationDataResponse = locationData; - return locData; - // try { - // console.log(requestBody); - // const response = await axios.put( - // getAPIGatewayURL() + "/api/loadLocationData", - // requestBody - // ); + try { + console.log(requestBody); + const response = await axios.put( + getAPIGatewayURL() + "/api/loadLocationData", + requestBody + ); - // if (response.status === 200) { - // return response.data; - // } else { - // console.error( - // `Error loading location data, status code: ${response.status}, message: ${response.statusText}` - // ); - // return undefined; - // } - // } catch (error) { - // if (axios.isAxiosError(error) && error.response) { - // console.error( - // `Error loading location data, status code: ${error.response.status}, message: ${error.response.statusText}`, - // error.response.data - // ); - // } else if (axios.isAxiosError(error)) { - // console.error("Axios error loading location data:", error.message); - // } else { - // console.error("Unknown error loading location data", error); - // } - // return undefined; - // } + if (response.status === 200) { + return response.data; + } else { + console.error( + `Error loading location data, status code: ${response.status}, message: ${response.statusText}` + ); + return undefined; + } + } catch (error) { + if (axios.isAxiosError(error) && error.response) { + console.error( + `Error loading location data, status code: ${error.response.status}, message: ${error.response.statusText}`, + error.response.data + ); + } else if (axios.isAxiosError(error)) { + console.error("Axios error loading location data:", error.message); + } else { + console.error("Unknown error loading location data", error); + } + return undefined; + } }; diff --git a/frontend/src/services/location_data_response.json b/frontend/src/services/location_data_response.json deleted file mode 100644 index f75e2737..00000000 --- a/frontend/src/services/location_data_response.json +++ /dev/null @@ -1,92 +0,0 @@ -{ - "individualData": [ - { - "displayName": "Location_22", - "datasetID": "EV_charging_stations", - "value": "Test", - "coordinate": [67.47326087656165, 68.66789203109832], - "subdata": [] - }, - { - "displayName": "Location_79", - "datasetID": "ID_54", - "coordinate": [-153.8015366197477, 60.72947223636345], - "value": "Test2", - "subdata": [ - { - "key": "key_42", - "value": "value_71" - }, - { - "key": "key_10", - "value": "value_18" - }, - { - "key": "key_35", - "value": "value_11" - } - ] - }, - { - "displayName": "Location_41", - "datasetID": "ID_95", - "coordinate": [-27.664057470945153, -60.83479865708518], - "subdata": [] - } - ], - "selectionData": [ - { - "displayName": "Location_14", - "datasetID": "EV_charging_stations", - "coordinate": [-11.636436692793325, -26.03179483172533], - "subdata": [ - { - "key": "key_99", - "value": "value_66" - }, - { - "key": "key_84", - "value": "value_97" - }, - { - "key": "key_89", - "value": "value_11" - } - ] - }, - { - "displayName": "Location_22", - "datasetID": "house_footprints", - "coordinate": [124.75995564607109, -58.085713596555834], - "subdata": [ - { - "key": "key_50", - "value": "value_42" - }, - { - "key": "key_68", - "value": "value_70" - } - ] - }, - { - "displayName": "Location_91", - "datasetID": "actual_use", - "coordinate": [-155.22762742587116, -78.55297532214685], - "subdata": [ - { - "key": "key_69", - "value": "value_8" - }, - { - "key": "key_8", - "value": "value_90" - }, - { - "key": "key_97", - "value": "value_90" - } - ] - } - ] -}