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
				
			
		|  | @ -44,6 +44,7 @@ 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 FilterView from "./UI/BigComponents/FilterView"; | ||||||
|  | import { TagsFilter } from "./Logic/Tags/TagsFilter"; | ||||||
| 
 | 
 | ||||||
| export class InitUiElements { | export class InitUiElements { | ||||||
|   static InitAll( |   static InitAll( | ||||||
|  | @ -59,8 +60,7 @@ export class InitUiElements { | ||||||
|         `Error: incorrect layout <i>${layoutName}</i><br/><a href='https://${window.location.host}/'>Go back</a>` |         `Error: incorrect layout <i>${layoutName}</i><br/><a href='https://${window.location.host}/'>Go back</a>` | ||||||
|       ) |       ) | ||||||
|         .AttachTo("centermessage") |         .AttachTo("centermessage") | ||||||
|                 .onClick(() => { |         .onClick(() => {}); | ||||||
|                 }); |  | ||||||
|       throw "Incorrect layout"; |       throw "Incorrect layout"; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | @ -222,7 +222,6 @@ export class InitUiElements { | ||||||
|       State.state.locationControl.ping(); |       State.state.locationControl.ping(); | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
|     // To download pdf of leaflet you need to turn it into and image first
 |     // To download pdf of leaflet you need to turn it into and image first
 | ||||||
|     // Then export that image as a pdf
 |     // Then export that image as a pdf
 | ||||||
|     // leaflet-simple-map-screenshoter: to make image
 |     // leaflet-simple-map-screenshoter: to make image
 | ||||||
|  | @ -237,10 +236,10 @@ export class InitUiElements { | ||||||
|       console.log("Debug - Screenshot"); |       console.log("Debug - Screenshot"); | ||||||
|       screenshotter.addTo(State.state.leafletMap.data); |       screenshotter.addTo(State.state.leafletMap.data); | ||||||
|       let doc = new jsPDF(); |       let doc = new jsPDF(); | ||||||
|             screenshotter.takeScreen('image').then(image => { |       screenshotter.takeScreen("image").then((image) => { | ||||||
|         // TO DO: scale image on pdf to its original size
 |         // TO DO: scale image on pdf to its original size
 | ||||||
|                 doc.addImage(image, 'PNG', 0, 0, screen.width / 10, screen.height / 10); |         doc.addImage(image, "PNG", 0, 0, screen.width / 10, screen.height / 10); | ||||||
|                 doc.setDisplayMode('fullheight'); |         doc.setDisplayMode("fullheight"); | ||||||
|         doc.save("Screenshot"); |         doc.save("Screenshot"); | ||||||
|       }); |       }); | ||||||
|       //screenshotter.remove();
 |       //screenshotter.remove();
 | ||||||
|  | @ -249,7 +248,9 @@ export class InitUiElements { | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     new Combine( |     new Combine( | ||||||
|             [plus, min, geolocationButton, screenshot].map((el) => el.SetClass("m-0.5 md:m-1")) |       [plus, min, geolocationButton, screenshot].map((el) => | ||||||
|  |         el.SetClass("m-0.5 md:m-1") | ||||||
|  |       ) | ||||||
|     ) |     ) | ||||||
|       .SetClass("flex flex-col") |       .SetClass("flex flex-col") | ||||||
|       .AttachTo("bottom-right"); |       .AttachTo("bottom-right"); | ||||||
|  | @ -396,31 +397,29 @@ export class InitUiElements { | ||||||
|       State.state.featureSwitchLayers |       State.state.featureSwitchLayers | ||||||
|     ); |     ); | ||||||
| 
 | 
 | ||||||
|         const filterView = new FilterView(State.state.FilterIsOpened).SetClass( |     // const filterView = new FilterView(State.state.FilterIsOpened).SetClass(
 | ||||||
|             "block p-1 rounded-full" |     //     "block p-1 rounded-full"
 | ||||||
|         ); |     // );
 | ||||||
| 
 | 
 | ||||||
|         const filterMapControlButton = new MapControlButton( |     // const filterMapControlButton = new MapControlButton(
 | ||||||
|             new CenterFlexedElement( |     //     new CenterFlexedElement(
 | ||||||
|                 Img.AsImageElement(Svg.filter, "", "width:1.25rem;height:1.25rem") |     //         Img.AsImageElement(Svg.filter, "", "width:1.25rem;height:1.25rem")
 | ||||||
|             ) |     //     )
 | ||||||
|         ); |     // );
 | ||||||
| 
 | 
 | ||||||
|         const filterButton = new Toggle( |     // const filterButton = new Toggle(
 | ||||||
|             filterView, |     //     filterView,
 | ||||||
|             filterMapControlButton, |     //     filterMapControlButton,
 | ||||||
|             State.state.FilterIsOpened |     //     State.state.FilterIsOpened
 | ||||||
|         ).ToggleOnClick(); |     // ).ToggleOnClick();
 | ||||||
| 
 | 
 | ||||||
|         const filterControl = new Toggle( |     // const filterControl = new Toggle(
 | ||||||
|             filterButton, |     //     filterButton,
 | ||||||
|             "", |     //     "",
 | ||||||
|             State.state.featureSwitchFilter |     //     State.state.featureSwitchFilter
 | ||||||
|         ); |     // );
 | ||||||
| 
 | 
 | ||||||
|         new Combine([copyrightButton, layerControl, filterControl]).AttachTo( |     new Combine([copyrightButton, layerControl]).AttachTo("bottom-left"); | ||||||
|             "bottom-left" |  | ||||||
|         ); |  | ||||||
| 
 | 
 | ||||||
|     State.state.locationControl.addCallback(() => { |     State.state.locationControl.addCallback(() => { | ||||||
|       // Close the layer selection when the map is moved
 |       // Close the layer selection when the map is moved
 | ||||||
|  | @ -435,7 +434,8 @@ export class InitUiElements { | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   private static InitBaseMap() { |   private static InitBaseMap() { | ||||||
|         State.state.availableBackgroundLayers = AvailableBaseLayers.AvailableLayersAt(State.state.locationControl); |     State.state.availableBackgroundLayers = | ||||||
|  |       AvailableBaseLayers.AvailableLayersAt(State.state.locationControl); | ||||||
|     State.state.backgroundLayer = State.state.backgroundLayerId.map( |     State.state.backgroundLayer = State.state.backgroundLayerId.map( | ||||||
|       (selectedId: string) => { |       (selectedId: string) => { | ||||||
|         if (selectedId === undefined) { |         if (selectedId === undefined) { | ||||||
|  | @ -519,6 +519,7 @@ export class InitUiElements { | ||||||
|         const flayer = { |         const flayer = { | ||||||
|           isDisplayed: isDisplayed, |           isDisplayed: isDisplayed, | ||||||
|           layerDef: layer, |           layerDef: layer, | ||||||
|  |           appliedFilters: new UIEventSource<TagsFilter>(undefined) | ||||||
|         }; |         }; | ||||||
|         flayers.push(flayer); |         flayers.push(flayer); | ||||||
|       } |       } | ||||||
|  |  | ||||||
|  | @ -3,64 +3,85 @@ 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>; | ||||||
|  |         layerDef: LayerConfig; | ||||||
|  |         appliedFilters: UIEventSource<TagsFilter>; | ||||||
|  |       }[] | ||||||
|  |     >, | ||||||
|     location: UIEventSource<Loc>, |     location: UIEventSource<Loc>, | ||||||
|     selectedElement: UIEventSource<any>, |     selectedElement: UIEventSource<any>, | ||||||
|                 upstream: FeatureSource) { |     upstream: FeatureSource | ||||||
| 
 |   ) { | ||||||
|     const self = this; |     const self = this; | ||||||
| 
 | 
 | ||||||
|     function update() { |     function update() { | ||||||
| 
 |  | ||||||
|       const layerDict = {}; |       const layerDict = {}; | ||||||
|       if (layers.data.length == 0) { |       if (layers.data.length == 0) { | ||||||
|                 console.warn("No layers defined!") |         console.warn("No layers defined!"); | ||||||
|         return; |         return; | ||||||
|       } |       } | ||||||
|       for (const layer of layers.data) { |       for (const layer of layers.data) { | ||||||
|         layerDict[layer.layerDef.id] = layer; |         layerDict[layer.layerDef.id] = layer; | ||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
|             const features: { feature: any, freshness: Date }[] = upstream.features.data; |       const features: { feature: any; freshness: Date }[] = | ||||||
|  |         upstream.features.data; | ||||||
| 
 | 
 | ||||||
|       const missingLayers = new Set<string>(); |       const missingLayers = new Set<string>(); | ||||||
| 
 | 
 | ||||||
|             const newFeatures = features.filter(f => { |       const newFeatures = features.filter((f) => { | ||||||
|         const layerId = f.feature._matching_layer_id; |         const layerId = f.feature._matching_layer_id; | ||||||
| 
 | 
 | ||||||
|                 if(selectedElement.data?.id === f.feature.id || f.feature.id === Hash.hash.data){ |         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
 |           // This is the selected object - it gets a free pass even if zoom is not sufficient
 | ||||||
|           return true; |           return true; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         if (layerId !== undefined) { |         if (layerId !== undefined) { | ||||||
|           const layer: { |           const layer: { | ||||||
|                         isDisplayed: UIEventSource<boolean>, |             isDisplayed: UIEventSource<boolean>; | ||||||
|                         layerDef: LayerConfig |             layerDef: LayerConfig; | ||||||
|  |             appliedFilters: UIEventSource<TagsFilter>; | ||||||
|           } = layerDict[layerId]; |           } = layerDict[layerId]; | ||||||
|           if (layer === undefined) { |           if (layer === undefined) { | ||||||
|                         missingLayers.add(layerId) |             missingLayers.add(layerId); | ||||||
|             return true; |             return true; | ||||||
|           } |           } | ||||||
| 
 | 
 | ||||||
|                     const isShown = layer.layerDef.isShown |           const isShown = layer.layerDef.isShown; | ||||||
|           const tags = f.feature.properties; |           const tags = f.feature.properties; | ||||||
|           if (isShown.IsKnown(tags)) { |           if (isShown.IsKnown(tags)) { | ||||||
|                         const result = layer.layerDef.isShown.GetRenderValue(f.feature.properties).txt; |             const result = layer.layerDef.isShown.GetRenderValue( | ||||||
|  |               f.feature.properties | ||||||
|  |             ).txt; | ||||||
|             if (result !== "yes") { |             if (result !== "yes") { | ||||||
|               return false; |               return false; | ||||||
|             } |             } | ||||||
|           } |           } | ||||||
| 
 | 
 | ||||||
|           if (FilteringFeatureSource.showLayer(layer, location)) { |           if (FilteringFeatureSource.showLayer(layer, location)) { | ||||||
|  |             const tagsFilter = layer.appliedFilters.data; | ||||||
|  | 
 | ||||||
|  |             if (tagsFilter) { | ||||||
|  |               const properties = f.feature.properties; | ||||||
|  |               if (!tagsFilter.matchesProperties(properties)) { | ||||||
|  |                 return false; | ||||||
|  |               } | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|             return true; |             return true; | ||||||
|           } |           } | ||||||
|         } |         } | ||||||
|  | @ -69,25 +90,36 @@ export default class FilteringFeatureSource implements FeatureSource { | ||||||
|           if (!FilteringFeatureSource.showLayer(toCheck, location)) { |           if (!FilteringFeatureSource.showLayer(toCheck, location)) { | ||||||
|             continue; |             continue; | ||||||
|           } |           } | ||||||
|                     if (toCheck.layerDef.source.osmTags.matchesProperties(f.feature.properties)) { |           if ( | ||||||
|  |             toCheck.layerDef.source.osmTags.matchesProperties( | ||||||
|  |               f.feature.properties | ||||||
|  |             ) | ||||||
|  |           ) { | ||||||
|             return true; |             return true; | ||||||
|           } |           } | ||||||
|         } |         } | ||||||
|         return false; |         return false; | ||||||
| 
 |  | ||||||
|       }); |       }); | ||||||
|             console.log("Filtering layer source: input: ", upstream.features.data?.length, "output:", newFeatures.length) |       console.log( | ||||||
|  |         "Filtering layer source: input: ", | ||||||
|  |         upstream.features.data?.length, | ||||||
|  |         "output:", | ||||||
|  |         newFeatures.length | ||||||
|  |       ); | ||||||
|       self.features.setData(newFeatures); |       self.features.setData(newFeatures); | ||||||
|       if (missingLayers.size > 0) { |       if (missingLayers.size > 0) { | ||||||
|                 console.error("Some layers were not found: ", Array.from(missingLayers)) |         console.error( | ||||||
|  |           "Some layers were not found: ", | ||||||
|  |           Array.from(missingLayers) | ||||||
|  |         ); | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
|     upstream.features.addCallback(() => { |     upstream.features.addCallback(() => { | ||||||
|             update() |       update(); | ||||||
|     }); |     }); | ||||||
|         location.map(l => { |     location | ||||||
|  |       .map((l) => { | ||||||
|         // We want something that is stable for the shown layers
 |         // We want something that is stable for the shown layers
 | ||||||
|         const displayedLayerIndexes = []; |         const displayedLayerIndexes = []; | ||||||
|         for (let i = 0; i < layers.data.length; i++) { |         for (let i = 0; i < layers.data.length; i++) { | ||||||
|  | @ -103,32 +135,40 @@ export default class FilteringFeatureSource implements FeatureSource { | ||||||
|           } |           } | ||||||
|           displayedLayerIndexes.push(i); |           displayedLayerIndexes.push(i); | ||||||
|         } |         } | ||||||
|             return displayedLayerIndexes.join(",") |         return displayedLayerIndexes.join(","); | ||||||
|         }).addCallback(() => { |       }) | ||||||
|  |       .addCallback(() => { | ||||||
|         update(); |         update(); | ||||||
|       }); |       }); | ||||||
| 
 | 
 | ||||||
|     layers.addCallback(update); |     layers.addCallback(update); | ||||||
| 
 | 
 | ||||||
|     const registered = new Set<UIEventSource<boolean>>(); |     const registered = new Set<UIEventSource<boolean>>(); | ||||||
|         layers.addCallbackAndRun(layers => { |     layers.addCallbackAndRun((layers) => { | ||||||
|       for (const layer of layers) { |       for (const layer of layers) { | ||||||
|         if (registered.has(layer.isDisplayed)) { |         if (registered.has(layer.isDisplayed)) { | ||||||
|           continue; |           continue; | ||||||
|         } |         } | ||||||
|         registered.add(layer.isDisplayed); |         registered.add(layer.isDisplayed); | ||||||
|         layer.isDisplayed.addCallback(() => update()); |         layer.isDisplayed.addCallback(() => update()); | ||||||
|  |         layer.appliedFilters.addCallback(() => update()); | ||||||
|       } |       } | ||||||
|         }) |     }); | ||||||
| 
 | 
 | ||||||
|     update(); |     update(); | ||||||
| 
 |  | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|     private static showLayer(layer: { |   private static showLayer( | ||||||
|         isDisplayed: UIEventSource<boolean>, |     layer: { | ||||||
|         layerDef: LayerConfig |       isDisplayed: UIEventSource<boolean>; | ||||||
|     }, location: UIEventSource<Loc>) { |       layerDef: LayerConfig; | ||||||
|         return layer.isDisplayed.data && (layer.layerDef.minzoom <= location.data.zoom) && (layer.layerDef.maxzoom >= location.data.zoom) |     }, | ||||||
|  |     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; | ||||||
|     }[]>([]); |     }[]>([]); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -2,45 +2,42 @@ 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[]>) { |   constructor( | ||||||
|  |     contents: UIEventSource<string | BaseUIElement | BaseUIElement[]> | ||||||
|  |   ) { | ||||||
|     super(); |     super(); | ||||||
| 
 | 
 | ||||||
|         this._element = document.createElement("span") |     this._element = document.createElement("span"); | ||||||
|         const el = this._element |     const el = this._element; | ||||||
|         contents.addCallbackAndRun(contents => { |     contents.addCallbackAndRun((contents) => { | ||||||
|       while (el.firstChild) { |       while (el.firstChild) { | ||||||
|                 el.removeChild( |         el.removeChild(el.lastChild); | ||||||
|                     el.lastChild |  | ||||||
|                 ) |  | ||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
|       if (contents === undefined) { |       if (contents === undefined) { | ||||||
|         return el; |         return el; | ||||||
|       } |       } | ||||||
|       if (typeof contents === "string") { |       if (typeof contents === "string") { | ||||||
|                 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); | ||||||
|           } |           } | ||||||
| 
 |  | ||||||
|         } |         } | ||||||
|       } else { |       } else { | ||||||
|         const c = contents.ConstructElement(); |         const c = contents.ConstructElement(); | ||||||
|         if (c !== undefined && c !== null) { |         if (c !== undefined && c !== null) { | ||||||
|                     el.appendChild(c) |           el.appendChild(c); | ||||||
|         } |         } | ||||||
|       } |       } | ||||||
|         }) |     }); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   protected InnerConstructElement(): HTMLElement { |   protected InnerConstructElement(): HTMLElement { | ||||||
|     return this._element; |     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,34 +14,28 @@ 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; | ||||||
| 
 |  | ||||||
|     if (State.state.filteredLayers.data.length > 1) { |  | ||||||
|       let activeLayers = State.state.filteredLayers; |  | ||||||
| 
 |  | ||||||
|       if (activeLayers === undefined) { |  | ||||||
|         throw "ActiveLayers should be defined..."; |  | ||||||
|       } |  | ||||||
| 
 |  | ||||||
|       const checkboxes: BaseUIElement[] = []; |  | ||||||
| 
 |  | ||||||
|       for (const layer of activeLayers.data) { |  | ||||||
|     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); | ||||||
|  | @ -46,13 +43,15 @@ export default class FilterView extends ScrollableFullScreen { | ||||||
|       iconStyle |       iconStyle | ||||||
|     ); |     ); | ||||||
| 
 | 
 | ||||||
|         if (layer.layerDef.name === undefined) { |     if (filteredLayer.layerDef.name === undefined) { | ||||||
|           continue; |       return; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     const style = "display:flex;align-items:center;color:#007759"; |     const style = "display:flex;align-items:center;color:#007759"; | ||||||
| 
 | 
 | ||||||
|         const name: Translation = Translations.WT(layer.layerDef.name)?.Clone(); |     const name: Translation = Translations.WT( | ||||||
|  |       filteredLayer.layerDef.name | ||||||
|  |     )?.Clone(); | ||||||
| 
 | 
 | ||||||
|     const styledNameChecked = name |     const styledNameChecked = name | ||||||
|       .Clone() |       .Clone() | ||||||
|  | @ -62,28 +61,57 @@ export default class FilterView extends ScrollableFullScreen { | ||||||
|       .Clone() |       .Clone() | ||||||
|       .SetStyle("font-size:large;padding-left:1.25rem"); |       .SetStyle("font-size:large;padding-left:1.25rem"); | ||||||
| 
 | 
 | ||||||
|         const layerChecked = new Combine([icon, styledNameChecked]).SetStyle( |     const layerChecked = new Combine([icon, styledNameChecked]).SetStyle(style); | ||||||
|           style |  | ||||||
|         ); |  | ||||||
| 
 | 
 | ||||||
|     const layerNotChecked = new Combine([ |     const layerNotChecked = new Combine([ | ||||||
|       iconUnselected, |       iconUnselected, | ||||||
|       styledNameUnChecked, |       styledNameUnChecked, | ||||||
|     ]).SetStyle(style); |     ]).SetStyle(style); | ||||||
| 
 | 
 | ||||||
|         checkboxes.push( |     let listFilterElements: InputElement<TagsFilter>[] = layer.filters.map( | ||||||
|           new Toggle(layerChecked, layerNotChecked, layer.isDisplayed) |       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() |       .ToggleOnClick() | ||||||
|             .SetStyle("margin:0.3em;") |       .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 combinedCheckboxes = new Combine(checkboxes); |     let options = filterConfig.options; | ||||||
|       combinedCheckboxes.SetStyle("display:flex;flex-direction:column;"); |  | ||||||
| 
 | 
 | ||||||
|       filterPanel = new Combine([combinedCheckboxes]); |     return new RadioButton( | ||||||
| 
 |       options.map( | ||||||
|       return filterPanel; |         (option) => | ||||||
|     } |           new FixedInputElement(option.question.Clone(), option.osmTags) | ||||||
|  |       ) | ||||||
|  |     ); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -8,41 +8,52 @@ 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>) { |   constructor(isShown: UIEventSource<boolean>) { | ||||||
|         super(LayerControlPanel.GenTitle, LayerControlPanel.GeneratePanel, "layers", isShown); |     super( | ||||||
|  |       LayerControlPanel.GenTitle, | ||||||
|  |       LayerControlPanel.GeneratePanel, | ||||||
|  |       "layers", | ||||||
|  |       isShown | ||||||
|  |     ); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   private static GenTitle(): BaseUIElement { |   private static GenTitle(): BaseUIElement { | ||||||
|         return Translations.t.general.layerSelection.title.Clone().SetClass("text-2xl break-words font-bold p-2") |     return Translations.t.general.layerSelection.title | ||||||
|  |       .Clone() | ||||||
|  |       .SetClass("text-2xl break-words font-bold p-2"); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   private static GeneratePanel(): BaseUIElement { |   private static GeneratePanel(): BaseUIElement { | ||||||
|         const elements: BaseUIElement[] = [] |     const elements: BaseUIElement[] = []; | ||||||
| 
 | 
 | ||||||
|     if (State.state.layoutToUse.data.enableBackgroundLayerSelection) { |     if (State.state.layoutToUse.data.enableBackgroundLayerSelection) { | ||||||
|       const backgroundSelector = new BackgroundSelector(); |       const backgroundSelector = new BackgroundSelector(); | ||||||
|       backgroundSelector.SetStyle("margin:1em"); |       backgroundSelector.SetStyle("margin:1em"); | ||||||
|             backgroundSelector.onClick(() => { |       backgroundSelector.onClick(() => {}); | ||||||
|             }); |       elements.push(backgroundSelector); | ||||||
|             elements.push(backgroundSelector) |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|         elements.push(new Toggle( |     elements.push( | ||||||
|             new LayerSelection(State.state.filteredLayers), |       new Toggle( | ||||||
|  |         new FilterView(State.state.filteredLayers), | ||||||
|         undefined, |         undefined, | ||||||
|             State.state.filteredLayers.map(layers => layers.length > 1) |         State.state.filteredLayers.map( | ||||||
|         )) |           (layers) => layers.length > 1 || layers[0].layerDef.filters.length > 0 | ||||||
|  |         ) | ||||||
|  |       ) | ||||||
|  |     ); | ||||||
| 
 | 
 | ||||||
|         elements.push(new Toggle( |     elements.push( | ||||||
|  |       new Toggle( | ||||||
|         new ExportDataButton(), |         new ExportDataButton(), | ||||||
|         undefined, |         undefined, | ||||||
|         State.state.featureSwitchEnableExport |         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