diff --git a/package-lock.json b/package-lock.json index f8a1cae..17d311a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,7 +20,7 @@ "bootstrap-icons": "^1.11.2", "ol": "^6.15.1", "ol-ext": "^3.2.30", - "ol-layerswitcher": "^3.8.3", + "ol-layerswitcher": "^4.1.2", "proj4": "^2.9.0", "react": "17.0.2", "react-dom": "17.0.2", @@ -19068,9 +19068,9 @@ } }, "node_modules/ol-layerswitcher": { - "version": "3.8.3", - "resolved": "https://registry.npmjs.org/ol-layerswitcher/-/ol-layerswitcher-3.8.3.tgz", - "integrity": "sha512-UwUhalf/sGXjz3rvr0EjwsaUVlJAhyJCfcIPciKk1QdNbMKq/2ZXNKGafOjwP2eDxiqhkvnhpIrDGD8+gQ19Cg==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/ol-layerswitcher/-/ol-layerswitcher-4.1.2.tgz", + "integrity": "sha512-yF1iNqPKWr7AcsIWnr5He+T/J4X3qdce7qr8QUi/5Xyjo6uFjVvVijdAhMwiNfJrBNh9JhrXeDSl+1/cTT58KA==", "peerDependencies": { "ol": ">=5.0.0" } diff --git a/package.json b/package.json index 54cd0c1..6fb9585 100644 --- a/package.json +++ b/package.json @@ -87,7 +87,7 @@ "bootstrap-icons": "^1.11.2", "ol": "^6.15.1", "ol-ext": "^3.2.30", - "ol-layerswitcher": "^3.8.3", + "ol-layerswitcher": "^4.1.2", "proj4": "^2.9.0", "react": "17.0.2", "react-dom": "17.0.2", diff --git a/src/GeomapPanel.css b/src/GeomapPanel.css index 153dc8b..299ef3c 100644 --- a/src/GeomapPanel.css +++ b/src/GeomapPanel.css @@ -1,30 +1,45 @@ .ol-control.layer-switcher { top: 0.5em; right: 0.5em; - text-align: right; + /* text-align: right; */ + text-align: left; } + .ol-control.layer-switcher.shown.layer-switcher-activation-mode-click { padding-right: 0.5em; } + .ol-control.layer-switcher.shown.layer-switcher-activation-mode-click > button { right: 0; border-left: 0; } + .ol-control.layer-switcher.shown.layer-switcher-activation-mode-click > .panel { display: block; } .ol-control.layer-switcher.layer-switcher-activation-mode-click > .panel { display: none; } + .ol-control.layer-switcher button { right: 0; border-left: 0; + /* content: url('./img/icons/layers.svg'); */ + /* background-image: url('./img/icons/layers.svg'); + background-repeat: no-repeat; */ + /* https://icons.getbootstrap.com/#usage */ } .ol-control.layer-switcher li.layer { list-style: none; } +.layer-switcher li label { + padding-left: 0.5em; + /* padding-right: 1.2em; */ + display: inline-block; + margin-top: 1px; +} .data-layer-add { display: flex; diff --git a/src/GeomapPanel.tsx b/src/GeomapPanel.tsx index 9ede3e8..ab6e257 100644 --- a/src/GeomapPanel.tsx +++ b/src/GeomapPanel.tsx @@ -10,9 +10,12 @@ import MouseWheelZoom from 'ol/interaction/MouseWheelZoom'; import { createEmpty, extend } from 'ol/extent'; import VectorLayer from 'ol/layer/Vector'; import { Vector } from 'ol/source'; -import LayerSwitcher from 'ol-layerswitcher'; +// import LayerSwitcher from 'ol-layerswitcher'; import { isArray, isEqual } from 'lodash'; import './GeomapPanel.css'; +import "bootstrap-icons/font/bootstrap-icons.css"; + +// import 'ol-layerswitcher/dist/ol-layerswitcher.css'; // import WKT from 'ol/format/WKT.js'; // import Polygon from 'ol/geom/Polygon.js'; @@ -53,6 +56,8 @@ import SpatialFilterControl from './mapcontrols/SpatialFilter'; import { testIds } from 'e2eTestIds'; import { Global } from '@emotion/react'; import { Subscription } from 'rxjs'; +import { DataExtentZoom } from 'mapcontrols/DataExtentZoom'; +import { CustomLayerSwitcher } from 'mapcontrols/CustomLayerSwitcher'; // import { BasemapLegend } from 'mapcontrols/BasemapLegend'; // import { VariablesChangedEvent } from // import {getBottomLeft, getBottomRight, getTopLeft, getTopRight} from 'ol/extent'; @@ -606,11 +611,20 @@ export class GeomapPanel extends Component { if (options.showLayercontrol) { this.map.addControl( - new LayerSwitcher({ - label: 'L', + // new LayerSwitcher({ + // label: '', + // collapseLabel: '›', + // tipLabel: 'Select layers', + // groupSelectStyle: 'none', + // activationMode: 'click', + // }) + new CustomLayerSwitcher({ + label: '', + collapseLabel: '›', tipLabel: 'Select layers', groupSelectStyle: 'none', activationMode: 'click', + hiddenClassNameButton: "bi bi-layers" }) ); } @@ -622,11 +636,11 @@ export class GeomapPanel extends Component { ); } - // if (options.showBasemapLegend === true) { - // this.map.addControl( - // new BasemapLegend(this.basemap!, this.props) - // ); - // } + if (options.showDataExtentZoom === true) { + this.map.addControl( + new DataExtentZoom() + ); + } const map = this.map; @@ -659,7 +673,13 @@ export class GeomapPanel extends Component { this.mouseWheelZoom!.setActive(Boolean(options.mouseWheelZoom)); if (options.showAttribution) { - this.map.addControl(new Attribution({ collapsed: true, collapsible: true })); + this.map.addControl(new Attribution({ + collapsed: true, + collapsible: true , + label: '', + expandClassName: 'bi bi-info-circle', + // collapseClassName: '' + })); } // Update the react overlays diff --git a/src/mapcontrols/CustomLayerSwitcher.ts b/src/mapcontrols/CustomLayerSwitcher.ts new file mode 100644 index 0000000..587a633 --- /dev/null +++ b/src/mapcontrols/CustomLayerSwitcher.ts @@ -0,0 +1,61 @@ +import LayerSwitcher, { Options } from "ol-layerswitcher"; + +export class CustomLayerSwitcher extends LayerSwitcher { + protected shownClassNameButton: string; + protected hiddenClassNameButton: string; + protected icon: HTMLElement; + protected text: Text; + + constructor(opts: CustomOptions) { + const {shownClassNameButton, hiddenClassNameButton, ...opt_options} = opts; + super({ ...opt_options }); + + this.shownClassNameButton = shownClassNameButton ? shownClassNameButton : ""; + this.hiddenClassNameButton = hiddenClassNameButton ? hiddenClassNameButton : ""; + + this.icon = document.createElement('i'); + this.icon.setAttribute("class", hiddenClassNameButton!); + this.button.appendChild(this.icon); + + this.text = document.createTextNode(""); + this.button.appendChild(this.text); + + this.updateButton(); + } + + protected updateButton(): void { + + if (!this.icon || !this.text) { + return; + } + + if (this.element.classList.contains(this.shownClassName)) { + // this.button.textContent = this.collapseLabel; + // this.button.innerText = this.collapseLabel; + this.button.setAttribute('title', this.collapseTipLabel); + this.button.setAttribute('aria-label', this.collapseTipLabel); + if (this.text) { + this.text.textContent = this.collapseLabel; + } + if (this.icon) { + this.icon.setAttribute("class", this.shownClassNameButton); + } + } else { + // this.button.textContent = this.label; + // this.button.innerText = this.label; + this.button.setAttribute('title', this.tipLabel); + this.button.setAttribute('aria-label', this.tipLabel); + if (this.text) { + this.text.textContent = this.label; + } + if (this.icon) { + this.icon.setAttribute("class", this.hiddenClassNameButton); + } + } + } +} + +interface CustomOptions extends Options { + shownClassNameButton?: string, + hiddenClassNameButton?: string +} diff --git a/src/mapcontrols/DataExtentZoom.ts b/src/mapcontrols/DataExtentZoom.ts new file mode 100644 index 0000000..530958e --- /dev/null +++ b/src/mapcontrols/DataExtentZoom.ts @@ -0,0 +1,83 @@ +import "bootstrap-icons/font/bootstrap-icons.css"; +// import { BusEventBase } from "@grafana/data"; +import Control from "ol/control/Control"; +// import BaseLayer from "ol/layer/Base"; +// import LayerGroup from "ol/layer/Group"; +// import { ImageWMS } from "ol/source"; +import * as olCss from "ol/css"; +import { createEmpty, extend } from "ol/extent"; +import { isEqual } from "lodash"; +import VectorLayer from "ol/layer/Vector"; +import { Vector } from "ol/source"; + +export const DATA_EXTENT_ZOOM_TEST_ID = "data-testid dataextentzoom-button"; + +// class PanelOptionsChangedEvent extends BusEventBase { +// static type = 'panels-options-changed'; +// } + +export class DataExtentZoom extends Control { + static CONTROL_NAME = "DataExtentZoom"; + + constructor(opt_options?: any) { + const options = opt_options || {}; + + const button = document.createElement('button'); + // button.ariaLabel = "wms legend collapse button"; + button.setAttribute("data-testid", DATA_EXTENT_ZOOM_TEST_ID); + button.title = options.tooltipTitle || "Zoom to data extent"; + const icon = document.createElement('i'); + icon.className = "bi bi-bounding-box-circles"; + // icon.className = "bi bi-aspect-ratio"; + // icon.className = "bi bi-arrows-fullscreen"; + button.appendChild(icon); + + const element = document.createElement('div'); + // element.className = `ol-zoom ol-touch ${olCss.CLASS_UNSELECTABLE}`; + element.className = `${olCss.CLASS_CONTROL} ol-zoom ol-touch ${olCss.CLASS_UNSELECTABLE}`; + element.style.top = "80%"; + + element.appendChild(button); + + super({ + element: element, + target: options.target, + }); + + const eventHandler = () => { + let extent = createEmpty(); + // If layer is group layer extract the layers => see markersLayer.tsx line 324 where the markers layer is returned as Group + // const layers = this.map.getLayers().getArray(); + const layers = this.getMap()?.getAllLayers(); + if (!layers) { + return; + } + for (let layer of layers) { + if (layer instanceof VectorLayer) { + let source = layer.getSource(); + if (source !== undefined && source instanceof Vector) { + let features = source.getFeatures(); + for (let feature of features) { + let geo = feature.getGeometry(); + if (geo) { + extend(extent, geo.getExtent()); + } + } + } + } + } + if (!isEqual(extent, createEmpty())) { + this.getMap()?.getView().fit(extent); + let zoom = this.getMap()?.getView().getZoom(); + if (zoom) { + this.getMap()?.getView().setZoom(zoom - 0.5); + } + } + }; + + button.addEventListener("click", () => { + eventHandler(); + }); + + } +} diff --git a/src/mapcontrols/SpatialFilter.ts b/src/mapcontrols/SpatialFilter.ts index be16807..f7fba26 100644 --- a/src/mapcontrols/SpatialFilter.ts +++ b/src/mapcontrols/SpatialFilter.ts @@ -40,6 +40,7 @@ class SpatialFilterControl extends Control { const button = document.createElement('button'); button.setAttribute("type", "button"); + button.title = options.tooltipTitle || "Spatial filter tool"; // button.innerHTML = 'D'; const icon = document.createElement('i'); icon.className = "bi bi-funnel"; diff --git a/src/mapcontrols/WMSLegend.ts b/src/mapcontrols/WMSLegend.ts index fff2833..464d024 100644 --- a/src/mapcontrols/WMSLegend.ts +++ b/src/mapcontrols/WMSLegend.ts @@ -30,9 +30,13 @@ export class WMSLegend extends Control { const options = opt_options || {}; const button = document.createElement('button'); - button.innerHTML = '>'; + // button.innerHTML = '>'; // button.ariaLabel = "wms legend collapse button"; button.setAttribute("aria-label", "wms legend collapse button"); + button.title = options.tooltipTitle || "WMS layer legend"; + const icon = document.createElement('i'); + icon.className = "bi bi-list-task"; + button.appendChild(icon); const legendContainer = document.createElement("div"); legendContainer.style.display = "block"; @@ -69,8 +73,14 @@ export class WMSLegend extends Control { let eventHandler = () => { if (this.legendOpened) { - button.innerHTML = ">"; + // button.innerHTML = ">"; // this.legendContainer.className = styles.basemapLegend_hidden; + // button.getElementsByTagName('i')[0].setAttribute("class", "bi bi-list-task"); + button.innerHTML = ""; + const icon = document.createElement('i'); + icon.className = "bi bi-list-task"; + button.appendChild(icon); + this.element.style.width = ""; this.element.style.height = ""; this.element.style.overflow = ""; @@ -79,7 +89,9 @@ export class WMSLegend extends Control { this.element.removeChild(this.legendContainer); } else { - button.innerHTML = "<"; + button.getElementsByTagName('i')[0].remove(); + button.innerHTML = "‹"; + // button.getElementsByTagName('i')[0].setAttribute("class", "bi bi-chevron-left"); // this.legendContainer.className = styles.basemapLegend_visible; this.element.style.overflow = "hidden"; // "scroll"; diff --git a/src/module.ts b/src/module.ts index 2592d32..e87537b 100644 --- a/src/module.ts +++ b/src/module.ts @@ -98,12 +98,12 @@ export const plugin = new PanelPlugin(GeomapPanel) name: 'Show spatial filter tool', description: 'Show tool for interactive spatial filtering', defaultValue: false, + }) + .addBooleanSwitch({ + category, + path: 'controls.showDataExtentZoom', + name: 'Show data extent zoom', + description: 'Fit map view to data extent', + defaultValue: true, }); - // .addBooleanSwitch({ - // category, - // path: 'controls.showBasemapLegend', - // name: 'Show basemap legend', - // description: 'Show legend of basemap if available', - // defaultValue: false, - // }); }); diff --git a/src/types.ts b/src/types.ts index 9d3727f..dbc4ac2 100644 --- a/src/types.ts +++ b/src/types.ts @@ -25,8 +25,8 @@ export interface ControlsOptions { // Custom control: spatial filter showSpatialFilter?: boolean - // Custom control: spatial filter - // showBasemapLegend?: boolean + // Custom control: data extent zoom + showDataExtentZoom?: boolean } export interface MapViewConfig {