From 364fd164e6edd7377ea3888745b7fd9cc75ee64b Mon Sep 17 00:00:00 2001 From: linusfj Date: Fri, 24 Feb 2023 10:50:56 +0100 Subject: [PATCH 01/13] Add zoom level warning message to layer visibility If the layer's visibility range is outside the current zoom level, a warning message is displayed. --- .../components/LayerGroupItem.js | 32 ++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/new-client/src/plugins/LayerSwitcher/components/LayerGroupItem.js b/new-client/src/plugins/LayerSwitcher/components/LayerGroupItem.js index 780928f66..b0d8c360f 100644 --- a/new-client/src/plugins/LayerSwitcher/components/LayerGroupItem.js +++ b/new-client/src/plugins/LayerSwitcher/components/LayerGroupItem.js @@ -1,6 +1,7 @@ import React, { Component } from "react"; import { Button, Tooltip, Typography, Grid, Box } from "@mui/material"; import { styled } from "@mui/material/styles"; +import { withSnackbar } from "notistack"; import IconWarning from "@mui/icons-material/Warning"; import CallMadeIcon from "@mui/icons-material/CallMade"; import InfoIcon from "@mui/icons-material/Info"; @@ -338,6 +339,35 @@ class LayerGroupItem extends Component { .join(","), }); + // Check if the layer has minimum and maximum zoom levels set. + const layerProperties = this.props.layer.getProperties(); + const minZoom = layerProperties.minZoom; + const maxZoom = layerProperties.maxZoom; + const currentZoom = this.props.model.olMap.getView().getZoom(); + + // If the current zoom level is outside the range of the layer's visibility, show a warning message. + if ( + (minZoom && currentZoom < minZoom) || + (maxZoom && currentZoom > maxZoom) + ) { + this.zoomWarningSnack = this.props.enqueueSnackbar( + `Lagret "${this.caption}" visas endast vid specifika skalor.`, + { + variant: "warning", + preventDuplicate: true, + onClose: () => { + this.zoomWarningSnack = null; + }, + } + ); + } else { + // If the layer is visible at the current zoom level, close any open warning message. + if (this.zoomWarningSnack) { + this.props.closeSnackbar(this.zoomWarningSnack); + this.zoomWarningSnack = null; + } + } + this.setState({ visible: true, visibleSubLayers: subLayersToShow, @@ -662,4 +692,4 @@ class LayerGroupItem extends Component { } } -export default LayerGroupItem; +export default withSnackbar(LayerGroupItem); From f3cc6c8dd019b520a62fc0c886fc5b3fe08b0e5a Mon Sep 17 00:00:00 2001 From: linusfj Date: Fri, 24 Feb 2023 11:04:09 +0100 Subject: [PATCH 02/13] Update Snackbar message text --- .../src/plugins/LayerSwitcher/components/LayerGroupItem.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/new-client/src/plugins/LayerSwitcher/components/LayerGroupItem.js b/new-client/src/plugins/LayerSwitcher/components/LayerGroupItem.js index b0d8c360f..a95ea9069 100644 --- a/new-client/src/plugins/LayerSwitcher/components/LayerGroupItem.js +++ b/new-client/src/plugins/LayerSwitcher/components/LayerGroupItem.js @@ -351,7 +351,7 @@ class LayerGroupItem extends Component { (maxZoom && currentZoom > maxZoom) ) { this.zoomWarningSnack = this.props.enqueueSnackbar( - `Lagret "${this.caption}" visas endast vid specifika skalor.`, + `Lagret "${layerProperties.caption}" är inte synligt vid aktuell zoomnivå. Zooma in eller ut för att visa lagret.`, { variant: "warning", preventDuplicate: true, From d68f368a1882b4efe9a552e5ce4b45f2526c1efe Mon Sep 17 00:00:00 2001 From: linusfj Date: Fri, 24 Feb 2023 16:12:18 +0100 Subject: [PATCH 03/13] Add sublayer caption to warning message --- .../plugins/LayerSwitcher/components/LayerGroupItem.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/new-client/src/plugins/LayerSwitcher/components/LayerGroupItem.js b/new-client/src/plugins/LayerSwitcher/components/LayerGroupItem.js index a95ea9069..d06dbe378 100644 --- a/new-client/src/plugins/LayerSwitcher/components/LayerGroupItem.js +++ b/new-client/src/plugins/LayerSwitcher/components/LayerGroupItem.js @@ -298,7 +298,7 @@ class LayerGroupItem extends Component { } }; - setVisible = (la) => { + setVisible = (la, subLayer) => { let l, subLayersToShow = null; @@ -351,7 +351,11 @@ class LayerGroupItem extends Component { (maxZoom && currentZoom > maxZoom) ) { this.zoomWarningSnack = this.props.enqueueSnackbar( - `Lagret "${layerProperties.caption}" är inte synligt vid aktuell zoomnivå. Zooma in eller ut för att visa lagret.`, + `Lagret "${ + subLayer + ? layerProperties.layerInfo.layersInfo[subLayer].caption + : layerProperties.caption + }" är inte synligt vid aktuell zoomnivå. Zooma in eller ut för att visa lagret.`, { variant: "warning", preventDuplicate: true, @@ -404,10 +408,12 @@ class LayerGroupItem extends Component { if (!visible && visibleSubLayers.length > 0) { layerVisibility = true; + this.setVisible(this.props.layer, subLayer); } if (visibleSubLayers.length === 0) { layerVisibility = false; + this.setHidden(this.props.layer); } if (visibleSubLayers.length >= 1) { From b0abf9822d4907c5b61d163966f3c69db37a327b Mon Sep 17 00:00:00 2001 From: linusfj Date: Fri, 24 Feb 2023 16:32:02 +0100 Subject: [PATCH 04/13] Close Snackbar if layer is not visible --- .../src/plugins/LayerSwitcher/components/LayerGroupItem.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/new-client/src/plugins/LayerSwitcher/components/LayerGroupItem.js b/new-client/src/plugins/LayerSwitcher/components/LayerGroupItem.js index d06dbe378..f7e416f87 100644 --- a/new-client/src/plugins/LayerSwitcher/components/LayerGroupItem.js +++ b/new-client/src/plugins/LayerSwitcher/components/LayerGroupItem.js @@ -290,6 +290,8 @@ class LayerGroupItem extends Component { // Hide the layer in OL layer.setVisible(false); + this.props.closeSnackbar(this.zoomWarningSnack); + // Update UI state this.setState({ visible: false, From 10e637de2a0654f186a4f789b0647c923ec6f1ba Mon Sep 17 00:00:00 2001 From: linusfj Date: Tue, 28 Feb 2023 15:56:29 +0100 Subject: [PATCH 05/13] Simplify text --- .../src/plugins/LayerSwitcher/components/LayerGroupItem.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/new-client/src/plugins/LayerSwitcher/components/LayerGroupItem.js b/new-client/src/plugins/LayerSwitcher/components/LayerGroupItem.js index f7e416f87..159f5e811 100644 --- a/new-client/src/plugins/LayerSwitcher/components/LayerGroupItem.js +++ b/new-client/src/plugins/LayerSwitcher/components/LayerGroupItem.js @@ -357,7 +357,7 @@ class LayerGroupItem extends Component { subLayer ? layerProperties.layerInfo.layersInfo[subLayer].caption : layerProperties.caption - }" är inte synligt vid aktuell zoomnivå. Zooma in eller ut för att visa lagret.`, + }" är inte synligt vid aktuell zoomnivå.`, { variant: "warning", preventDuplicate: true, From 8796ec90e1814c2785bb641a13b4af2a02961344 Mon Sep 17 00:00:00 2001 From: linusfj Date: Fri, 24 Mar 2023 15:38:09 +0100 Subject: [PATCH 06/13] Add Snackbar for sublayer outside zoom range - Implement conditions to display Snackbar message when a sublayer is toggled on and the current zoom level is outside its defined visibility range. - Add `showZoomSnack` method to display Snackbar. --- .../components/LayerGroupItem.js | 52 ++++++++++++++----- 1 file changed, 38 insertions(+), 14 deletions(-) diff --git a/new-client/src/plugins/LayerSwitcher/components/LayerGroupItem.js b/new-client/src/plugins/LayerSwitcher/components/LayerGroupItem.js index 159f5e811..5ec98cee0 100644 --- a/new-client/src/plugins/LayerSwitcher/components/LayerGroupItem.js +++ b/new-client/src/plugins/LayerSwitcher/components/LayerGroupItem.js @@ -352,20 +352,7 @@ class LayerGroupItem extends Component { (minZoom && currentZoom < minZoom) || (maxZoom && currentZoom > maxZoom) ) { - this.zoomWarningSnack = this.props.enqueueSnackbar( - `Lagret "${ - subLayer - ? layerProperties.layerInfo.layersInfo[subLayer].caption - : layerProperties.caption - }" är inte synligt vid aktuell zoomnivå.`, - { - variant: "warning", - preventDuplicate: true, - onClose: () => { - this.zoomWarningSnack = null; - }, - } - ); + this.showZoomSnack(subLayer); } else { // If the layer is visible at the current zoom level, close any open warning message. if (this.zoomWarningSnack) { @@ -444,6 +431,23 @@ class LayerGroupItem extends Component { visible: layerVisibility, visibleSubLayers: visibleSubLayers, }); + + // Display the Snackbar message when the following conditions are met: + // 1. A sublayer is being toggled on (i.e., made visible) + // 2. The current zoom level of the map is outside the sublayer's defined visibility range (minZoom and maxZoom) + if (!isVisible) { + const layerProperties = this.props.layer.getProperties(); + const minZoom = layerProperties.minZoom; + const maxZoom = layerProperties.maxZoom; + const currentZoom = this.props.model.olMap.getView().getZoom(); + + if ( + (minZoom && currentZoom < minZoom) || + (maxZoom && currentZoom > maxZoom) + ) { + this.showZoomSnack(subLayer); + } + } } else { this.setHidden(this.props.layer); } @@ -620,6 +624,26 @@ class LayerGroupItem extends Component { ); }; + showZoomSnack(subLayer) { + if (this.zoomWarningSnack) return; + + const layerProperties = this.props.layer.getProperties(); + const layerCaption = subLayer + ? layerProperties.layerInfo.layersInfo[subLayer].caption + : layerProperties.caption; + + this.zoomWarningSnack = this.props.enqueueSnackbar( + `Lagret "${layerCaption}" är inte synligt vid aktuell zoomnivå.`, + { + variant: "warning", + preventDuplicate: false, + onClose: () => { + this.zoomWarningSnack = null; + }, + } + ); + } + render() { const { cqlFilterVisible, layer } = this.props; const { open, visible, visibleSubLayers, toggleSettings, infoVisible } = From 1c3865f60294fa8a63da1094397e40d24d73ec0b Mon Sep 17 00:00:00 2001 From: linusfj Date: Fri, 24 Mar 2023 15:50:10 +0100 Subject: [PATCH 07/13] Add `CheckBoxIcon` warning color Update `CheckBoxIcon` color based on layer visibility and current zoom level. --- .../components/LayerGroupItem.js | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/new-client/src/plugins/LayerSwitcher/components/LayerGroupItem.js b/new-client/src/plugins/LayerSwitcher/components/LayerGroupItem.js index 780928f66..2f40feb89 100644 --- a/new-client/src/plugins/LayerSwitcher/components/LayerGroupItem.js +++ b/new-client/src/plugins/LayerSwitcher/components/LayerGroupItem.js @@ -422,6 +422,19 @@ class LayerGroupItem extends Component { ); const toggleSettings = this.toggleSubLayerSettings.bind(this, index); const legendIcon = layer.layersInfo[subLayer].legendIcon; + const currentZoom = this.props.model.olMap.getView().getZoom(); + + const layerProperties = this.props.layer.getProperties(); + const minZoom = layerProperties.minZoom; + const maxZoom = layerProperties.maxZoom; + + const checkBoxColor = (theme) => + !visible || + (minZoom && currentZoom < minZoom) || + (maxZoom && currentZoom > maxZoom) + ? theme.palette.warning.dark + : ""; + return ( @@ -432,7 +445,11 @@ class LayerGroupItem extends Component { onClick={this.toggleLayerVisible(subLayer)} > - {visible ? : } + {!visible ? ( + + ) : ( + + )} {legendIcon && this.renderLegendIcon(legendIcon)} {layer.layersInfo[subLayer].caption} From dd6a5478daa8694dfe0a2689a510c17770a36171 Mon Sep 17 00:00:00 2001 From: linusfj Date: Thu, 6 Apr 2023 15:14:09 +0200 Subject: [PATCH 08/13] Add check and update Snackbar caption Update the `showZoomSnack` function to check if a layer is a `LayerGroupItem` and also update the Snackbar caption. --- .../src/plugins/LayerSwitcher/components/LayerItem.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/new-client/src/plugins/LayerSwitcher/components/LayerItem.js b/new-client/src/plugins/LayerSwitcher/components/LayerItem.js index 566195320..a93c8081f 100644 --- a/new-client/src/plugins/LayerSwitcher/components/LayerItem.js +++ b/new-client/src/plugins/LayerSwitcher/components/LayerItem.js @@ -215,8 +215,14 @@ class LayerItem extends React.PureComponent { showZoomSnack() { if (this.zoomWarningSnack) return; + + // If the layer is a LayerGroupItem (meaning it contains more than one object in the "layersInfo" array), + // then no message should be displayed. + if (Object.keys(this.props.layer.get("layerInfo").layersInfo).length > 1) + return; + this.zoomWarningSnack = this.props.enqueueSnackbar( - `Lagret "${this.caption}" visas endast vid specifika skalor.`, + `Lagret "${this.caption}" är inte synligt vid aktuell zoomnivå.`, { variant: "warning", preventDuplicate: true, From b20c3d5322dc96637248e562a96a1ac9a963c14c Mon Sep 17 00:00:00 2001 From: linusfj Date: Thu, 6 Apr 2023 16:18:09 +0200 Subject: [PATCH 09/13] Update layer visibility and Snackbar handling Updates in this commit include: - Add `PropTypes` validation. - Display `Snackbars` and change checkbox color to red at appropriate zoom levels, reverting to black when the layer becomes visible again. - Ensure only the layer's name, not the group's name, is displayed in the `Snackbar` message. - Fix the logic to correctly toggle groups of layers. - Show individual `Snackbars` for each toggled layer when multiple layers are toggled. --- .../components/LayerGroupItem.js | 310 +++++++++++++----- 1 file changed, 231 insertions(+), 79 deletions(-) diff --git a/new-client/src/plugins/LayerSwitcher/components/LayerGroupItem.js b/new-client/src/plugins/LayerSwitcher/components/LayerGroupItem.js index c1303c123..e9a016cf3 100644 --- a/new-client/src/plugins/LayerSwitcher/components/LayerGroupItem.js +++ b/new-client/src/plugins/LayerSwitcher/components/LayerGroupItem.js @@ -1,4 +1,5 @@ import React, { Component } from "react"; +import PropTypes from "prop-types"; import { Button, Tooltip, Typography, Grid, Box } from "@mui/material"; import { styled } from "@mui/material/styles"; import { withSnackbar } from "notistack"; @@ -86,8 +87,18 @@ const StyledList = styled("ul")(() => ({ })); class LayerGroupItem extends Component { + static propTypes = { + options: PropTypes.object, + layer: PropTypes.object.isRequired, + cqlFilterVisible: PropTypes.bool, + model: PropTypes.object.isRequired, + observer: PropTypes.object, + chapters: PropTypes.array, + }; + constructor(props) { super(props); + const { layer } = props; const layerInfo = props.layer.get("layerInfo"); this.state = { caption: layerInfo.caption, @@ -121,6 +132,8 @@ class LayerGroupItem extends Component { this.renderSubLayer = this.renderSubLayer.bind(this); this.hideExpandArrow = layerInfo?.hideExpandArrow === true ? true : false; + this.usesMinMaxZoom = this.layerUsesMinMaxZoom(); + this.minMaxZoomAlertOnToggleOnly = layer.get("minMaxZoomAlertOnToggleOnly"); } /** * Triggered when the component is successfully mounted into the DOM. @@ -134,6 +147,17 @@ class LayerGroupItem extends Component { model.observer.subscribe("showLayer", this.setVisible); model.observer.subscribe("toggleGroup", this.toggleGroupVisible); + this.props.layer.on?.("change:visible", (e) => { + const visible = !e.oldValue; + this.setState({ + visible, + }); + + this.listenToZoomChange(visible); + }); + + this.listenToZoomChange(this.state.visible); + // Set load status by subscribing to a global event. Expect ID (int) of layer // and status (string "ok"|"loaderror"). Also, once status was set to "loaderror", // don't change it back to "ok": we'll get a response for each tile, so most of @@ -148,6 +172,64 @@ class LayerGroupItem extends Component { }); } + layerUsesMinMaxZoom() { + const lprops = this.props.layer.getProperties(); + const maxZ = lprops.maxZoom ?? 0; + const minZ = lprops.minZoom ?? 0; + + return (maxZ > 0 && maxZ < Infinity) || (minZ > 0 && minZ < Infinity); + } + + zoomEndHandler = (e) => { + // Display a Snackbar message if the layer is not visible at the current zoom level. + const zoom = this.props.model.olMap.getView().getZoom(); + const lprops = this.props.layer.getProperties(); + const layerIsZoomVisible = zoom > lprops.minZoom && zoom <= lprops.maxZoom; + + let showSnack = false; + + if (this.minMaxZoomAlertOnToggleOnly === true) { + if (!this.state.visible && !layerIsZoomVisible && e?.type === "click") { + showSnack = true; + } + } else { + if ( + !layerIsZoomVisible && + (this.state.zoomVisible || !this.state.visible) + ) { + showSnack = true; + } + } + + if (showSnack === true) { + this.showZoomSnack(); + } + + this.setState({ + zoomVisible: layerIsZoomVisible, + }); + return layerIsZoomVisible; + }; + + listenToZoomChange(bListen) { + const { model } = this.props; + + if (!this.usesMinMaxZoom) return; + + const eventName = "core.zoomEnd"; + if (bListen && !this.zoomEndListener) { + this.zoomEndListener = model.globalObserver.subscribe( + eventName, + this.zoomEndHandler + ); + } else { + if (this.zoomEndListener) { + model.globalObserver.unsubscribe(eventName, this.zoomEndListener); + this.zoomEndListener = null; + } + } + } + /** * Render the load information component. * @instance @@ -239,14 +321,6 @@ class LayerGroupItem extends Component { } } - toggleVisible = (layer) => (e) => { - const visible = !this.state.visible; - this.setState({ - visible: visible, - }); - layer.setVisible(visible); - }; - toggle() { this.setState({ open: !this.state.open, @@ -341,26 +415,27 @@ class LayerGroupItem extends Component { .join(","), }); - // Check if the layer has minimum and maximum zoom levels set. - const layerProperties = this.props.layer.getProperties(); - const minZoom = layerProperties.minZoom; - const maxZoom = layerProperties.maxZoom; - const currentZoom = this.props.model.olMap.getView().getZoom(); + const { layer } = this.props; + const layerProperties = layer.getProperties(); + const layerInfo = layerProperties.layerInfo || {}; + const layersInfo = layerInfo.layersInfo || {}; - // If the current zoom level is outside the range of the layer's visibility, show a warning message. - if ( - (minZoom && currentZoom < minZoom) || - (maxZoom && currentZoom > maxZoom) - ) { - this.showZoomSnack(subLayer); - } else { - // If the layer is visible at the current zoom level, close any open warning message. - if (this.zoomWarningSnack) { - this.props.closeSnackbar(this.zoomWarningSnack); - this.zoomWarningSnack = null; - } + let visibleLayers = [...subLayersToShow]; + if (layer.get("layers") && layer.get("layers").length > 0) { + visibleLayers.push(layer.get("layers")[0]); } + const layerCaptions = visibleLayers.map((subLayer) => { + return layersInfo[subLayer]?.caption; + }); + + const layerCaption = + layerCaptions.length > 1 + ? `Lagren "${layerCaptions[0]}" och ${ + layerCaptions.length - 1 + } andra lager` + : `Lagret "${layerCaptions[0]}"`; + this.setState({ visible: true, visibleSubLayers: subLayersToShow, @@ -372,6 +447,40 @@ class LayerGroupItem extends Component { const visible = !this.state.visible; if (visible) { this.setVisible(layer); + + // Get all sublayers of the layer group. + const subLayers = Object.keys(layer.getProperties().layerInfo.layersInfo); + + // Get the current zoom level. + const zoom = this.props.model.olMap.getView().getZoom(); + const lprops = this.props.layer.getProperties(); + const layerIsZoomVisible = + zoom > lprops.minZoom && zoom <= lprops.maxZoom; + + let showSnack = false; + + // If the layer is not visible at the current zoom level, show a Snackbar. + if (this.minMaxZoomAlertOnToggleOnly === true) { + if (!this.state.visible && !layerIsZoomVisible && e?.type === "click") { + showSnack = true; + } + } else { + if ( + !layerIsZoomVisible && + (this.state.zoomVisible || !this.state.visible) + ) { + showSnack = true; + } + } + + if (showSnack === true) { + this.showZoomSnack(subLayers, true); + } + + this.setState({ + zoomVisible: layerIsZoomVisible, + visibleSubLayers: subLayers, + }); } else { this.setHidden(layer); } @@ -387,12 +496,15 @@ class LayerGroupItem extends Component { const { visible } = this.state; layerVisibility = visible; + let isNewSubLayer = false; + if (isVisible) { visibleSubLayers = visibleSubLayers.filter( (visibleSubLayer) => visibleSubLayer !== subLayer ); } else { visibleSubLayers.push(subLayer); + isNewSubLayer = true; } if (!visible && visibleSubLayers.length > 0) { @@ -432,22 +544,38 @@ class LayerGroupItem extends Component { visibleSubLayers: visibleSubLayers, }); - // Display the Snackbar message when the following conditions are met: - // 1. A sublayer is being toggled on (i.e., made visible) - // 2. The current zoom level of the map is outside the sublayer's defined visibility range (minZoom and maxZoom) - if (!isVisible) { - const layerProperties = this.props.layer.getProperties(); - const minZoom = layerProperties.minZoom; - const maxZoom = layerProperties.maxZoom; - const currentZoom = this.props.model.olMap.getView().getZoom(); + // Display a Snackbar message if the layer is not visible at the current zoom level. + const zoom = this.props.model.olMap.getView().getZoom(); + const lprops = this.props.layer.getProperties(); + const layerIsZoomVisible = + zoom > lprops.minZoom && zoom <= lprops.maxZoom; + let showSnack = false; + + if (this.minMaxZoomAlertOnToggleOnly === true) { + if (!this.state.visible && !layerIsZoomVisible && e?.type === "click") { + showSnack = true; + } + } else { if ( - (minZoom && currentZoom < minZoom) || - (maxZoom && currentZoom > maxZoom) + !layerIsZoomVisible && + (this.state.zoomVisible || !this.state.visible) ) { - this.showZoomSnack(subLayer); + showSnack = true; } } + + if (isNewSubLayer && !layerIsZoomVisible) { + showSnack = true; + } + + if (showSnack === true) { + this.showZoomSnack(subLayer, false); + } + + this.setState({ + zoomVisible: layerIsZoomVisible, + }); } else { this.setHidden(this.props.layer); } @@ -464,18 +592,6 @@ class LayerGroupItem extends Component { ); const toggleSettings = this.toggleSubLayerSettings.bind(this, index); const legendIcon = layer.layersInfo[subLayer].legendIcon; - const currentZoom = this.props.model.olMap.getView().getZoom(); - - const layerProperties = this.props.layer.getProperties(); - const minZoom = layerProperties.minZoom; - const maxZoom = layerProperties.maxZoom; - - const checkBoxColor = (theme) => - !visible || - (minZoom && currentZoom < minZoom) || - (maxZoom && currentZoom > maxZoom) - ? theme.palette.warning.dark - : ""; return ( @@ -490,7 +606,14 @@ class LayerGroupItem extends Component { {!visible ? ( ) : ( - + + !this.state.zoomVisible && this.state.visible + ? theme.palette.warning.dark + : "", + }} + /> )} {legendIcon && this.renderLegendIcon(legendIcon)} @@ -641,44 +764,73 @@ class LayerGroupItem extends Component { ); }; - showZoomSnack(subLayer) { + showZoomSnack(sublayer, isGroupLayer) { if (this.zoomWarningSnack) return; - const layerProperties = this.props.layer.getProperties(); - const layerCaption = subLayer - ? layerProperties.layerInfo.layersInfo[subLayer].caption - : layerProperties.caption; - - this.zoomWarningSnack = this.props.enqueueSnackbar( - `Lagret "${layerCaption}" är inte synligt vid aktuell zoomnivå.`, - { - variant: "warning", - preventDuplicate: false, - onClose: () => { - this.zoomWarningSnack = null; - }, + const { layer } = this.props; + const layerProperties = layer.getProperties(); + const layerInfo = layerProperties.layerInfo || {}; + const layersInfo = layerInfo.layersInfo || {}; + + let visibleLayers = [...this.state.visibleSubLayers]; + if (layer.get("layers") && layer.get("layers").length > 0) { + visibleLayers.push(layer.get("layers")[0]); + } + + const addSubLayerCaptions = (subLayer) => { + if (subLayer) { + const layerCaption = layersInfo[subLayer]?.caption; + if (layerCaption) { + const message = `Lagret "${layerCaption}" är inte synligt vid aktuell zoomnivå.`; + + this.zoomWarningSnack = this.props.enqueueSnackbar(message, { + variant: "warning", + preventDuplicate: false, + onClose: () => { + this.zoomWarningSnack = null; + }, + }); + } } + }; + + // Check if isGroupLayer is true and sublayer is an array. + if (isGroupLayer && Array.isArray(sublayer)) { + // Iterate through the sublayer array and call addSubLayerCaptions for each subLayer. + sublayer.forEach((subLayer) => { + addSubLayerCaptions(subLayer); + }); + } else if (sublayer) { + // Check if sublayer is defined (not undefined or null). + addSubLayerCaptions(sublayer); + } else { + // If sublayer is undefined, iterate through the visibleLayers array and call addSubLayerCaptions for each subLayer. + visibleLayers.forEach((subLayer) => { + addSubLayerCaptions(subLayer); + }); + } + } + + getCheckBox() { + const { layer } = this.props; + const { visible, visibleSubLayers } = this.state; + return ( + + {!visible ? ( + + ) : visibleSubLayers.length !== layer.subLayers.length ? ( + + ) : ( + + )} + ); } render() { const { cqlFilterVisible, layer } = this.props; - const { open, visible, visibleSubLayers, toggleSettings, infoVisible } = - this.state; + const { open, toggleSettings, infoVisible } = this.state; - function getCheckBox() { - return ( - - {!visible ? ( - - ) : visibleSubLayers.length !== layer.subLayers.length ? ( - - ) : ( - - )} - - ); - } const legendIcon = layer.get("layerInfo").legendIcon; return ( - {getCheckBox()} + {this.getCheckBox()} {legendIcon && this.renderLegendIcon(legendIcon)} {layer.get("caption")} From 2502f64ba835e17c3e31a0a26343f1aa92e41f53 Mon Sep 17 00:00:00 2001 From: linusfj Date: Fri, 5 May 2023 14:20:32 +0200 Subject: [PATCH 10/13] Add code comments with explanations and examples Add explanations and examples to enhance code readability and understanding. --- .../components/LayerGroupItem.js | 37 ++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/new-client/src/plugins/LayerSwitcher/components/LayerGroupItem.js b/new-client/src/plugins/LayerSwitcher/components/LayerGroupItem.js index e9a016cf3..cec726373 100644 --- a/new-client/src/plugins/LayerSwitcher/components/LayerGroupItem.js +++ b/new-client/src/plugins/LayerSwitcher/components/LayerGroupItem.js @@ -132,7 +132,9 @@ class LayerGroupItem extends Component { this.renderSubLayer = this.renderSubLayer.bind(this); this.hideExpandArrow = layerInfo?.hideExpandArrow === true ? true : false; + // Check if the layer uses min and max zoom levels. this.usesMinMaxZoom = this.layerUsesMinMaxZoom(); + // Get the minMaxZoomAlertOnToggleOnly property from the layer. this.minMaxZoomAlertOnToggleOnly = layer.get("minMaxZoomAlertOnToggleOnly"); } /** @@ -147,15 +149,19 @@ class LayerGroupItem extends Component { model.observer.subscribe("showLayer", this.setVisible); model.observer.subscribe("toggleGroup", this.toggleGroupVisible); + // Listen for changes in the layer's visibility. this.props.layer.on?.("change:visible", (e) => { + // Update the 'visible' state based on the layer's new visibility. const visible = !e.oldValue; this.setState({ visible, }); + // Listen to zoom changes if the layer is visible. this.listenToZoomChange(visible); }); + // Initially listen to zoom changes if the layer is visible. this.listenToZoomChange(this.state.visible); // Set load status by subscribing to a global event. Expect ID (int) of layer @@ -172,22 +178,33 @@ class LayerGroupItem extends Component { }); } + // Checks if the layer has minZoom and maxZoom properties defined. + // Example: If the layer has minZoom = 5 and maxZoom = 10, it will only be visible when the map's zoom level is between 5 and 10. layerUsesMinMaxZoom() { + // Retrieve the layer properties from the layer object. const lprops = this.props.layer.getProperties(); + + // Get the maxZoom and minZoom properties if they exist, otherwise set them to 0. const maxZ = lprops.maxZoom ?? 0; const minZ = lprops.minZoom ?? 0; + // Check if either minZoom or maxZoom is within a valid range (0 < value < Infinity). + // Return true if any of them are within the valid range, otherwise return false. return (maxZ > 0 && maxZ < Infinity) || (minZ > 0 && minZ < Infinity); } + // Handles the zoom end event and displays a Snackbar message if the layer is not visible at the current zoom level. zoomEndHandler = (e) => { - // Display a Snackbar message if the layer is not visible at the current zoom level. + // Get the current map zoom level. const zoom = this.props.model.olMap.getView().getZoom(); + // Retrieve the layer properties. const lprops = this.props.layer.getProperties(); + // Check if the current zoom level is within the allowed range of minZoom and maxZoom. const layerIsZoomVisible = zoom > lprops.minZoom && zoom <= lprops.maxZoom; let showSnack = false; + // Determine if the Snackbar message should be shown based on the layer visibility and zoom level conditions. if (this.minMaxZoomAlertOnToggleOnly === true) { if (!this.state.visible && !layerIsZoomVisible && e?.type === "click") { showSnack = true; @@ -201,22 +218,30 @@ class LayerGroupItem extends Component { } } + // If the Snackbar message should be shown, call the showZoomSnack function. if (showSnack === true) { this.showZoomSnack(); } + // Update the state with the new value for zoomVisible. this.setState({ zoomVisible: layerIsZoomVisible, }); return layerIsZoomVisible; }; + // Subscribes or unsubscribes to zoom change events. + // Example: If the layer is visible, subscribe to the map's "moveend" event to listen for zoom changes; if it's not visible, unsubscribe from the event. listenToZoomChange(bListen) { const { model } = this.props; + // If the layer doesn't use minZoom and maxZoom properties, return without doing anything. if (!this.usesMinMaxZoom) return; + // Define the event name for zoom change events. const eventName = "core.zoomEnd"; + + // Subscribe or unsubscribe to the zoom change event based on the 'bListen' parameter. if (bListen && !this.zoomEndListener) { this.zoomEndListener = model.globalObserver.subscribe( eventName, @@ -364,6 +389,7 @@ class LayerGroupItem extends Component { // Hide the layer in OL layer.setVisible(false); + // Close any existing zoom warning Snackbars. this.props.closeSnackbar(this.zoomWarningSnack); // Update UI state @@ -443,7 +469,11 @@ class LayerGroupItem extends Component { } }; + // Toggles the visibility of the layer group and displays a Snackbar message if any + // of the layers within the group are not visible at the current zoom level. + // Example: If a layer group has 3 sublayers, clicking the checkbox will show or hide all 3 sublayers at once. toggleGroupVisible = (layer) => (e) => { + // Toggle the visibility state of the layer. const visible = !this.state.visible; if (visible) { this.setVisible(layer); @@ -473,10 +503,12 @@ class LayerGroupItem extends Component { } } + // If a Snackbar should be shown, call the showZoomSnack function with the subLayers array and set isGroupLayer to true. if (showSnack === true) { this.showZoomSnack(subLayers, true); } + // Update the state with the new values for zoomVisible and visibleSubLayers. this.setState({ zoomVisible: layerIsZoomVisible, visibleSubLayers: subLayers, @@ -764,7 +796,10 @@ class LayerGroupItem extends Component { ); }; + // Displays a snackbar warning message if the layer is not visible at the current zoom level. showZoomSnack(sublayer, isGroupLayer) { + // If a zoom warning snackbar is already displayed, return without doing anything. + // This method ensures that only one Snackbar notification is displayed at a time, preventing multiple notifications from overlapping. if (this.zoomWarningSnack) return; const { layer } = this.props; From 3983e67cb632378f711f1efa90375d60d89dac95 Mon Sep 17 00:00:00 2001 From: linusfj Date: Fri, 5 May 2023 15:24:04 +0200 Subject: [PATCH 11/13] Update comments with more details and return types --- .../components/LayerGroupItem.js | 99 +++++++++++++++++-- 1 file changed, 89 insertions(+), 10 deletions(-) diff --git a/new-client/src/plugins/LayerSwitcher/components/LayerGroupItem.js b/new-client/src/plugins/LayerSwitcher/components/LayerGroupItem.js index cec726373..05b3f87d6 100644 --- a/new-client/src/plugins/LayerSwitcher/components/LayerGroupItem.js +++ b/new-client/src/plugins/LayerSwitcher/components/LayerGroupItem.js @@ -178,8 +178,18 @@ class LayerGroupItem extends Component { }); } - // Checks if the layer has minZoom and maxZoom properties defined. - // Example: If the layer has minZoom = 5 and maxZoom = 10, it will only be visible when the map's zoom level is between 5 and 10. + /** + * Determines if the layer has minimum and maximum zoom level restrictions. + * + * This function checks the layer properties to see if the layer has minimum and/or maximum + * zoom levels defined. If either minZoom or maxZoom is within the valid range (0 to Infinity), + * the function returns true, indicating that the layer has zoom restrictions. + * + * Example: If the layer has minZoom = 5 and maxZoom = 10, it will only be visible + * when the map's zoom level is between 5 and 10. + * + * @returns {boolean} - True if the layer has zoom restrictions, false otherwise. + */ layerUsesMinMaxZoom() { // Retrieve the layer properties from the layer object. const lprops = this.props.layer.getProperties(); @@ -193,7 +203,18 @@ class LayerGroupItem extends Component { return (maxZ > 0 && maxZ < Infinity) || (minZ > 0 && minZ < Infinity); } - // Handles the zoom end event and displays a Snackbar message if the layer is not visible at the current zoom level. + /** + * Handles the zoom end event to check if the layer should be visible at the current zoom level. + * + * This function is triggered when the map's zoom level changes. It checks if the layer's + * visibility is within the specified minZoom and maxZoom range. If the layer is not visible + * at the current zoom level and the conditions to show a Snackbar message are met, it calls + * the showZoomSnack() function to display a message. The function also updates the + * state.zoomVisible property accordingly. + * + * @param {Object} e - The event object (optional). + * @returns {boolean} - True if the layer is visible at the current zoom level, false otherwise. + */ zoomEndHandler = (e) => { // Get the current map zoom level. const zoom = this.props.model.olMap.getView().getZoom(); @@ -230,8 +251,18 @@ class LayerGroupItem extends Component { return layerIsZoomVisible; }; - // Subscribes or unsubscribes to zoom change events. - // Example: If the layer is visible, subscribe to the map's "moveend" event to listen for zoom changes; if it's not visible, unsubscribe from the event. + /** + * Subscribes or unsubscribes to the zoom end event based on the provided parameter. + * + * This function either subscribes or unsubscribes the zoomEndHandler() function to the + * 'core.zoomEnd' event, depending on the value of the bListen parameter. If the layer + * doesn't have any zoom restrictions, the function returns without doing anything. + * + * Example: If the layer is visible, subscribe to the map's "moveend" event to listen for + * zoom changes; if it's not visible, unsubscribe from the event. + * + * @param {boolean} bListen - If true, subscribes to the zoom end event; if false, unsubscribes. + */ listenToZoomChange(bListen) { const { model } = this.props; @@ -469,13 +500,28 @@ class LayerGroupItem extends Component { } }; - // Toggles the visibility of the layer group and displays a Snackbar message if any - // of the layers within the group are not visible at the current zoom level. - // Example: If a layer group has 3 sublayers, clicking the checkbox will show or hide all 3 sublayers at once. + /** + * Toggles the visibility of a group layer and handles Snackbar messages for sublayers. + * + * This function toggles the visibility of the provided group layer. If the layer becomes visible, + * it calls setVisible() and checks the current zoom level to see if the layer should be visible. + * If the layer is not visible at the current zoom level and the conditions to show a Snackbar + * message are met, it calls the showZoomSnack() function to display a message. The function + * also updates the state.zoomVisible and state.visibleSubLayers properties accordingly. + * If the layer becomes hidden, it calls setHidden(). + * + * Example: If a layer group has 3 sublayers, clicking the checkbox will show + * or hide all 3 sublayers at once. + * + * @param {Object} layer - The group layer to toggle visibility for. + * @returns {function} - A function to handle the onClick event for the group layer. + */ toggleGroupVisible = (layer) => (e) => { - // Toggle the visibility state of the layer. const visible = !this.state.visible; + + // If the layer is becoming visible. if (visible) { + // Set the layer as visible. this.setVisible(layer); // Get all sublayers of the layer group. @@ -490,11 +536,15 @@ class LayerGroupItem extends Component { let showSnack = false; // If the layer is not visible at the current zoom level, show a Snackbar. + // Example: If minMaxZoomAlertOnToggleOnly is set to true, a Snackbar will only be shown + // when the layer is toggled on and is not visible at the current zoom level. if (this.minMaxZoomAlertOnToggleOnly === true) { if (!this.state.visible && !layerIsZoomVisible && e?.type === "click") { showSnack = true; } } else { + // If the layer is not visible at the current zoom level and either + // state.zoomVisible is true or state.visible is false, a Snackbar will be shown. if ( !layerIsZoomVisible && (this.state.zoomVisible || !this.state.visible) @@ -504,6 +554,8 @@ class LayerGroupItem extends Component { } // If a Snackbar should be shown, call the showZoomSnack function with the subLayers array and set isGroupLayer to true. + // Example: If a group layer has 3 sublayers and none are visible at the current zoom level, + // the Snackbar will display a message for each sublayer. if (showSnack === true) { this.showZoomSnack(subLayers, true); } @@ -514,6 +566,7 @@ class LayerGroupItem extends Component { visibleSubLayers: subLayers, }); } else { + // If the layer is becoming hidden, call setHidden() to set the layer as hidden. this.setHidden(layer); } }; @@ -796,7 +849,16 @@ class LayerGroupItem extends Component { ); }; - // Displays a snackbar warning message if the layer is not visible at the current zoom level. + /** + * Displays a Snackbar message to inform the user about the layer's visibility at the current zoom level. + * + * This function shows a Snackbar message for each visible sublayer that is not visible at the + * current zoom level, informing the user that the layer is not visible. If the layer is a + * group layer, it handles the Snackbar messages for all sublayers within the group. + * + * @param {Array|string} sublayer - The sublayer or an array of sublayers to show the Snackbar message for. + * @param {boolean} isGroupLayer - True if the layer is a group layer, false otherwise. + */ showZoomSnack(sublayer, isGroupLayer) { // If a zoom warning snackbar is already displayed, return without doing anything. // This method ensures that only one Snackbar notification is displayed at a time, preventing multiple notifications from overlapping. @@ -812,6 +874,15 @@ class LayerGroupItem extends Component { visibleLayers.push(layer.get("layers")[0]); } + /** + * Adds captions to sublayers for display in a Snackbar message. + * + * This function receives a sublayer and retrieves the corresponding layer caption from the + * layerInfo object. It then creates a message string and enqueues a Snackbar with the message. + * The Snackbar will be displayed with a "warning" variant and will be removed when closed. + * + * @param {Object} subLayer - The sublayer for which to add captions. + */ const addSubLayerCaptions = (subLayer) => { if (subLayer) { const layerCaption = layersInfo[subLayer]?.caption; @@ -846,6 +917,14 @@ class LayerGroupItem extends Component { } } + /** + * Returns a checkbox element for the layer group. + * + * This function creates and returns a checkbox element for the layer group. The checkbox + * will toggle the visibility of the layer group when clicked. + * + * @returns {ReactElement} - A checkbox element for the layer group. + */ getCheckBox() { const { layer } = this.props; const { visible, visibleSubLayers } = this.state; From 0a3ae2105b7f24896d18cde315873dbcb771cfd4 Mon Sep 17 00:00:00 2001 From: linusfj Date: Fri, 5 May 2023 15:40:30 +0200 Subject: [PATCH 12/13] Remove unused 'const' in code --- .../LayerSwitcher/components/LayerGroupItem.js | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/new-client/src/plugins/LayerSwitcher/components/LayerGroupItem.js b/new-client/src/plugins/LayerSwitcher/components/LayerGroupItem.js index 05b3f87d6..2f0da36a2 100644 --- a/new-client/src/plugins/LayerSwitcher/components/LayerGroupItem.js +++ b/new-client/src/plugins/LayerSwitcher/components/LayerGroupItem.js @@ -473,26 +473,12 @@ class LayerGroupItem extends Component { }); const { layer } = this.props; - const layerProperties = layer.getProperties(); - const layerInfo = layerProperties.layerInfo || {}; - const layersInfo = layerInfo.layersInfo || {}; let visibleLayers = [...subLayersToShow]; if (layer.get("layers") && layer.get("layers").length > 0) { visibleLayers.push(layer.get("layers")[0]); } - const layerCaptions = visibleLayers.map((subLayer) => { - return layersInfo[subLayer]?.caption; - }); - - const layerCaption = - layerCaptions.length > 1 - ? `Lagren "${layerCaptions[0]}" och ${ - layerCaptions.length - 1 - } andra lager` - : `Lagret "${layerCaptions[0]}"`; - this.setState({ visible: true, visibleSubLayers: subLayersToShow, From 66d356c7b916448e5bbde8d4bfff7d3241e75ff4 Mon Sep 17 00:00:00 2001 From: linusfj Date: Thu, 11 May 2023 13:14:10 +0200 Subject: [PATCH 13/13] Fix TypeError in `showZoomSnack` Fix a TypeError that occurs when `layerInfo` or `layersInfo` properties are missing from the layer object in the `showZoomSnack` function, for example with vector layers. The fix includes an additional check to ensure that `layerInfo` and `layersInfo` are defined before trying to access its keys. --- .../plugins/LayerSwitcher/components/LayerItem.js | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/new-client/src/plugins/LayerSwitcher/components/LayerItem.js b/new-client/src/plugins/LayerSwitcher/components/LayerItem.js index a93c8081f..f366bf243 100644 --- a/new-client/src/plugins/LayerSwitcher/components/LayerItem.js +++ b/new-client/src/plugins/LayerSwitcher/components/LayerItem.js @@ -216,10 +216,21 @@ class LayerItem extends React.PureComponent { showZoomSnack() { if (this.zoomWarningSnack) return; + // We're fetching layerInfo object from the layer object. + const layerInfo = this.props.layer.get("layerInfo"); + + // If layerInfo is defined, we get layersInfo from it. + // Otherwise, layersInfo is set as undefined. + const layersInfo = layerInfo ? layerInfo.layersInfo : undefined; + // If the layer is a LayerGroupItem (meaning it contains more than one object in the "layersInfo" array), // then no message should be displayed. - if (Object.keys(this.props.layer.get("layerInfo").layersInfo).length > 1) + // Here we also ensure that layersInfo is defined and contains more than one layer + // before trying to access its keys. This prevents a TypeError when layersInfo + // is undefined. + if (layersInfo && Object.keys(layersInfo).length > 1) { return; + } this.zoomWarningSnack = this.props.enqueueSnackbar( `Lagret "${this.caption}" är inte synligt vid aktuell zoomnivå.`,