Finetuning of the filter functionality

This commit is contained in:
pietervdvn 2021-07-27 19:39:57 +02:00
parent 31d2bd83b9
commit 79569f5119
17 changed files with 219 additions and 309 deletions

View file

@ -34,7 +34,8 @@ export default class LayerConfig {
passAllFeatures: boolean; passAllFeatures: boolean;
isShown: TagRenderingConfig; isShown: TagRenderingConfig;
minzoom: number; minzoom: number;
maxzoom: number; minzoomVisible: number;
maxzoom:number;
title?: TagRenderingConfig; title?: TagRenderingConfig;
titleIcons: TagRenderingConfig[]; titleIcons: TagRenderingConfig[];
icon: TagRenderingConfig; icon: TagRenderingConfig;
@ -143,7 +144,7 @@ export default class LayerConfig {
this.doNotDownload = json.doNotDownload ?? false; this.doNotDownload = json.doNotDownload ?? false;
this.passAllFeatures = json.passAllFeatures ?? false; this.passAllFeatures = json.passAllFeatures ?? false;
this.minzoom = json.minzoom ?? 0; this.minzoom = json.minzoom ?? 0;
this.maxzoom = json.maxzoom ?? 1000; this.minzoomVisible = json.minzoomVisible ?? this.minzoom;
this.wayHandling = json.wayHandling ?? 0; this.wayHandling = json.wayHandling ?? 0;
this.presets = (json.presets ?? []).map((pr, i) => { this.presets = (json.presets ?? []).map((pr, i) => {
if (pr.preciseInput === true) { if (pr.preciseInput === true) {

View file

@ -92,16 +92,16 @@ export interface LayerConfigJson {
/** /**
* The zoomlevel at which point the data is shown and loaded. * The minimum needed zoomlevel required before loading of the data start
* Default: 0 * Default: 0
*/ */
minzoom?: number; minzoom?: number;
/** /**
* The zoomlevel at which point the data is hidden again * If zoomed out below this zoomlevel, the data will be hidden.
* Default: 100 (thus: always visible * Default: minzoom
*/ */
maxzoom?: number; minzoomVisible?: number;
/** /**
* The title shown in a popup for elements of this layer. * The title shown in a popup for elements of this layer.

View file

@ -1,19 +1,19 @@
import { CenterFlexedElement } from "./UI/Base/CenterFlexedElement"; import {CenterFlexedElement} from "./UI/Base/CenterFlexedElement";
import { FixedUiElement } from "./UI/Base/FixedUiElement"; import {FixedUiElement} from "./UI/Base/FixedUiElement";
import Toggle from "./UI/Input/Toggle"; import Toggle from "./UI/Input/Toggle";
import { Basemap } from "./UI/BigComponents/Basemap"; import {Basemap} from "./UI/BigComponents/Basemap";
import State from "./State"; import State from "./State";
import LoadFromOverpass from "./Logic/Actors/OverpassFeatureSource"; import LoadFromOverpass from "./Logic/Actors/OverpassFeatureSource";
import { UIEventSource } from "./Logic/UIEventSource"; import {UIEventSource} from "./Logic/UIEventSource";
import { QueryParameters } from "./Logic/Web/QueryParameters"; import {QueryParameters} from "./Logic/Web/QueryParameters";
import StrayClickHandler from "./Logic/Actors/StrayClickHandler"; import StrayClickHandler from "./Logic/Actors/StrayClickHandler";
import SimpleAddUI from "./UI/BigComponents/SimpleAddUI"; import SimpleAddUI from "./UI/BigComponents/SimpleAddUI";
import CenterMessageBox from "./UI/CenterMessageBox"; import CenterMessageBox from "./UI/CenterMessageBox";
import UserBadge from "./UI/BigComponents/UserBadge"; import UserBadge from "./UI/BigComponents/UserBadge";
import SearchAndGo from "./UI/BigComponents/SearchAndGo"; import SearchAndGo from "./UI/BigComponents/SearchAndGo";
import GeoLocationHandler from "./Logic/Actors/GeoLocationHandler"; import GeoLocationHandler from "./Logic/Actors/GeoLocationHandler";
import { LocalStorageSource } from "./Logic/Web/LocalStorageSource"; import {LocalStorageSource} from "./Logic/Web/LocalStorageSource";
import { Utils } from "./Utils"; import {Utils} from "./Utils";
import Svg from "./Svg"; import Svg from "./Svg";
import Link from "./UI/Base/Link"; import Link from "./UI/Base/Link";
import * as personal from "./assets/themes/personalLayout/personalLayout.json"; import * as personal from "./assets/themes/personalLayout/personalLayout.json";
@ -34,17 +34,16 @@ import MapControlButton from "./UI/MapControlButton";
import Combine from "./UI/Base/Combine"; import Combine from "./UI/Base/Combine";
import SelectedFeatureHandler from "./Logic/Actors/SelectedFeatureHandler"; import SelectedFeatureHandler from "./Logic/Actors/SelectedFeatureHandler";
import LZString from "lz-string"; import LZString from "lz-string";
import { LayoutConfigJson } from "./Customizations/JSON/LayoutConfigJson"; import {LayoutConfigJson} from "./Customizations/JSON/LayoutConfigJson";
import AttributionPanel from "./UI/BigComponents/AttributionPanel"; import AttributionPanel from "./UI/BigComponents/AttributionPanel";
import ContributorCount from "./Logic/ContributorCount"; import ContributorCount from "./Logic/ContributorCount";
import FeatureSource from "./Logic/FeatureSource/FeatureSource"; import FeatureSource from "./Logic/FeatureSource/FeatureSource";
import AllKnownLayers from "./Customizations/AllKnownLayers"; import AllKnownLayers from "./Customizations/AllKnownLayers";
import LayerConfig from "./Customizations/JSON/LayerConfig"; import LayerConfig from "./Customizations/JSON/LayerConfig";
import AvailableBaseLayers from "./Logic/Actors/AvailableBaseLayers"; import AvailableBaseLayers from "./Logic/Actors/AvailableBaseLayers";
import { SimpleMapScreenshoter } from "leaflet-simple-map-screenshoter"; import {SimpleMapScreenshoter} from "leaflet-simple-map-screenshoter";
import jsPDF from "jspdf"; import jsPDF from "jspdf";
import FilterView from "./UI/BigComponents/FilterView"; import {TagsFilter} from "./Logic/Tags/TagsFilter";
import { TagsFilter } from "./Logic/Tags/TagsFilter";
export class InitUiElements { export class InitUiElements {
static InitAll( static InitAll(

View file

@ -1,6 +1,6 @@
import FeatureSource from "./FeatureSource"; import FeatureSource from "./FeatureSource";
import {UIEventSource} from "../UIEventSource"; import {UIEventSource} from "../UIEventSource";
import LayerConfig from "../../Customizations/JSON/LayerConfig"; import FilteredLayer from "../../Models/FilteredLayer";
/** /**
@ -13,7 +13,7 @@ export default class FeatureDuplicatorPerLayer implements FeatureSource {
public readonly name; public readonly name;
constructor(layers: UIEventSource<{ layerDef: LayerConfig }[]>, upstream: FeatureSource) { constructor(layers: UIEventSource<FilteredLayer[]>, upstream: FeatureSource) {
this.name = "FeatureDuplicator of "+upstream.name; this.name = "FeatureDuplicator of "+upstream.name;
this.features = upstream.features.map(features => { this.features = upstream.features.map(features => {
const newFeatures: { feature: any, freshness: Date }[] = []; const newFeatures: { feature: any, freshness: Date }[] = [];

View file

@ -13,6 +13,7 @@ import Loc from "../../Models/Loc";
import GeoJsonSource from "./GeoJsonSource"; import GeoJsonSource from "./GeoJsonSource";
import MetaTaggingFeatureSource from "./MetaTaggingFeatureSource"; import MetaTaggingFeatureSource from "./MetaTaggingFeatureSource";
import RegisteringFeatureSource from "./RegisteringFeatureSource"; import RegisteringFeatureSource from "./RegisteringFeatureSource";
import FilteredLayer from "../../Models/FilteredLayer";
export default class FeaturePipeline implements FeatureSource { export default class FeaturePipeline implements FeatureSource {
@ -20,7 +21,7 @@ export default class FeaturePipeline implements FeatureSource {
public readonly name = "FeaturePipeline" public readonly name = "FeaturePipeline"
constructor(flayers: UIEventSource<{ isDisplayed: UIEventSource<boolean>, layerDef: LayerConfig }[]>, constructor(flayers: UIEventSource<FilteredLayer[]>,
updater: FeatureSource, updater: FeatureSource,
fromOsmApi: FeatureSource, fromOsmApi: FeatureSource,
layout: UIEventSource<LayoutConfig>, layout: UIEventSource<LayoutConfig>,

View file

@ -1,173 +1,169 @@
import FeatureSource from "./FeatureSource"; import FeatureSource from "./FeatureSource";
import { UIEventSource } from "../UIEventSource"; import {UIEventSource} from "../UIEventSource";
import LayerConfig from "../../Customizations/JSON/LayerConfig"; import LayerConfig from "../../Customizations/JSON/LayerConfig";
import Loc from "../../Models/Loc"; import Loc from "../../Models/Loc";
import Hash from "../Web/Hash"; import Hash from "../Web/Hash";
import { TagsFilter } from "../Tags/TagsFilter"; import {TagsFilter} from "../Tags/TagsFilter";
export default class FilteringFeatureSource implements FeatureSource { export default class FilteringFeatureSource implements FeatureSource {
public features: UIEventSource<{ feature: any; freshness: Date }[]> = public features: UIEventSource<{ feature: any; freshness: Date }[]> =
new UIEventSource<{ feature: any; freshness: Date }[]>([]); new UIEventSource<{ feature: any; freshness: Date }[]>([]);
public readonly name = "FilteringFeatureSource"; public readonly name = "FilteringFeatureSource";
constructor( constructor(
layers: UIEventSource< layers: UIEventSource<{
{
isDisplayed: UIEventSource<boolean>;
layerDef: LayerConfig;
appliedFilters: UIEventSource<TagsFilter>;
}[]
>,
location: UIEventSource<Loc>,
selectedElement: UIEventSource<any>,
upstream: FeatureSource
) {
const self = this;
function update() {
const layerDict = {};
if (layers.data.length == 0) {
console.warn("No layers defined!");
return;
}
for (const layer of layers.data) {
layerDict[layer.layerDef.id] = layer;
}
const features: { feature: any; freshness: Date }[] =
upstream.features.data;
const missingLayers = new Set<string>();
const newFeatures = features.filter((f) => {
const layerId = f.feature._matching_layer_id;
if (
selectedElement.data?.id === f.feature.id ||
f.feature.id === Hash.hash.data
) {
// This is the selected object - it gets a free pass even if zoom is not sufficient
return true;
}
if (layerId !== undefined) {
const layer: {
isDisplayed: UIEventSource<boolean>; isDisplayed: UIEventSource<boolean>;
layerDef: LayerConfig; layerDef: LayerConfig;
appliedFilters: UIEventSource<TagsFilter>; appliedFilters: UIEventSource<TagsFilter>;
} = layerDict[layerId]; }[]>,
if (layer === undefined) { location: UIEventSource<Loc>,
missingLayers.add(layerId); selectedElement: UIEventSource<any>,
return true; upstream: FeatureSource
} ) {
const self = this;
const isShown = layer.layerDef.isShown; function update() {
const tags = f.feature.properties; const layerDict = {};
if (isShown.IsKnown(tags)) { if (layers.data.length == 0) {
const result = layer.layerDef.isShown.GetRenderValue( console.warn("No layers defined!");
f.feature.properties return;
).txt;
if (result !== "yes") {
return false;
} }
} for (const layer of layers.data) {
const prev = layerDict[layer.layerDef.id]
if (FilteringFeatureSource.showLayer(layer, location)) { if (prev !== undefined) {
const tagsFilter = layer.appliedFilters.data; // We have seen this layer before!
if (tagsFilter) { // We prefer the one which has a name
const properties = f.feature.properties; if (layer.layerDef.name === undefined) {
if (!tagsFilter.matchesProperties(properties)) { // This one is hidden, so we skip it
return false; console.log("Ignoring layer selection from ", layer)
} continue;
}
}
layerDict[layer.layerDef.id] = layer;
} }
return true; const features: { feature: any; freshness: Date }[] =
} upstream.features.data;
const missingLayers = new Set<string>();
const newFeatures = features.filter((f) => {
const layerId = f.feature._matching_layer_id;
if (
selectedElement.data?.id === f.feature.id ||
f.feature.id === Hash.hash.data) {
// This is the selected object - it gets a free pass even if zoom is not sufficient or it is filtered away
return true;
}
if (layerId === undefined) {
return false;
}
const layer: {
isDisplayed: UIEventSource<boolean>;
layerDef: LayerConfig;
appliedFilters: UIEventSource<TagsFilter>;
} = layerDict[layerId];
if (layer === undefined) {
missingLayers.add(layerId);
return false;
}
const isShown = layer.layerDef.isShown;
const tags = f.feature.properties;
if (isShown.IsKnown(tags)) {
const result = layer.layerDef.isShown.GetRenderValue(
f.feature.properties
).txt;
if (result !== "yes") {
return false;
}
}
const tagsFilter = layer.appliedFilters.data;
if (tagsFilter) {
if (!tagsFilter.matchesProperties(f.feature.properties)) {
// Hidden by the filter on the layer itself - we want to hide it no matter wat
return false;
}
}
if (!FilteringFeatureSource.showLayer(layer, location)) {
// The layer itself is either disabled or hidden due to zoom constraints
// We should return true, but it might still match some other layer
return false;
}
return true;
});
console.log(
"Filtering layer source: input: ",
upstream.features.data?.length,
"output:",
newFeatures.length
);
self.features.setData(newFeatures);
if (missingLayers.size > 0) {
console.error(
"Some layers were not found: ",
Array.from(missingLayers)
);
}
} }
// Does it match any other layer - e.g. because of a switch?
for (const toCheck of layers.data) { upstream.features.addCallback(() => {
if (!FilteringFeatureSource.showLayer(toCheck, location)) { update();
continue; });
} location
if ( .map((l) => {
toCheck.layerDef.source.osmTags.matchesProperties( // We want something that is stable for the shown layers
f.feature.properties const displayedLayerIndexes = [];
) for (let i = 0; i < layers.data.length; i++) {
) { const layer = layers.data[i];
return true; if (l.zoom < layer.layerDef.minzoom) {
} continue;
} }
return false;
}); if (!layer.isDisplayed.data) {
console.log( continue;
"Filtering layer source: input: ", }
upstream.features.data?.length, displayedLayerIndexes.push(i);
"output:", }
newFeatures.length return displayedLayerIndexes.join(",");
); })
self.features.setData(newFeatures); .addCallback(() => {
if (missingLayers.size > 0) { update();
console.error( });
"Some layers were not found: ",
Array.from(missingLayers) layers.addCallback(update);
);
} const registered = new Set<UIEventSource<boolean>>();
layers.addCallbackAndRun((layers) => {
for (const layer of layers) {
if (registered.has(layer.isDisplayed)) {
continue;
}
registered.add(layer.isDisplayed);
layer.isDisplayed.addCallback(() => update());
layer.appliedFilters.addCallback(() => update());
}
});
update();
} }
upstream.features.addCallback(() => { private static showLayer(
update(); layer: {
}); isDisplayed: UIEventSource<boolean>;
location layerDef: LayerConfig;
.map((l) => { },
// We want something that is stable for the shown layers location: UIEventSource<Loc>
const displayedLayerIndexes = []; ) {
for (let i = 0; i < layers.data.length; i++) { return (
const layer = layers.data[i]; layer.isDisplayed.data &&
if (l.zoom < layer.layerDef.minzoom) { layer.layerDef.minzoomVisible <= location.data.zoom
continue; );
} }
if (l.zoom > layer.layerDef.maxzoom) {
continue;
}
if (!layer.isDisplayed.data) {
continue;
}
displayedLayerIndexes.push(i);
}
return displayedLayerIndexes.join(",");
})
.addCallback(() => {
update();
});
layers.addCallback(update);
const registered = new Set<UIEventSource<boolean>>();
layers.addCallbackAndRun((layers) => {
for (const layer of layers) {
if (registered.has(layer.isDisplayed)) {
continue;
}
registered.add(layer.isDisplayed);
layer.isDisplayed.addCallback(() => update());
layer.appliedFilters.addCallback(() => update());
}
});
update();
}
private static showLayer(
layer: {
isDisplayed: UIEventSource<boolean>;
layerDef: LayerConfig;
},
location: UIEventSource<Loc>
) {
return (
layer.isDisplayed.data &&
layer.layerDef.minzoom <= location.data.zoom &&
layer.layerDef.maxzoom >= location.data.zoom
);
}
} }

View file

@ -50,7 +50,7 @@ export default class GeoJsonSource implements FeatureSource {
* @param locationControl * @param locationControl
* @constructor * @constructor
*/ */
public static ConstructMultiSource(flayers: { isDisplayed: UIEventSource<boolean>, layerDef: LayerConfig }[], locationControl: UIEventSource<Loc>): FeatureSource[] { public static ConstructMultiSource(flayers: { isDisplayed: UIEventSource<boolean>, layerDef: LayerConfig }[], locationControl: UIEventSource<Loc>): GeoJsonSource[] {
const flayersPerSource = new Map<string, { isDisplayed: UIEventSource<boolean>, layerDef: LayerConfig }[]>(); const flayersPerSource = new Map<string, { isDisplayed: UIEventSource<boolean>, layerDef: LayerConfig }[]>();
for (const flayer of flayers) { for (const flayer of flayers) {
@ -65,7 +65,7 @@ export default class GeoJsonSource implements FeatureSource {
flayersPerSource.get(url).push(flayer) flayersPerSource.get(url).push(flayer)
} }
const sources: FeatureSource[] = [] const sources: GeoJsonSource[] = []
flayersPerSource.forEach((flayers, key) => { flayersPerSource.forEach((flayers, key) => {
if (flayers.length == 1) { if (flayers.length == 1) {
@ -118,8 +118,7 @@ export default class GeoJsonSource implements FeatureSource {
return undefined; return undefined;
} }
if (location.zoom < flayer.layerDef.minzoom || if (location.zoom < flayer.layerDef.minzoom) {
location.zoom > flayer.layerDef.maxzoom) {
// No need to download! - the layer is disabled // No need to download! - the layer is disabled
return undefined; return undefined;
} }

View file

@ -1,25 +0,0 @@
import FeatureSource from "./FeatureSource";
import {UIEventSource} from "../UIEventSource";
import LayerConfig from "../../Customizations/JSON/LayerConfig";
export default class ZoomRespectingFeatureSource implements FeatureSource{
public readonly features: UIEventSource<{ feature: any; freshness: Date }[]>;
public readonly name: string;
constructor(layerConfig: LayerConfig, location: UIEventSource<{zoom: number}>, upstream: FeatureSource) {
this.name = "zoomrespecting("+upstream.name+")"
const empty = []
this.features = upstream.features.map(
features => {
const z = location.data.zoom
if(layerConfig.minzoom < z || layerConfig.maxzoom > z){
return empty
}
return features
},[location]
)
}
}

9
Models/FilteredLayer.ts Normal file
View file

@ -0,0 +1,9 @@
import {UIEventSource} from "../Logic/UIEventSource";
import {TagsFilter} from "../Logic/Tags/TagsFilter";
import LayerConfig from "../Customizations/JSON/LayerConfig";
export default interface FilteredLayer {
readonly isDisplayed: UIEventSource<boolean>;
readonly appliedFilters: UIEventSource<TagsFilter>;
readonly layerDef: LayerConfig;
}

View file

@ -21,6 +21,7 @@ import {Relation} from "./Logic/Osm/ExtractRelations";
import OsmApiFeatureSource from "./Logic/FeatureSource/OsmApiFeatureSource"; import OsmApiFeatureSource from "./Logic/FeatureSource/OsmApiFeatureSource";
import FeaturePipeline from "./Logic/FeatureSource/FeaturePipeline"; import FeaturePipeline from "./Logic/FeatureSource/FeaturePipeline";
import { TagsFilter } from "./Logic/Tags/TagsFilter"; import { TagsFilter } from "./Logic/Tags/TagsFilter";
import FilteredLayer from "./Models/FilteredLayer";
/** /**
* Contains the global state: a bunch of UI-event sources * Contains the global state: a bunch of UI-event sources
@ -61,15 +62,7 @@ export default class State {
public osmApiFeatureSource: OsmApiFeatureSource; public osmApiFeatureSource: OsmApiFeatureSource;
public filteredLayers: UIEventSource<{ public filteredLayers: UIEventSource<FilteredLayer[]> = new UIEventSource<FilteredLayer[]>([]);
readonly isDisplayed: UIEventSource<boolean>;
readonly appliedFilters: UIEventSource<TagsFilter>;
readonly layerDef: LayerConfig;
}[]> = new UIEventSource<{
readonly isDisplayed: UIEventSource<boolean>;
readonly appliedFilters: UIEventSource<TagsFilter>;
readonly layerDef: LayerConfig;
}[]>([]);
/** /**
The latest element that was selected The latest element that was selected

View file

@ -23,7 +23,7 @@ export class VariableUiElement extends BaseUIElement {
el.innerHTML = contents; el.innerHTML = contents;
} else if (contents instanceof Array) { } else if (contents instanceof Array) {
for (const content of contents) { for (const content of contents) {
const c = content.ConstructElement(); const c = content?.ConstructElement();
if (c !== undefined && c !== null) { if (c !== undefined && c !== null) {
el.appendChild(c); el.appendChild(c);
} }

View file

@ -13,6 +13,9 @@ import {TagsFilter} from "../../Logic/Tags/TagsFilter";
import {And} from "../../Logic/Tags/And"; import {And} from "../../Logic/Tags/And";
import {UIEventSource} from "../../Logic/UIEventSource"; import {UIEventSource} from "../../Logic/UIEventSource";
import BaseUIElement from "../BaseUIElement"; import BaseUIElement from "../BaseUIElement";
import State from "../../State";
import {control} from "leaflet";
/** /**
* Shows the filter * Shows the filter
@ -22,12 +25,16 @@ export default class FilterView extends VariableUiElement {
constructor(filteredLayer) { constructor(filteredLayer) {
super( super(
filteredLayer.map((filteredLayers) => filteredLayer.map((filteredLayers) =>
filteredLayers.map(FilterView.createOneFilteredLayerElement) filteredLayers.map(l => FilterView.createOneFilteredLayerElement(l))
) )
); );
} }
static createOneFilteredLayerElement(filteredLayer) { private static createOneFilteredLayerElement(filteredLayer) {
if(filteredLayer.layerDef.name === undefined){
// Name is not defined: we hide this one
return undefined;
}
const iconStyle = "width:1.5rem;height:1.5rem;margin-left:1.25rem"; const iconStyle = "width:1.5rem;height:1.5rem;margin-left:1.25rem";
const icon = new Combine([Svg.checkbox_filled]).SetStyle(iconStyle); const icon = new Combine([Svg.checkbox_filled]).SetStyle(iconStyle);
@ -52,9 +59,19 @@ export default class FilterView extends VariableUiElement {
.Clone() .Clone()
.SetStyle("font-size:large;padding-left:1.25rem"); .SetStyle("font-size:large;padding-left:1.25rem");
const zoomStatus =
new Toggle(
undefined,
Translations.t.general.layerSelection.zoomInToSeeThisLayer.Clone()
.SetClass("alert")
.SetStyle("display: block ruby;width:min-content;"),
State.state.locationControl.map(location =>location.zoom > filteredLayer.layerDef.minzoom )
)
const style = const style =
"display:flex;align-items:center;color:#007759;padding:0.5rem 0;"; "display:flex;align-items:center;color:#007759;padding:0.5rem 0;";
const layerChecked = new Combine([icon, styledNameChecked]) const layerChecked = new Combine([icon, styledNameChecked, zoomStatus])
.SetStyle(style) .SetStyle(style)
.onClick(() => filteredLayer.isDisplayed.setData(false)); .onClick(() => filteredLayer.isDisplayed.setData(false));
@ -62,25 +79,27 @@ export default class FilterView extends VariableUiElement {
.SetStyle(style) .SetStyle(style)
.onClick(() => filteredLayer.isDisplayed.setData(true)); .onClick(() => filteredLayer.isDisplayed.setData(true));
const filterPanel: BaseUIElement = FilterView.createFilterPanel(filteredLayer) const filterPanel: BaseUIElement = FilterView.createFilterPanel(filteredLayer)
return new Toggle( return new Toggle(
new Combine([layerChecked, filterPanel]), new Combine([layerChecked, filterPanel]),
layerNotChecked, layerNotChecked,
filteredLayer.isDisplayed filteredLayer.isDisplayed
); );
} }
static createFilterPanel(flayer: { static createFilterPanel(flayer: {
layerDef: LayerConfig, layerDef: LayerConfig,
appliedFilters: UIEventSource<TagsFilter> appliedFilters: UIEventSource<TagsFilter>
}): BaseUIElement{ }): BaseUIElement {
const layer = flayer.layerDef const layer = flayer.layerDef
if(layer.filters.length === 0){ if (layer.filters.length === 0) {
return undefined; return undefined;
} }
let listFilterElements: [BaseUIElement, UIEventSource<TagsFilter>][] = layer.filters.map( let listFilterElements: [BaseUIElement, UIEventSource<TagsFilter>][] = layer.filters.map(
FilterView.createFilter FilterView.createFilter
); );

View file

@ -1,6 +1,5 @@
import State from "../../State"; import State from "../../State";
import BackgroundSelector from "./BackgroundSelector"; import BackgroundSelector from "./BackgroundSelector";
import LayerSelection from "./LayerSelection";
import Combine from "../Base/Combine"; import Combine from "../Base/Combine";
import ScrollableFullScreen from "../Base/ScrollableFullScreen"; import ScrollableFullScreen from "../Base/ScrollableFullScreen";
import Translations from "../i18n/Translations"; import Translations from "../i18n/Translations";

View file

@ -1,81 +0,0 @@
import {UIEventSource} from "../../Logic/UIEventSource";
import {VariableUiElement} from "../Base/VariableUIElement";
import State from "../../State";
import Toggle from "../Input/Toggle";
import Combine from "../Base/Combine";
import Translations from "../i18n/Translations";
import LayerConfig from "../../Customizations/JSON/LayerConfig";
import BaseUIElement from "../BaseUIElement";
import {Translation} from "../i18n/Translation";
/**
* Shows the panel with all layers and a toggle for each of them
*/
export default class LayerSelection extends Combine {
constructor(activeLayers: UIEventSource<{
readonly isDisplayed: UIEventSource<boolean>,
readonly layerDef: LayerConfig;
}[]>) {
if (activeLayers === undefined) {
throw "ActiveLayers should be defined..."
}
const checkboxes: BaseUIElement[] = [];
for (const layer of activeLayers.data) {
const leafletStyle = layer.layerDef.GenerateLeafletStyle(
new UIEventSource<any>({id: "node/-1"}),
false)
const leafletStyleNa = layer.layerDef.GenerateLeafletStyle(
new UIEventSource<any>({id: "node/-1"}),
false)
const icon = new Combine([leafletStyle.icon.html]).SetClass("single-layer-selection-toggle")
let iconUnselected: BaseUIElement = new Combine([leafletStyleNa.icon.html])
.SetClass("single-layer-selection-toggle")
.SetStyle("opacity:0.2;");
if (layer.layerDef.name === undefined) {
continue;
}
const name: Translation = Translations.WT(layer.layerDef.name)?.Clone()
name.SetStyle("font-size:large;margin-left: 0.5em;");
const zoomStatus = new VariableUiElement(State.state.locationControl.map(location => {
if (location.zoom < layer.layerDef.minzoom) {
return Translations.t.general.layerSelection.zoomInToSeeThisLayer.Clone()
.SetClass("alert")
.SetStyle("display: block ruby;width:min-content;")
}
return ""
}))
const zoomStatusNonActive = new VariableUiElement(State.state.locationControl.map(location => {
if (location.zoom < layer.layerDef.minzoom) {
return Translations.t.general.layerSelection.zoomInToSeeThisLayer.Clone()
.SetClass("alert")
.SetStyle("display: block ruby;width:min-content;")
}
return ""
}))
const style = "display:flex;align-items:center;"
const styleWhole = "display:flex; flex-wrap: wrap"
checkboxes.push(new Toggle(
new Combine([new Combine([icon, name.Clone()]).SetStyle(style), zoomStatus])
.SetStyle(styleWhole),
new Combine([new Combine([iconUnselected, "<del>", name.Clone(), "</del>"]).SetStyle(style), zoomStatusNonActive])
.SetStyle(styleWhole),
layer.isDisplayed).ToggleOnClick()
.SetStyle("margin:0.3em;")
);
}
super(checkboxes)
this.SetStyle("display:flex;flex-direction:column;")
}
}

View file

@ -118,7 +118,7 @@ export class RadioButton<T> extends InputElement<T> {
const label = document.createElement("label"); const label = document.createElement("label");
label.appendChild(labelHtml); label.appendChild(labelHtml);
label.htmlFor = input.id; label.htmlFor = input.id;
label.classList.add("block", "w-full", "cursor-pointer", "bg-red"); label.classList.add("flex", "w-full", "cursor-pointer", "bg-red");
if (!this._dontStyle) { if (!this._dontStyle) {
labelHtml.classList.add("p-2") labelHtml.classList.add("p-2")

View file

@ -19,7 +19,7 @@ export default class Translations {
static T(t: string | any, context = undefined): Translation { static T(t: string | any, context = undefined): Translation {
if(t === undefined){ if(t === undefined || t === null){
return undefined; return undefined;
} }
if(typeof t === "string"){ if(typeof t === "string"){
@ -38,7 +38,7 @@ export default class Translations {
private static wtcache = {} private static wtcache = {}
public static WT(s: string | Translation): Translation { public static WT(s: string | Translation): Translation {
if(s === undefined){ if(s === undefined || s === null){
return undefined; return undefined;
} }
if (typeof (s) === "string") { if (typeof (s) === "string") {

View file

@ -48,7 +48,7 @@
:root { :root {
--subtle-detail-color: #007759; --subtle-detail-color: #e5f5ff;
--subtle-detail-color-contrast: black; --subtle-detail-color-contrast: black;
--subtle-detail-color-light-contrast: lightgrey; --subtle-detail-color-light-contrast: lightgrey;