forked from MapComplete/MapComplete
		
	Move AllTagsPanel to separate class; various small fixes
This commit is contained in:
		
							parent
							
								
									27f12b1f9d
								
							
						
					
					
						commit
						9f5c506e17
					
				
					 12 changed files with 85 additions and 411 deletions
				
			
		
							
								
								
									
										46
									
								
								UI/AllTagsPanel.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								UI/AllTagsPanel.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,46 @@ | |||
| import {VariableUiElement} from "./Base/VariableUIElement"; | ||||
| import {UIEventSource} from "../Logic/UIEventSource"; | ||||
| import Table from "./Base/Table"; | ||||
| 
 | ||||
| export class AllTagsPanel extends VariableUiElement { | ||||
| 
 | ||||
|     constructor(tags: UIEventSource<any>, state?) { | ||||
| 
 | ||||
|         const calculatedTags = [].concat( | ||||
|            // SimpleMetaTagger.lazyTags,
 | ||||
|             ...(state?.layoutToUse?.layers?.map(l => l.calculatedTags?.map(c => c[0]) ?? []) ?? [])) | ||||
| 
 | ||||
| 
 | ||||
|         super(tags.map(tags => { | ||||
|             const parts = []; | ||||
|             for (const key in tags) { | ||||
|                 if (!tags.hasOwnProperty(key)) { | ||||
|                     continue | ||||
|                 } | ||||
|                 let v = tags[key] | ||||
|                 if (v === "") { | ||||
|                     v = "<b>empty string</b>" | ||||
|                 } | ||||
|                 parts.push([key, v ?? "<b>undefined</b>"]); | ||||
|             } | ||||
| 
 | ||||
|             for (const key of calculatedTags) { | ||||
|                 const value = tags[key] | ||||
|                 if (value === undefined) { | ||||
|                     continue | ||||
|                 } | ||||
|                 let type = ""; | ||||
|                 if (typeof value !== "string") { | ||||
|                     type = " <i>" + (typeof value) + "</i>" | ||||
|                 } | ||||
|                 parts.push(["<i>" + key + "</i>", value]) | ||||
|             } | ||||
| 
 | ||||
|             return new Table( | ||||
|                 ["key", "value"], | ||||
|                 parts | ||||
|             ) | ||||
|                 .SetStyle("border: 1px solid black; border-radius: 1em;padding:1em;display:block;").SetClass("zebra-table") | ||||
|         })) | ||||
|     } | ||||
| } | ||||
|  | @ -16,7 +16,7 @@ export class VariableUiElement extends BaseUIElement { | |||
|     } | ||||
| 
 | ||||
|     AsMarkdown(): string { | ||||
|         const d = this._contents.data; | ||||
|         const d = this._contents?.data; | ||||
|         if (typeof d === "string") { | ||||
|             return d; | ||||
|         } | ||||
|  | @ -29,7 +29,7 @@ export class VariableUiElement extends BaseUIElement { | |||
|     protected InnerConstructElement(): HTMLElement { | ||||
|         const el = document.createElement("span"); | ||||
|         const self = this; | ||||
|         this._contents.addCallbackAndRun((contents) => { | ||||
|         this._contents?.addCallbackAndRun((contents) => { | ||||
|             if (self.isDestroyed) { | ||||
|                 return true; | ||||
|             } | ||||
|  |  | |||
|  | @ -94,7 +94,7 @@ export default abstract class BaseUIElement { | |||
|      * The same as 'Render', but creates a HTML element instead of the HTML representation | ||||
|      */ | ||||
|     public ConstructElement(): HTMLElement { | ||||
|         if (Utils.runningFromConsole) { | ||||
|         if (typeof window === undefined) { | ||||
|             return undefined; | ||||
|         } | ||||
| 
 | ||||
|  |  | |||
|  | @ -55,7 +55,7 @@ export default class DeleteImage extends Toggle { | |||
|                 tags.map(tags => (tags[key] ?? "") !== "") | ||||
|             ), | ||||
|             undefined /*Login (and thus editing) is disabled*/, | ||||
|             state.osmConnection.isLoggedIn | ||||
|             state?.osmConnection?.isLoggedIn | ||||
|         ) | ||||
|         this.SetClass("cursor-pointer") | ||||
|     } | ||||
|  |  | |||
|  | @ -169,7 +169,7 @@ export class ImageUploadFlow extends Toggle { | |||
|                 state?.osmConnection?.isLoggedIn | ||||
|             ), | ||||
|             undefined /* Nothing as the user badge is disabled*/, | ||||
|             state.featureSwitchUserbadge | ||||
|             state?.featureSwitchUserbadge | ||||
|         ) | ||||
| 
 | ||||
|     } | ||||
|  |  | |||
|  | @ -21,9 +21,9 @@ import {VariableUiElement} from "../Base/VariableUIElement"; | |||
| import {FixedUiElement} from "../Base/FixedUiElement"; | ||||
| import {FlowStep} from "./FlowStep"; | ||||
| import ScrollableFullScreen from "../Base/ScrollableFullScreen"; | ||||
| import {AllTagsPanel} from "../SpecialVisualizations"; | ||||
| import Title from "../Base/Title"; | ||||
| import CheckBoxes from "../Input/Checkboxes"; | ||||
| import {AllTagsPanel} from "../AllTagsPanel"; | ||||
| 
 | ||||
| class PreviewPanel extends ScrollableFullScreen { | ||||
| 
 | ||||
|  |  | |||
|  | @ -13,7 +13,7 @@ export default class Toggle extends VariableUiElement { | |||
| 
 | ||||
|     constructor(showEnabled: string | BaseUIElement, showDisabled: string | BaseUIElement, isEnabled: UIEventSource<boolean> = new UIEventSource<boolean>(false)) { | ||||
|         super( | ||||
|             isEnabled.map(isEnabled => isEnabled ? showEnabled : showDisabled) | ||||
|             isEnabled?.map(isEnabled => isEnabled ? showEnabled : showDisabled) | ||||
|         ); | ||||
|         this.isEnabled = isEnabled | ||||
|     } | ||||
|  |  | |||
|  | @ -1,4 +1,5 @@ | |||
| import {Utils} from "../../Utils"; | ||||
| import opening_hours from "opening_hours"; | ||||
| 
 | ||||
| export interface OpeningHour { | ||||
|     weekday: number, // 0 is monday, 1 is tuesday, ...
 | ||||
|  | @ -458,6 +459,17 @@ export class OH { | |||
|         return [changeHours, changeHourText] | ||||
|     } | ||||
| 
 | ||||
|     public static CreateOhObject(tags: object & {_lat: number, _lon: number, _country?: string}, textToParse: string){ | ||||
|         // noinspection JSPotentiallyInvalidConstructorUsage
 | ||||
|         return new opening_hours(textToParse, { | ||||
|             lat: tags._lat, | ||||
|             lon: tags._lon, | ||||
|             address: { | ||||
|                 country_code: tags._country | ||||
|             }, | ||||
|         }, {tag_key: "opening_hours"}); | ||||
|     } | ||||
|      | ||||
|     /* | ||||
|  Calculates when the business is opened (or on holiday) between two dates. | ||||
|  Returns a matrix of ranges, where [0] is a list of ranges when it is opened on monday, [1] is a list of ranges for tuesday, ... | ||||
|  | @ -599,6 +611,12 @@ export class OH { | |||
|         } | ||||
|         return ohs; | ||||
|     } | ||||
|     public static getMondayBefore(d) { | ||||
|         d = new Date(d); | ||||
|         const day = d.getDay(); | ||||
|         const diff = d.getDate() - day + (day == 0 ? -6 : 1); // adjust when day is sunday
 | ||||
|         return new Date(d.setDate(diff)); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -4,7 +4,6 @@ import {FixedUiElement} from "../Base/FixedUiElement"; | |||
| import {OH} from "./OpeningHours"; | ||||
| import Translations from "../i18n/Translations"; | ||||
| import Constants from "../../Models/Constants"; | ||||
| import opening_hours from "opening_hours"; | ||||
| import BaseUIElement from "../BaseUIElement"; | ||||
| import Toggle from "../Input/Toggle"; | ||||
| import {VariableUiElement} from "../Base/VariableUIElement"; | ||||
|  | @ -24,7 +23,6 @@ export default class OpeningHoursVisualization extends Toggle { | |||
|     ] | ||||
| 
 | ||||
|     constructor(tags: UIEventSource<any>, state: { osmConnection?: OsmConnection }, key: string, prefix = "", postfix = "") { | ||||
|         const tagsDirect = tags.data; | ||||
|         const ohTable = new VariableUiElement(tags | ||||
|             .map(tags => { | ||||
|                 const value: string = tags[key]; | ||||
|  | @ -41,16 +39,8 @@ export default class OpeningHoursVisualization extends Toggle { | |||
|                         return new FixedUiElement("No opening hours defined with key " + key).SetClass("alert") | ||||
|                     } | ||||
|                     try { | ||||
|                         // noinspection JSPotentiallyInvalidConstructorUsage
 | ||||
|                         const oh = new opening_hours(ohtext, { | ||||
|                             lat: tagsDirect._lat, | ||||
|                             lon: tagsDirect._lon, | ||||
|                             address: { | ||||
|                                 country_code: tagsDirect._country | ||||
|                             }, | ||||
|                         }, {tag_key: "opening_hours"}); | ||||
| 
 | ||||
|                         return OpeningHoursVisualization.CreateFullVisualisation(oh) | ||||
|                         return OpeningHoursVisualization.CreateFullVisualisation( | ||||
|                             OH.CreateOhObject(tags.data, ohtext)) | ||||
|                     } catch (e) { | ||||
|                         console.warn(e, e.stack); | ||||
|                         return new Combine([Translations.t.general.opening_hours.error_loading, | ||||
|  | @ -78,7 +68,7 @@ export default class OpeningHoursVisualization extends Toggle { | |||
| 
 | ||||
|         const today = new Date(); | ||||
|         today.setHours(0, 0, 0, 0); | ||||
|         const lastMonday = OpeningHoursVisualization.getMonday(today); | ||||
|         const lastMonday = OH.getMondayBefore(today); | ||||
|         const nextSunday = new Date(lastMonday); | ||||
|         nextSunday.setDate(nextSunday.getDate() + 7); | ||||
| 
 | ||||
|  | @ -283,11 +273,5 @@ export default class OpeningHoursVisualization extends Toggle { | |||
|         return Translations.t.general.opening_hours.closed_until.Subs({date: willOpenAt}) | ||||
|     } | ||||
| 
 | ||||
|     private static getMonday(d) { | ||||
|         d = new Date(d); | ||||
|         const day = d.getDay(); | ||||
|         const diff = d.getDate() - day + (day == 0 ? -6 : 1); // adjust when day is sunday
 | ||||
|         return new Date(d.setDate(diff)); | ||||
|     } | ||||
| 
 | ||||
|     | ||||
| } | ||||
|  | @ -1,50 +1,5 @@ | |||
| import {UIEventSource} from "../../Logic/UIEventSource"; | ||||
| import LayerConfig from "../../Models/ThemeConfig/LayerConfig"; | ||||
| import {ShowDataLayerOptions} from "./ShowDataLayerOptions"; | ||||
| import {ElementStorage} from "../../Logic/ElementStorage"; | ||||
| import RenderingMultiPlexerFeatureSource from "../../Logic/FeatureSource/Sources/RenderingMultiPlexerFeatureSource"; | ||||
| import ScrollableFullScreen from "../Base/ScrollableFullScreen"; | ||||
| /* | ||||
| // import 'leaflet-polylineoffset'; 
 | ||||
| We don't actually import it here. It is imported in the 'MinimapImplementation'-class, which'll result in a patched 'L' object. | ||||
|  Even though actually importing this here would seem cleaner, we don't do this as this breaks some scripts: | ||||
|  - Scripts are ran in ts-node | ||||
|  - ts-node doesn't define the 'window'-object | ||||
|  - Importing this will execute some code which needs the window object | ||||
| 
 | ||||
|  */ | ||||
| 
 | ||||
| /** | ||||
|  * The data layer shows all the given geojson elements with the appropriate icon etc | ||||
|  */ | ||||
| export default class ShowDataLayer { | ||||
| 
 | ||||
|     private static dataLayerIds = 0 | ||||
|     private readonly _leafletMap: UIEventSource<L.Map>; | ||||
|     private readonly _enablePopups: boolean; | ||||
|     private readonly _features: RenderingMultiPlexerFeatureSource | ||||
|     private readonly _layerToShow: LayerConfig; | ||||
|     private readonly _selectedElement: UIEventSource<any> | ||||
|     private readonly allElements: ElementStorage | ||||
|     // Used to generate a fresh ID when needed
 | ||||
|     private _cleanCount = 0; | ||||
|     private geoLayer = undefined; | ||||
| 
 | ||||
|     /** | ||||
|      * A collection of functions to call when the current geolayer is unregistered | ||||
|      */ | ||||
|     private unregister: (() => void)[] = []; | ||||
|     private isDirty = false; | ||||
|     /** | ||||
|      * If the selected element triggers, this is used to lookup the correct layer and to open the popup | ||||
|      * Used to avoid a lot of callbacks on the selected element | ||||
|      * | ||||
|      * Note: the key of this dictionary is 'feature.properties.id+features.geometry.type' as one feature might have multiple presentations | ||||
|      * @private | ||||
|      */ | ||||
|     private readonly leafletLayersPerId = new Map<string, { feature: any, leafletlayer: any }>() | ||||
|     private readonly showDataLayerid: number; | ||||
|     private readonly createPopup: (tags: UIEventSource<any>, layer: LayerConfig) => ScrollableFullScreen | ||||
| 
 | ||||
|     /** | ||||
|      * Creates a datalayer. | ||||
|  | @ -52,299 +7,10 @@ export default class ShowDataLayer { | |||
|      * If 'createPopup' is set, this function is called every time that 'popupOpen' is called | ||||
|      * @param options | ||||
|      */ | ||||
|     constructor(options: ShowDataLayerOptions & { layerToShow: LayerConfig }) { | ||||
|         this._leafletMap = options.leafletMap; | ||||
|         this.showDataLayerid = ShowDataLayer.dataLayerIds; | ||||
|         ShowDataLayer.dataLayerIds++ | ||||
|         if (options.features === undefined) { | ||||
|             console.error("Invalid ShowDataLayer invocation: options.features is undefed") | ||||
|             throw "Invalid ShowDataLayer invocation: options.features is undefed" | ||||
|         } | ||||
|         this._features = new RenderingMultiPlexerFeatureSource(options.features, options.layerToShow); | ||||
|         this._layerToShow = options.layerToShow; | ||||
|         this._selectedElement = options.selectedElement | ||||
|         this.allElements = options.state?.allElements; | ||||
|         this.createPopup = undefined; | ||||
|         this._enablePopups = options.popup !== undefined; | ||||
|         if (options.popup !== undefined) { | ||||
|             this.createPopup = options.popup | ||||
|         } | ||||
|         const self = this; | ||||
| 
 | ||||
|         options.leafletMap.addCallback(_ => { | ||||
|                 return self.update(options) | ||||
|             } | ||||
|         ); | ||||
| 
 | ||||
|         this._features.features.addCallback(_ => self.update(options)); | ||||
|         options.doShowLayer?.addCallback(doShow => { | ||||
|             const mp = options.leafletMap.data; | ||||
|             if (mp === null) { | ||||
|                 self.Destroy() | ||||
|                 return true; | ||||
|             } | ||||
|             if (mp == undefined) { | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             if (doShow) { | ||||
|                 if (self.isDirty) { | ||||
|                     return self.update(options) | ||||
|                 } else { | ||||
|                     mp.addLayer(this.geoLayer) | ||||
|                 } | ||||
|             } else { | ||||
|                 if (this.geoLayer !== undefined) { | ||||
|                     mp.removeLayer(this.geoLayer) | ||||
|                     this.unregister.forEach(f => f()) | ||||
|                     this.unregister = [] | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|         }) | ||||
| 
 | ||||
| 
 | ||||
|         this._selectedElement?.addCallbackAndRunD(selected => { | ||||
|             self.openPopupOfSelectedElement(selected) | ||||
|         }) | ||||
| 
 | ||||
|         this.update(options) | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     private Destroy() { | ||||
|         this.unregister.forEach(f => f()) | ||||
|     } | ||||
| 
 | ||||
|     private openPopupOfSelectedElement(selected) { | ||||
|         if (selected === undefined) { | ||||
|             return | ||||
|         } | ||||
|         if (this._leafletMap.data === undefined) { | ||||
|             return; | ||||
|         } | ||||
|         const v = this.leafletLayersPerId.get(selected.properties.id + selected.geometry.type) | ||||
|         if (v === undefined) { | ||||
|             return; | ||||
|         } | ||||
|         const leafletLayer = v.leafletlayer | ||||
|         const feature = v.feature | ||||
|         if (leafletLayer.getPopup().isOpen()) { | ||||
|             return; | ||||
|         } | ||||
|         if (selected.properties.id !== feature.properties.id) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         if (feature.id !== feature.properties.id) { | ||||
|             // Probably a feature which has renamed
 | ||||
|             // the feature might have as id 'node/-1' and as 'feature.properties.id' = 'the newly assigned id'. That is no good too
 | ||||
|             console.log("Not opening the popup for", feature, "as probably renamed") | ||||
|             return; | ||||
|         } | ||||
|         if (selected.geometry.type === feature.geometry.type  // If a feature is rendered both as way and as point, opening one popup might trigger the other to open, which might trigger the one to open again
 | ||||
|         ) { | ||||
|             leafletLayer.openPopup() | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private update(options: ShowDataLayerOptions): boolean { | ||||
|         if (this._features.features.data === undefined) { | ||||
|             return; | ||||
|         } | ||||
|         this.isDirty = true; | ||||
|         if (options?.doShowLayer?.data === false) { | ||||
|             return; | ||||
|         } | ||||
|         const mp = options.leafletMap.data; | ||||
| 
 | ||||
|         if (mp === null) { | ||||
|             return true; // Unregister as the map has been destroyed
 | ||||
|         } | ||||
|         if (mp === undefined) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         this._cleanCount++ | ||||
|         // clean all the old stuff away, if any
 | ||||
|         if (this.geoLayer !== undefined) { | ||||
|             mp.removeLayer(this.geoLayer); | ||||
|         } | ||||
| 
 | ||||
|         const self = this; | ||||
|         const data = { | ||||
|             type: "FeatureCollection", | ||||
|             features: [] | ||||
|         } | ||||
|         // @ts-ignore
 | ||||
|         this.geoLayer = L.geoJSON(data, { | ||||
|             style: feature => self.createStyleFor(feature), | ||||
|             pointToLayer: (feature, latLng) => self.pointToLayer(feature, latLng), | ||||
|             onEachFeature: (feature, leafletLayer) => self.postProcessFeature(feature, leafletLayer) | ||||
|         }); | ||||
| 
 | ||||
|         const selfLayer = this.geoLayer; | ||||
|         const allFeats = this._features.features.data; | ||||
|         for (const feat of allFeats) { | ||||
|             if (feat === undefined) { | ||||
|                 continue | ||||
|             } | ||||
|             try { | ||||
|                 if (feat.geometry.type === "LineString") { | ||||
|                     const coords = L.GeoJSON.coordsToLatLngs(feat.geometry.coordinates) | ||||
|                     const tagsSource = this.allElements?.addOrGetElement(feat) ?? new UIEventSource<any>(feat.properties); | ||||
|                     let offsettedLine; | ||||
|                     tagsSource | ||||
|                         .map(tags => this._layerToShow.lineRendering[feat.lineRenderingIndex].GenerateLeafletStyle(tags), [], undefined, true) | ||||
|                         .withEqualityStabilized((a, b) => { | ||||
|                             if (a === b) { | ||||
|                                 return true | ||||
|                             } | ||||
|                             if (a === undefined || b === undefined) { | ||||
|                                 return false | ||||
|                             } | ||||
|                             return a.offset === b.offset && a.color === b.color && a.weight === b.weight && a.dashArray === b.dashArray | ||||
|                         }) | ||||
|                         .addCallbackAndRunD(lineStyle => { | ||||
|                             if (offsettedLine !== undefined) { | ||||
|                                 self.geoLayer.removeLayer(offsettedLine) | ||||
|                             } | ||||
|                             // @ts-ignore
 | ||||
|                             offsettedLine = L.polyline(coords, lineStyle); | ||||
|                             this.postProcessFeature(feat, offsettedLine) | ||||
|                             offsettedLine.addTo(this.geoLayer) | ||||
| 
 | ||||
|                             // If 'self.geoLayer' is not the same as the layer the feature is added to, we can safely remove this callback
 | ||||
|                             return self.geoLayer !== selfLayer | ||||
|                         }) | ||||
|                 } else { | ||||
|                     this.geoLayer.addData(feat); | ||||
|                 } | ||||
|             } catch (e) { | ||||
|                 console.error("Could not add ", feat, "to the geojson layer in leaflet due to", e, e.stack) | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         if (options.zoomToFeatures ?? false) { | ||||
|             if (this.geoLayer.getLayers().length > 0) { | ||||
|                 try { | ||||
|                     const bounds = this.geoLayer.getBounds() | ||||
|                     mp.fitBounds(bounds, {animate: false}) | ||||
|                 } catch (e) { | ||||
|                     console.debug("Invalid bounds", e) | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         if (options.doShowLayer?.data ?? true) { | ||||
|             mp.addLayer(this.geoLayer) | ||||
|         } | ||||
|         this.isDirty = false; | ||||
|         this.openPopupOfSelectedElement(this._selectedElement?.data) | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     private createStyleFor(feature) { | ||||
|         const tagsSource = this.allElements?.addOrGetElement(feature) ?? new UIEventSource<any>(feature.properties); | ||||
|         // Every object is tied to exactly one layer
 | ||||
|         const layer = this._layerToShow | ||||
| 
 | ||||
|         const pointRenderingIndex = feature.pointRenderingIndex | ||||
|         const lineRenderingIndex = feature.lineRenderingIndex | ||||
| 
 | ||||
|         if (pointRenderingIndex !== undefined) { | ||||
|             const style = layer.mapRendering[pointRenderingIndex].GenerateLeafletStyle(tagsSource, this._enablePopups) | ||||
|             return { | ||||
|                 icon: style | ||||
|             } | ||||
|         } | ||||
|         if (lineRenderingIndex !== undefined) { | ||||
|             return layer.lineRendering[lineRenderingIndex].GenerateLeafletStyle(tagsSource.data) | ||||
|         } | ||||
| 
 | ||||
|         throw "Neither lineRendering nor mapRendering defined for " + feature | ||||
|     } | ||||
| 
 | ||||
|     private pointToLayer(feature, latLng): L.Layer { | ||||
|         // Leaflet cannot handle geojson points natively
 | ||||
|         // We have to convert them to the appropriate icon
 | ||||
|         // Click handling is done in the next step
 | ||||
| 
 | ||||
|         const layer: LayerConfig = this._layerToShow | ||||
|         if (layer === undefined) { | ||||
|             return; | ||||
|         } | ||||
|         let tagSource = this.allElements?.getEventSourceById(feature.properties.id) ?? new UIEventSource<any>(feature.properties) | ||||
|         const clickable = !(layer.title === undefined && (layer.tagRenderings ?? []).length === 0) && this._enablePopups | ||||
|         let style: any = layer.mapRendering[feature.pointRenderingIndex].GenerateLeafletStyle(tagSource, clickable); | ||||
|         const baseElement = style.html; | ||||
|         if (!this._enablePopups) { | ||||
|             baseElement.SetStyle("cursor: initial !important") | ||||
|         } | ||||
|         style.html = style.html.ConstructElement() | ||||
|         return L.marker(latLng, { | ||||
|             icon: L.divIcon(style) | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Post processing - basically adding the popup | ||||
|      * @param feature | ||||
|      * @param leafletLayer | ||||
|      * @private | ||||
|      */ | ||||
|     private postProcessFeature(feature, leafletLayer: L.Layer) { | ||||
|         const layer: LayerConfig = this._layerToShow | ||||
|         if (layer.title === undefined || !this._enablePopups) { | ||||
|             // No popup action defined -> Don't do anything
 | ||||
|             // or probably a map in the popup - no popups needed!
 | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         const popup = L.popup({ | ||||
|             autoPan: true, | ||||
|             closeOnEscapeKey: true, | ||||
|             closeButton: false, | ||||
|             autoPanPaddingTopLeft: [15, 15], | ||||
| 
 | ||||
|         }, leafletLayer); | ||||
| 
 | ||||
|         leafletLayer.bindPopup(popup); | ||||
| 
 | ||||
|         let infobox: ScrollableFullScreen = undefined; | ||||
|         const id = `popup-${feature.properties.id}-${feature.geometry.type}-${this.showDataLayerid}-${this._cleanCount}-${feature.pointRenderingIndex ?? feature.lineRenderingIndex}-${feature.multiLineStringIndex ?? ""}` | ||||
|         popup.setContent(`<div style='height: 65vh' id='${id}'>Popup for ${feature.properties.id} ${feature.geometry.type} ${id} is loading</div>`) | ||||
|         const createpopup = this.createPopup; | ||||
|         leafletLayer.on("popupopen", () => { | ||||
|             if (infobox === undefined) { | ||||
|                 const tags = this.allElements?.getEventSourceById(feature.properties.id) ?? new UIEventSource<any>(feature.properties); | ||||
|                 infobox = createpopup(tags, layer); | ||||
| 
 | ||||
|                 infobox.isShown.addCallback(isShown => { | ||||
|                     if (!isShown) { | ||||
|                         leafletLayer.closePopup() | ||||
|                     } | ||||
|                 }); | ||||
|             } | ||||
|             infobox.AttachTo(id) | ||||
|             infobox.Activate(); | ||||
|             this.unregister.push(() => { | ||||
|                 console.log("Destroying infobox") | ||||
|                 infobox.Destroy(); | ||||
|             }) | ||||
|             if (this._selectedElement?.data?.properties?.id !== feature.properties.id) { | ||||
|                 this._selectedElement?.setData(feature) | ||||
|             } | ||||
| 
 | ||||
|         }); | ||||
| 
 | ||||
| 
 | ||||
|         // Add the feature to the index to open the popup when needed
 | ||||
|         this.leafletLayersPerId.set(feature.properties.id + feature.geometry.type, { | ||||
|             feature: feature, | ||||
|             leafletlayer: leafletLayer | ||||
|         }) | ||||
|     constructor(options) { | ||||
|         | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|    | ||||
| } | ||||
|  | @ -24,7 +24,6 @@ import ShowDataMultiLayer from "./ShowDataLayer/ShowDataMultiLayer"; | |||
| import Minimap from "./Base/Minimap"; | ||||
| import AllImageProviders from "../Logic/ImageProviders/AllImageProviders"; | ||||
| import WikipediaBox from "./Wikipedia/WikipediaBox"; | ||||
| import SimpleMetaTagger from "../Logic/SimpleMetaTagger"; | ||||
| import MultiApply from "./Popup/MultiApply"; | ||||
| import ShowDataLayer from "./ShowDataLayer/ShowDataLayer"; | ||||
| import {SubtleButton} from "./Base/SubtleButton"; | ||||
|  | @ -48,6 +47,7 @@ import {SubstitutedTranslation} from "./SubstitutedTranslation"; | |||
| import {TextField} from "./Input/TextField"; | ||||
| import Wikidata, {WikidataResponse} from "../Logic/Web/Wikidata"; | ||||
| import {Translation} from "./i18n/Translation"; | ||||
| import {AllTagsPanel} from "./AllTagsPanel"; | ||||
| 
 | ||||
| export interface SpecialVisualization { | ||||
|     funcName: string, | ||||
|  | @ -58,49 +58,6 @@ export interface SpecialVisualization { | |||
|     getLayerDependencies?: (argument: string[]) => string[] | ||||
| } | ||||
| 
 | ||||
| export class AllTagsPanel extends VariableUiElement { | ||||
| 
 | ||||
|     constructor(tags: UIEventSource<any>, state?) { | ||||
| 
 | ||||
|         const calculatedTags = [].concat( | ||||
|             SimpleMetaTagger.lazyTags, | ||||
|             ...(state?.layoutToUse?.layers?.map(l => l.calculatedTags?.map(c => c[0]) ?? []) ?? [])) | ||||
| 
 | ||||
| 
 | ||||
|         super(tags.map(tags => { | ||||
|             const parts = []; | ||||
|             for (const key in tags) { | ||||
|                 if (!tags.hasOwnProperty(key)) { | ||||
|                     continue | ||||
|                 } | ||||
|                 let v = tags[key] | ||||
|                 if (v === "") { | ||||
|                     v = "<b>empty string</b>" | ||||
|                 } | ||||
|                 parts.push([key, v ?? "<b>undefined</b>"]); | ||||
|             } | ||||
| 
 | ||||
|             for (const key of calculatedTags) { | ||||
|                 const value = tags[key] | ||||
|                 if (value === undefined) { | ||||
|                     continue | ||||
|                 } | ||||
|                 let type = ""; | ||||
|                 if(typeof value !== "string"){ | ||||
|                     type = " <i>"+(typeof value)+"</i>" | ||||
|                 } | ||||
|                 parts.push(["<i>" + key + "</i>", value]) | ||||
|             } | ||||
| 
 | ||||
|             return new Table( | ||||
|                 ["key", "value"], | ||||
|                 parts | ||||
|             ) | ||||
|                 .SetStyle("border: 1px solid black; border-radius: 1em;padding:1em;display:block;").SetClass("zebra-table") | ||||
|         })) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| class CloseNoteButton implements SpecialVisualization { | ||||
|     public readonly funcName = "close_note" | ||||
|     public readonly docs = "Button to close a note. A predifined text can be defined to close the note with. If the note is already closed, will show a small text." | ||||
|  | @ -347,6 +304,9 @@ export default class SpecialVisualizations { | |||
|                     example: "`{minimap()}`, `{minimap(17, id, _list_of_embedded_feature_ids_calculated_by_calculated_tag):height:10rem; border: 2px solid black}`", | ||||
|                     constr: (state, tagSource, args, _) => { | ||||
| 
 | ||||
|                         if(state === undefined){ | ||||
|                             return undefined | ||||
|                         } | ||||
|                         const keys = [...args] | ||||
|                         keys.splice(0, 1) | ||||
|                         const featureStore = state.allElements.ContainingFeatures | ||||
|  | @ -655,7 +615,7 @@ export default class SpecialVisualizations { | |||
|                                 if (value === undefined) { | ||||
|                                     return undefined | ||||
|                                 } | ||||
|                                 const allUnits = [].concat(...state.layoutToUse.layers.map(lyr => lyr.units)) | ||||
|                                 const allUnits = [].concat(...(state?.layoutToUse?.layers?.map(lyr => lyr.units) ?? [])) | ||||
|                                 const unit = allUnits.filter(unit => unit.isApplicableToKey(key))[0] | ||||
|                                 if (unit === undefined) { | ||||
|                                     return value; | ||||
|  |  | |||
|  | @ -54,7 +54,7 @@ export class SubstitutedTranslation extends VariableUiElement { | |||
|                         } | ||||
|                         const viz = proto.special; | ||||
|                         try { | ||||
|                             return viz.func.constr(state, tagsSource, proto.special.args, DefaultGuiState.state).SetStyle(proto.special.style); | ||||
|                             return viz.func.constr(state, tagsSource, proto.special.args, DefaultGuiState.state)?.SetStyle(proto.special.style); | ||||
|                         } catch (e) { | ||||
|                             console.error("SPECIALRENDERING FAILED for", tagsSource.data?.id, e) | ||||
|                             return new FixedUiElement(`Could not generate special rendering for ${viz.func.funcName}(${viz.args.join(", ")}) ${e}`).SetStyle("alert") | ||||
|  | @ -80,7 +80,7 @@ export class SubstitutedTranslation extends VariableUiElement { | |||
|         } | ||||
|     }[] { | ||||
| 
 | ||||
|         for (const knownSpecial of SpecialVisualizations.specialVisualizations.concat(extraMappings)) { | ||||
|         for (const knownSpecial of extraMappings.concat(SpecialVisualizations.specialVisualizations)) { | ||||
| 
 | ||||
|             // Note: the '.*?' in the regex reads as 'any character, but in a non-greedy way'
 | ||||
|             const matched = template.match(`(.*){${knownSpecial.funcName}\\((.*?)\\)(:.*)?}(.*)`); | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue