@geogirafe/lib-geoportal
Version:
GeoGirafe is a flexible application to build online geoportals.
292 lines (291 loc) • 11.4 kB
JavaScript
// SPDX-License-Identifier: Apache-2.0
import GirafeSingleton from '../../base/GirafeSingleton.js';
import GroupLayer from '../../models/layers/grouplayer.js';
import Layer from '../../models/layers/layer.js';
import ThemeLayer from '../../models/layers/themelayer.js';
class LayerManager extends GirafeSingleton {
layerClones = [];
layerIdToClone = new Map();
get state() {
return this.context.stateManager.state;
}
initializeSingleton() {
this.context.stateManager.subscribe(/layers\.layersList\..*\.activeState/, (_oldActive, _newActive, layer) => this.onLayerToggled(layer));
this.context.stateManager.subscribe('layers.layersList', (oldLayers, newLayers) => this.onLayersListChanged(oldLayers, newLayers));
this.context.stateManager.subscribe(/layers\.layersList\..*\.children/, (oldChildren, newChildren) => this.onChildrenListChanged(oldChildren, newChildren));
}
onLayersListChanged(oldLayers, newLayers) {
let addedLayers = newLayers;
if (oldLayers) {
addedLayers = newLayers.filter((newLayer) => !oldLayers.find((oldLayer) => oldLayer.treeItemId === newLayer.treeItemId));
}
this.layerClones.push(...addedLayers);
this.fillLayerIdToClone(addedLayers);
this.activateDefaultLayers(addedLayers);
this.highlightNewLayers(addedLayers);
}
fillLayerIdToClone(layers) {
for (const layer of layers) {
this.layerIdToClone.set(layer.treeItemId, layer);
if (layer instanceof GroupLayer || layer instanceof ThemeLayer) {
this.fillLayerIdToClone(layer.children);
}
}
}
onChildrenListChanged(oldChildren, newChildren) {
// If we added a new group to the list of layers
// Then we activate the layers that should be activated by default
const addedLayers = newChildren.filter((newChild) => !oldChildren.find((oldChild) => oldChild.treeItemId === newChild.treeItemId));
this.fillLayerIdToClone(addedLayers);
this.activateDefaultLayers(addedLayers);
}
getTreeItem(treeItemId) {
// The object is not in the list of active layers any more.
// We look in the list of clones
const treeItem = this.layerIdToClone.get(treeItemId);
if (treeItem) {
return treeItem;
}
throw new Error(`BaseLayer ${treeItemId} not found !`);
}
getTreeItemByLayerName(layerName) {
return this.getFlattenedLayerTree(this.state.layers.layersList).find((l) => l.name === layerName) ?? null;
}
getFlattenedLayerTree(layers) {
const allLayers = [];
for (const layer of layers) {
allLayers.push(layer);
if (layer instanceof GroupLayer || layer instanceof ThemeLayer) {
allLayers.push(...this.getFlattenedLayerTree(layer.children));
}
}
return allLayers;
}
activateIfDefaultChecked(layer) {
if (layer.isDefaultChecked) {
this.toggle(layer, 'on');
}
}
toggle(layer, state) {
if (layer instanceof GroupLayer || layer instanceof ThemeLayer) {
this.toggleGroupOrTheme(layer, state);
}
else if (layer instanceof Layer) {
this.toggleLayer(layer, state);
}
}
onLayerToggled(layer) {
if (layer instanceof GroupLayer || layer instanceof ThemeLayer) {
// Toggle parents if necessary
this.manageExclusiveGroups(layer);
this.toggleParent(layer);
// Toggle childs if necessary
if (layer.activeState !== 'semi') {
this.toggleChilds(layer, layer.activeState);
}
}
else if (layer instanceof Layer) {
// Hide the legend when the layer is deactivated (if configured so)
if (this.isLayerWithLegend(layer) &&
this.context.configManager.Config.treeview.hideLegendWhenLayerIsDeactivated) {
if (layer.active) {
layer.isLegendExpanded = layer.wasLegendExpanded;
}
else {
layer.wasLegendExpanded = layer.isLegendExpanded;
layer.isLegendExpanded = false;
}
}
// Toggle parents if necessary
this.manageExclusiveGroups(layer);
this.toggleParent(layer);
}
this.manageDisclaimer(layer);
}
manageDisclaimer(layer) {
if (layer.active && layer.disclaimer) {
this.state.infobox.elements.push({
id: layer.treeItemId,
text: layer.disclaimer,
type: 'info'
});
}
if (layer.inactive && layer.disclaimer) {
const index = this.state.infobox.elements.findIndex((el) => el.id === layer.treeItemId);
if (index >= 0) {
this.state.infobox.elements.splice(index, 1);
}
}
}
toggleLayer(layer, state) {
if (!(layer instanceof Layer)) {
throw new Error('This method should only be called on leafs layers, not on groups');
}
let newState;
if (state) {
newState = state;
}
else if (layer.activeState === 'off') {
newState = 'on';
}
else {
newState = 'off';
}
if (layer.activeState != newState) {
console.debug(`Setting Layer ${layer.name} to ${newState}`);
this.getTreeItem(layer.treeItemId).activeState = newState;
}
}
toggleGroupOrTheme(groupOrTheme, state) {
let newState;
if (state) {
newState = state;
}
else if (groupOrTheme.activeState === 'off') {
newState = 'on';
}
else {
newState = 'off';
}
if (groupOrTheme.activeState != newState) {
console.debug(`Setting Group ${groupOrTheme.name} to ${newState}`);
this.getTreeItem(groupOrTheme.treeItemId).activeState = newState;
}
}
toggleParent(layer) {
if (layer.parent) {
if ((layer.parent instanceof GroupLayer && layer.parent.isExclusiveGroup) ||
(layer.parent instanceof ThemeLayer && layer.parent.isExclusiveTheme)) {
if (this.isAnyChildActive(layer.parent)) {
this.toggleGroupOrTheme(layer.parent, 'on');
}
else {
this.toggleGroupOrTheme(layer.parent, 'off');
}
}
else if (this.areAllChildrenActive(layer.parent)) {
this.toggleGroupOrTheme(layer.parent, 'on');
}
else if (this.areAllChildrenInactive(layer.parent)) {
this.toggleGroupOrTheme(layer.parent, 'off');
}
else {
this.toggleGroupOrTheme(layer.parent, 'semi');
}
}
}
toggleChilds(group, state) {
if (group instanceof GroupLayer && group.active && group.isExclusiveGroup && group.children.length >= 1) {
// We activate a group, and this group is an exclusive group.
// If there isn't any active child yet, we activate the first one
if (!this.isAnyChildActive(group)) {
this.toggle(group.children[0], state);
}
}
else {
// In all other cases, we activate/deactivate all children
for (const child of group.children) {
this.toggle(child, state);
}
}
}
manageExclusiveGroups(layer) {
// This method manages the case of exclusives groups:
// If we have activate a layer, and if the parent group is defined as "exclusive"
// It means only 1 child can be activated at the same time.
// Therefore, we have to deactivate all other childs for the parent group.
if ((layer.active || ((layer instanceof GroupLayer || layer instanceof ThemeLayer) && layer.semiActive)) &&
layer.parent != null &&
((layer.parent instanceof GroupLayer && layer.parent.isExclusiveGroup) ||
(layer.parent instanceof ThemeLayer && layer.parent.isExclusiveTheme))) {
// Deactivate all other layers
for (const child of layer.parent.children) {
const otherLayer = this.getTreeItem(child.treeItemId);
if (otherLayer.treeItemId !== layer.treeItemId &&
(otherLayer.active ||
((otherLayer instanceof GroupLayer || otherLayer instanceof ThemeLayer) && otherLayer.semiActive))) {
this.toggle(otherLayer, 'off');
}
}
}
}
areAllChildrenActive(groupOrTheme) {
let allActive = true;
for (const child of groupOrTheme.children) {
if (!child.active) {
allActive = false;
}
}
return allActive;
}
areAllChildrenInactive(groupOrTheme) {
let allInactive = true;
for (const child of groupOrTheme.children) {
if (!child.inactive) {
allInactive = false;
}
}
return allInactive;
}
isAnyChildActive(groupOrTheme) {
for (const child of groupOrTheme.children) {
if (child.active) {
return true;
}
if (child instanceof GroupLayer && child.semiActive) {
// A semi active group is considered as an active child in this case
return true;
}
}
return false;
}
setError(layer, error) {
layer.hasError = true;
layer.errorMessage = error;
console.warn(layer.errorMessage);
}
unsetError(layer) {
layer.hasError = false;
layer.errorMessage = null;
}
isLayerWithLegend(layer) {
return layer.isLegendExpanded !== undefined;
}
isLayerWithFilter(layer) {
return layer.filter !== undefined;
}
getSortedLayers(layers) {
const orderedLayers = layers.slice().sort((l1, l2) => {
return l1.order - l2.order;
});
return orderedLayers ?? [];
}
activateDefaultLayers(layers) {
for (const layer of layers) {
// Activate the layer by default
this.activateIfDefaultChecked(layer);
// Manage the legend visibility
if (layer instanceof Layer &&
!layer.active &&
this.context.configManager.Config.treeview.hideLegendWhenLayerIsDeactivated &&
this.isLayerWithLegend(layer)) {
// Hide Legend
layer.isLegendExpanded = false;
layer.wasLegendExpanded = true;
}
// Continue recursively
if (layer instanceof GroupLayer || layer instanceof ThemeLayer) {
this.activateDefaultLayers(layer.children);
}
}
}
highlightNewLayers(layers) {
// Do not highlight at application start
if (this.state.application.isReady) {
for (const layer of layers) {
layer.isHighlighted = true;
}
}
}
}
export default LayerManager;