Skip to content

Commit

Permalink
Improved slider controls (#210)
Browse files Browse the repository at this point in the history
* Add label to basemap opacity and saturation sliders

* Replace let with const and add docs

* Chnage basemap and layer sliders to use panel helper and simplify active layers rendering code

* Add explanatory message to min/max zoom setting

* Swap opacity and saturation sliders location in active layers list

* Add step values to sliders

* Add labelled slider to layer edit screen
  • Loading branch information
RobQuincey-DC authored Jan 3, 2024
1 parent d4c8b6d commit 7452a3c
Show file tree
Hide file tree
Showing 6 changed files with 250 additions and 325 deletions.
212 changes: 50 additions & 162 deletions GIFrameworkMaps.Web/Scripts/Panels/BasemapsPanel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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();
Expand All @@ -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<any, any>, 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';
Expand All @@ -76,196 +81,79 @@ 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<any, any>, 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',
`<div class="alert alert-warning">The projection of the source data is different from the map. This basemap may have some rendering issues (such as blurring or mis-matching)</div>`
);
}
}
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);
meta.appendChild(aboutLink);
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 = <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 = <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 {
Expand All @@ -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);
Expand Down
Loading

0 comments on commit 7452a3c

Please sign in to comment.