diff --git a/.env b/.env new file mode 100644 index 00000000..87c01f4e --- /dev/null +++ b/.env @@ -0,0 +1,5 @@ +REACT_APP_SECURED="false" +REACT_APP_TENANT="TENANT_ID" +REACT_APP_DEFAULT_SCOPE="DEFAULT_SCOPE" +REACT_APP_CLIENTID="CLIENT_ID" +REACT_APP_AUTHORITY="AUTHORITY" \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 674242c7..ccb3f74c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ #Stage 1 -FROM node:18-alpine -# Create app directory -WORKDIR /app +# FROM node:18-alpine +# # Create app directory +# WORKDIR /app FROM nginx:1.25.0-alpine WORKDIR /usr/share/nginx/html RUN rm -rf ./* diff --git a/package-lock.json b/package-lock.json index 1ef0c98f..d8d8b9b6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,13 +25,13 @@ "rc-menu": "^9.11.1", "rc-slider": "^9.7.5", "re-resizable": "^6.9.1", - "react": "^17.0.2", + "react": "^18.2.0", "react-collapsible": "^2.10.0", "react-color": "^2.19.3", "react-copy-to-clipboard": "^5.0.4", "react-datepicker": "^4.16.0", "react-device-detect": "^2.2.3", - "react-dom": "^17.0.2", + "react-dom": "^18.2.0", "react-ga4": "^2.0.0", "react-highlight-words": "^0.20.0", "react-icons": "^4.8.0", @@ -3903,9 +3903,9 @@ } }, "node_modules/@rc-component/trigger": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@rc-component/trigger/-/trigger-2.0.0.tgz", - "integrity": "sha512-niwKADPdY5dhdIblV6uwSayVivwo2uUISfJqri+/ovYQcH/omxDYBJKo755QKeoIIsWptxnRpgr7reEnNEZGFg==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@rc-component/trigger/-/trigger-2.1.1.tgz", + "integrity": "sha512-UjHkedkgtEcgQu87w1VuWug1idoDJV7VUt0swxHXRcmei2uu1AuUzGBPEUlmOmXGJ+YtTgZfVLi7kuAUKoZTMA==", "dependencies": { "@babel/runtime": "^7.23.2", "@rc-component/portal": "^1.1.0", @@ -15372,12 +15372,11 @@ } }, "node_modules/react": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz", - "integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==", + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", + "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", "dependencies": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1" + "loose-envify": "^1.1.0" }, "engines": { "node": ">=0.10.0" @@ -15562,16 +15561,15 @@ } }, "node_modules/react-dom": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz", - "integrity": "sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==", + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", + "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", "dependencies": { "loose-envify": "^1.1.0", - "object-assign": "^4.1.1", - "scheduler": "^0.20.2" + "scheduler": "^0.23.0" }, "peerDependencies": { - "react": "17.0.2" + "react": "^18.2.0" } }, "node_modules/react-error-overlay": { @@ -16534,12 +16532,11 @@ } }, "node_modules/scheduler": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz", - "integrity": "sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==", + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", + "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", "dependencies": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1" + "loose-envify": "^1.1.0" } }, "node_modules/schema-utils": { diff --git a/package.json b/package.json index 3b204cfd..eceeef0c 100644 --- a/package.json +++ b/package.json @@ -21,13 +21,13 @@ "rc-menu": "^9.11.1", "rc-slider": "^9.7.5", "re-resizable": "^6.9.1", - "react": "^17.0.2", + "react": "^18.2.0", "react-collapsible": "^2.10.0", "react-color": "^2.19.3", "react-copy-to-clipboard": "^5.0.4", "react-datepicker": "^4.16.0", "react-device-detect": "^2.2.3", - "react-dom": "^17.0.2", + "react-dom": "^18.2.0", "react-ga4": "^2.0.0", "react-highlight-words": "^0.20.0", "react-icons": "^4.8.0", diff --git a/src/App.test.js b/src/App.test.js index 635a333f..0d5b554a 100644 --- a/src/App.test.js +++ b/src/App.test.js @@ -1,9 +1,10 @@ import React from "react"; -import ReactDOM from "react-dom"; +import { createRoot } from "react-dom/client"; import App from "./App"; it("renders without crashing", () => { - const div = document.createElement("div"); - ReactDOM.render(, div); - ReactDOM.unmountComponentAtNode(div); + const div = document.createElement("div"); + const root = createRoot(div); // createRoot(container!) if you use TypeScript + root.render(); + root.unmount(); }); diff --git a/src/header/Header.jsx b/src/header/Header.jsx index 9875d130..375c3a6d 100644 --- a/src/header/Header.jsx +++ b/src/header/Header.jsx @@ -1,5 +1,4 @@ import React, { useState, useEffect } from "react"; -import ReactDOM from "react-dom"; import "./Header.css"; import Search from "./Search.jsx"; import * as helpers from "../helpers/helpers"; @@ -50,7 +49,7 @@ const Header = (props) => { ); - ReactDOM.render(menu, document.getElementById("portal-root")); + window.portalRoot.render(menu); helpers.addAppStat("Header Dot Menu", "Click"); }; diff --git a/src/helpers/AttributeTable.jsx b/src/helpers/AttributeTable.jsx index 10723d42..e07dbdfb 100644 --- a/src/helpers/AttributeTable.jsx +++ b/src/helpers/AttributeTable.jsx @@ -1,4 +1,4 @@ -import React, { Component } from "react"; +import React, { Component, useEffect } from "react"; import * as helpers from "./helpers"; import "./AttributeTable.css"; import { Resizable } from "re-resizable"; @@ -6,12 +6,12 @@ import AttributeTableTabs from "./AttributeTableTabs.jsx"; import FloatingMenu, { FloatingMenuItem } from "../helpers/FloatingMenu.jsx"; import { Item as MenuItem } from "rc-menu"; import Portal from "../helpers/Portal.jsx"; -import ReactDOM from "react-dom"; +import { createRoot } from "react-dom/client"; class AttrbuteTable extends Component { constructor(props) { super(props); - + this.resizable = {}; this.numRecordsToPull = 200; this.state = { visible: false, @@ -44,6 +44,7 @@ class AttrbuteTable extends Component { resizeFromMap = () => { const mapWidth = document.getElementById("map").offsetWidth; + if (!this.resizable) return; this.resizable.updateSize({ width: mapWidth, height: this.resizable.resizable.offsetHeight, @@ -195,7 +196,21 @@ class AttrbuteTable extends Component { ); - ReactDOM.render(menu, document.getElementById("portal-root")); + const uniqueId = `portal-root-${"attribute-table-header"}}`; + const element = document.createElement("div"); + element.setAttribute("id", uniqueId); + document.getElementById("portal-root").appendChild(element); + const root = createRoot(document.getElementById(uniqueId)); + const MenuWithCallback = () => { + useEffect(() => { + return () => { + root.unmount(); + document.getElementById(uniqueId).remove(); + }; + }, []); + return menu; + }; + root.render(); }; onRowClick = (evt, item, rowIndex) => { @@ -217,7 +232,22 @@ class AttrbuteTable extends Component { ); - ReactDOM.render(menu, document.getElementById("portal-root")); + const uniqueId = `portal-root-${"attribute-table-row"}`; + const element = document.createElement("div"); + element.setAttribute("id", uniqueId); + document.getElementById("portal-root").appendChild(element); + const root = createRoot(document.getElementById(uniqueId)); + const MenuWithCallback = () => { + useEffect(() => { + return () => { + root.unmount(); + document.getElementById(uniqueId).remove(); + }; + }, []); + return menu; + }; + + root.render(); }; onRowMenuItemClick = (key, item, rowIndex) => { diff --git a/src/helpers/FloatingMenu.jsx b/src/helpers/FloatingMenu.jsx index 752ea046..ae4c123d 100644 --- a/src/helpers/FloatingMenu.jsx +++ b/src/helpers/FloatingMenu.jsx @@ -7,13 +7,16 @@ import { waitForLoad } from "./helpers"; // PROPER USE OF THIS COMPONENT IS TO USE A PORTAL. HAVE A LOOK AT MyMapsItem FOR AN EXAMPLE. const FloatingMenu = (props) => { + let { positionX, positionY } = props; + if (positionX === undefined) positionX = props.buttonEvent.pageX; + if (positionY === undefined) positionY = props.buttonEvent.pageY; const isMounted = useRef(false); const [isVisible, setIsVisible] = useState(true); const [style, setStyle] = useState({ position: "absolute", zIndex: 10000, - top: props.buttonEvent.pageY, - left: props.buttonEvent.pageX, + top: positionY, + left: positionX, backgroundColor: "white", width: "180px", }); @@ -36,7 +39,7 @@ const FloatingMenu = (props) => { getStyle((newStyle) => { setStyle(newStyle); }); - }, [props.autoX, props.autoY, props.buttonEvent.pageX, props.buttonEvent.pageY, props.styleMode]); + }, [props.autoX, props.autoY, props.buttonEvent.pageX, props.buttonEvent.pageY, props.styleMode, props.positionX, props.positionY, props.yOffset, props.xOffset, props.width]); const cleanup = () => { document.body.removeEventListener("click", handleClickAway, true); container.current = null; @@ -89,17 +92,17 @@ const FloatingMenu = (props) => { } if (props.styleMode === "right") { - xOffset = props.buttonEvent.pageX; + xOffset = positionX; } else if (props.styleMode === "left") { - xOffset = props.buttonEvent.pageX - 180; + xOffset = positionX - 180; } else if (props.autoX) { - if (props.buttonEvent.pageX < 180) { - xOffset = props.buttonEvent.pageX; + if (positionX < 180) { + xOffset = positionX; } else { - xOffset = props.buttonEvent.pageX - 180; + xOffset = positionX - 180; } } else { - xOffset = props.buttonEvent.pageX; + xOffset = positionX; } if (props.yOffset !== undefined) yOffset = props.yOffset; @@ -108,7 +111,7 @@ const FloatingMenu = (props) => { style = { position: "absolute", zIndex: 1000, - top: props.buttonEvent.pageY - yOffset, + top: positionY - yOffset, //left: this.state.styleMode === "right" ? this.props.buttonEvent.pageX : this.props.buttonEvent.pageX - 180, left: xOffset, backgroundColor: "white", diff --git a/src/helpers/ModalWindowR.jsx b/src/helpers/ModalWindowR.jsx index c54dfc06..801aa436 100644 --- a/src/helpers/ModalWindowR.jsx +++ b/src/helpers/ModalWindowR.jsx @@ -1,28 +1,25 @@ import React from "react"; -import ReactDOM from "react-dom"; import Modal from "react-responsive-modal"; export default class ModalWindowR extends React.Component { - state = { - open: this.props.open ? true : false, - }; + state = { + open: this.props.open ? true : false, + }; - onOpenModal = () => { - this.setState({ open: true }); - }; + onOpenModal = () => { + this.setState({ open: true }); + }; - onCloseModal = () => { - this.setState({ open: false }); - }; + onCloseModal = () => { + this.setState({ open: false }); + }; - render() { - const { open } = this.state; - return ( - -

