forked from MapComplete/MapComplete
		
	start creating extra filter
This commit is contained in:
		
							parent
							
								
									97c85d6909
								
							
						
					
					
						commit
						e9160504a6
					
				
					 7 changed files with 872 additions and 800 deletions
				
			
		
							
								
								
									
										1057
									
								
								InitUiElements.ts
									
										
									
									
									
								
							
							
						
						
									
										1057
									
								
								InitUiElements.ts
									
										
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							|  | @ -1,134 +1,174 @@ | ||||||
| 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"; | ||||||
| 
 | 
 | ||||||
| export default class FilteringFeatureSource implements FeatureSource { | export default class FilteringFeatureSource implements FeatureSource { | ||||||
|     public features: UIEventSource<{ feature: any; freshness: Date }[]> = new UIEventSource<{ feature: any; freshness: Date }[]>([]); |   public features: UIEventSource<{ feature: any; freshness: Date }[]> = | ||||||
|     public readonly name = "FilteringFeatureSource" |     new UIEventSource<{ feature: any; freshness: Date }[]>([]); | ||||||
|  |   public readonly name = "FilteringFeatureSource"; | ||||||
| 
 | 
 | ||||||
|     constructor(layers: UIEventSource<{ |   constructor( | ||||||
|                     isDisplayed: UIEventSource<boolean>, |     layers: UIEventSource< | ||||||
|                     layerDef: LayerConfig |       { | ||||||
|                 }[]>, |         isDisplayed: UIEventSource<boolean>; | ||||||
|                 location: UIEventSource<Loc>, |         layerDef: LayerConfig; | ||||||
|                 selectedElement: UIEventSource<any>, |         appliedFilters: UIEventSource<TagsFilter>; | ||||||
|                 upstream: FeatureSource) { |       }[] | ||||||
|  |     >, | ||||||
|  |     location: UIEventSource<Loc>, | ||||||
|  |     selectedElement: UIEventSource<any>, | ||||||
|  |     upstream: FeatureSource | ||||||
|  |   ) { | ||||||
|  |     const self = this; | ||||||
| 
 | 
 | ||||||
|         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; | ||||||
|  |       } | ||||||
| 
 | 
 | ||||||
|         function update() { |       const features: { feature: any; freshness: Date }[] = | ||||||
|  |         upstream.features.data; | ||||||
| 
 | 
 | ||||||
|             const layerDict = {}; |       const missingLayers = new Set<string>(); | ||||||
|             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 newFeatures = features.filter((f) => { | ||||||
|  |         const layerId = f.feature._matching_layer_id; | ||||||
| 
 | 
 | ||||||
|             const missingLayers = new Set<string>(); |         if ( | ||||||
| 
 |           selectedElement.data?.id === f.feature.id || | ||||||
|             const newFeatures = features.filter(f => { |           f.feature.id === Hash.hash.data | ||||||
|                 const layerId = f.feature._matching_layer_id; |         ) { | ||||||
|                  |           // This is the selected object - it gets a free pass even if zoom is not sufficient
 | ||||||
|                 if(selectedElement.data?.id === f.feature.id || f.feature.id === Hash.hash.data){ |           return true; | ||||||
|                     // 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>, |  | ||||||
|                         layerDef: LayerConfig |  | ||||||
|                     } = layerDict[layerId]; |  | ||||||
|                     if (layer === undefined) { |  | ||||||
|                         missingLayers.add(layerId) |  | ||||||
|                         return true; |  | ||||||
|                     } |  | ||||||
| 
 |  | ||||||
|                     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; |  | ||||||
|                         } |  | ||||||
|                     } |  | ||||||
| 
 |  | ||||||
|                     if (FilteringFeatureSource.showLayer(layer, location)) { |  | ||||||
|                         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: ", 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)) |  | ||||||
|             } |  | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  |         if (layerId !== undefined) { | ||||||
|  |           const layer: { | ||||||
|  |             isDisplayed: UIEventSource<boolean>; | ||||||
|  |             layerDef: LayerConfig; | ||||||
|  |             appliedFilters: UIEventSource<TagsFilter>; | ||||||
|  |           } = layerDict[layerId]; | ||||||
|  |           if (layer === undefined) { | ||||||
|  |             missingLayers.add(layerId); | ||||||
|  |             return true; | ||||||
|  |           } | ||||||
| 
 | 
 | ||||||
|         upstream.features.addCallback(() => { |           const isShown = layer.layerDef.isShown; | ||||||
|             update() |           const tags = f.feature.properties; | ||||||
|         }); |           if (isShown.IsKnown(tags)) { | ||||||
|         location.map(l => { |             const result = layer.layerDef.isShown.GetRenderValue( | ||||||
|             // We want something that is stable for the shown layers
 |               f.feature.properties | ||||||
|             const displayedLayerIndexes = []; |             ).txt; | ||||||
|             for (let i = 0; i < layers.data.length; i++) { |             if (result !== "yes") { | ||||||
|                 const layer = layers.data[i]; |               return false; | ||||||
|                 if (l.zoom < layer.layerDef.minzoom) { |  | ||||||
|                     continue; |  | ||||||
|                 } |  | ||||||
|                 if (l.zoom > layer.layerDef.maxzoom) { |  | ||||||
|                     continue; |  | ||||||
|                 } |  | ||||||
|                 if (!layer.isDisplayed.data) { |  | ||||||
|                     continue; |  | ||||||
|                 } |  | ||||||
|                 displayedLayerIndexes.push(i); |  | ||||||
|             } |             } | ||||||
|             return displayedLayerIndexes.join(",") |           } | ||||||
|         }).addCallback(() => { |  | ||||||
|             update(); |  | ||||||
|         }); |  | ||||||
| 
 | 
 | ||||||
|         layers.addCallback(update); |           if (FilteringFeatureSource.showLayer(layer, location)) { | ||||||
|  |             const tagsFilter = layer.appliedFilters.data; | ||||||
| 
 | 
 | ||||||
|         const registered = new Set<UIEventSource<boolean>>(); |             if (tagsFilter) { | ||||||
|         layers.addCallbackAndRun(layers => { |               const properties = f.feature.properties; | ||||||
|             for (const layer of layers) { |               if (!tagsFilter.matchesProperties(properties)) { | ||||||
|                 if (registered.has(layer.isDisplayed)) { |                 return false; | ||||||
|                     continue; |               } | ||||||
|                 } |  | ||||||
|                 registered.add(layer.isDisplayed); |  | ||||||
|                 layer.isDisplayed.addCallback(() => update()); |  | ||||||
|             } |             } | ||||||
|         }) |  | ||||||
| 
 | 
 | ||||||
|  |             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: ", | ||||||
|  |         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) | ||||||
|  |         ); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     upstream.features.addCallback(() => { | ||||||
|  |       update(); | ||||||
|  |     }); | ||||||
|  |     location | ||||||
|  |       .map((l) => { | ||||||
|  |         // We want something that is stable for the shown layers
 | ||||||
|  |         const displayedLayerIndexes = []; | ||||||
|  |         for (let i = 0; i < layers.data.length; i++) { | ||||||
|  |           const layer = layers.data[i]; | ||||||
|  |           if (l.zoom < layer.layerDef.minzoom) { | ||||||
|  |             continue; | ||||||
|  |           } | ||||||
|  |           if (l.zoom > layer.layerDef.maxzoom) { | ||||||
|  |             continue; | ||||||
|  |           } | ||||||
|  |           if (!layer.isDisplayed.data) { | ||||||
|  |             continue; | ||||||
|  |           } | ||||||
|  |           displayedLayerIndexes.push(i); | ||||||
|  |         } | ||||||
|  |         return displayedLayerIndexes.join(","); | ||||||
|  |       }) | ||||||
|  |       .addCallback(() => { | ||||||
|         update(); |         update(); | ||||||
|  |       }); | ||||||
| 
 | 
 | ||||||
|     } |     layers.addCallback(update); | ||||||
| 
 | 
 | ||||||
|     private static showLayer(layer: { |     const registered = new Set<UIEventSource<boolean>>(); | ||||||
|         isDisplayed: UIEventSource<boolean>, |     layers.addCallbackAndRun((layers) => { | ||||||
|         layerDef: LayerConfig |       for (const layer of layers) { | ||||||
|     }, location: UIEventSource<Loc>) { |         if (registered.has(layer.isDisplayed)) { | ||||||
|         return layer.isDisplayed.data && (layer.layerDef.minzoom <= location.data.zoom) && (layer.layerDef.maxzoom >= location.data.zoom) |           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 | ||||||
|  |     ); | ||||||
|  |   } | ||||||
| } | } | ||||||
							
								
								
									
										3
									
								
								State.ts
									
										
									
									
									
								
							
							
						
						
									
										3
									
								
								State.ts
									
										
									
									
									
								
							|  | @ -20,6 +20,7 @@ import PendingChangesUploader from "./Logic/Actors/PendingChangesUploader"; | ||||||
| import {Relation} from "./Logic/Osm/ExtractRelations"; | 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"; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Contains the global state: a bunch of UI-event sources |  * Contains the global state: a bunch of UI-event sources | ||||||
|  | @ -62,9 +63,11 @@ export default class State { | ||||||
| 
 | 
 | ||||||
|     public filteredLayers: UIEventSource<{ |     public filteredLayers: UIEventSource<{ | ||||||
|         readonly isDisplayed: UIEventSource<boolean>; |         readonly isDisplayed: UIEventSource<boolean>; | ||||||
|  |         readonly appliedFilters: UIEventSource<TagsFilter>; | ||||||
|         readonly layerDef: LayerConfig; |         readonly layerDef: LayerConfig; | ||||||
|     }[]> = new UIEventSource<{ |     }[]> = new UIEventSource<{ | ||||||
|         readonly isDisplayed: UIEventSource<boolean>; |         readonly isDisplayed: UIEventSource<boolean>; | ||||||
|  |         readonly appliedFilters: UIEventSource<TagsFilter>; | ||||||
|         readonly layerDef: LayerConfig; |         readonly layerDef: LayerConfig; | ||||||
|     }[]>([]); |     }[]>([]); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,46 +1,43 @@ | ||||||
| import {UIEventSource} from "../../Logic/UIEventSource"; | import { UIEventSource } from "../../Logic/UIEventSource"; | ||||||
| import BaseUIElement from "../BaseUIElement"; | import BaseUIElement from "../BaseUIElement"; | ||||||
| 
 | 
 | ||||||
| export class VariableUiElement extends BaseUIElement { | export class VariableUiElement extends BaseUIElement { | ||||||
|  |   private _element: HTMLElement; | ||||||
| 
 | 
 | ||||||
|     private _element : HTMLElement; |   constructor( | ||||||
|  |     contents: UIEventSource<string | BaseUIElement | BaseUIElement[]> | ||||||
|  |   ) { | ||||||
|  |     super(); | ||||||
| 
 | 
 | ||||||
|     constructor(contents: UIEventSource<string | BaseUIElement | BaseUIElement[]>) { |     this._element = document.createElement("span"); | ||||||
|         super(); |     const el = this._element; | ||||||
|  |     contents.addCallbackAndRun((contents) => { | ||||||
|  |       while (el.firstChild) { | ||||||
|  |         el.removeChild(el.lastChild); | ||||||
|  |       } | ||||||
| 
 | 
 | ||||||
|         this._element = document.createElement("span") |       if (contents === undefined) { | ||||||
|         const el = this._element |         return el; | ||||||
|         contents.addCallbackAndRun(contents => { |       } | ||||||
|             while (el.firstChild) { |       if (typeof contents === "string") { | ||||||
|                 el.removeChild( |         el.innerHTML = contents; | ||||||
|                     el.lastChild |       } else if (contents instanceof Array) { | ||||||
|                 ) |         for (const content of contents) { | ||||||
|             } |           const c = content.ConstructElement(); | ||||||
| 
 |           if (c !== undefined && c !== null) { | ||||||
|             if (contents === undefined) { |             el.appendChild(c); | ||||||
|                 return el; |           } | ||||||
|             } |         } | ||||||
|             if (typeof contents === "string") { |       } else { | ||||||
|                 el.innerHTML = contents |         const c = contents.ConstructElement(); | ||||||
|             } else if (contents instanceof Array) { |         if (c !== undefined && c !== null) { | ||||||
|                 for (const content of contents) { |           el.appendChild(c); | ||||||
|                     const c = content.ConstructElement(); |         } | ||||||
|                     if (c !== undefined && c !== null) { |       } | ||||||
|                         el.appendChild(c)   |     }); | ||||||
|                     } |   } | ||||||
| 
 |  | ||||||
|                 } |  | ||||||
|             } else { |  | ||||||
|                 const c = contents.ConstructElement(); |  | ||||||
|                 if (c !== undefined && c !== null) { |  | ||||||
|                     el.appendChild(c) |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         }) |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     protected InnerConstructElement(): HTMLElement { |  | ||||||
|         return this._element; |  | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|  |   protected InnerConstructElement(): HTMLElement { | ||||||
|  |     return this._element; | ||||||
|  |   } | ||||||
| } | } | ||||||
|  | @ -1,3 +1,6 @@ | ||||||
|  | import { Utils } from "./../../Utils"; | ||||||
|  | import { FixedInputElement } from "./../Input/FixedInputElement"; | ||||||
|  | import { RadioButton } from "./../Input/RadioButton"; | ||||||
| import { FixedUiElement } from "./../Base/FixedUiElement"; | import { FixedUiElement } from "./../Base/FixedUiElement"; | ||||||
| import { LayerConfigJson } from "./../../Customizations/JSON/LayerConfigJson"; | import { LayerConfigJson } from "./../../Customizations/JSON/LayerConfigJson"; | ||||||
| import { UIEventSource } from "../../Logic/UIEventSource"; | import { UIEventSource } from "../../Logic/UIEventSource"; | ||||||
|  | @ -11,79 +14,104 @@ import BaseUIElement from "../BaseUIElement"; | ||||||
| import { Translation } from "../i18n/Translation"; | import { Translation } from "../i18n/Translation"; | ||||||
| import ScrollableFullScreen from "../Base/ScrollableFullScreen"; | import ScrollableFullScreen from "../Base/ScrollableFullScreen"; | ||||||
| import Svg from "../../Svg"; | import Svg from "../../Svg"; | ||||||
|  | import FilterConfig from "../../Customizations/JSON/FilterConfig"; | ||||||
|  | import CheckBoxes from "../Input/Checkboxes"; | ||||||
|  | import { InputElement } from "../Input/InputElement"; | ||||||
|  | import { TagsFilter } from "../../Logic/Tags/TagsFilter"; | ||||||
|  | import InputElementMap from "../Input/InputElementMap"; | ||||||
|  | import { And } from "../../Logic/Tags/And"; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Shows the filter |  * Shows the filter | ||||||
|  */ |  */ | ||||||
| export default class FilterView extends ScrollableFullScreen { |  | ||||||
|   constructor(isShown: UIEventSource<boolean>) { |  | ||||||
|     super(FilterView.GenTitle, FilterView.Generatecontent, "filter", isShown); |  | ||||||
|   } |  | ||||||
| 
 | 
 | ||||||
|   private static GenTitle(): BaseUIElement { | export default class FilterView extends VariableUiElement { | ||||||
|     return new FixedUiElement(`Filter`).SetClass( |   constructor(filteredLayer) { | ||||||
|       "text-2xl break-words font-bold p-2" |     super( | ||||||
|  |       filteredLayer.map((filteredLayers) => | ||||||
|  |         filteredLayers.map(FilterView.createOneFilteredLayerElement) | ||||||
|  |       ) | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   private static Generatecontent(): BaseUIElement { |   static createOneFilteredLayerElement(filteredLayer) { | ||||||
|     let filterPanel: BaseUIElement = new FixedUiElement(""); |     const layer: LayerConfig = filteredLayer.layerDef; | ||||||
|  |     const iconStyle = "width:1.5rem;height:1.5rem;margin-left:1.25rem"; | ||||||
| 
 | 
 | ||||||
|     if (State.state.filteredLayers.data.length > 1) { |     const icon = new Combine([Svg.checkbox_filled]).SetStyle(iconStyle); | ||||||
|       let activeLayers = State.state.filteredLayers; |     const iconUnselected = new Combine([Svg.checkbox_empty]).SetStyle( | ||||||
|  |       iconStyle | ||||||
|  |     ); | ||||||
| 
 | 
 | ||||||
|       if (activeLayers === undefined) { |     if (filteredLayer.layerDef.name === undefined) { | ||||||
|         throw "ActiveLayers should be defined..."; |       return; | ||||||
|       } |  | ||||||
| 
 |  | ||||||
|       const checkboxes: BaseUIElement[] = []; |  | ||||||
| 
 |  | ||||||
|       for (const layer of activeLayers.data) { |  | ||||||
|         const iconStyle = "width:1.5rem;height:1.5rem;margin-left:1.25rem"; |  | ||||||
| 
 |  | ||||||
|         const icon = new Combine([Svg.checkbox_filled]).SetStyle(iconStyle); |  | ||||||
|         const iconUnselected = new Combine([Svg.checkbox_empty]).SetStyle( |  | ||||||
|           iconStyle |  | ||||||
|         ); |  | ||||||
| 
 |  | ||||||
|         if (layer.layerDef.name === undefined) { |  | ||||||
|           continue; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         const style = "display:flex;align-items:center;color:#007759"; |  | ||||||
| 
 |  | ||||||
|         const name: Translation = Translations.WT(layer.layerDef.name)?.Clone(); |  | ||||||
| 
 |  | ||||||
|         const styledNameChecked = name |  | ||||||
|           .Clone() |  | ||||||
|           .SetStyle("font-size:large;padding-left:1.25rem"); |  | ||||||
| 
 |  | ||||||
|         const styledNameUnChecked = name |  | ||||||
|           .Clone() |  | ||||||
|           .SetStyle("font-size:large;padding-left:1.25rem"); |  | ||||||
| 
 |  | ||||||
|         const layerChecked = new Combine([icon, styledNameChecked]).SetStyle( |  | ||||||
|           style |  | ||||||
|         ); |  | ||||||
| 
 |  | ||||||
|         const layerNotChecked = new Combine([ |  | ||||||
|           iconUnselected, |  | ||||||
|           styledNameUnChecked, |  | ||||||
|         ]).SetStyle(style); |  | ||||||
| 
 |  | ||||||
|         checkboxes.push( |  | ||||||
|           new Toggle(layerChecked, layerNotChecked, layer.isDisplayed) |  | ||||||
|             .ToggleOnClick() |  | ||||||
|             .SetStyle("margin:0.3em;") |  | ||||||
|         ); |  | ||||||
|       } |  | ||||||
| 
 |  | ||||||
|       let combinedCheckboxes = new Combine(checkboxes); |  | ||||||
|       combinedCheckboxes.SetStyle("display:flex;flex-direction:column;"); |  | ||||||
| 
 |  | ||||||
|       filterPanel = new Combine([combinedCheckboxes]); |  | ||||||
| 
 |  | ||||||
|       return filterPanel; |  | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     const style = "display:flex;align-items:center;color:#007759"; | ||||||
|  | 
 | ||||||
|  |     const name: Translation = Translations.WT( | ||||||
|  |       filteredLayer.layerDef.name | ||||||
|  |     )?.Clone(); | ||||||
|  | 
 | ||||||
|  |     const styledNameChecked = name | ||||||
|  |       .Clone() | ||||||
|  |       .SetStyle("font-size:large;padding-left:1.25rem"); | ||||||
|  | 
 | ||||||
|  |     const styledNameUnChecked = name | ||||||
|  |       .Clone() | ||||||
|  |       .SetStyle("font-size:large;padding-left:1.25rem"); | ||||||
|  | 
 | ||||||
|  |     const layerChecked = new Combine([icon, styledNameChecked]).SetStyle(style); | ||||||
|  | 
 | ||||||
|  |     const layerNotChecked = new Combine([ | ||||||
|  |       iconUnselected, | ||||||
|  |       styledNameUnChecked, | ||||||
|  |     ]).SetStyle(style); | ||||||
|  | 
 | ||||||
|  |     let listFilterElements: InputElement<TagsFilter>[] = layer.filters.map( | ||||||
|  |       FilterView.createFilter | ||||||
|  |     ); | ||||||
|  | 
 | ||||||
|  |     function update() { | ||||||
|  |       let listTagsFilters = Utils.NoNull( | ||||||
|  |         listFilterElements.map((input) => input.GetValue().data) | ||||||
|  |       ); | ||||||
|  |       filteredLayer.appliedTags.setData(new And(listTagsFilters)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     listFilterElements.forEach((inputElement) => | ||||||
|  |       inputElement.GetValue().addCallback((_) => update()) | ||||||
|  |     ); | ||||||
|  | 
 | ||||||
|  |     return new Toggle( | ||||||
|  |       new Combine([layerChecked, ...listFilterElements]), | ||||||
|  |       layerNotChecked, | ||||||
|  |       filteredLayer.isDisplayed | ||||||
|  |     ) | ||||||
|  |       .ToggleOnClick() | ||||||
|  |       .SetStyle("margin:0.3em;"); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static createFilter(filterConfig: FilterConfig): InputElement<TagsFilter> { | ||||||
|  |     if (filterConfig.options.length === 1) { | ||||||
|  |       let option = filterConfig.options[0]; | ||||||
|  |       let checkboxes = new CheckBoxes([option.question.Clone()]); | ||||||
|  | 
 | ||||||
|  |       return new InputElementMap( | ||||||
|  |         checkboxes, | ||||||
|  |         (t0, t1) => t0 === t1, | ||||||
|  |         (numbers) => (numbers.length > 0 ? option.osmTags : undefined), | ||||||
|  |         (tagsFilter) => (tagsFilter == undefined ? [] : [0]) | ||||||
|  |       ); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     let options = filterConfig.options; | ||||||
|  | 
 | ||||||
|  |     return new RadioButton( | ||||||
|  |       options.map( | ||||||
|  |         (option) => | ||||||
|  |           new FixedInputElement(option.question.Clone(), option.osmTags) | ||||||
|  |       ) | ||||||
|  |     ); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -4,45 +4,56 @@ 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"; | ||||||
| import {UIEventSource} from "../../Logic/UIEventSource"; | import { UIEventSource } from "../../Logic/UIEventSource"; | ||||||
| import BaseUIElement from "../BaseUIElement"; | import BaseUIElement from "../BaseUIElement"; | ||||||
| import Toggle from "../Input/Toggle"; | import Toggle from "../Input/Toggle"; | ||||||
| import {ExportDataButton} from "./ExportDataButton"; | import { ExportDataButton } from "./ExportDataButton"; | ||||||
|  | import FilterView from "./FilterView"; | ||||||
| 
 | 
 | ||||||
| export default class LayerControlPanel extends ScrollableFullScreen { | export default class LayerControlPanel extends ScrollableFullScreen { | ||||||
|  |   constructor(isShown: UIEventSource<boolean>) { | ||||||
|  |     super( | ||||||
|  |       LayerControlPanel.GenTitle, | ||||||
|  |       LayerControlPanel.GeneratePanel, | ||||||
|  |       "layers", | ||||||
|  |       isShown | ||||||
|  |     ); | ||||||
|  |   } | ||||||
| 
 | 
 | ||||||
|     constructor(isShown: UIEventSource<boolean>) { |   private static GenTitle(): BaseUIElement { | ||||||
|         super(LayerControlPanel.GenTitle, LayerControlPanel.GeneratePanel, "layers", isShown); |     return Translations.t.general.layerSelection.title | ||||||
|  |       .Clone() | ||||||
|  |       .SetClass("text-2xl break-words font-bold p-2"); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   private static GeneratePanel(): BaseUIElement { | ||||||
|  |     const elements: BaseUIElement[] = []; | ||||||
|  | 
 | ||||||
|  |     if (State.state.layoutToUse.data.enableBackgroundLayerSelection) { | ||||||
|  |       const backgroundSelector = new BackgroundSelector(); | ||||||
|  |       backgroundSelector.SetStyle("margin:1em"); | ||||||
|  |       backgroundSelector.onClick(() => {}); | ||||||
|  |       elements.push(backgroundSelector); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private static GenTitle(): BaseUIElement { |     elements.push( | ||||||
|         return Translations.t.general.layerSelection.title.Clone().SetClass("text-2xl break-words font-bold p-2") |       new Toggle( | ||||||
|     } |         new FilterView(State.state.filteredLayers), | ||||||
|  |         undefined, | ||||||
|  |         State.state.filteredLayers.map( | ||||||
|  |           (layers) => layers.length > 1 || layers[0].layerDef.filters.length > 0 | ||||||
|  |         ) | ||||||
|  |       ) | ||||||
|  |     ); | ||||||
| 
 | 
 | ||||||
|     private static GeneratePanel(): BaseUIElement { |     elements.push( | ||||||
|         const elements: BaseUIElement[] = [] |       new Toggle( | ||||||
| 
 |         new ExportDataButton(), | ||||||
|         if (State.state.layoutToUse.data.enableBackgroundLayerSelection) { |         undefined, | ||||||
|             const backgroundSelector = new BackgroundSelector(); |         State.state.featureSwitchEnableExport | ||||||
|             backgroundSelector.SetStyle("margin:1em"); |       ) | ||||||
|             backgroundSelector.onClick(() => { |     ); | ||||||
|             }); |  | ||||||
|             elements.push(backgroundSelector) |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         elements.push(new Toggle( |  | ||||||
|             new LayerSelection(State.state.filteredLayers), |  | ||||||
|             undefined, |  | ||||||
|             State.state.filteredLayers.map(layers => layers.length > 1) |  | ||||||
|         )) |  | ||||||
| 
 |  | ||||||
|         elements.push(new Toggle( |  | ||||||
|             new ExportDataButton(), |  | ||||||
|             undefined, |  | ||||||
|             State.state.featureSwitchEnableExport |  | ||||||
|         )) |  | ||||||
| 
 |  | ||||||
|         return new Combine(elements).SetClass("flex flex-col") |  | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|  |     return new Combine(elements).SetClass("flex flex-col"); | ||||||
|  |   } | ||||||
| } | } | ||||||
|  | @ -71,9 +71,7 @@ | ||||||
|         "ru": "Книжный шкаф", |         "ru": "Книжный шкаф", | ||||||
|         "it": "Microbiblioteca" |         "it": "Microbiblioteca" | ||||||
|       }, |       }, | ||||||
|       "tags": [ |       "tags": ["amenity=public_bookcase"], | ||||||
|         "amenity=public_bookcase" |  | ||||||
|       ], |  | ||||||
|       "preciseInput": { |       "preciseInput": { | ||||||
|         "preferredBackground": "photo" |         "preferredBackground": "photo" | ||||||
|       } |       } | ||||||
|  | @ -107,10 +105,7 @@ | ||||||
|       "mappings": [ |       "mappings": [ | ||||||
|         { |         { | ||||||
|           "if": { |           "if": { | ||||||
|             "and": [ |             "and": ["noname=yes", "name="] | ||||||
|               "noname=yes", |  | ||||||
|               "name=" |  | ||||||
|             ] |  | ||||||
|           }, |           }, | ||||||
|           "then": { |           "then": { | ||||||
|             "en": "This bookcase doesn't have a name", |             "en": "This bookcase doesn't have a name", | ||||||
|  | @ -316,18 +311,12 @@ | ||||||
|             "it": "Fa parte della rete 'Little Free Library'" |             "it": "Fa parte della rete 'Little Free Library'" | ||||||
|           }, |           }, | ||||||
|           "if": { |           "if": { | ||||||
|             "and": [ |             "and": ["brand=Little Free Library", "nobrand="] | ||||||
|               "brand=Little Free Library", |  | ||||||
|               "nobrand=" |  | ||||||
|             ] |  | ||||||
|           } |           } | ||||||
|         }, |         }, | ||||||
|         { |         { | ||||||
|           "if": { |           "if": { | ||||||
|             "and": [ |             "and": ["nobrand=yes", "brand="] | ||||||
|               "nobrand=yes", |  | ||||||
|               "brand=" |  | ||||||
|             ] |  | ||||||
|           }, |           }, | ||||||
|           "then": { |           "then": { | ||||||
|             "en": "This public bookcase is not part of a bigger network", |             "en": "This public bookcase is not part of a bigger network", | ||||||
|  | @ -368,11 +357,7 @@ | ||||||
|             "it": "Questa microbiblioteca non fa parte di una rete" |             "it": "Questa microbiblioteca non fa parte di una rete" | ||||||
|           }, |           }, | ||||||
|           "if": { |           "if": { | ||||||
|             "and": [ |             "and": ["nobrand=yes", "brand=", "ref="] | ||||||
|               "nobrand=yes", |  | ||||||
|               "brand=", |  | ||||||
|               "ref=" |  | ||||||
|             ] |  | ||||||
|           } |           } | ||||||
|         } |         } | ||||||
|       ] |       ] | ||||||
|  | @ -424,11 +409,18 @@ | ||||||
|   ], |   ], | ||||||
|   "deletion": { |   "deletion": { | ||||||
|     "softDeletionTags": { |     "softDeletionTags": { | ||||||
|       "and": [ |       "and": ["disused:amenity=public_bookcase", "amenity="] | ||||||
|         "disused:amenity=public_bookcase", |  | ||||||
|         "amenity=" |  | ||||||
|       ] |  | ||||||
|     }, |     }, | ||||||
|     "neededChangesets": 5 |     "neededChangesets": 5 | ||||||
|   } |   }, | ||||||
|  |   "filter": [ | ||||||
|  |     { | ||||||
|  |       "options": [ | ||||||
|  |         { | ||||||
|  |           "question": "Kinderboeken aanwezig?", | ||||||
|  |           "osmTags": "books~.*children.*" | ||||||
|  |         } | ||||||
|  |       ] | ||||||
|  |     } | ||||||
|  |   ] | ||||||
| } | } | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue