diff --git a/GIFrameworkMaps.Web/Scripts/Panels/BasemapsPanel.ts b/GIFrameworkMaps.Web/Scripts/Panels/BasemapsPanel.ts index bd7ffe51..e835a8d9 100644 --- a/GIFrameworkMaps.Web/Scripts/Panels/BasemapsPanel.ts +++ b/GIFrameworkMaps.Web/Scripts/Panels/BasemapsPanel.ts @@ -7,6 +7,7 @@ import { Basemap } from "../Interfaces/Basemap"; import { MetadataViewer } from "../Metadata/MetadataViewer"; import { Layer } from "../Interfaces/Layer"; import { Projection } from "ol/proj"; +import { PanelHelper } from "./PanelHelper"; export class BasemapsPanel implements SidebarPanel { container: string; @@ -17,16 +18,14 @@ export class BasemapsPanel implements SidebarPanel { init() { this.renderBasemapsPanel(); this.attachCloseButton(); - //this.attachBasemapSelectors(); - //this.attachMetaControls(); }; render() { this.renderBasemapsPanel(); }; /*TODO - Make this generic*/ private attachCloseButton():void { - let container = document.querySelector(this.container); - let closeButton = container.querySelector('button[data-gifw-dismiss-sidebar]'); + const container = document.querySelector(this.container); + const closeButton = container.querySelector('button[data-gifw-dismiss-sidebar]'); if (closeButton !== null) { closeButton.addEventListener('click', (e) => { Sidebar.close(); @@ -35,34 +34,40 @@ export class BasemapsPanel implements SidebarPanel { }; private renderBasemapsPanel(): void { - let container = document.querySelector(this.container); - let basemapsListContainer = container.querySelector('#basemaps-list-container'); + const container = document.querySelector(this.container); + const basemapsListContainer = container.querySelector('#basemaps-list-container'); /*TODO - Update rendering to only do a full render once, then just update on following renders*/ basemapsListContainer.innerHTML = ''; - let basemaps = this.gifwMapInstance.getLayerGroupOfType(LayerGroupType.Basemap) + const basemaps = this.gifwMapInstance.getLayerGroupOfType(LayerGroupType.Basemap) .olLayerGroup .getLayersArray() .sort((basemapA, basemapB) => { - let sortA = this.gifwMapInstance.config.basemaps.filter(b => b.id === basemapA.get('layerId'))[0].sortOrder; - let sortB = this.gifwMapInstance.config.basemaps.filter(b => b.id === basemapB.get('layerId'))[0].sortOrder; + const sortA = this.gifwMapInstance.config.basemaps.filter(b => b.id === basemapA.get('layerId'))[0].sortOrder; + const sortB = this.gifwMapInstance.config.basemaps.filter(b => b.id === basemapB.get('layerId'))[0].sortOrder; return sortA - sortB; }); basemaps.forEach(basemap => { - let basemapConfiguration = this.gifwMapInstance.config.basemaps.filter(b => b.id === basemap.get('layerId'))[0]; + const basemapConfiguration = this.gifwMapInstance.config.basemaps.filter(b => b.id === basemap.get('layerId'))[0]; basemapsListContainer.append(this.renderBasemapTile(basemap, basemapConfiguration)); basemapsListContainer.append(this.renderBasemapMeta(basemap, basemapConfiguration)); }) } + /** + * Create the basemap tile element + * @param basemap The OpenLayers layer we are creating a tile for + * @param basemapConfiguration The GIFramework Basemaps configuration for the basemap + * @returns Bootstrap Card element + */ private renderBasemapTile(basemap: olLayer, basemapConfiguration: Basemap): HTMLElement { - let card = document.createElement('div') + const card = document.createElement('div') card.className = `card text-white basemaps-selector ${basemap.getVisible() ? 'active' : ''}`; card.style.backgroundImage = `${basemapConfiguration.previewImageURL ? `url(${basemapConfiguration.previewImageURL})` : ''}`; card.id = `basemaps-selector-${basemapConfiguration.id}`; - let cardBody = document.createElement('div'); + const cardBody = document.createElement('div'); cardBody.className = 'card-body'; - let cardBodyLink = document.createElement('a'); + const cardBodyLink = document.createElement('a'); cardBodyLink.href = '#'; cardBodyLink.textContent = basemapConfiguration.name; cardBodyLink.className = 'card-title stretched-link'; @@ -76,14 +81,20 @@ export class BasemapsPanel implements SidebarPanel { return card; } + /** + * Create the meta elements for a particular basemap, including projection warnings, opacity/saturation sliders and metadata links + * @param basemap The OpenLayers layer we are creating a tile for + * @param basemapConfiguration The GIFramework Basemaps configuration for the basemap + * @returns HTML Div element containing relevant meta information + */ private renderBasemapMeta(basemap: olLayer, basemapConfiguration: Basemap): HTMLElement { - let meta = document.createElement('div'); + const meta = document.createElement('div'); meta.className = `basemaps-meta border-start border-bottom border-2 d-block ${basemap.getVisible() ? 'd-block' : 'd-none'}`; meta.id = `basemaps-meta-${basemapConfiguration.id}`; - let source = basemap.getSource(); + const source = basemap.getSource(); if (source.getProjection) { - let proj = source.getProjection() as Projection; + const proj = source.getProjection() as Projection; if (proj && (proj.getCode() !== this.gifwMapInstance.olMap.getView().getProjection().getCode())) { meta.insertAdjacentHTML( 'afterbegin', @@ -91,12 +102,11 @@ export class BasemapsPanel implements SidebarPanel { ); } } + const opacityControls = PanelHelper.renderSliderControl(basemapConfiguration.id, basemap.getOpacity() * 100, 5, 'opacity', 'basemap', this.gifwMapInstance); - let opacityControls = this.renderOpacityControls(basemapConfiguration, basemap.getOpacity()); + const saturationControls = PanelHelper.renderSliderControl(basemapConfiguration.id, basemap.get('saturation'), 5, 'saturation', 'basemap', this.gifwMapInstance); - let saturationControls = this.renderSaturationControls(basemapConfiguration, basemap.get('saturation')); - - let aboutLink = this.renderAboutLink(basemapConfiguration); + const aboutLink = this.renderAboutLink(basemapConfiguration); meta.appendChild(opacityControls); meta.appendChild(saturationControls); @@ -104,168 +114,46 @@ export class BasemapsPanel implements SidebarPanel { return meta; } - private renderOpacityControls(basemapConfiguration: Basemap, startingOpacity: number): HTMLElement { - - let opacityControlsContainer = document.createElement('div'); - - let opacityLabel = document.createElement('label'); - opacityLabel.textContent = 'Transparency' - opacityLabel.htmlFor = `basemaps-transparency-${basemapConfiguration.id}`; - opacityLabel.className = 'form-label'; - - let opacityControl = document.createElement('input'); - opacityControl.type = 'range'; - opacityControl.id = `basemaps-transparency-${basemapConfiguration.id}`; - opacityControl.className = 'form-range'; - opacityControl.dataset.gifwControlsTransparencyBasemap = basemapConfiguration.id; - opacityControl.value = (startingOpacity * 100).toString(); - opacityControl.addEventListener('input', e => { - let linkedInvisibleCheckbox: HTMLInputElement = document.querySelector(this.container).querySelector(`input[data-gifw-controls-invisible-basemap="${basemapConfiguration.id}"]`); - let opacity = parseInt((e.currentTarget as HTMLInputElement).value); - if (opacity === 0) { - linkedInvisibleCheckbox.checked = true; - } else { - linkedInvisibleCheckbox.checked = false; - } - this.gifwMapInstance.setTransparencyOfActiveBasemap(opacity); - }); - - let invisibleCheckbox = document.createElement('input'); - invisibleCheckbox.type = 'checkbox'; - invisibleCheckbox.id = `basemaps-invisible-check-${basemapConfiguration.id}`; - invisibleCheckbox.className = 'form-check-input'; - if (startingOpacity === 0) { - invisibleCheckbox.checked = true; - } - invisibleCheckbox.dataset.gifwControlsInvisibleBasemap = basemapConfiguration.id; - invisibleCheckbox.addEventListener('change', e => { - let element: HTMLInputElement = (e.currentTarget); - - let basemapId = element.dataset.gifwControlsInvisibleBasemap; - let linkedTransparencySlider: HTMLInputElement = document.querySelector(this.container).querySelector(`input[data-gifw-controls-transparency-basemap="${basemapId}"]`); - if (element.checked) { - linkedTransparencySlider.value = "0"; - } else { - linkedTransparencySlider.value = "100"; - } - let evt = new InputEvent('input'); - linkedTransparencySlider.dispatchEvent(evt); - }); - - let invisibleCheckboxLabel = document.createElement('label');; - invisibleCheckboxLabel.htmlFor = `basemaps-invisible-check-${basemapConfiguration.id}`; - invisibleCheckboxLabel.className = 'form-check-label'; - invisibleCheckboxLabel.textContent = "Invisible"; - - let invisibleCheckboxContainer = document.createElement('div'); - invisibleCheckboxContainer.className = 'form-check'; - - invisibleCheckboxContainer.appendChild(invisibleCheckbox); - invisibleCheckboxContainer.appendChild(invisibleCheckboxLabel); - - opacityControlsContainer.appendChild(opacityLabel); - opacityControlsContainer.appendChild(opacityControl); - opacityControlsContainer.appendChild(invisibleCheckboxContainer); - - return opacityControlsContainer; - } - - private renderSaturationControls(basemapConfiguration: Basemap, startingSaturation: number = 100) { - let saturationControlsContainer = document.createElement('div'); - - let saturationLabel = document.createElement('label'); - saturationLabel.textContent = 'Saturation' - saturationLabel.htmlFor = `basemaps-saturation-${basemapConfiguration.id}`; - saturationLabel.className = 'form-label'; - - let saturationControl = document.createElement('input'); - saturationControl.type = 'range'; - saturationControl.id = `basemaps-saturation-${basemapConfiguration.id}`; - saturationControl.className = 'form-range'; - saturationControl.dataset.gifwControlsSaturationBasemap = basemapConfiguration.id; - saturationControl.value = (startingSaturation).toString(); - saturationControl.addEventListener('input', e => { - let linkedGreyscaleCheckbox: HTMLInputElement = document.querySelector(this.container).querySelector(`input[data-gifw-controls-greyscale-basemap="${basemapConfiguration.id}"]`); - let saturation = parseInt((e.currentTarget as HTMLInputElement).value); - if (saturation === 0) { - linkedGreyscaleCheckbox.checked = true; - } else { - linkedGreyscaleCheckbox.checked = false; - } - this.gifwMapInstance.setSaturationOfActiveBasemap(saturation); - }); - - let greyscaleCheckbox = document.createElement('input'); - greyscaleCheckbox.type = 'checkbox'; - greyscaleCheckbox.id = `basemaps-saturation-check-${basemapConfiguration.id}`; - greyscaleCheckbox.className = 'form-check-input'; - if (startingSaturation === 0) { - greyscaleCheckbox.checked = true; - } - greyscaleCheckbox.dataset.gifwControlsGreyscaleBasemap = basemapConfiguration.id; - greyscaleCheckbox.addEventListener('change', e => { - let element: HTMLInputElement = (e.currentTarget); - - let basemapId = element.dataset.gifwControlsGreyscaleBasemap; - let linkedGreyscaleSlider: HTMLInputElement = document.querySelector(this.container).querySelector(`input[data-gifw-controls-saturation-basemap="${basemapId}"]`); - if (element.checked) { - linkedGreyscaleSlider.value = "0"; - } else { - linkedGreyscaleSlider.value = "100"; - } - let evt = new InputEvent('input'); - linkedGreyscaleSlider.dispatchEvent(evt); - }); - - let greyscaleCheckboxLabel = document.createElement('label');; - greyscaleCheckboxLabel.htmlFor = `basemaps-saturation-check-${basemapConfiguration.id}`; - greyscaleCheckboxLabel.className = 'form-check-label'; - greyscaleCheckboxLabel.textContent = "Greyscale"; - - let invisibleCheckboxContainer = document.createElement('div'); - invisibleCheckboxContainer.className = 'form-check'; - - invisibleCheckboxContainer.appendChild(greyscaleCheckbox); - invisibleCheckboxContainer.appendChild(greyscaleCheckboxLabel); - - saturationControlsContainer.appendChild(saturationLabel); - saturationControlsContainer.appendChild(saturationControl); - saturationControlsContainer.appendChild(invisibleCheckboxContainer); - - return saturationControlsContainer; - } - + /** + * Create the about link for a basemap + * @param basemapConfiguration The GIFramework Basemaps configuration for the basemap + * @returns HTML Paragraph that opens metadata information for the basemap + */ private renderAboutLink(basemapConfiguration: Basemap): HTMLElement { - let link = document.createElement('a'); + const link = document.createElement('a'); link.href = `#basemaps-meta-${basemapConfiguration.id}`; link.textContent = 'about'; link.title = `Learn more about the ${basemapConfiguration.name} basemap`; link.dataset.gifwAboutBasemap = basemapConfiguration.id; link.addEventListener('click', e => { //open modal for metadata - let eTarget = e.currentTarget as HTMLElement; - let layerGroup = this.gifwMapInstance.getLayerGroupOfType(LayerGroupType.Basemap); - let layerConfig = (layerGroup.layers as Layer[]).filter(l => l.id == eTarget.dataset.gifwAboutBasemap); + const eTarget = e.currentTarget as HTMLElement; + const layerGroup = this.gifwMapInstance.getLayerGroupOfType(LayerGroupType.Basemap); + const layerConfig = (layerGroup.layers as Layer[]).filter(l => l.id == eTarget.dataset.gifwAboutBasemap); let proxyEndpoint = ""; if (layerConfig[0].proxyMetaRequests) { proxyEndpoint = `${document.location.protocol}//${this.gifwMapInstance.config.appRoot}proxy`; } if (layerConfig && layerConfig.length === 1) { - let olLayer = this.gifwMapInstance.getActiveBasemap(); + const olLayer = this.gifwMapInstance.getActiveBasemap(); MetadataViewer.showMetadataModal(layerConfig[0], olLayer, undefined, proxyEndpoint); } e.preventDefault(); }) - let para = document.createElement('p'); + const para = document.createElement('p'); para.className = 'text-end'; para.appendChild(link); return para; } + /** + * Toggles a basemap on and turns off other basemaps, as well as hiding and showing relevant elements in the panel + * @param basemapId The id of the basemap to toggle + */ private toggleBasemap(basemapId: string) { - let basemapSelector = document.getElementById(`basemaps-selector-${basemapId}`); - let basemapMeta = document.getElementById(`basemaps-meta-${basemapId}`); + const basemapSelector = document.getElementById(`basemaps-selector-${basemapId}`); + const basemapMeta = document.getElementById(`basemaps-meta-${basemapId}`); if (basemapSelector.classList.contains('active')) { //basemap is already active } else { @@ -281,9 +169,9 @@ export class BasemapsPanel implements SidebarPanel { basemapSelector.classList.add('active'); basemapMeta.classList.remove('d-none'); - let layerGroup = this.gifwMapInstance.olMap.getLayers().getArray().filter(g => g.get('type') === 'base')[0]; - let basemap = layerGroup.getLayersArray().filter(l => l.get('layerId') == basemapId)[0]; - let otherBasemaps = layerGroup.getLayersArray().filter(l => l.get('layerId') != basemapId); + const layerGroup = this.gifwMapInstance.olMap.getLayers().getArray().filter(g => g.get('type') === 'base')[0]; + const basemap = layerGroup.getLayersArray().filter(l => l.get('layerId') == basemapId)[0]; + const otherBasemaps = layerGroup.getLayersArray().filter(l => l.get('layerId') != basemapId); basemap.setVisible(true) otherBasemaps.forEach(b => { b.setVisible(false); diff --git a/GIFrameworkMaps.Web/Scripts/Panels/LayersPanel.ts b/GIFrameworkMaps.Web/Scripts/Panels/LayersPanel.ts index 8b57683d..dc8e83d4 100644 --- a/GIFrameworkMaps.Web/Scripts/Panels/LayersPanel.ts +++ b/GIFrameworkMaps.Web/Scripts/Panels/LayersPanel.ts @@ -25,6 +25,7 @@ import { Style } from "../Interfaces/OGCMetadata/Style"; import ImageLayer from "ol/layer/Image"; import { LayerFilter } from "../LayerFilter"; import { UserSettings } from "../UserSettings"; +import { PanelHelper } from "./PanelHelper"; export class LayersPanel implements SidebarPanel { container: string; @@ -127,67 +128,105 @@ export class LayersPanel implements SidebarPanel { //let curZoom = Math.ceil(this.gifwMapInstance.olMap.getView().getZoom()); activeLayersContainer.innerHTML = '

Drag layers using the drag handle to reorder them on the map

'; - let accordion = document.createElement('div'); + const accordion = document.createElement('div'); accordion.classList.add("accordion","mt-2","active-layers-list"); switchedOnLayers.sort((a, b) => b.getZIndex() - a.getZIndex()).forEach(l => { - let layerId = l.get('layerId'); - let layerConfig = this.gifwMapInstance.getLayerConfigById(layerId, [LayerGroupType.Overlay, LayerGroupType.SystemNative, LayerGroupType.UserNative]); - let layerHtml = ` -
-

- -

-
-
-
- - -
- - -
- - -
- - -
- ${l.getSource() instanceof TileWMS || l.getSource() instanceof ImageWMS ? `` : ``} - ${this.isLayerFilterable(layerConfig, l) ? `` : ``} -
-
-
-
- ` - accordion.insertAdjacentHTML('beforeend',layerHtml) + const layerId = l.get('layerId'); + const layerConfig = this.gifwMapInstance.getLayerConfigById(layerId, [LayerGroupType.Overlay, LayerGroupType.SystemNative, LayerGroupType.UserNative]); + accordion.appendChild(this.renderActiveLayerAccordionItem(l, layerConfig)); }) activeLayersContainer.insertAdjacentElement('beforeend', accordion); this.setLayerVisibilityState(); - let _this = this; Sortable.create(accordion, { swapThreshold: 0.70, animation: 150, filter: '.disabled, input', preventOnFilter: false, handle: '.handle', - onChange: function (e) { - _this.updateLayerOrderingFromList(); + onChange: (e) => { + this.updateLayerOrderingFromList(); } }); - this.attachStyleControls(); } else { activeLayersContainer.innerHTML = `
You don't have any layers turned on. Go to the picker to turn some layers on
`; } } + private renderActiveLayerAccordionItem(layer: olLayer, layerConfig: Layer) { + const layerId = layer.get('layerId'); + const layerName = layer.get('name') + const accordionContainer = document.createElement('div'); + accordionContainer.id = `active-layer-${layerId}`; + accordionContainer.className = 'accordion-item'; + accordionContainer.dataset.gifwLayerId = layerId; + accordionContainer.appendChild(this.renderActiveLayerHeader(layerId, layerName)); + accordionContainer.appendChild(this.renderActiveLayerContent(layer, layerConfig)); + return accordionContainer; + } + + private renderActiveLayerHeader(layerId: string, layerName: string) { + const header = document.createElement('h2'); + header.className = "accordion-header"; + header.innerHTML = `