Simple centered modal

-
- ); - } + render() { + const { open } = this.state; + return ( + +

Simple centered modal

+
+ ); + } } - -//ReactDOM.render(, document.getElementById('root')); diff --git a/src/helpers/Popup.jsx b/src/helpers/Popup.jsx index bc8729ee..01e52b8b 100644 --- a/src/helpers/Popup.jsx +++ b/src/helpers/Popup.jsx @@ -1,6 +1,7 @@ // THIS CODE WAS PULLED FROM https://github.com/walkermatt/ol-popup import React from "react"; -import ReactDOM from "react-dom"; +import { createRoot } from "react-dom/client"; + import Overlay from "ol/Overlay"; import "./Popup.css"; import { getWidth } from "ol/extent.js"; @@ -67,7 +68,6 @@ export default class Popup extends Overlay { this.contentNextButton.href = "#"; this.contentNextButtonContainer.appendChild(this.contentNextButton); this.contentPrevButtonContainer.appendChild(this.contentPrevButton); - this.contentNextButton.addEventListener("click", () => { this.contentIndex++; if (this.contentIndex >= this.contentArray.length) { @@ -84,16 +84,14 @@ export default class Popup extends Overlay { this.contentNextButtonContainer.className = "sc-popup-content-next-button"; this.contentPrevButtonContainer.className = "sc-popup-content-prev-button"; } - this.headerTitle.innerHTML = this.contentArray[this.contentIndex].title || "Info"; - //ReactDOM.render(html, this.content); if (isDOMTypeElement(this.contentArray[this.contentIndex].html)) { // REGULAR HTML this.content.innerHTML = ""; this.content.appendChild(this.contentArray[this.contentIndex].html); } else { // REACT COMPONENT - ReactDOM.render(this.contentArray[this.contentIndex].html, this.content); + this.root.render(this.contentArray[this.contentIndex].html); } }); this.contentPrevButton.addEventListener("click", () => { @@ -113,14 +111,13 @@ export default class Popup extends Overlay { } // SET TITLE this.headerTitle.innerHTML = this.contentArray[this.contentIndex].title; - //ReactDOM.render(html, this.content); if (isDOMTypeElement(this.contentArray[this.contentIndex].html)) { // REGULAR HTML this.content.innerHTML = ""; this.content.appendChild(this.contentArray[this.contentIndex].html); } else { // REACT COMPONENT - ReactDOM.render(this.contentArray[this.contentIndex].html, this.content); + this.root.render(this.contentArray[this.contentIndex].html); } }); this.headerCloseContainer = document.createElement("div"); @@ -162,6 +159,9 @@ export default class Popup extends Overlay { this.content = document.createElement("div"); this.content.className = "ol-popup-content"; + this.uniqueId = `sc-popup-content-${helpers.getUID()}}`; + this.content.setAttribute("id", this.uniqueId); + this.root = createRoot(this.content); this.container.appendChild(this.content); // Apply workaround to enable scrolling of content div on touch devices @@ -225,14 +225,13 @@ export default class Popup extends Overlay { // SET TITLE this.headerTitle.innerHTML = title; - //ReactDOM.render(html, this.content); if (isDOMTypeElement(html)) { // REGULAR HTML this.content.innerHTML = ""; this.content.appendChild(html); } else { // REACT COMPONENT - ReactDOM.render(html, this.content); + this.root.render(html); } this.container.style.display = "block"; diff --git a/src/helpers/ShowMessage.jsx b/src/helpers/ShowMessage.jsx index 39997a21..cafb725b 100644 --- a/src/helpers/ShowMessage.jsx +++ b/src/helpers/ShowMessage.jsx @@ -27,6 +27,7 @@ class ShowMessage extends Component { this._isMounted = false; } onCloseClick = (value) => { + if (this.props.onClose) this.props.onClose(value); if (this._isMounted) this.setState({ hide: true }); }; diff --git a/src/helpers/helpers.js b/src/helpers/helpers.js index a90683c0..baa2e699 100644 --- a/src/helpers/helpers.js +++ b/src/helpers/helpers.js @@ -1,6 +1,6 @@ // REACT -import React from "react"; -import ReactDOM from "react-dom"; +import React, { useEffect } from "react"; +import { createRoot } from "react-dom/client"; // OPEN LAYERS import Feature from "ol/Feature"; @@ -123,22 +123,58 @@ export function isMobile() { // SHOW CONTENT WINDOW export function showWindow(contents, options = { title: "Information", showFooter: false, mode: "normal", hideScroll: false }) { - ReactDOM.render( - , - document.getElementById("map-modal-window") - ); + const uniqueId = `map-modal-window-${getUID()}}`; + const element = document.createElement("div"); + element.setAttribute("id", uniqueId); + document.getElementById("map-modal-window").appendChild(element); + const root = createRoot(document.getElementById(uniqueId)); + + const ShowWindowWithCallback = (props) => { + useEffect(() => { + return () => { + try { + root.unmount(); + document.getElementById(uniqueId).remove(); + } catch (err) { + console.log(err); + } + }; + }, []); + return ; + }; + root.render(); } // SHOW URL WINDOW export function showURLWindow(url, showFooter = false, mode = "normal", honorDontShow = false, hideScroll = false) { let isSameOrigin = true; + const uniqueId = `map-modal-window-${getUID()}}`; + const element = document.createElement("div"); + element.setAttribute("id", uniqueId); + document.getElementById("map-modal-window").appendChild(element); + const root = createRoot(document.getElementById(uniqueId)); + + const URLWindowWithCallback = (props) => { + useEffect(() => { + return () => { + try { + root.unmount(); + document.getElementById(uniqueId).remove(); + } catch (err) { + console.log(err); + } + }; + }, []); + return ; + }; + waitForLoad("settings", Date.now(), 30, () => { if (window.config.restrictOriginForUrlWindow) { if (url !== undefined) isSameOrigin = url.toLowerCase().indexOf(window.location.origin.toLowerCase()) !== -1; } if (isSameOrigin) { - ReactDOM.render(, document.getElementById("map-modal-window")); + root.render(); } else { window.open(url, "_blank"); } @@ -414,7 +450,7 @@ export function showTerms(title = "Terms and Condition", messageText = "Message" const domId = "portal-root"; const termsDomId = "sc-show-terms-root"; - ReactDOM.render( + window.portalRoot.render( { try { - ReactDOM.unmountComponentAtNode(ref.current.parentNode); + window.portalRoot.unmount(); } catch (err) { console.log(err); } }} - />, - document.getElementById("portal-root") + /> ); } @@ -442,24 +477,42 @@ export function showMessage(title = "Info", messageText = "Message", color = mes const domId = "sc-show-message-content"; var existingMsg = document.getElementById(domId); if (existingMsg !== undefined && existingMsg !== null) existingMsg.remove(); - try { - ReactDOM.unmountComponentAtNode(document.getElementById("sc-sidebar-message-container")); - } catch {} - const message = ReactDOM.render( - , - document.getElementById("sc-sidebar-message-container"), - () => { + const uniqueId = `sc-sidebar-message-container-${getUID()}}`; + const messageElement = document.createElement("div"); + messageElement.setAttribute("id", uniqueId); + document.getElementById("sc-sidebar-message-container").appendChild(messageElement); + + const root = createRoot(document.getElementById(uniqueId), { identifierPrefix: getUID() }); + + const ShowMessageWithCallback = (props) => { + const handleOnClose = () => { + try { + root.unmount(); + document.getElementById(uniqueId).remove(); + } catch (err) { + console.log(err); + } + }; + useEffect(() => { setTimeout(() => { try { - ReactDOM.unmountComponentAtNode(document.getElementById("sc-sidebar-message-container")); + handleOnClose(); } catch (err) { console.log(err); } - }, timeout); - } - ); + }, props.timeout); + return () => { + try { + if (props.onClose) props.onClose(); + } catch (err) { + console.log(err); + } + }; + }, [props.timeout]); + return ; + }; + root.render(); } - export function searchArrayByKey(nameKey, myArray) { for (var i = 0; i < myArray.length; i++) { if (myArray[i].name === nameKey) { @@ -1184,7 +1237,7 @@ function _escapeRegExp(str) { } export function showFeaturePopup(coord, feature) { - window.popup.show(coord, ); + window.popup.show(coord, , feature.get("layerDisplayName") || "Feature Info"); } export function removeURLParameter(url, parameter) { @@ -1211,7 +1264,22 @@ export function FilterKeys(feature) { //WILDCARD = .* //LITERAL STRING = [] EG: [_][.] //NOT STRING = (?!string) EG: geostasis[.](?!test).* - const filterKeys = ["[_].*", "id", "geometry", "geom", "extent_geom", ".*gid.*", "globalid", "objectid.*", "shape.*", "displayfieldname", "displayfieldvalue", "geostasis[.].*", ".*fid.*"]; + const filterKeys = [ + "[_].*", + "id", + "geometry", + "geom", + "extent_geom", + ".*gid.*", + "globalid", + "objectid.*", + "shape.*", + "displayfieldname", + "displayfieldvalue", + "layerdisplayname", + "geostasis[.].*", + ".*fid.*", + ]; const featureProps = feature.getProperties(); let keys = Object.keys(featureProps); @@ -1403,7 +1471,7 @@ export function getBase64FromImageUrl(url, callback) { export function waitForLoad(items, startTime = Date.now(), timeout = 30, callback) { if (startTime + timeout * 1000 <= Date.now()) { showMessage("Timeout", "Items Failed to load in a timely manner. Please reload the page", messageColors.red); - console.error("timeout loading", items); + console.error("timeout loading", items, window.loaded); } else { if (isLoaded(items)) { callback(); diff --git a/src/index.js b/src/index.js index df59ac05..76656eba 100644 --- a/src/index.js +++ b/src/index.js @@ -1,28 +1,36 @@ // import 'react-app-polyfill/ie11'; import React from "react"; -import ReactDOM from "react-dom"; +import { createRoot } from "react-dom/client"; + import "./index.css"; import * as serviceWorker from "./serviceWorker"; import { MsalProvider } from "@azure/msal-react"; import { Configuration, PublicClientApplication } from "@azure/msal-browser"; import "alertifyjs/build/css/alertify.css"; import "alertifyjs/build/css/themes/default.min.css"; + +const root = createRoot(document.getElementById("root")); +let element = document.createElement("div"); +element.setAttribute("id", "portal-root"); +document.getElementById("root").appendChild(element); +window.portalRoot = createRoot(document.getElementById("portal-root")); + if (process.env.REACT_APP_SECURED === "true") { import(`./AppSecure.jsx`) .then((module) => { const AppSecure = module.default; // MSAL configuration - const configuration: Configuration = { + const configuration = Configuration({ auth: { clientId: process.env.REACT_APP_CLIENTID, }, - }; + }); const pca = new PublicClientApplication(configuration); - ReactDOM.render( + + root.render( - , - document.getElementById("root") + ); }) .catch((error) => { @@ -32,7 +40,7 @@ if (process.env.REACT_APP_SECURED === "true") { import(`./App.js`) .then((module) => { const App = module.default; - ReactDOM.render(, document.getElementById("root")); + root.render(); }) .catch((error) => { console.log(error); diff --git a/src/layerInfo/App.test.js b/src/layerInfo/App.test.js index 635a333f..bb0dc284 100644 --- a/src/layerInfo/App.test.js +++ b/src/layerInfo/App.test.js @@ -1,9 +1,11 @@ import React from "react"; -import ReactDOM from "react-dom"; +import { createRoot } from "react-dom/client"; import App from "./App"; it("renders without crashing", () => { - const div = document.createElement("div"); - ReactDOM.render(, div); - ReactDOM.unmountComponentAtNode(div); + const div = document.createElement("div"); + + const root = createRoot(div); // createRoot(container!) if you use TypeScript + root.render(); + root.unmount(); }); diff --git a/src/layerInfo/index.js b/src/layerInfo/index.js index 781cb802..cecbde2b 100644 --- a/src/layerInfo/index.js +++ b/src/layerInfo/index.js @@ -1,10 +1,15 @@ import React from "react"; -import ReactDOM from "react-dom"; +import { createRoot } from "react-dom/client"; import "./index.css"; import App from "./App"; import * as serviceWorker from "./serviceWorker"; -ReactDOM.render(, document.getElementById("root")); +const uniqueId = `root-${"layerInfo"}}`; +const element = document.createElement("div"); +element.setAttribute("id", uniqueId); +document.getElementById("root").appendChild(element); +const root = createRoot(document.getElementById(uniqueId)); +root.render(); // If you want your app to work offline and load faster, you can change // unregister() to register() below. Note this comes with some pitfalls. diff --git a/src/legend/App.test.js b/src/legend/App.test.js index 635a333f..0d5b554a 100644 --- a/src/legend/App.test.js +++ b/src/legend/App.test.js @@ -1,9 +1,10 @@ import React from "react"; -import ReactDOM from "react-dom"; +import { createRoot } from "react-dom/client"; import App from "./App"; it("renders without crashing", () => { - const div = document.createElement("div"); - ReactDOM.render(, div); - ReactDOM.unmountComponentAtNode(div); + const div = document.createElement("div"); + const root = createRoot(div); // createRoot(container!) if you use TypeScript + root.render(); + root.unmount(); }); diff --git a/src/map/BasicBasemapSwitcher.jsx b/src/map/BasicBasemapSwitcher.jsx index b42bbc5e..48d10e59 100644 --- a/src/map/BasicBasemapSwitcher.jsx +++ b/src/map/BasicBasemapSwitcher.jsx @@ -1,5 +1,4 @@ import React, { useState, useRef, useContext, useEffect } from "react"; -import ReactDOM from "react-dom"; import Slider from "rc-slider"; import "./BasicBasemapSwitcher.css"; import * as helpers from "../helpers/helpers"; @@ -186,7 +185,7 @@ const BasicBasemapSwitcher = (props) => { ); - ReactDOM.render(menu, document.getElementById("portal-root")); + window.portalRoot.render(menu); }; const controlStateChange = (control, state) => { diff --git a/src/map/SCMap.jsx b/src/map/SCMap.jsx index f0bddbd1..550d4dfa 100644 --- a/src/map/SCMap.jsx +++ b/src/map/SCMap.jsx @@ -1,5 +1,4 @@ import React, { useState, useRef, useEffect, Fragment } from "react"; -import ReactDOM from "react-dom"; // import GitHubButton from "react-github-btn"; import GitHubButton from "../components/sc-github-btn"; //OPENLAYERS @@ -230,7 +229,8 @@ const SCMap = (props) => { ); - ReactDOM.render(menu, document.getElementById("portal-root")); + + window.portalRoot.render(menu); }); // APP STAT diff --git a/src/sidebar/Sidebar.jsx b/src/sidebar/Sidebar.jsx index 599c4488..6d61cc50 100644 --- a/src/sidebar/Sidebar.jsx +++ b/src/sidebar/Sidebar.jsx @@ -1,4 +1,5 @@ -import React, { Component } from "react"; +import React, { useState, useRef, useEffect } from "react"; +import { flushSync } from "react-dom"; import "./Sidebar.css"; import * as helpers from "../helpers/helpers"; @@ -13,179 +14,165 @@ import SidebarComponent from "../components/sc-sidebar.jsx"; import SidebarSlim from "./SidebarSlim.jsx"; import MenuButton from "./MenuButton.jsx"; -class Sidebar extends Component { - constructor(props) { - super(props); - - // BIND THIS FUNCTIONS - this.togglePanelVisibility = this.togglePanelVisibility.bind(this); - this.activateSidebarItem = this.activateSidebarItem.bind(this); - this.onPanelComponentClose = this.onPanelComponentClose.bind(this); - this.onSetSidebarOpen = this.onSetSidebarOpen.bind(this); - - this.state = { - // TOOLS AND THEMES ARE IN HERE - toolComponents: [], - - // CLASSES - tabClassName: "sidebar-advanced-tab", - sidebarOpen: false, - tocLoaded: false, - // SELECTED TAB - tabIndex: 0, - isMyMapsEditing: false, - //hide components - hideTools: false, - hideThemes: false, - hideLayers: false, - hideMyMaps: false, - hideReports: false, - //TAB CONFIG ITEMS - layers: { - title: "Layers", - icon: "legend-32x32.png", - }, - tools: { - title: "Tools", - icon: "tools-32x32.png", - }, - myMaps: { - title: "My Maps", - icon: "map-32x32.png", - }, - themes: { - title: "Themes", - icon: "theme-32x32.png", - }, - reports: { - title: "Reports", - icon: "report-32x32.png", - }, - // COMPONENTS - activeTabComponents: { - layers: , - mymaps: , - reports: { - default: , - loadedComponent: null, - }, - tools: { - default: , - loadedComponent: null, - }, - themes: { - default: , - loadedComponent: null, - }, - }, - }; - // LISTEN FOR TOC TO LOAD - window.emitter.addListener("tocLoaded", () => this.setState({ tocLoaded: true })); - } - - onMyMapsEditing = (isMyMapsEditing) => { +const Sidebar = (props) => { + const toolComponentsRef = useRef([]); + + const toolComponentsLoaded = useRef(false); + const mapLoadingRef = useRef(props.mapLoading); + const headerLoadingRef = useRef(props.headerLoading); + + const tabClassName = "sidebar-advanced-tab"; + const [sidebarOpen, setSidebarOpen] = useState(false); + const [tabIndex, setTabIndex] = useState(0); + const [isMyMapsEditing, setIsMyMapsEditing] = useState(false); + const [hideTools, setHideTools] = useState(false); + const [hideThemes, setHideThemes] = useState(false); + const [hideLayers, setHideLayers] = useState(false); + const [hideMyMaps, setHideMyMaps] = useState(false); + const [hideReports, setHideReports] = useState(false); + + const layers = { + title: "Layers", + icon: "legend-32x32.png", + }; + const [tools, setTools] = useState({ + title: "Tools", + icon: "tools-32x32.png", + }); + const myMaps = { + title: "My Maps", + icon: "map-32x32.png", + }; + const [themes, setThemes] = useState({ + title: "Themes", + icon: "theme-32x32.png", + }); + const reports = { + title: "Reports", + icon: "report-32x32.png", + }; + const onMyMapsEditing = (isMyMapsEditingLocal) => { // DISABLE PARCEL CLICK - window.disableParcelClick = isMyMapsEditing; + window.disableParcelClick = isMyMapsEditingLocal; // DISABLE POPUPS - window.isDrawingOrEditing = isMyMapsEditing; - - this.setState({ isMyMapsEditing }); + window.isDrawingOrEditing = isMyMapsEditingLocal; + setIsMyMapsEditing(isMyMapsEditingLocal); }; + // TOOL AND THEME ITEMS CLICK + const activateSidebarItem = (name, type, options = undefined) => { + // THIS HANDLES WHAT TOOL/THEME IS LOADED IN THE SIDEBAR + if (type === "tools") { + toolComponentsRef.current.map((Component) => { + if (Component.props.name.toLowerCase() === name.toLowerCase()) { + // CREATE TOOL COMPONENT + var comp = ( + togglePanelVisibility()} + config={Component.props.config} + options={options} + /> + ); + setToolTabComponent(comp); + return comp; + } else return null; + }); - onSetSidebarOpen(open) { - this.setState({ sidebarOpen: open }); - } - - addComponent = (componentConfig, typeFolder, callback = undefined) => { - // THIS IMPORTS THE COMPONENTS - //console.log(`Loading ${componentConfig.name} component...`); - const typeLowerCase = `${componentConfig.componentName}`.toLowerCase().replace(/\s/g, ""); - const path = `./components/${typeFolder}/${typeLowerCase}/${componentConfig.componentName}.jsx`; - - import(`${path}`) - .then((component) => { - // SET PROPS FROM CONFIG - let comp = component.default; - comp.props = []; - comp.props.active = false; - comp.props.id = helpers.getUID(); - comp.props.description = componentConfig.description; - comp.props.name = componentConfig.name; - comp.props.componentName = componentConfig.componentName; - comp.props.helpLink = componentConfig.helpLink; - comp.props.config = componentConfig.config; - if (componentConfig.hideHeader !== undefined) comp.props.hideHeader = componentConfig.hideHeader; - - // ADD COMPONENT TO LIST - this.setState( - { - toolComponents: this.state.toolComponents.concat(comp), - }, - callback("success") - ); - }) - .catch((error) => { - console.log(error); - console.error(`"${componentConfig.name}" not yet supported`); - callback("failure"); - }) - .finally(() => { - if (callback === undefined) return "Done"; + helpers.addAppStat("Tool", name); + } else { + toolComponentsRef.current.map((Component) => { + if (Component.props.name.toLowerCase() === name.toLowerCase()) { + // CREATE THEME COMPONENT + var comp = ( + togglePanelVisibility()} + config={Component.props.config} + options={options} + /> + ); + setThemeTabComponent(comp); + return comp; + } else return null; }); - }; - componentDidMount() { + helpers.addAppStat("Theme", name); + } + }; + const defaultLayersTabComponentGUID = helpers.getUID(); + const defaultToolTabComponentGUID = helpers.getUID(); + const defaultThemeTabComponentGUID = helpers.getUID(); + const defaultMyMapTabComponentGUID = helpers.getUID(); + const defaultReportTabComponentGUID = helpers.getUID(); + const defaultLayersTabComponent = ; + const defaultToolTabComponent = ; + const defaultThemeTabComponent = ; + const defaultMyMapTabComponent = ; + const defaultReportTabComponent = ; + + const [activeLayersTabComponent, setLayersTabComponent] = useState(defaultLayersTabComponent); + const [activeToolTabComponent, setToolTabComponent] = useState(null); + const [activeThemeTabComponent, setThemeTabComponent] = useState(null); + const [activeMyMapTabComponent, setMyMapTabComponent] = useState(defaultMyMapTabComponent); + const [activeReportTabComponent, setReportTabComponent] = useState(null); + + useEffect(() => { // LISTEN FOR ITEM ACTIVATION FROM OTHER COMPONENTS - window.emitter.addListener("activateSidebarItem", (name, type, options = undefined) => { - helpers.waitForLoad("sidebar", Date.now(), 30, () => { - this.activateItemFromEmmiter(name, type, options); - }); - }); + window.emitter.addListener("activateSidebarItem", activateItemFromEmmiter); + // LISTEN FOR OPEN OR CLOSE FROM OTHER COMPONENTS (CLOSE OR OPEN) - window.emitter.addListener("setSidebarVisiblity", (openOrClose) => { - helpers.waitForLoad("sidebar", Date.now(), 30, () => { - this.sidebarVisiblityEventHandler(openOrClose); - }); - }); + window.emitter.addListener("setSidebarVisiblity", sidebarVisiblityEventHandler); // LISTEN FOR TAB ACTIVATION FROM OTHER COMPONENTS - window.emitter.addListener("activateTab", (tabName) => { - helpers.waitForLoad("sidebar", Date.now(), 30, () => { - this.activateTab(tabName); - }); - }); + window.emitter.addListener("activateTab", activateTab); // LISTEN FOR REPORT LOADING - window.emitter.addListener("loadReport", (content) => { - helpers.waitForLoad("sidebar", Date.now(), 30, () => { - this.loadReport(content); - }); - }); + window.emitter.addListener("loadReport", loadReport); + initSidebar(); + + return () => { + window.emitter.removeListener("activateSidebarItem", activateItemFromEmmiter); + window.emitter.removeListener("setSidebarVisiblity", sidebarVisiblityEventHandler); + window.emitter.removeListener("activateTab", activateTab); + window.emitter.removeListener("loadReport", loadReport); + }; + }, []); + + const initSidebar = () => { helpers.waitForLoad("settings", Date.now(), 30, () => { // IMPORT TOOLS FROM CONFIG and CHECK VISIBILITY let tools = window.config.sidebarToolComponents; tools = tools.filter((item) => item.enabled === undefined || item.enabled); if (tools.length === 1) { - this.setState({ tools: { title: tools[0].name, icon: tools[0].imageName } }); + setTools({ title: tools[0].name, icon: tools[0].imageName }); tools[0]["hideHeader"] = true; } - if (tools.length === 0 || (window.config.mainSidebarItems !== undefined && window.config.mainSidebarItems["hideTools"])) this.setState({ hideTools: true }); - let loadedTools = []; - let toolsProcessed = 0; - tools.map((component) => - this.addComponent(component, "tools", (result) => { - if (result === "success") loadedTools.push(component); - else { - window.config.sidebarToolComponents = window.config.sidebarToolComponents.map((item) => { - if (component.name === item.name) item["enabled"] = false; - return item; + if (tools.length === 0 || (window.config.mainSidebarItems !== undefined && window.config.mainSidebarItems["hideTools"])) setHideTools(true); + let componentPromises = []; + componentPromises.push( + ...tools.map((component) => { + return new Promise((resolve, reject) => { + addComponent(component, "tools", (result) => { + if (result) { + resolve({ component: result, type: "tools", loadedComponent: component }); + } else { + window.config.sidebarToolComponents = window.config.sidebarToolComponents.map((item) => { + if (component.name === item.name) item["enabled"] = false; + return item; + }); + reject(); + } }); - } - toolsProcessed++; - if (toolsProcessed >= tools.length) { - helpers.addIsLoaded("tools"); - } + }); }) ); @@ -193,68 +180,132 @@ class Sidebar extends Component { let themes = window.config.sidebarThemeComponents; themes = themes.filter((item) => item.enabled === undefined || item.enabled); if (themes.length === 1) { - this.setState({ themes: { title: themes[0].name, icon: themes[0].imageName } }); + setThemes({ title: themes[0].name, icon: themes[0].imageName }); themes[0]["hideHeader"] = true; } - if (themes.length === 0 || (window.config.mainSidebarItems !== undefined && window.config.mainSidebarItems["hideThemes"])) this.setState({ hideThemes: true }); - let loadedThemes = []; - let themesProcessed = 0; - themes.map((component) => - this.addComponent(component, "themes", (result) => { - if (result === "success") loadedThemes.push(component); - else { - window.config.sidebarThemeComponents = window.config.sidebarThemeComponents.map((item) => { - if (component.name === item.name) item["enabled"] = false; - return item; + if (themes.length === 0 || (window.config.mainSidebarItems !== undefined && window.config.mainSidebarItems["hideThemes"])) setHideThemes(true); + componentPromises.push( + ...themes.map((component) => { + return new Promise((resolve, reject) => { + addComponent(component, "themes", (result) => { + if (result) { + resolve({ component: result, type: "themes", loadedComponent: component }); + } else { + window.config.sidebarThemeComponents = window.config.sidebarThemeComponents.map((item) => { + if (component.name === item.name) item["enabled"] = false; + return item; + }); + reject(); + } }); - } - themesProcessed++; - if (themesProcessed >= themes.length) { - helpers.addIsLoaded("themes"); - } + }); }) ); + let loadedThemes = []; + let loadedTools = []; + Promise.allSettled(componentPromises).then((results) => { + let loadedComponents = []; + results + .filter((result) => result.status === "fulfilled") + .forEach((result) => { + const { value } = result; + if (value.type === "themes") loadedThemes.push(value.loadedComponent); + if (value.type === "tools") loadedTools.push(value.loadedComponent); + loadedComponents.push(value.component); + }); + + toolComponentsLoaded.current = true; + toolComponentsRef.current = loadedComponents; + }); + // CHECK VISIBILITY OF LAYERS MENUE - if (window.config.mainSidebarItems !== undefined && window.config.mainSidebarItems["hideLayers"]) this.setState({ hideLayers: true }); + if (window.config.mainSidebarItems !== undefined && window.config.mainSidebarItems["hideLayers"]) setHideLayers(true); // CHECK VISIBILITY OF MY MAPS - if (window.config.mainSidebarItems !== undefined && window.config.mainSidebarItems["hideMyMaps"]) this.setState({ hideMyMaps: true }); + if (window.config.mainSidebarItems !== undefined && window.config.mainSidebarItems["hideMyMaps"]) setHideMyMaps(true); // CHECK VISIBILITY OF REPORTS - if (window.config.mainSidebarItems !== undefined && window.config.mainSidebarItems["hideReports"]) this.setState({ hideReports: true }); + if (window.config.mainSidebarItems !== undefined && window.config.mainSidebarItems["hideReports"]) setHideReports(true); const shortcuts = window.config.sidebarShortcutParams; // HANDLE ADVANCED MODE PARAMETER window.sidebarOpen = false; if (window.config.viewerMode !== undefined && window.config.viewerMode !== null) { - if (window.config.viewerMode.toUpperCase() === "ADVANCED") this.sidebarVisiblityEventHandler("OPEN"); + if (window.config.viewerMode.toUpperCase() === "ADVANCED") sidebarVisiblityEventHandler("OPEN"); } - helpers.waitForLoad(["tools", "themes"], Date.now(), 30, () => { - this.initToolAndThemeUrlParameter({ tools: loadedTools, themes: loadedThemes, shortcuts: shortcuts }, () => { + + helpers.waitForLoad(["tools", "themes", "header", "map"], Date.now(), 30, () => { + initToolAndThemeUrlParameter({ tools: loadedTools, themes: loadedThemes, shortcuts: shortcuts }, () => { // TAB PARAMETER const tabNameParameter = helpers.getURLParameter("TAB"); if (tabNameParameter != null) { - this.sidebarVisiblityEventHandler("OPEN", () => { - this.activateTab(tabNameParameter.toLowerCase()); + sidebarVisiblityEventHandler("OPEN", () => { + activateTab(tabNameParameter.toLowerCase()); }); } - window.emitter.emit("sidebarLoaded"); helpers.addIsLoaded("sidebar"); }); }); }); - } + }; + + useEffect(() => { + helpers.addIsLoaded("tools"); + helpers.addIsLoaded("themes"); + }, [toolComponentsRef]); - initToolAndThemeUrlParameter = (components, callback) => { - if (components.tools.length + components.themes.length === this.state.toolComponents.length && !this.props.mapLoading && !this.props.headerLoading) { + useEffect(() => { + mapLoadingRef.current = props.mapLoading; + }, [props.mapLoading]); + + useEffect(() => { + headerLoadingRef.current = props.headerLoading; + }, [props.headerLoading]); + const onSetSidebarOpen = (open) => { + setSidebarOpen(open); + }; + + const addComponent = (componentConfig, typeFolder, callback = undefined) => { + // THIS IMPORTS THE COMPONENTS + const typeLowerCase = `${componentConfig.componentName}`.toLowerCase().replace(/\s/g, ""); + const path = `./components/${typeFolder}/${typeLowerCase}/${componentConfig.componentName}.jsx`; + + import(`${path}`) + .then((component) => { + // SET PROPS FROM CONFIG + let comp = component.default; + comp.props = []; + comp.props.active = false; + comp.props.id = helpers.getUID(); + comp.props.description = componentConfig.description; + comp.props.name = componentConfig.name; + comp.props.componentName = componentConfig.componentName; + comp.props.helpLink = componentConfig.helpLink; + comp.props.config = componentConfig.config; + if (componentConfig.hideHeader !== undefined) comp.props.hideHeader = componentConfig.hideHeader; + + // ADD COMPONENT TO LIST + callback(comp); + }) + .catch((error) => { + console.log(error); + console.error(`"${componentConfig.name}" not yet supported`); + callback(); + }) + .finally(() => { + if (callback === undefined) return "Done"; + }); + }; + + const initToolAndThemeUrlParameter = (components, callback) => { + if (components.tools.length + components.themes.length === toolComponentsRef.current.length && !mapLoadingRef.current && !headerLoadingRef.current) { // HANDLE ADVANCED MODE PARAMETER callback(); const queryString = window.location.search; if (queryString !== "") { const urlParams = new URLSearchParams(queryString.toLowerCase()); - const item = undefined; let shortcuts = []; let params = []; - components.tools.map((item) => { + components.tools.forEach((item) => { shortcuts.push({ name: item.name.toLowerCase(), component: item.name, @@ -263,7 +314,7 @@ class Sidebar extends Component { }); if (!params.includes("tool")) params.push("tool"); }); - components.themes.map((item) => { + components.themes.forEach((item) => { shortcuts.push({ name: item.name.toLowerCase(), component: item.name, @@ -272,7 +323,7 @@ class Sidebar extends Component { }); if (!params.includes("theme")) params.push("theme"); }); - components.shortcuts.map((item) => { + components.shortcuts.forEach((item) => { shortcuts.push({ name: item.matchValue, component: item.component, @@ -283,7 +334,7 @@ class Sidebar extends Component { }); if (!params.includes(item.url_param.toLowerCase())) params.push(item.url_param.toLowerCase()); }); - params.map((param) => { + params.forEach((param) => { var shortcutParam = urlParams.get(param); if (shortcutParam !== null) { const shortcut = shortcuts.filter( @@ -293,8 +344,8 @@ class Sidebar extends Component { if (shortcut.type === "search") { window.emitter.emit("searchItem", shortcut.component, shortcutParam, shortcut.hidden, shortcut.timeout); } else { - this.sidebarVisiblityEventHandler("OPEN", () => { - this.activateItemFromEmmiter(shortcut.component, shortcut.type); + sidebarVisiblityEventHandler("OPEN", () => { + activateItemFromEmmiter(shortcut.component, shortcut.type); }); } } @@ -303,273 +354,206 @@ class Sidebar extends Component { } } else { setTimeout(() => { - this.initToolAndThemeUrlParameter(components, callback); - }, 50); + initToolAndThemeUrlParameter(components, callback); + }, 500); } }; - loadReport = (content) => { - let activeTabComponents = this.state.activeTabComponents; - activeTabComponents.reports.loadedComponent = {content}; - this.setState({ activeTabComponents: activeTabComponents }); - this.sidebarVisiblityEventHandler("OPEN"); - this.activateTab("reports"); + const loadReport = (content) => { + helpers.waitForLoad("sidebar", Date.now(), 30, () => { + setReportTabComponent({content}); + sidebarVisiblityEventHandler("OPEN"); + activateTab("reports"); + }); }; - activateItemFromEmmiter(name, type, options = undefined) { - const active = this.state.activeTabComponents; - if (type === "tools") { - //SAME TOOL WAS SELECTED - if (active.tools.loadedComponent != null && type === "tools" && active.tools.loadedComponent.props.name === name) { - this.activateTab("tools"); - return; - } - - this.setState({ tabIndex: 1 }, () => { - //CLEAR LOADED TOOL - let activeTabComponents = this.state.activeTabComponents; - activeTabComponents.tools.loadedComponent = null; - this.setState({ activeTabComponents: activeTabComponents }, () => { - // ASK TOOLS TO CLOSE - window.emitter.emit("closeToolsOrThemes", type); - - // ACTIVATE THE NEW ITEM - this.activateSidebarItem(name, type, options); + const activateItemFromEmmiter = (name, type, options = undefined) => { + helpers.waitForLoad("sidebar", Date.now(), 30, () => { + if (type === "tools") { + //SAME TOOL WAS SELECTED + if (activeToolTabComponent != null && type === "tools" && activeToolTabComponent.props.name === name) { + activateTab("tools"); + return; + } + flushSync(() => { + setTabIndex(1); + //CLEAR LOADED TOOL + setToolTabComponent(null); }); - }); - } else if (type === "themes") { - // SAME THEME WAS SELECTED - if (active.themes.loadedComponent != null && type === "themes" && active.themes.loadedComponent.props.name === name) { - this.activateTab("themes"); - return; - } - - this.setState({ tabIndex: 3 }, () => { - //CLEAR LOADED THEME - let activeTabComponents = this.state.activeTabComponents; - activeTabComponents.themes.loadedComponent = null; - this.setState({ activeTabComponents: activeTabComponents }); + // ASK TOOLS TO CLOSE + window.emitter.emit("closeToolsOrThemes", type); + // ACTIVATE THE NEW ITEM + activateSidebarItem(name, type, options); + } else if (type === "themes") { + // SAME THEME WAS SELECTED + if (activeThemeTabComponent != null && type === "themes" && activeThemeTabComponent.props.name === name) { + activateTab("themes"); + return; + } + flushSync(() => { + setTabIndex(3); + //CLEAR LOADED THEME + setThemeTabComponent(null); + }); // ASK THEMES TO CLOSE window.emitter.emit("closeToolsOrThemes", type); // ACTIVATE THE NEW ITEM - this.activateSidebarItem(name, type, options); - }); - } else if (type === "mymaps") { - this.activateTab("mymaps"); - } else if (type === "reports") { - this.activateTab("reports"); - } + activateSidebarItem(name, type, options); + } else if (type === "mymaps") { + activateTab("mymaps"); + } else if (type === "reports") { + activateTab("reports"); + } - // OPEN PANEL - this.sidebarVisiblityEventHandler("OPEN"); - } - - activateTab(tabName) { - // OPEN PANEL - this.sidebarVisiblityEventHandler("OPEN"); - - // SET SELECTED TAB - if (tabName === "layers") this.setState({ tabIndex: 0 }); - else if (tabName === "tools") this.setState({ tabIndex: 1 }); - else if (tabName === "mymaps") this.setState({ tabIndex: 2 }); - else if (tabName === "themes") this.setState({ tabIndex: 3 }); - else if (tabName === "reports") this.setState({ tabIndex: 4 }); - else console.log("NO VALID TAB FOUND"); - } - - sidebarVisiblityEventHandler(openOrClose, callback = undefined) { - // CHECK IF NEED TO DO ANYTHING - if ((openOrClose === "CLOSE" && window.sidebarOpen === false) || (openOrClose === "OPEN" && window.sidebarOpen === true)) { - if (callback === undefined) return; - else callback(); - } else { - // TOGGLE IT - this.togglePanelVisibility(callback); - } - } + // OPEN PANEL + sidebarVisiblityEventHandler("OPEN"); + }); + }; - togglePanelVisibility(callback = undefined) { - // PANEL IN AND OUT CLASSES - window.sidebarOpen = !window.sidebarOpen; - this.setState({ sidebarOpen: window.sidebarOpen }, () => { - // EMIT A CHANGE IN THE SIDEBAR (IN OR OUT) - window.emitter.emit("sidebarChanged", window.sidebarOpen); - if (callback !== undefined && typeof callback === "function") callback(); + const activateTab = (tabName) => { + helpers.waitForLoad("sidebar", Date.now(), 30, () => { + // OPEN PANEL + sidebarVisiblityEventHandler("OPEN"); + flushSync(() => { + // SET SELECTED TAB + if (tabName === "layers") setTabIndex(0); + else if (tabName === "tools") setTabIndex(1); + else if (tabName === "mymaps") setTabIndex(2); + else if (tabName === "themes") setTabIndex(3); + else if (tabName === "reports") setTabIndex(4); + else console.log("NO VALID TAB FOUND"); + }); }); - } + }; - // TOOL AND THEME ITEMS CLICK - activateSidebarItem(name, type, options = undefined) { - // THIS HANDLES WHAT TOOL/THEME IS LOADED IN THE SIDEBAR - if (type === "tools") { - var loadedTool = this.state.activeTabComponents.tools.loadedComponent; - if (loadedTool != null) { - this.setState({ activeComponent: loadedTool }); + const sidebarVisiblityEventHandler = (openOrClose, callback = undefined) => { + helpers.waitForLoad("sidebar", Date.now(), 30, () => { + // CHECK IF NEED TO DO ANYTHING + if ((openOrClose === "CLOSE" && window.sidebarOpen === false) || (openOrClose === "OPEN" && window.sidebarOpen === true)) { + if (callback === undefined) return; + else callback(); } else { - this.state.toolComponents.map((Component) => { - if (Component.props.name.toLowerCase() === name.toLowerCase()) { - // CREATE TOOL COMPONENT - var comp = ( - this.togglePanelVisibility()} - config={Component.props.config} - options={options} - /> - ); - let activeTabComponents = this.state.activeTabComponents; - activeTabComponents.tools.loadedComponent = comp; - this.setState({ activeTabComponents: activeTabComponents }); - return comp; - } else return null; - }); + // TOGGLE IT + togglePanelVisibility(callback); } + }); + }; - helpers.addAppStat("Tool", name); - } else { - var loadedTheme = this.state.activeTabComponents.themes.loadedComponent; - if (loadedTheme != null) this.setState({ activeComponent: loadedTheme }); - else { - this.state.toolComponents.map((Component) => { - if (Component.props.name.toLowerCase() === name.toLowerCase()) { - // CREATE THEME COMPONENT - var comp = ( - this.togglePanelVisibility()} - config={Component.props.config} - options={options} - /> - ); - let activeTabComponents = this.state.activeTabComponents; - activeTabComponents.themes.loadedComponent = comp; - this.setState({ activeTabComponents: activeTabComponents }); - //helpers.showMessage("Property Report", "Property Report Click is disabled while theme is active.", "green" , 5000); - return comp; - } else return null; - }); - } - helpers.addAppStat("Theme", name); - } - } + const togglePanelVisibility = (callback = undefined) => { + // PANEL IN AND OUT CLASSES + window.sidebarOpen = !window.sidebarOpen; + setSidebarOpen(window.sidebarOpen); + // EMIT A CHANGE IN THE SIDEBAR (IN OR OUT) + window.emitter.emit("sidebarChanged", window.sidebarOpen); + if (callback !== undefined && typeof callback === "function") callback(); + }; - onPanelComponentClose(evt) { + const onPanelComponentClose = (evt) => { // HANDLES UNLOADING OF TOOL/THEME - if (this.state.tabIndex === 1) { + if (tabIndex === 1) { // SET TOOLS - let activeTabComponents = this.state.activeTabComponents; - activeTabComponents.tools.loadedComponent = null; - this.setState({ activeTabComponents: activeTabComponents }); - } else if (this.state.tabIndex === 3) { + setToolTabComponent(null); + } else if (tabIndex === 3) { // SET THEMES - let activeTabComponents = this.state.activeTabComponents; - activeTabComponents.themes.loadedComponent = null; - this.setState({ activeTabComponents: activeTabComponents }); + setThemeTabComponent(null); } - } + }; - slimSidebarButtonClick = (name) => { - this.sidebarVisiblityEventHandler("OPEN", () => { - this.activateTab(name); + const slimSidebarButtonClick = (name) => { + sidebarVisiblityEventHandler("OPEN", () => { + activateTab(name); helpers.addAppStat("Sidebar Slim", name); }); }; - onTabSelect = (tabIndex) => { - this.setState({ tabIndex }); - if (tabIndex === 0) helpers.addAppStat("Tab", "Layers"); - else if (tabIndex === 1) helpers.addAppStat("Tab", "Tools"); - else if (tabIndex === 2) helpers.addAppStat("Tab", "MyMaps"); - else if (tabIndex === 3) helpers.addAppStat("Tab", "Themes"); - else if (tabIndex === 4) helpers.addAppStat("Tab", "Reports"); + const onTabSelect = (tabIndexLocal) => { + setTabIndex(tabIndexLocal); + if (tabIndexLocal === 0) helpers.addAppStat("Tab", "Layers"); + else if (tabIndexLocal === 1) helpers.addAppStat("Tab", "Tools"); + else if (tabIndexLocal === 2) helpers.addAppStat("Tab", "MyMaps"); + else if (tabIndexLocal === 3) helpers.addAppStat("Tab", "Themes"); + else if (tabIndexLocal === 4) helpers.addAppStat("Tab", "Reports"); }; - // this.setState({ tabIndex })} selectedIndex={this.state.tabIndex}> - render() { - return ( - - - - - - - - - - - - - - - - - - - - - {this.state.activeTabComponents.layers} - - {this.state.activeTabComponents.tools.loadedComponent ? this.state.activeTabComponents.tools.loadedComponent : this.state.activeTabComponents.tools.default} - - {this.state.activeTabComponents.mymaps} - - {this.state.activeTabComponents.themes.loadedComponent ? this.state.activeTabComponents.themes.loadedComponent : this.state.activeTabComponents.themes.default} - - - {this.state.activeTabComponents.reports.loadedComponent ? this.state.activeTabComponents.reports.loadedComponent : this.state.activeTabComponents.reports.default} - - - -
this.togglePanelVisibility()}> - Close Tab -
- -
- {this.state.sidebarOpen ? : ""} - - } - open={this.state.sidebarOpen} - onSetOpen={this.onSetSidebarOpen} - styles={{ sidebar: { background: "white" } }} - /> - ); - } -} + return ( + + + + + + + + + + + + + + + + + + + + + + {activeLayersTabComponent} + + + {activeToolTabComponent ? activeToolTabComponent : defaultToolTabComponent} + + + {activeMyMapTabComponent} + + + {activeThemeTabComponent ? activeThemeTabComponent : defaultThemeTabComponent} + + + {activeReportTabComponent ? activeReportTabComponent : defaultReportTabComponent} + + + +
togglePanelVisibility()}> + Close Tab +
+ +
+ {sidebarOpen ? : ""} + + } + open={sidebarOpen} + onSetOpen={onSetSidebarOpen} + styles={{ sidebar: { background: "white" } }} + /> + ); +}; export default Sidebar; diff --git a/src/sidebar/components/mymaps/ColorPicker.jsx b/src/sidebar/components/mymaps/ColorPicker.jsx index 1efb2e54..fbf5d294 100644 --- a/src/sidebar/components/mymaps/ColorPicker.jsx +++ b/src/sidebar/components/mymaps/ColorPicker.jsx @@ -1,50 +1,49 @@ import React from "react"; -import ReactDOM from "react-dom"; +// import * as helpers from "../../../helpers/helpers"; import Portal from "../../../helpers/Portal.jsx"; export default class ColorPicker { - constructor(evt, elements, callerId) { - this.evt = evt; - this.elements = elements; - this.callerId = callerId; - - this.documentClick = (evt) => { - const e = document.elementFromPoint(evt.pageX, evt.pageY); - if (evt.target.id !== this.callerId && e.getAttribute("spellcheck") === null) { - this.hide(); - } - }; - - this.clickHandler = this.documentClick.bind(this); - - // CLICK ANYWHERE ELSE WILL CLOSE MENU - document.body.addEventListener("click", this.clickHandler); - } - - show() { - if (document.getElementById("sc-color-picker-container") !== null) document.getElementById("sc-color-picker-container").classList.remove("sc-hidden"); - - const portalStyle = { - position: "absolute", - zIndex: 10000, - top: this.evt.pageY, - left: this.evt.pageX, - }; - - const menu = ( - -
- {this.elements} -
-
- ); - - ReactDOM.render(menu, document.getElementById("portal-root")); - } - - hide() { - if (document.getElementById("sc-color-picker-container") !== null) document.getElementById("sc-color-picker-container").classList.add("sc-hidden"); - - document.body.removeEventListener("click", this.clickHandler); - } + constructor(evt, elements, callerId) { + this.evt = evt; + this.elements = elements; + this.callerId = callerId; + + this.documentClick = (evt) => { + const e = document.elementFromPoint(evt.pageX, evt.pageY); + if (evt.target.id !== this.callerId && e.getAttribute("spellcheck") === null) { + this.hide(); + } + }; + + this.clickHandler = this.documentClick.bind(this); + + // CLICK ANYWHERE ELSE WILL CLOSE MENU + document.body.addEventListener("click", this.clickHandler); + } + + show() { + if (document.getElementById("sc-color-picker-container") !== null) document.getElementById("sc-color-picker-container").classList.remove("sc-hidden"); + + const portalStyle = { + position: "absolute", + zIndex: 10000, + top: this.evt.pageY, + left: this.evt.pageX, + }; + + const menu = ( + +
+ {this.elements} +
+
+ ); + + window.portalRoot.render(menu); + } + + hide() { + if (document.getElementById("sc-color-picker-container") !== null) document.getElementById("sc-color-picker-container").classList.add("sc-hidden"); + document.body.removeEventListener("click", this.clickHandler); + } } diff --git a/src/sidebar/components/mymaps/MyMaps.jsx b/src/sidebar/components/mymaps/MyMaps.jsx index 46b04d22..ac0aa92d 100644 --- a/src/sidebar/components/mymaps/MyMaps.jsx +++ b/src/sidebar/components/mymaps/MyMaps.jsx @@ -1,6 +1,6 @@ // REACT -import React, { Component, Fragment } from "react"; -import ReactDOM from "react-dom"; +import React, { Component, Fragment, useEffect } from "react"; +import { createRoot } from "react-dom/client"; import { CSSTransition, TransitionGroup } from "react-transition-group"; import { SubMenu, Item as MenuItem } from "rc-menu"; @@ -811,8 +811,7 @@ class MyMaps extends Component { ); - - ReactDOM.render(menu, document.getElementById("portal-root")); + window.portalRoot.render(menu); }; onMenuItemClick = (action, item) => { @@ -963,7 +962,6 @@ class MyMaps extends Component { var evtClone = Object.assign({}, evt); const feature = helpers.getFeatureFromGeoJSON(item.featureGeoJSON); const showCoordinates = feature.get("is_open_data"); - const menu = ( ); - - ReactDOM.render(menu, document.getElementById("portal-root")); + window.portalRoot.render(menu); }; deleteSelected = (selected) => { diff --git a/src/sidebar/components/mymaps/MyMapsFooter.jsx b/src/sidebar/components/mymaps/MyMapsFooter.jsx index 6d7b23a8..0c8b1ea6 100644 --- a/src/sidebar/components/mymaps/MyMapsFooter.jsx +++ b/src/sidebar/components/mymaps/MyMapsFooter.jsx @@ -1,5 +1,4 @@ import React from "react"; -import ReactDOM from "react-dom"; import "./MyMapsFooter.css"; import * as helpers from "../../../helpers/helpers"; import FloatingMenu, { FloatingMenuItem } from "../../../helpers/FloatingMenu.jsx"; @@ -13,11 +12,18 @@ const MyMapsFooter = (props) => { // PARENT CHANGES THE COLOR FROM STORAGE //useEffect(() => {}); + function getPositionXY(element) { + let rect = element.getBoundingClientRect(); + return { positionX: rect.x, positionY: rect.y }; + } function onToolsClick(evt) { var evtClone = Object.assign({}, evt); + const { positionX, positionY } = getPositionXY(document.getElementById("sc-mymaps-footer-button-tools")); const menu = ( { ); - ReactDOM.render(menu, document.getElementById("portal-root")); + window.portalRoot.render(menu); } return ( @@ -63,7 +69,7 @@ const MyMapsFooter = (props) => { -
diff --git a/src/sidebar/components/themes/localrealestate/LocalRealEstate.jsx b/src/sidebar/components/themes/localrealestate/LocalRealEstate.jsx index c18d6870..252e171f 100644 --- a/src/sidebar/components/themes/localrealestate/LocalRealEstate.jsx +++ b/src/sidebar/components/themes/localrealestate/LocalRealEstate.jsx @@ -1,10 +1,11 @@ import React, { Component } from "react"; +import { createRoot } from "react-dom/client"; + import "./LocalRealEstate.css"; import PanelComponent from "../../../PanelComponent"; import config from "./config.json"; import LocalRealEstateLayerToggler from "./LocalRealEstateLayerToggler.jsx"; import * as helpers from "../../../../helpers/helpers"; -import ReactDOM from "react-dom"; import LocalRealEstateImageSlider from "./LocalRealEstateImageSlider.jsx"; import LocalRealEstateRecents from "./LocalRealEstateRecents.jsx"; import LocalRealEstatePopupContent from "./LocalRealEstatePopupContent.jsx"; @@ -26,13 +27,14 @@ class LocalRealEstate extends Component { this.imageSlider.id = "sc-theme-real-estate-photo-slider"; this.storageKey = "theme-real-estate"; this.state = { visibleLayers: null, viewedItems: [] }; + this.root = null; } componentDidMount() { //window.disableParcelClick = true; // CREATE DIV FOR SLIDER document.body.appendChild(this.imageSlider); - + this.root = createRoot(document.getElementById(this.imageSlider.id)); let visibleLayers = []; config.layers.forEach((layer) => { if (layer.visible && layer.displayName !== "All") visibleLayers.push(layer.displayName); @@ -46,10 +48,7 @@ class LocalRealEstate extends Component { } renderImageSlider = () => { - ReactDOM.render( - , - document.getElementById(this.imageSlider.id) - ); + this.root.render(); }; componentWillUnmount() { //window.disableParcelClick = false; diff --git a/src/sidebar/components/toc/TOC.jsx b/src/sidebar/components/toc/TOC.jsx index 15a3c238..193db79d 100644 --- a/src/sidebar/components/toc/TOC.jsx +++ b/src/sidebar/components/toc/TOC.jsx @@ -1,6 +1,5 @@ // REACT import React, { Component } from "react"; -import ReactDOM from "react-dom"; import { isMobile } from "react-device-detect"; //CUSTOM @@ -98,7 +97,10 @@ class TOC extends Component { layers.forEach((layer) => { if (layer.visible && layer.liveLayer) { LayerHelpers.identifyFeatures(layer.layer, evt.coordinate, (feature) => { - if (feature !== undefined) helpers.showFeaturePopup(evt.coordinate, feature); + if (feature !== undefined) { + feature.setProperties({ layerDisplayName: layer.displayName }); + helpers.showFeaturePopup(evt.coordinate, feature); + } }); } }); @@ -494,7 +496,10 @@ class TOC extends Component { allLayers.forEach((layer) => { if (layer.visible && layer.liveLayer) { LayerHelpers.identifyFeatures(layer.layer, evt.coordinate, (feature) => { - if (feature !== undefined) helpers.showFeaturePopup(evt.coordinate, feature); + if (feature !== undefined) { + feature.setProperties({ layerDisplayName: layer.displayName }); + helpers.showFeaturePopup(evt.coordinate, feature); + } }); } }); @@ -923,7 +928,7 @@ class TOC extends Component { onLayerOptionsClick = (evt, layerInfo) => { var evtClone = Object.assign({}, evt); const menu = ; - ReactDOM.render(menu, document.getElementById("portal-root")); + window.portalRoot.render(menu); }; //#endregion //#region HANDLE HEADER CALLBACKS diff --git a/src/sidebar/components/toc/common/LayerLegend.jsx b/src/sidebar/components/toc/common/LayerLegend.jsx index 9fa93eb7..ce5302b4 100644 --- a/src/sidebar/components/toc/common/LayerLegend.jsx +++ b/src/sidebar/components/toc/common/LayerLegend.jsx @@ -1,55 +1,54 @@ // REACT import React, { Component } from "react"; -import ReactDOM from "react-dom"; import * as helpers from "../../../../helpers/helpers"; import "./LayerLegend.css"; class LayerLegend extends Component { - constructor(props) { - super(props); - this.state = {}; - } + constructor(props) { + super(props); + this.state = {}; + } - render() { - return ( -
-
- -
- ); - } + render() { + return ( +
+
+ +
+ ); + } } export default LayerLegend; const Legend = ({ legendImage, legendObj }) => { - if (legendImage !== undefined && legendImage !== null) { - return style; - } else if (legendObj !== undefined) { - if (legendObj.legend === undefined) return
; - return ( -
    - {legendObj.legend.map((item) => { - return ; - })} -
- ); - } else { - return
; - } + if (legendImage !== undefined && legendImage !== null) { + return style; + } else if (legendObj !== undefined) { + if (legendObj.legend === undefined) return
; + return ( +
    + {legendObj.legend.map((item) => { + return ; + })} +
+ ); + } else { + return
; + } }; const LegendItem = ({ legend }) => { - return ( -
  • - style -
    - {legend.label.trim()} -
    -
  • - ); + return ( +
  • + style +
    + {legend.label.trim()} +
    +
  • + ); }; diff --git a/src/sidebar/components/toc/common/TOCHeader.jsx b/src/sidebar/components/toc/common/TOCHeader.jsx index c3e435c3..2e032664 100644 --- a/src/sidebar/components/toc/common/TOCHeader.jsx +++ b/src/sidebar/components/toc/common/TOCHeader.jsx @@ -1,6 +1,5 @@ // REACT import React, { Component } from "react"; -import ReactDOM from "react-dom"; import Switch from "react-switch"; import { Item as MenuItem } from "rc-menu"; import { Tooltip as ReactTooltip } from "react-tooltip"; @@ -19,7 +18,6 @@ class TOCHeader extends Component { searchText: "", }; } - onSettingsClick = (evt) => { var evtClone = Object.assign({}, evt); var switchMenuLabel = `Switch to ${this.props.tocType === "LIST" ? "Folder" : "List"} View`; @@ -61,7 +59,7 @@ class TOCHeader extends Component { ); - ReactDOM.render(menu, document.getElementById("portal-root")); + window.portalRoot.render(menu); }; onSettingsMenuItemClick = (action) => { diff --git a/src/sidebar/components/toc/toc-folder-view/GroupItem.jsx b/src/sidebar/components/toc/toc-folder-view/GroupItem.jsx index fb5b0466..8bb79d85 100644 --- a/src/sidebar/components/toc/toc-folder-view/GroupItem.jsx +++ b/src/sidebar/components/toc/toc-folder-view/GroupItem.jsx @@ -1,5 +1,4 @@ import React, { Component } from "react"; -import ReactDOM from "react-dom"; import * as helpers from "../../../../helpers/helpers"; import FloatingMenu, { FloatingMenuItem } from "../../../../helpers/FloatingMenu.jsx"; import { Item as MenuItem } from "rc-menu"; @@ -104,7 +103,7 @@ class GroupItem extends Component { ); - ReactDOM.render(menu, document.getElementById("portal-root")); + window.portalRoot.render(menu); }; componentDidMount() { this._isMounted = true; diff --git a/src/sidebar/components/toc/toc-folder-view/Layers.jsx b/src/sidebar/components/toc/toc-folder-view/Layers.jsx index 4dcf1275..7ee4e12f 100644 --- a/src/sidebar/components/toc/toc-folder-view/Layers.jsx +++ b/src/sidebar/components/toc/toc-folder-view/Layers.jsx @@ -53,7 +53,7 @@ class Layers extends Component { }); return ( -
    +
    {layers.map((layer) => ( div > div { +.sc-toc-layer-container-folder > div > div { height: unset; } -.sc-toc-layer-container > div > div > div { +.sc-toc-layer-container-folder > div > div > div { max-height: unset; }