diff --git a/package-lock.json b/package-lock.json index 699fa39..0b7010b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,8 @@ "file-saver": "^2.0.5", "i18next": "^23.7.6", "i18next-browser-languagedetector": "^7.2.0", + "json-edit-react": "^1.15.4", + "jszip": "^3.10.1", "papaparse": "^5.4.1", "react": "^18.2.0", "react-azure-maps": "^1.0.0", @@ -11028,8 +11030,7 @@ "node_modules/core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", - "dev": true + "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==" }, "node_modules/cosmiconfig": { "version": "7.1.0", @@ -15438,6 +15439,11 @@ "node": ">= 4" } }, + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==" + }, "node_modules/immer": { "version": "9.0.18", "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.18.tgz", @@ -15519,8 +15525,7 @@ "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "node_modules/ini": { "version": "2.0.0", @@ -20379,6 +20384,18 @@ "node": ">=4" } }, + "node_modules/json-edit-react": { + "version": "1.15.4", + "resolved": "https://registry.npmjs.org/json-edit-react/-/json-edit-react-1.15.4.tgz", + "integrity": "sha512-DxuIcBJ0FsuNrYzODDMc/C5TNP3sGpLR1Iq6UFtbYTKWJhOAt6BVncQdNZlwJ7lYO0HY0pjNyAUiWQCX3Sbayw==", + "dependencies": { + "object-property-assigner": "^1.3.0", + "object-property-extractor": "^1.0.11" + }, + "peerDependencies": { + "react": ">=16.0.0" + } + }, "node_modules/json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", @@ -20474,6 +20491,49 @@ "node": ">=4.0" } }, + "node_modules/jszip": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", + "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", + "dependencies": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "setimmediate": "^1.0.5" + } + }, + "node_modules/jszip/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + }, + "node_modules/jszip/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/jszip/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/jszip/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/keyborg": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/keyborg/-/keyborg-2.2.0.tgz", @@ -20552,6 +20612,14 @@ "node": ">= 0.8.0" } }, + "node_modules/lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "dependencies": { + "immediate": "~3.0.5" + } + }, "node_modules/lilconfig": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.6.tgz", @@ -21596,6 +21664,16 @@ "node": ">= 0.4" } }, + "node_modules/object-property-assigner": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/object-property-assigner/-/object-property-assigner-1.3.0.tgz", + "integrity": "sha512-19A0RsC9rP9klCKHDPL/MeERxeopV9wyMNfP+eD2uKOafzLjF+OUEN4FoP6RAlCFHmerBPJ4ohNv/WrgaNpeIA==" + }, + "node_modules/object-property-extractor": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/object-property-extractor/-/object-property-extractor-1.0.11.tgz", + "integrity": "sha512-VnDQcyN0FTXZ0hMZS/CTb2QkIssZ9XKB8zlf5rnFh1HjFQX1P73EHawavSztBOiPDGqAPNXebv4agjhF9eACAw==" + }, "node_modules/object.assign": { "version": "4.1.4", "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", @@ -21953,6 +22031,11 @@ "node": ">=6" } }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" + }, "node_modules/papaparse": { "version": "5.4.1", "resolved": "https://registry.npmjs.org/papaparse/-/papaparse-5.4.1.tgz", @@ -23973,8 +24056,7 @@ "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" }, "node_modules/proj4": { "version": "2.8.1", @@ -28602,8 +28684,7 @@ "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, "node_modules/util.promisify": { "version": "1.0.1", @@ -38057,8 +38138,7 @@ "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", - "dev": true + "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==" }, "cosmiconfig": { "version": "7.1.0", @@ -41358,6 +41438,11 @@ "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", "dev": true }, + "immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==" + }, "immer": { "version": "9.0.18", "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.18.tgz", @@ -41414,8 +41499,7 @@ "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "ini": { "version": "2.0.0", @@ -45188,6 +45272,15 @@ "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", "dev": true }, + "json-edit-react": { + "version": "1.15.4", + "resolved": "https://registry.npmjs.org/json-edit-react/-/json-edit-react-1.15.4.tgz", + "integrity": "sha512-DxuIcBJ0FsuNrYzODDMc/C5TNP3sGpLR1Iq6UFtbYTKWJhOAt6BVncQdNZlwJ7lYO0HY0pjNyAUiWQCX3Sbayw==", + "requires": { + "object-property-assigner": "^1.3.0", + "object-property-extractor": "^1.0.11" + } + }, "json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", @@ -45266,6 +45359,51 @@ "object.assign": "^4.1.3" } }, + "jszip": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", + "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", + "requires": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "setimmediate": "^1.0.5" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + }, + "readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, "keyborg": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/keyborg/-/keyborg-2.2.0.tgz", @@ -45326,6 +45464,14 @@ "type-check": "~0.4.0" } }, + "lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "requires": { + "immediate": "~3.0.5" + } + }, "lilconfig": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.6.tgz", @@ -46090,6 +46236,16 @@ "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" }, + "object-property-assigner": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/object-property-assigner/-/object-property-assigner-1.3.0.tgz", + "integrity": "sha512-19A0RsC9rP9klCKHDPL/MeERxeopV9wyMNfP+eD2uKOafzLjF+OUEN4FoP6RAlCFHmerBPJ4ohNv/WrgaNpeIA==" + }, + "object-property-extractor": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/object-property-extractor/-/object-property-extractor-1.0.11.tgz", + "integrity": "sha512-VnDQcyN0FTXZ0hMZS/CTb2QkIssZ9XKB8zlf5rnFh1HjFQX1P73EHawavSztBOiPDGqAPNXebv4agjhF9eACAw==" + }, "object.assign": { "version": "4.1.4", "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", @@ -46344,6 +46500,11 @@ "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", "dev": true }, + "pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" + }, "papaparse": { "version": "5.4.1", "resolved": "https://registry.npmjs.org/papaparse/-/papaparse-5.4.1.tgz", @@ -47649,8 +47810,7 @@ "process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" }, "proj4": { "version": "2.8.1", @@ -51165,8 +51325,7 @@ "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, "util.promisify": { "version": "1.0.1", diff --git a/package.json b/package.json index 9f0fd49..c2f3130 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,8 @@ "file-saver": "^2.0.5", "i18next": "^23.7.6", "i18next-browser-languagedetector": "^7.2.0", + "json-edit-react": "^1.15.4", + "jszip": "^3.10.1", "papaparse": "^5.4.1", "react": "^18.2.0", "react-azure-maps": "^1.0.0", diff --git a/src/pages/conversion/download-imdf.js b/src/pages/conversion/download-imdf.js index 7a3b863..ba991ab 100644 --- a/src/pages/conversion/download-imdf.js +++ b/src/pages/conversion/download-imdf.js @@ -1,11 +1,44 @@ -import { cx } from '@emotion/css'; -import { PrimaryButton } from '@fluentui/react'; -import { failedLogsButton, logsButton } from './style'; +import { cx } from '@emotion/css'; +import { PrimaryButton } from '@fluentui/react'; +import { logsButton } from './style'; +import React, { useState } from 'react'; +import JSZip from 'jszip'; +import { saveAs } from 'file-saver'; + +// This component downloads the updated IMDF zip file and is triggered by users clicking "Download IMDF" button +export const DownloadIMDF = ({ imdfPackageLocation, units, levels, footprint, building }) => { + const [isLoading, setIsLoading] = useState(false); + + // Fetches current zip file, updates the geojson files and downloads the updated zip + const handleUpdateZip = () => { + setIsLoading(true); + fetch(imdfPackageLocation) + .then(response => response.arrayBuffer()) + .then(data => { + const zip = new JSZip(); + return zip.loadAsync(data); + }) + .then(zip => { + zip.file('unit.geojson', JSON.stringify(units, null, 2)); + zip.file('level.geojson', JSON.stringify(levels, null, 2)); + zip.file('footprint.geojson', JSON.stringify(footprint, null, 2)); + zip.file('building.geojson', JSON.stringify(building, null, 2)); + return zip.generateAsync({ type: 'blob' }); + }) + .then(updatedZip => { + saveAs(updatedZip, 'updated_imdf_package.zip'); + setIsLoading(false); + }) + .catch(() => { + setIsLoading(false); + }); + }; -export const DownloadIMDF = ({ isFailed, link }) => { return ( - - Download IMDF - +
+ + {isLoading ? 'Downloading..' : 'Download IMDF'} + +
); }; diff --git a/src/pages/conversion/imdf-conversion.js b/src/pages/conversion/imdf-conversion.js index db513ed..6367e43 100644 --- a/src/pages/conversion/imdf-conversion.js +++ b/src/pages/conversion/imdf-conversion.js @@ -1,4 +1,5 @@ import { cx } from '@emotion/css'; +import { useEffect, useState } from 'react'; import { MessageBar, MessageBarType } from '@fluentui/react'; import { Icon } from '@fluentui/react/lib/Icon'; import { PATHS } from 'common'; @@ -13,9 +14,34 @@ import { actionButtonsLeft, actionButtonsWrapper, messageWrapper } from './imdf- import { ImdfDiagnostics } from './imdf-diagnostics'; import PlacesPreviewMap from './places-preview-map'; import StepButton from './step-button'; +import { processZip } from './places-preview-map/utils'; import { container, content, enabledStep, step as stepStyle, stepTitle, stepsContainer } from './style'; const ImdfConversion = () => { + const [units, setUnits] = useState({ features: [] }); + const [levels, setLevels] = useState({ features: [] }); + const [footprint, setFootprint] = useState({ features: [] }); + const [building, setBuilding] = useState({ features: [] }); + + const handleUnitsChange = (editedUnits) => { + if(!editedUnits.type) { + editedUnits.type = 'FeatureCollection'; + } + setUnits(editedUnits); + }; + + const handleLevelsChange = (editedLevels) => { + setLevels(editedLevels); + }; + + const handleFootprintChange = (editedFootprint) => { + setFootprint(editedFootprint); + }; + + const handleBuildingChange = (editedBuilding) => { + setBuilding(editedBuilding); + }; + const { t } = useTranslation(); const navigate = useCustomNavigate(); @@ -28,7 +54,25 @@ const ImdfConversion = () => { s.imdfPackageLocation, s.diagnosticPackageLocation, ]); + + useEffect(() => { + if (!imdfPackageLocation) return; + + processZip(imdfPackageLocation).then(files => { + const unitFile = files.find(file => file.filename === 'unit.geojson'); + const levelFile = files.find(file => file.filename === 'level.geojson'); + const buildingFile = files.find(file => file.filename === 'building.geojson'); + const footprintFile = files.find(file => file.filename === 'footprint.geojson'); + if (unitFile && levelFile && footprintFile) { + setUnits(unitFile.content); + setLevels(levelFile.content); + setBuilding(buildingFile.content); + setFootprint(footprintFile.content); + } + }); + }, [imdfPackageLocation]); + const { isRunningIMDFConversion, hasCompletedIMDFConversion, imdfConversionStatus, errorList } = useIMDFConversionStatus(); @@ -76,7 +120,7 @@ const ImdfConversion = () => {
{imdfConversionStatus === conversionStatuses.finishedSuccessfully && ( - + )}
@@ -84,7 +128,7 @@ const ImdfConversion = () => {
{imdfConversionStatus === conversionStatuses.finishedSuccessfully && ( - {({ height }) => } + {({ height }) => } )} diff --git a/src/pages/conversion/places-preview-map/imdf-model-helpers/index.js b/src/pages/conversion/places-preview-map/imdf-model-helpers/index.js index 3c3a29b..ef5b068 100644 --- a/src/pages/conversion/places-preview-map/imdf-model-helpers/index.js +++ b/src/pages/conversion/places-preview-map/imdf-model-helpers/index.js @@ -8,12 +8,12 @@ function drawingModeChanged(allLayers) { } // Displays information of a feature that is being drawn/edited, at time of click -function currentEditData(map, drawingManager) { +function currentEditData(map, drawingManager, setJsonData) { map.events.add('drawingstarted', drawingManager, (f) => { if(f?.data) { - var shapeId = f.data.id; - var geojsonData = drawingManager.getSource().getShapeById(shapeId).data; - document.getElementById('infoPanel-json').value = JSON.stringify(geojsonData, null, 2); + var shapeId = f.data.id; + var geojsonData = drawingManager.getSource().getShapeById(shapeId).data; + setJsonData(geojsonData); } }); } @@ -35,26 +35,119 @@ function groupAndSort(units, language, selectedLevel) { return groupedFeatures; } -function writeToInfoPanel(geojsonData) { - const { map, ...obj } = geojsonData; - document.getElementById('infoPanel-json').value = JSON.stringify(obj, null, 2); +// Changes the cursor to be a pointer when a clickable feature is hovered over +function grabToPointer(layerName, hoverLayer, map) { + map.events.add('mousemove', layerName, function (e) { + hoverLayer.setOptions({ filter: ['==', ['get', '_azureMapsShapeId'], e.shapes[0].getProperties()['_azureMapsShapeId']] }); + map.getCanvas().style.cursor = 'pointer'; + }); + + map.events.add('mouseleave', layerName, function (e) { + hoverLayer.setOptions({ filter: ['==', ['get', '_azureMapsShapeId'], ''] }); + map.getCanvas().style.cursor = 'grab'; + }); + + map.events.add(['mousedown', 'mouseup'], layerName, function (e) { + map.getCanvas().style.cursor = 'pointer'; + }); } -// Changes the cursor to be a pointer when a clickable feature is hovered over -function grabToPointer(layerName, map) { - map.events.add('mouseover', layerName, function () { - map.getCanvasContainer().style.cursor = 'pointer'; +// Changes cursor between grabbing and grabbing when map is clicked and dragged +function grabAndGrabbing(map) { + map.events.add('mousedown', () => { + map.getCanvas().style.cursor = 'grabbing'; }); - map.events.add('mouseout', layerName, function () { - map.getCanvasContainer().style.cursor = 'grab'; + map.events.add('mouseup', () => { + map.getCanvas().style.cursor = 'grab'; }); } +function updateSelectedColor(map, unitSelected) { + if(unitSelected) { + const lineLayer = map.layers.getLayerById('lineClickLayer'); + try { + lineLayer.setOptions({strokeColor: 'hsla(0, 0%, 0%, 0)'}); + } catch { + console.log('No line layer to change color of.') + } + const unitLayer = map.layers.getLayerById('unitClickChange'); + try { + unitLayer.setOptions({fillColor: 'rgba(75, 146, 210, 0.8)'}); + } catch { + console.log('No unit layer to change color of.') + } + } else { + const polygonLayer = map.layers.getLayerById('lineClickLayer'); + try { + polygonLayer.setOptions({strokeColor: 'rgba(75, 146, 210, 0.8)'}); + } catch { + console.log('No unit layer to change color of.') + } + const unitLayer = map.layers.getLayerById('unitClickChange'); + try { + unitLayer.setOptions({fillColor: 'hsla(0, 0%, 0%, 0)'}); + } catch { + console.log('No unit layer to change color of.') + } + } +} + +function updateLevels(levels, selectedLevel, newValue) { + levels.features = levels.features.map(item => + item.id === selectedLevel.id ? newValue : item + ); + + return levels; +} + +function setFields(feature, selectedLevel) { + if (!feature.data.properties.name) { + feature.data.properties.name = {}; + feature.data.properties.name.en = ''; + } + + if(!feature.data.properties.category) { + feature.data.properties.category = 'unspecified'; + } + + if(!feature.data.properties.level_id) { + feature.data.properties.level_id = selectedLevel.id; + } + + if(!feature.data.properties.display_point) { + feature.data.properties.display_point = {}; + feature.data.properties.display_point.type = 'Point'; + feature.data.properties.display_point.coordinates = []; + } + + if(!feature.data.properties.label) { + feature.data.properties.label = ''; + } + + if(feature.data.properties._azureMapsShapeId) { + let savedAzureId = feature.data.properties._azureMapsShapeId; + delete feature.data.properties._azureMapsShapeId; + feature.data.properties._azureMapsShapeId = savedAzureId; + } + + if ('bbox' in feature.data.geometry) { + delete feature.data.geometry.bbox; + } +} + +function deleteUnitPrevEdits(units, selectedLevel) { + units.features = units.features.filter(item => item.properties.level_id !== selectedLevel.id); +} + export { currentEditData, groupAndSort, drawingModeChanged, - writeToInfoPanel, - grabToPointer + grabToPointer, + updateLevels, + setFields, + deleteUnitPrevEdits, + grabAndGrabbing, + updateSelectedColor }; \ No newline at end of file diff --git a/src/pages/conversion/places-preview-map/indes.style.js b/src/pages/conversion/places-preview-map/indes.style.js index 0f78087..6179d5d 100644 --- a/src/pages/conversion/places-preview-map/indes.style.js +++ b/src/pages/conversion/places-preview-map/indes.style.js @@ -18,27 +18,21 @@ export const imdfPreviewMap = css` export const layerSelect = css ` position: relative; - top: 65%; + bottom: -55%; `; + export const textWrapper = css ` - flex: 1 1 4rem; + flex: 1 1 5rem; position: relative; display: inline; - margin-top: 1%; `; -export const textArea = css ` - width: 95%; - height: 95%; - white-space: nowrap; - resize: none; +export const toolbar = css ` + backgroundColor: #f0f0f0, + border: 2px solid #0078d4, + color: #0078d4, + cursor: not-allowed, + pointerEvents: none `; -export const saveButtonWrapper = css ` - display: flex; - flex-direction: row; - justify-content: flex-end; -`; - - diff --git a/src/pages/conversion/places-preview-map/index.js b/src/pages/conversion/places-preview-map/index.js index 1807348..d90d4ec 100644 --- a/src/pages/conversion/places-preview-map/index.js +++ b/src/pages/conversion/places-preview-map/index.js @@ -1,29 +1,32 @@ -import { Map, layer, source, control } from 'azure-maps-control'; +import { Map, layer, source, control, HtmlMarker } from 'azure-maps-control'; import { control as draw_control, drawing } from 'azure-maps-drawing-tools'; import { getDomain, useConversionStore, useLevelsStore, useUserStore } from 'common/store'; -import { useEffect, useMemo, useState } from 'react'; -import { DefaultButton } from '@fluentui/react'; -import { logsButton } from '../style'; -import { imdfPreviewMap, imdfPreviewMapWrapper, layerSelect, textWrapper, textArea, mapTextWrapper, saveButtonWrapper } from './indes.style'; +import { useEffect, useMemo, useState, useRef } from 'react'; +import { imdfPreviewMap, imdfPreviewMapWrapper, layerSelect, textWrapper, mapTextWrapper } from './indes.style'; import LevelSelector from './level-selector'; import LayerSelector from './layer-selector'; import MapNotification from './map-notification'; import { calculateBoundingBox, getFeatureLabel, getFillStyles, getLineStyles, getTextStyle, processZip } from './utils'; -import { currentEditData, groupAndSort, drawingModeChanged, writeToInfoPanel, grabToPointer } from './imdf-model-helpers'; +import { currentEditData, groupAndSort, drawingModeChanged, updateLevels, setFields, deleteUnitPrevEdits, grabAndGrabbing, grabToPointer, updateSelectedColor } from './imdf-model-helpers'; +import { JsonEditor } from 'json-edit-react' import 'azure-maps-drawing-tools/dist/atlas-drawing.min.css'; import 'azure-maps-control/dist/atlas.min.css'; -const PlacesPreviewMap = ({ style }) => { +const PlacesPreviewMap = ({ style, unitsChanged, levelsChanged, footprintChanged, buildingChanged }) => { const [geography, subscriptionKey] = useUserStore(s => [s.geography, s.subscriptionKey]); const [imdfPackageLocation] = useConversionStore(s => [s.imdfPackageLocation]); const [language] = useLevelsStore(s => [s.language]); - const [units, setUnits] = useState({ features: [] }); + const [units, setUnits] = useState({ features: [], type: 'FeatureCollection' }); const [levels, setLevels] = useState({ features: [] }); - - // const [building, setBuilding] = useState({ features: [] }); // building will be used eventually + const [building, setBuilding] = useState({ features: [] }); const [footprint, setFootprint] = useState({ features: [] }); + const [jsonData, setJsonData] = useState({}); + const newDataRef = useRef(false); + + // const [prevStates, setPrevStates] = useState([]); // Tracking previous changes for Undo feature + useEffect(() => { if (!imdfPackageLocation) return; @@ -36,13 +39,15 @@ const PlacesPreviewMap = ({ style }) => { if (unitFile && levelFile && buildingFile && footprintFile) { setUnits(unitFile.content); setLevels(levelFile.content); - // setBuilding(buildingFile.content); + setBuilding(buildingFile.content); setFootprint(footprintFile.content); } }); }, [imdfPackageLocation]); const [selectedLevelId, setSelectedLevelId] = useState(null); + const [selectedLayerId, setSelectedLayerId] = useState(null); + const [drawNotif, setDrawNotif] = useState(false); // Triggers drawing tip if true const selectedLevel = useMemo(() => { const level = levels.features.find(item => item.id === selectedLevelId) || levels.features[0] || {}; @@ -50,8 +55,6 @@ const PlacesPreviewMap = ({ style }) => { return level; }, [levels, selectedLevelId]); - const [selectedLayerId, setSelectedLayerId] = useState(null); - useEffect(() => { var drawingManager; @@ -61,7 +64,7 @@ const PlacesPreviewMap = ({ style }) => { language: 'en-US', domain: getDomain(geography), staticAssetsDomain: getDomain(geography), - style: 'blank', + style: 'dark', }); map.controls.add([new control.ZoomControl()], { @@ -70,6 +73,8 @@ const PlacesPreviewMap = ({ style }) => { map.events.add('ready', () => { var drawingToolbar; + setJsonData({}); + if(selectedLayerId === 'unitButton') { drawingToolbar = new draw_control.DrawingToolbar({ position: 'bottom-right', @@ -104,18 +109,17 @@ const PlacesPreviewMap = ({ style }) => { }); footprintInteractions(footprint, drawingManager, map); + } else if(selectedLayerId === 'buildingButton') { + buildingInteractions(building, map); } else { - drawingToolbar = new draw_control.DrawingToolbar({ - position: 'bottom-right', - style: 'light', - buttons: ['edit-geometry'] - }); - - drawingManager = new drawing.DrawingManager(map, { - toolbar: drawingToolbar - }); + fullViewInteractions(units, levels, map); + } - fullViewInteractions(units, levels, drawingManager, map); + if(selectedLayerId !== 'buildingButton') { + var layers = drawingManager.getLayers(); + map.events.add('mousedown', layers.polygonLayer, handleDeletion); + map.events.add('touchstart', layers.polygonLayer, handleDeletion); + map.events.add('click', layers.polygonLayer, handleDeletion); } }); @@ -128,107 +132,178 @@ const PlacesPreviewMap = ({ style }) => { features = map.layers.getRenderedShapes(e.position, 'levelClick'); else if(selectedLayerId === 'footprintButton') features = map.layers.getRenderedShapes(e.position, 'footprintClick'); + else if(selectedLayerId === 'buildingButton') { + + } else features = map.layers.getRenderedShapes(e.position, ['unitClick', 'levelClick']); - features.forEach(function (feature) { - writeToInfoPanel(feature.data); - }); + if(selectedLayerId !== 'buildingButton') { + features.forEach(function (feature) { + const newData = feature.data || {}; + setJsonData(newData); + }); + } }); - // Shows change in corresponding feature color when mouse hovers over that feature - function featureHover(layerName, polygonHoverLayer) { - map.events.add('mousemove', layerName, function (e) { - polygonHoverLayer.setOptions({ filter: ['==', ['get', '_azureMapsShapeId'], e.shapes[0].getProperties()['_azureMapsShapeId']] }); - }); + grabAndGrabbing(map); - map.events.add('mouseleave', layerName, function (e) { - polygonHoverLayer.setOptions({ filter: ['==', ['get', '_azureMapsShapeId'], ''] }); - }); - } + // Shows change in corresponding feature color when mouse hovers over OR clicks on that feature + let selectedFeatureID = null; + function featureHoverClick(layerName, hoverLayer, clickLayer, unitSelected=false) { + grabToPointer(layerName, hoverLayer, map); - // Entry point when "unit.geojson" is pressed; the following code should be refactored due to redundancy - function unitInteractions(units, drawingManager, map) { - var unitLayer, unitLines, polygonHoverLayer, unitSymbols; - var layersAdded = [unitLayer, unitLines, polygonHoverLayer, unitSymbols]; - const groupedFeatures = groupAndSort(units, language, selectedLevel); - const keys = Object.keys(groupedFeatures); - - keys.forEach(category => { - const features = groupedFeatures[category].features; - - const dataSource = new source.DataSource(); - map.sources.add(dataSource); - dataSource.add(features); - - unitLayer = new layer.PolygonLayer(dataSource, 'unitClick', getFillStyles('unit', category)); - unitLines = new layer.LineLayer(dataSource, null, getLineStyles('unit', category)); - polygonHoverLayer = new layer.PolygonLayer(dataSource, null, { - fillColor: 'rgba(150, 50, 255, 0.2)', - filter: ['==', ['get', 'id'], ''] - }); + map.events.add('click', layerName, function (e) { + const clickedFeatureID = e.shapes[0].getProperties()['_azureMapsShapeId']; + map.getCanvas().style.cursor = 'pointer'; - unitSymbols = new layer.SymbolLayer(dataSource, null, getTextStyle(category)); - - map.layers.add([unitLayer, polygonHoverLayer, unitLines, unitSymbols], 'roomPolygons'); - - grabToPointer([unitLayer, polygonHoverLayer], map); - featureHover(unitLayer, polygonHoverLayer); - - // map.layers.add(new layer.SymbolLayer(dataSource, null, getTextStyle(category)), 'roomLabels'); - - var drawingSource = drawingManager.getSource(); - drawingSource.add(features); - - let dmLayers = drawingManager.getLayers(); - dmLayers.polygonLayer.setOptions({ visible: false }); - dmLayers.polygonOutlineLayer.setOptions({ visible: false }); - layersAdded = [unitLayer, unitLines, polygonHoverLayer, unitSymbols]; - - map.events.add('drawingmodechanged', drawingManager, (e) => { - let dmLayers = drawingManager.getLayers(); - layersAdded = [unitLayer, unitLines, polygonHoverLayer, unitSymbols]; - - if (e === 'idle') { - dmLayers.polygonLayer.setOptions({ visible: false }); - dmLayers.polygonOutlineLayer.setOptions({ visible: false }); - map.layers.remove([unitLayer, unitLines, polygonHoverLayer]); - - unitLayer = new layer.PolygonLayer(drawingManager.getSource(), 'unitClick', getFillStyles('unit', category)); - unitLines = new layer.LineLayer(drawingManager.getSource(), null, getLineStyles('unit', category)); - polygonHoverLayer = new layer.PolygonLayer(drawingManager.getSource(), null, { - fillColor: 'rgba(150, 50, 255, 0.2)', - filter: ['==', ['get', 'id'], ''] - }); - unitSymbols = new layer.SymbolLayer(drawingManager.getSource(), null, getTextStyle(category)); - - map.layers.add([unitLayer, polygonHoverLayer, unitLines, unitSymbols], 'roomPolygons'); - - grabToPointer([unitLayer, polygonHoverLayer], map); - featureHover(unitLayer, polygonHoverLayer); - layersAdded = [unitLayer, unitLines, polygonHoverLayer, unitSymbols]; - } - else if (e === 'edit-geometry' || e === 'erase-geometry' || e === 'draw-polygon') { - drawingModeChanged(layersAdded); - dmLayers.polygonLayer.setOptions({ visible: true }); - dmLayers.polygonOutlineLayer.setOptions({ visible: true }); - } - else { - // This will eventually be a visible pop-up - console.log('Not a valid drawing toolbar option.'); - } - }); - - currentEditData(map, drawingManager); + if (selectedFeatureID !== clickedFeatureID) { + // If feature is clicked, change color of feature + selectedFeatureID = clickedFeatureID; + clickLayer.setOptions({ + filter: ['==', ['get', '_azureMapsShapeId'], clickedFeatureID] + }); + + // Handles the change in color of the feature when clicked (for full view) + updateSelectedColor(map, unitSelected) + + var features; + if(selectedLayerId === 'unitButton') + features = map.layers.getRenderedShapes(e.position, 'unitClick'); + else if(selectedLayerId === 'levelButton') + features = map.layers.getRenderedShapes(e.position, 'levelFill'); + else if(selectedLayerId === 'footprintButton') + features = map.layers.getRenderedShapes(e.position, 'footprintClick'); + else + features = map.layers.getRenderedShapes(e.position, ['unitClick', 'levelClick']); + + features.forEach(function (feature) { + const newData = feature.data || {}; + setJsonData(newData); + }); + } + else { + // If feature clicked is currently chosen OR it is another feature, change color of feature back to original + selectedFeatureID = null; + clickLayer.setOptions({ + filter: ['==', ['get', '_azureMapsShapeId'], ''] + }); + setJsonData({}); + } }); } + function handleDeletion(e) { + if (drawingManager.getOptions().mode === 'erase-geometry') { + if (window.confirm('Do you want to proceed with removing this feature?')) { + drawingManager.getSource().remove(e.shapes[0]); + } + } + } + + // Entry point when "unit.geojson" is pressed; the following code should be refactored due to redundancy + function unitInteractions(units, drawingManager, map) { + // Update the units state with the edited features (for updating zip) + unitsChanged(units); + + var unitLayer, unitLines, polygonHoverLayer, polygonClickLayer, unitSymbols; + var layersAdded = [unitLayer, unitLines, polygonHoverLayer, polygonClickLayer, unitSymbols]; + const groupedFeatures = groupAndSort(units, language, selectedLevel); + const keys = Object.keys(groupedFeatures); + + keys.forEach(category => { + var features = groupedFeatures[category].features; + const dataSource = new source.DataSource(); + map.sources.add(dataSource); + dataSource.add(features); + + unitLayer = new layer.PolygonLayer(dataSource, 'unitClick', getFillStyles('unit', category)); + unitLines = new layer.LineLayer(dataSource, null, getLineStyles('unit', category)); + polygonHoverLayer = new layer.PolygonLayer(dataSource, null, { + fillColor: 'rgba(135, 206, 250, 0.8)', + filter: ['==', ['get', 'id'], ''], + cursor: 'pointer !important', + }); + + polygonClickLayer = new layer.PolygonLayer(dataSource, 'unitClickChange', { + fillColor: 'rgba(75, 146, 210, 0.8)', + filter: ['==', ['get', 'id'], ''] , + cursor: 'pointer !important', + }); + + unitSymbols = new layer.SymbolLayer(dataSource, null, getTextStyle(category)); + map.layers.add([unitLayer, polygonHoverLayer, unitLines, polygonClickLayer, unitSymbols], 'roomPolygons'); + featureHoverClick(unitLayer, polygonHoverLayer, polygonClickLayer, true); + + var drawingSource = drawingManager.getSource(); + drawingSource.add(features); + + let dmLayers = drawingManager.getLayers(); + dmLayers.polygonLayer.setOptions({ visible: false }); + dmLayers.polygonOutlineLayer.setOptions({ visible: false }); + layersAdded = [unitLayer, unitLines, polygonHoverLayer, polygonClickLayer, unitSymbols]; + + // Saving previous states for undo functionality + // if(prevStates.length === 0 || units.features !== prevStates[prevStates.length - 1].features) { + // setPrevStates(prev => [...prev, {...units}]); + // } + + map.events.add('drawingmodechanged', drawingManager, (e) => { + let dmLayers = drawingManager.getLayers(); + layersAdded = [unitLayer, unitLines, polygonHoverLayer, polygonClickLayer, unitSymbols]; + + if (e === 'idle') { + setDrawNotif(false); + let updatedFeatures = drawingManager.getSource().shapes; + + deleteUnitPrevEdits(units, selectedLevel); + + const newFeatures = updatedFeatures.reduce((acc, feature) => { + if (isNaN(feature.data.properties.ordinal)) { + setFields(feature, selectedLevel); + acc.push(feature.data); + } + return acc; + }, []); + + setUnits(prevUnits => ({ + features: [...prevUnits.features, ...newFeatures], + })); + } + else if (e === 'edit-geometry' || e === 'erase-geometry' || e === 'draw-polygon') { + drawingModeChanged(layersAdded); + dmLayers.polygonLayer.setOptions({ visible: true }); + dmLayers.polygonOutlineLayer.setOptions({ visible: true }); + + if(e === 'draw-polygon') { + setDrawNotif(true); + map.events.add('mousedown', function (e) { + map.getCanvas().style.cursor = 'crosshair'; + }); + + map.events.add('mouseup', function (e) { + map.getCanvas().style.cursor = 'crosshair'; + }); + } + if(e === 'edit-geometry') { + currentEditData(map, drawingManager, setJsonData); + } + } + else { + // This will eventually be a visible pop-up + console.log('Not a valid drawing toolbar option.'); + } + }); + }); + } + // Entry point when "level.geojson" is pressed; the following code should be refactored due to redundancy function levelInteractions(levels, drawingManager, map) { // Retrieve information about the level currently chosen by user + levelsChanged(levels); const selectedLevelDetails = levels.features.filter(item => item.id === selectedLevel.id); - var lineLayer, lineHoverLayer; - var layersAdded = [lineLayer, lineHoverLayer]; + var lineLayer, lineHoverLayer, lineClickLayer, linePolygonLayer; + var layersAdded = [lineLayer, lineHoverLayer, lineClickLayer, linePolygonLayer]; const dataSource = new source.DataSource(); map.sources.add(dataSource); @@ -236,15 +311,20 @@ const PlacesPreviewMap = ({ style }) => { // Displays outline of level + change in color when cursor is hovering lineLayer = new layer.LineLayer(dataSource, 'levelClick', getLineStyles('level', 'walkway')); - lineHoverLayer = new layer.LineLayer(dataSource, null, { - fillColor: 'rgba(150, 50, 255, 0.2)', + linePolygonLayer = new layer.PolygonLayer(dataSource, 'levelFill', getFillStyles('level', 'unspecified')); + lineHoverLayer = new layer.PolygonLayer(dataSource, null, { + fillColor: 'rgba(135, 206, 250, 0.8)', filter: ['==', ['get', '_azureMapsShapeId'], ''] }); - map.layers.add([lineLayer, lineHoverLayer], 'walkwayPolygons'); - grabToPointer([lineLayer, lineHoverLayer], map); - layersAdded = [lineLayer, lineHoverLayer]; - featureHover(lineLayer, lineHoverLayer); + lineClickLayer = new layer.PolygonLayer(dataSource, 'lineClickLayer', { + fillColor: 'rgba(75, 146, 210, 0.8)', + filter: ['==', ['get', 'id'], ''] + }); + + map.layers.add([lineLayer, linePolygonLayer, lineHoverLayer, lineClickLayer], 'walkwayPolygons'); + layersAdded = [lineLayer, lineHoverLayer, lineClickLayer, linePolygonLayer]; + featureHoverClick(linePolygonLayer, lineHoverLayer, lineClickLayer); var drawingSource = drawingManager.getSource(); drawingSource.add(selectedLevelDetails); @@ -260,32 +340,53 @@ const PlacesPreviewMap = ({ style }) => { dmLayers.polygonOutlineLayer.setOptions({ visible: false }); var lineLayer = new layer.LineLayer(drawingManager.getSource(), 'levelClick', getLineStyles('level', 'walkway')); - lineHoverLayer = new layer.LineLayer(drawingManager.getSource(), null, { - fillColor: 'rgba(150, 50, 255, 0.2)', + linePolygonLayer = new layer.PolygonLayer(drawingManager.getSource(), 'levelFill', getFillStyles('level', 'unspecified')); + lineHoverLayer = new layer.PolygonLayer(drawingManager.getSource(), null, { + fillColor: 'rgba(135, 206, 250, 0.8)', filter: ['==', ['get', '_azureMapsShapeId'], ''] }); + + lineClickLayer = new layer.PolygonLayer(drawingManager.getSource(), 'lineClickLayer', { + fillColor: 'rgba(75, 146, 210, 0.8)', + filter: ['==', ['get', 'id'], ''] + }); + + map.layers.add([lineLayer, lineHoverLayer, lineClickLayer, linePolygonLayer], 'walkwayPolygons'); + layersAdded = [lineLayer, lineHoverLayer, lineClickLayer, linePolygonLayer]; + featureHoverClick(linePolygonLayer, lineHoverLayer, lineClickLayer); + + let updatedFeatures = drawingManager.getSource().shapes; + setLevels(prevLevels => updateLevels(prevLevels, selectedLevel, (updatedFeatures[0]).data)); - map.layers.add([lineLayer, lineHoverLayer], 'walkwayPolygons'); - grabToPointer([lineLayer, lineHoverLayer], map); - layersAdded = [lineLayer, lineHoverLayer]; - featureHover(lineLayer, lineHoverLayer); + // Update the levels state with the edited features (for updating zip) + levelsChanged(levels); } - else if (e === 'edit-geometry' || e === 'erase-geometry' || e === 'draw-polygon') { + else if (e === 'edit-geometry') { drawingModeChanged(layersAdded); dmLayers.polygonOutlineLayer.setOptions({ visible: true }); - dmLayers.polygonLayer.setOptions({ visible: false }); + dmLayers.polygonLayer.setOptions({ visible: true }); + + currentEditData(map, drawingManager, setJsonData); + + document.addEventListener('keydown', function (e) { + // Check if the delete or backspace key is pressed + if (e.key === 'Delete' || e.key === 'Backspace') { + drawingManager.setOptions({ mode: 'idle' }); + } + }); } else { // This will eventually be a visible pop-up console.log('Not a valid drawing toolbar option.'); } - }); + }); } // Entry point when "footprint.geojson" is pressed; the following code should be refactored due to redundancy function footprintInteractions(footprint, drawingManager, map) { - var footprintLayer, footprintLines, footprintHoverLayer; - var layersAdded = [footprintLayer, footprintLines, footprintHoverLayer]; + footprintChanged(footprint); + var footprintLayer, footprintLines, footprintHoverLayer, footprintClickLayer; + var layersAdded = [footprintLayer, footprintLines, footprintHoverLayer, footprintClickLayer]; const groupedFeatures = {}; const keys = Object.keys(groupedFeatures); @@ -298,14 +399,18 @@ const PlacesPreviewMap = ({ style }) => { footprintLines = new layer.LineLayer(dataSource, null, getLineStyles('footprint', 'walkway')); footprintLayer = new layer.PolygonLayer(dataSource, 'footprintClick', getFillStyles('footprint', keys.category)); footprintHoverLayer = new layer.PolygonLayer(dataSource, null, { - fillColor: 'rgba(150, 50, 255, 0.2)', + fillColor: 'rgba(135, 206, 250, 0.8)', filter: ['==', ['get', '_azureMapsShapeId'], ''] }); - map.layers.add([footprintLayer, footprintHoverLayer, footprintLines], 'roomPolygons'); - grabToPointer([footprintLayer, footprintHoverLayer], map); - featureHover(footprintLayer, footprintHoverLayer); - layersAdded = [footprintLayer, footprintLines, footprintHoverLayer]; + footprintClickLayer = new layer.PolygonLayer(dataSource, null, { + fillColor: 'rgba(75, 146, 210, 0.8)', + filter: ['==', ['get', 'id'], ''] + }); + + map.layers.add([footprintLayer, footprintHoverLayer, footprintLines, footprintClickLayer], 'roomPolygons'); + featureHoverClick(footprintLayer, footprintHoverLayer, footprintClickLayer); + layersAdded = [footprintLayer, footprintLines, footprintHoverLayer, footprintClickLayer]; var drawingSource = drawingManager.getSource(); @@ -324,39 +429,113 @@ const PlacesPreviewMap = ({ style }) => { footprintLines = new layer.LineLayer(drawingManager.getSource(), null, getLineStyles('footprint', 'walkway')); footprintLayer = new layer.PolygonLayer(drawingManager.getSource(), 'footprintClick', getFillStyles('footprint', keys.category)); footprintHoverLayer = new layer.PolygonLayer(drawingManager.getSource(), null, { - fillColor: 'rgba(150, 50, 255, 0.2)', + fillColor: 'rgba(135, 206, 250, 0.8)', filter: ['==', ['get', '_azureMapsShapeId'], ''] }); + footprintClickLayer = new layer.PolygonLayer(drawingManager.getSource(), null, { + fillColor: 'rgba(75, 146, 210, 0.8)', + filter: ['==', ['get', 'id'], ''] + }); + + map.layers.add([footprintLayer, footprintHoverLayer, footprintLines, footprintClickLayer], 'roomPolygons'); + featureHoverClick(footprintLayer, footprintHoverLayer, footprintClickLayer); + layersAdded = [footprintLayer, footprintLines, footprintHoverLayer, footprintClickLayer]; - map.layers.add([footprintLayer, footprintHoverLayer, footprintLines], 'roomPolygons'); - grabToPointer([footprintLayer, footprintHoverLayer], map); - featureHover(footprintLayer, footprintHoverLayer); - layersAdded = [footprintLayer, footprintLines, footprintHoverLayer]; + let updatedFeatures = drawingManager.getSource().shapes; + footprint.features[0] = updatedFeatures[0].data; + + // Update the footprint state with the edited features (for updating zip) + footprintChanged(footprint); } - else if (e === 'edit-geometry' || e === 'erase-geometry' || e === 'draw-polygon') { + else if (e === 'edit-geometry') { drawingModeChanged(layersAdded); dmLayers.polygonLayer.setOptions({ visible: true }); dmLayers.polygonOutlineLayer.setOptions({ visible: true }); + + currentEditData(map, drawingManager, setJsonData); } else { // This will eventually be a visible pop-up console.log('Not a valid drawing toolbar option.'); } - }); + }); + } + + function buildingInteractions(building, map) { + var buildingLayer = new HtmlMarker({ + position: building.features[0].properties.display_point.coordinates, + }); + map.events.add('click', buildingLayer, highlight); + map.markers.add(buildingLayer); + + function highlight(e) { + setJsonData(building.features[0]); + } + + map.setCamera({ + zoom: 12 + }); } // Entry point when "full view" is pressed; the following code may need to be changed to allow fill color of units while editing - // Needs to be given edit properties - function fullViewInteractions(units, levels, drawingManager, map) { - unitInteractions(units, drawingManager, map); - levelInteractions(levels, drawingManager, map); + function fullViewInteractions(units, levels, map) { + unitsChanged(units); + levelsChanged(levels); + + var unitLayer, unitLines, polygonHoverLayer, polygonClickLayer, unitSymbols, lineLayer, lineHoverLayer, lineClickLayer; + const groupedFeatures = groupAndSort(units, language, selectedLevel); + const keys = Object.keys(groupedFeatures); + + keys.forEach(category => { + var features = groupedFeatures[category].features; + const dataSource = new source.DataSource(); + map.sources.add(dataSource); + dataSource.add(features); + + unitLayer = new layer.PolygonLayer(dataSource, 'unitClick', getFillStyles('unit', category)); + unitLines = new layer.LineLayer(dataSource, null, getLineStyles('unit', category)); + polygonHoverLayer = new layer.PolygonLayer(dataSource, null, { + fillColor: 'rgba(135, 206, 250, 0.8)', + filter: ['==', ['get', 'id'], ''], + cursor: 'pointer !important', + }); + + polygonClickLayer = new layer.PolygonLayer(dataSource, 'unitClickChange', { + fillColor: 'rgba(75, 146, 210, 0.8)', + filter: ['==', ['get', 'id'], ''] , + cursor: 'pointer !important', + }); + + unitSymbols = new layer.SymbolLayer(dataSource, null, getTextStyle(category)); + map.layers.add([unitLayer, polygonHoverLayer, unitLines, polygonClickLayer, unitSymbols], 'roomPolygons'); + featureHoverClick(unitLayer, polygonHoverLayer, polygonClickLayer, true); + }); + + const selectedLevelDetails = levels.features.filter(item => item.id === selectedLevel.id); + const dataSource = new source.DataSource(); + map.sources.add(dataSource); + dataSource.add(selectedLevelDetails); + + // Displays outline of level + change in color when cursor is hovering + lineLayer = new layer.LineLayer(dataSource, 'levelClick', getLineStyles('level', 'walkway')); + lineHoverLayer = new layer.LineLayer(dataSource, null, { + strokeColor: 'rgba(135, 206, 250, 0.8)', + filter: ['==', ['get', '_azureMapsShapeId'], ''] + }); + lineClickLayer = new layer.LineLayer(dataSource, 'lineClickLayer', { + strokeColor: 'rgba(75, 146, 210, 0.8)', + filter: ['==', ['get', 'id'], ''] + }); + + map.layers.add([lineLayer, lineHoverLayer, lineClickLayer], 'walkwayPolygons'); + featureHoverClick(lineLayer, lineHoverLayer, lineClickLayer, false); } // Cleanup function to remove the map instance when component unmounts or reinitializes return () => { map.dispose(); }; - }, [units, levels, footprint, selectedLevel, selectedLayerId, subscriptionKey, geography, language]); + }, [ units, levels, footprint, building, selectedLevel, selectedLayerId, subscriptionKey, geography, language, imdfPackageLocation, unitsChanged, levelsChanged, footprintChanged ]); const handleLevelChange = levelId => { setSelectedLevelId(levelId); @@ -366,6 +545,56 @@ const PlacesPreviewMap = ({ style }) => { setSelectedLayerId(layerId); }; + const updateJsonData = (newData) => { + setJsonData(newData); + }; + + // Handles updates when a property is changed in the JSON editor + const handleUpdate = ({ newData, currentData, newValue, currentValue, name, path }) => { + if(newData.feature_type === 'building') { + // Building + building.features = [newData]; + buildingChanged(building); + } + else if (newData.properties.name && isNaN(newData.properties.ordinal)) { + // Unit + let editedIndex = units.features.findIndex(unit => unit.id === currentData.id); + if (editedIndex !== -1) { + // Replace the old data with the new data for specific feature, then save to zip + const updatedFeatures = [...units.features]; + updatedFeatures[editedIndex] = { + ...newData, + properties: { + ...newData.properties, + label: newData.properties.name.en, + }, + }; + + setUnits({ features: updatedFeatures }); // To update state to trigger map refresh + unitsChanged(units); // To update zip + newDataRef.current = true; + } + else { + console.log('Invalid property change.'); + } + } + else if (newData.properties.name) { + // Level + let editedIndex = levels.features.findIndex(level => level.id === currentData.id); + if (editedIndex !== -1) { + // Replace the old data with the new data for specific feature, then save to zip + levels.features[editedIndex] = newData; + levelsChanged(levels); + } + else { + console.log('Invalid property change.'); + } + } + + return true; + }; + + return (
@@ -376,7 +605,6 @@ const PlacesPreviewMap = ({ style }) => { options={levels.features.map(level => ({ key: level.id, text: getFeatureLabel(level, language) }))} /> -
{
Zoom in to see labels and icons. + {drawNotif && Click to draw a point. To connect the final lines of current drawing, press 'c'.}
-
- +
+ { + const allowedFields = [ + 'properties.name.en', + 'properties.category' + ]; + return !allowedFields.includes(path.join('.')); + }} + restrictTypeSelection={ ({ path, value }) => { + if (typeof value === 'string') + return ['string']; + else + return ['string', 'number', 'boolean', 'array', 'object']; + }} + rootFontSize={12} + indent={2} + theme="githubLight" + onUpdate={handleUpdate} + />
-
- -
- Save Changes -
- -
+
); }; diff --git a/src/pages/conversion/places-preview-map/layer-selector/index.js b/src/pages/conversion/places-preview-map/layer-selector/index.js index 62b1592..821da13 100644 --- a/src/pages/conversion/places-preview-map/layer-selector/index.js +++ b/src/pages/conversion/places-preview-map/layer-selector/index.js @@ -4,10 +4,11 @@ import { buttonStyle, layerSelectorWrapper, selectedButtonStyle } from './index. const layerButtons = [ // will need to add a "buildingButton" once basemap is loaded - {id: 'fullViewButton', text: 'full view'}, - {id: 'footprintButton', text: 'footprint.geojson'}, - {id: 'levelButton', text: 'level.geojson'}, {id: 'unitButton', text: 'unit.geojson'}, + {id: 'levelButton', text: 'level.geojson'}, + {id: 'footprintButton', text: 'footprint.geojson'}, + {id: 'buildingButton', text: 'building.geojson'}, + {id: 'fullViewButton', text: 'full view'}, ]; const LayerSelector = props => { diff --git a/src/pages/conversion/places-preview-map/layer-selector/index.style.js b/src/pages/conversion/places-preview-map/layer-selector/index.style.js index c66977a..29a46a3 100644 --- a/src/pages/conversion/places-preview-map/layer-selector/index.style.js +++ b/src/pages/conversion/places-preview-map/layer-selector/index.style.js @@ -32,6 +32,7 @@ export const buttonStyle = css` background-repeat: no-repeat; overflow: hidden; + &:hover { color: #31acce; } diff --git a/src/pages/conversion/places-preview-map/utils.js b/src/pages/conversion/places-preview-map/utils.js index 037fa07..fb06013 100644 --- a/src/pages/conversion/places-preview-map/utils.js +++ b/src/pages/conversion/places-preview-map/utils.js @@ -7,13 +7,10 @@ import stairs from './assets/stairs.svg'; import { categoryToFillColor, defaultFillColor, levelStyles, levelOnlyStyles, textStyles, footprintStyles, unitStyles } from './theme/floorPlanStyles'; export function getFillStyles(featureType, category) { - if (featureType !== 'unit') { - return {}; - } - var fillStyle = { fillColor: categoryToFillColor.has(category) ? categoryToFillColor.get(category) : defaultFillColor, fillOpacity: 1, + cursor: 'pointer !important', }; return fillStyle; @@ -41,6 +38,7 @@ export function getLineStyles(featureType, category) { strokeWidth: levelStyles.lineWidth, lineJoin: 'round', lineCap: 'round', + fillColor: 'hsla(0, 0%, 0%, 0)', }; }