forked from MapComplete/MapComplete
		
	Finetuning of the filter functionality
This commit is contained in:
		
							parent
							
								
									31d2bd83b9
								
							
						
					
					
						commit
						79569f5119
					
				
					 17 changed files with 219 additions and 309 deletions
				
			
		|  | @ -34,6 +34,7 @@ export default class LayerConfig { | |||
|     passAllFeatures: boolean; | ||||
|     isShown: TagRenderingConfig; | ||||
|     minzoom: number; | ||||
|     minzoomVisible: number; | ||||
|     maxzoom:number; | ||||
|     title?: TagRenderingConfig; | ||||
|     titleIcons: TagRenderingConfig[]; | ||||
|  | @ -143,7 +144,7 @@ export default class LayerConfig { | |||
|         this.doNotDownload = json.doNotDownload ?? false; | ||||
|         this.passAllFeatures = json.passAllFeatures ?? false; | ||||
|         this.minzoom = json.minzoom ?? 0; | ||||
|         this.maxzoom = json.maxzoom ?? 1000; | ||||
|         this.minzoomVisible = json.minzoomVisible ?? this.minzoom; | ||||
|         this.wayHandling = json.wayHandling ?? 0; | ||||
|         this.presets = (json.presets ?? []).map((pr, i) => { | ||||
|             if (pr.preciseInput === true) { | ||||
|  |  | |||
|  | @ -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 | ||||
|      */ | ||||
|     minzoom?: number; | ||||
| 
 | ||||
|     /** | ||||
|      * The zoomlevel at which point the data is hidden again | ||||
|      * Default: 100 (thus: always visible | ||||
|      * If zoomed out below this zoomlevel, the data will be hidden. | ||||
|      * Default: minzoom | ||||
|      */ | ||||
|     maxzoom?: number; | ||||
|     minzoomVisible?: number; | ||||
| 
 | ||||
|     /** | ||||
|      * The title shown in a popup for elements of this layer. | ||||
|  |  | |||
|  | @ -43,7 +43,6 @@ import LayerConfig from "./Customizations/JSON/LayerConfig"; | |||
| import AvailableBaseLayers from "./Logic/Actors/AvailableBaseLayers"; | ||||
| import {SimpleMapScreenshoter} from "leaflet-simple-map-screenshoter"; | ||||
| import jsPDF from "jspdf"; | ||||
| import FilterView from "./UI/BigComponents/FilterView"; | ||||
| import {TagsFilter} from "./Logic/Tags/TagsFilter"; | ||||
| 
 | ||||
| export class InitUiElements { | ||||
|  |  | |||
|  | @ -1,6 +1,6 @@ | |||
| import FeatureSource from "./FeatureSource"; | ||||
| 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; | ||||
| 
 | ||||
|     constructor(layers: UIEventSource<{ layerDef: LayerConfig }[]>, upstream: FeatureSource) { | ||||
|     constructor(layers: UIEventSource<FilteredLayer[]>, upstream: FeatureSource) { | ||||
|         this.name = "FeatureDuplicator of "+upstream.name; | ||||
|         this.features = upstream.features.map(features => { | ||||
|             const newFeatures: { feature: any, freshness: Date }[] = []; | ||||
|  |  | |||
|  | @ -13,6 +13,7 @@ import Loc from "../../Models/Loc"; | |||
| import GeoJsonSource from "./GeoJsonSource"; | ||||
| import MetaTaggingFeatureSource from "./MetaTaggingFeatureSource"; | ||||
| import RegisteringFeatureSource from "./RegisteringFeatureSource"; | ||||
| import FilteredLayer from "../../Models/FilteredLayer"; | ||||
| 
 | ||||
| export default class FeaturePipeline implements FeatureSource { | ||||
| 
 | ||||
|  | @ -20,7 +21,7 @@ export default class FeaturePipeline implements FeatureSource { | |||
| 
 | ||||
|     public readonly name = "FeaturePipeline" | ||||
| 
 | ||||
|     constructor(flayers: UIEventSource<{ isDisplayed: UIEventSource<boolean>, layerDef: LayerConfig }[]>, | ||||
|     constructor(flayers: UIEventSource<FilteredLayer[]>, | ||||
|                 updater: FeatureSource, | ||||
|                 fromOsmApi: FeatureSource, | ||||
|                 layout: UIEventSource<LayoutConfig>, | ||||
|  |  | |||
|  | @ -11,13 +11,11 @@ export default class FilteringFeatureSource implements FeatureSource { | |||
|     public readonly name = "FilteringFeatureSource"; | ||||
| 
 | ||||
|     constructor( | ||||
|     layers: UIEventSource< | ||||
|       { | ||||
|         layers: UIEventSource<{ | ||||
|             isDisplayed: UIEventSource<boolean>; | ||||
|             layerDef: LayerConfig; | ||||
|             appliedFilters: UIEventSource<TagsFilter>; | ||||
|       }[] | ||||
|     >, | ||||
|         }[]>, | ||||
|         location: UIEventSource<Loc>, | ||||
|         selectedElement: UIEventSource<any>, | ||||
|         upstream: FeatureSource | ||||
|  | @ -31,6 +29,16 @@ export default class FilteringFeatureSource implements FeatureSource { | |||
|                 return; | ||||
|             } | ||||
|             for (const layer of layers.data) { | ||||
|                 const prev = layerDict[layer.layerDef.id] | ||||
|                 if (prev !== undefined) { | ||||
|                     // We have seen this layer before!
 | ||||
|                     // We prefer the one which has a name
 | ||||
|                     if (layer.layerDef.name === undefined) { | ||||
|                         // This one is hidden, so we skip it
 | ||||
|                         console.log("Ignoring layer selection from ", layer) | ||||
|                         continue; | ||||
|                     } | ||||
|                 } | ||||
|                 layerDict[layer.layerDef.id] = layer; | ||||
|             } | ||||
| 
 | ||||
|  | @ -44,13 +52,14 @@ export default class FilteringFeatureSource implements FeatureSource { | |||
| 
 | ||||
|                 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
 | ||||
|                     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) { | ||||
|                 if (layerId === undefined) { | ||||
|                     return false; | ||||
|                 } | ||||
|                 const layer: { | ||||
|                     isDisplayed: UIEventSource<boolean>; | ||||
|                     layerDef: LayerConfig; | ||||
|  | @ -58,7 +67,7 @@ export default class FilteringFeatureSource implements FeatureSource { | |||
|                 } = layerDict[layerId]; | ||||
|                 if (layer === undefined) { | ||||
|                     missingLayers.add(layerId); | ||||
|             return true; | ||||
|                     return false; | ||||
|                 } | ||||
| 
 | ||||
|                 const isShown = layer.layerDef.isShown; | ||||
|  | @ -72,32 +81,22 @@ export default class FilteringFeatureSource implements FeatureSource { | |||
|                     } | ||||
|                 }  | ||||
|                  | ||||
|           if (FilteringFeatureSource.showLayer(layer, location)) { | ||||
|                 const tagsFilter = layer.appliedFilters.data; | ||||
|                 if (tagsFilter) { | ||||
|               const properties = f.feature.properties; | ||||
|               if (!tagsFilter.matchesProperties(properties)) { | ||||
|                     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; | ||||
|           } | ||||
|         } | ||||
|         // Does it match any other layer - e.g. because of a switch?
 | ||||
|         for (const toCheck of layers.data) { | ||||
|           if (!FilteringFeatureSource.showLayer(toCheck, location)) { | ||||
|             continue; | ||||
|           } | ||||
|           if ( | ||||
|             toCheck.layerDef.source.osmTags.matchesProperties( | ||||
|               f.feature.properties | ||||
|             ) | ||||
|           ) { | ||||
|             return true; | ||||
|           } | ||||
|         } | ||||
|         return false; | ||||
|             }); | ||||
|             console.log( | ||||
|                 "Filtering layer source: input: ", | ||||
|  | @ -126,9 +125,7 @@ export default class FilteringFeatureSource implements FeatureSource { | |||
|                     if (l.zoom < layer.layerDef.minzoom) { | ||||
|                         continue; | ||||
|                     } | ||||
|           if (l.zoom > layer.layerDef.maxzoom) { | ||||
|             continue; | ||||
|           } | ||||
|                   | ||||
|                     if (!layer.isDisplayed.data) { | ||||
|                         continue; | ||||
|                     } | ||||
|  | @ -166,8 +163,7 @@ export default class FilteringFeatureSource implements FeatureSource { | |||
|     ) { | ||||
|         return ( | ||||
|             layer.isDisplayed.data && | ||||
|       layer.layerDef.minzoom <= location.data.zoom && | ||||
|       layer.layerDef.maxzoom >= location.data.zoom | ||||
|             layer.layerDef.minzoomVisible <= location.data.zoom | ||||
|         ); | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -50,7 +50,7 @@ export default class GeoJsonSource implements FeatureSource { | |||
|      * @param locationControl | ||||
|      * @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 }[]>(); | ||||
|         for (const flayer of flayers) { | ||||
|  | @ -65,7 +65,7 @@ export default class GeoJsonSource implements FeatureSource { | |||
|             flayersPerSource.get(url).push(flayer) | ||||
|         } | ||||
| 
 | ||||
|         const sources: FeatureSource[] = [] | ||||
|         const sources: GeoJsonSource[] = [] | ||||
| 
 | ||||
|         flayersPerSource.forEach((flayers, key) => { | ||||
|             if (flayers.length == 1) { | ||||
|  | @ -118,8 +118,7 @@ export default class GeoJsonSource implements FeatureSource { | |||
|                     return undefined; | ||||
|                 } | ||||
| 
 | ||||
|                 if (location.zoom < flayer.layerDef.minzoom || | ||||
|                     location.zoom > flayer.layerDef.maxzoom) { | ||||
|                 if (location.zoom < flayer.layerDef.minzoom) { | ||||
|                     // No need to download! - the layer is disabled
 | ||||
|                     return undefined; | ||||
|                 } | ||||
|  |  | |||
|  | @ -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
									
								
							
							
						
						
									
										9
									
								
								Models/FilteredLayer.ts
									
										
									
									
									
										Normal 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; | ||||
| } | ||||
							
								
								
									
										11
									
								
								State.ts
									
										
									
									
									
								
							
							
						
						
									
										11
									
								
								State.ts
									
										
									
									
									
								
							|  | @ -21,6 +21,7 @@ import {Relation} from "./Logic/Osm/ExtractRelations"; | |||
| import OsmApiFeatureSource from "./Logic/FeatureSource/OsmApiFeatureSource"; | ||||
| import FeaturePipeline from "./Logic/FeatureSource/FeaturePipeline"; | ||||
| import { TagsFilter } from "./Logic/Tags/TagsFilter"; | ||||
| import FilteredLayer from "./Models/FilteredLayer"; | ||||
| 
 | ||||
| /** | ||||
|  * Contains the global state: a bunch of UI-event sources | ||||
|  | @ -61,15 +62,7 @@ export default class State { | |||
| 
 | ||||
|     public osmApiFeatureSource: OsmApiFeatureSource; | ||||
| 
 | ||||
|     public filteredLayers: UIEventSource<{ | ||||
|         readonly isDisplayed: UIEventSource<boolean>; | ||||
|         readonly appliedFilters: UIEventSource<TagsFilter>; | ||||
|         readonly layerDef: LayerConfig; | ||||
|     }[]> = new UIEventSource<{ | ||||
|         readonly isDisplayed: UIEventSource<boolean>; | ||||
|         readonly appliedFilters: UIEventSource<TagsFilter>; | ||||
|         readonly layerDef: LayerConfig; | ||||
|     }[]>([]); | ||||
|     public filteredLayers: UIEventSource<FilteredLayer[]> = new UIEventSource<FilteredLayer[]>([]); | ||||
| 
 | ||||
|     /** | ||||
|      The latest element that was selected | ||||
|  |  | |||
|  | @ -23,7 +23,7 @@ export class VariableUiElement extends BaseUIElement { | |||
|         el.innerHTML = contents; | ||||
|       } else if (contents instanceof Array) { | ||||
|         for (const content of contents) { | ||||
|           const c = content.ConstructElement(); | ||||
|           const c = content?.ConstructElement(); | ||||
|           if (c !== undefined && c !== null) { | ||||
|             el.appendChild(c); | ||||
|           } | ||||
|  |  | |||
|  | @ -13,6 +13,9 @@ import {TagsFilter} from "../../Logic/Tags/TagsFilter"; | |||
| import {And} from "../../Logic/Tags/And"; | ||||
| import {UIEventSource} from "../../Logic/UIEventSource"; | ||||
| import BaseUIElement from "../BaseUIElement"; | ||||
| import State from "../../State"; | ||||
| import {control} from "leaflet"; | ||||
| 
 | ||||
| 
 | ||||
| /** | ||||
|  * Shows the filter | ||||
|  | @ -22,12 +25,16 @@ export default class FilterView extends VariableUiElement { | |||
|     constructor(filteredLayer) { | ||||
|         super( | ||||
|             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 icon = new Combine([Svg.checkbox_filled]).SetStyle(iconStyle); | ||||
|  | @ -52,9 +59,19 @@ export default class FilterView extends VariableUiElement { | |||
|             .Clone() | ||||
|             .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 = | ||||
|             "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) | ||||
|             .onClick(() => filteredLayer.isDisplayed.setData(false)); | ||||
| 
 | ||||
|  | @ -65,6 +82,8 @@ export default class FilterView extends VariableUiElement { | |||
| 
 | ||||
|         const filterPanel: BaseUIElement = FilterView.createFilterPanel(filteredLayer) | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|         return new Toggle( | ||||
|             new Combine([layerChecked, filterPanel]), | ||||
|             layerNotChecked, | ||||
|  |  | |||
|  | @ -1,6 +1,5 @@ | |||
| import State from "../../State"; | ||||
| import BackgroundSelector from "./BackgroundSelector"; | ||||
| import LayerSelection from "./LayerSelection"; | ||||
| import Combine from "../Base/Combine"; | ||||
| import ScrollableFullScreen from "../Base/ScrollableFullScreen"; | ||||
| import Translations from "../i18n/Translations"; | ||||
|  |  | |||
|  | @ -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;") | ||||
| 
 | ||||
|     } | ||||
| } | ||||
|  | @ -118,7 +118,7 @@ export class RadioButton<T> extends InputElement<T> { | |||
|             const label = document.createElement("label"); | ||||
|             label.appendChild(labelHtml); | ||||
|             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) { | ||||
|                 labelHtml.classList.add("p-2") | ||||
|  |  | |||
|  | @ -19,7 +19,7 @@ export default class Translations { | |||
| 
 | ||||
| 
 | ||||
|     static T(t: string | any, context = undefined): Translation { | ||||
|         if(t === undefined){ | ||||
|         if(t === undefined || t === null){ | ||||
|             return undefined; | ||||
|         } | ||||
|         if(typeof t === "string"){ | ||||
|  | @ -38,7 +38,7 @@ export default class Translations { | |||
| 
 | ||||
|     private static wtcache = {} | ||||
|     public static WT(s: string | Translation): Translation { | ||||
|         if(s === undefined){ | ||||
|         if(s === undefined || s === null){ | ||||
|             return undefined; | ||||
|         } | ||||
|         if (typeof (s) === "string") { | ||||
|  |  | |||
|  | @ -48,7 +48,7 @@ | |||
| 
 | ||||
| 
 | ||||
| :root { | ||||
|     --subtle-detail-color: #007759; | ||||
|     --subtle-detail-color: #e5f5ff; | ||||
|     --subtle-detail-color-contrast: black; | ||||
|     --subtle-detail-color-light-contrast: lightgrey; | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue