forked from MapComplete/MapComplete
		
	Feature(grb): add popup feature to validate e.g. a user profile
This commit is contained in:
		
							parent
							
								
									c3d905b26a
								
							
						
					
					
						commit
						ecd8f5e1da
					
				
					 11 changed files with 247 additions and 64 deletions
				
			
		|  | @ -19,6 +19,50 @@ | |||
|   "shortDescription": { | ||||
|     "nl": "Grb import helper tool" | ||||
|   }, | ||||
|   "popup": [ | ||||
|     { | ||||
|       "id": "wikilink-needed", | ||||
|       "condition": "_description!~.*https://wiki.openstreetmap.org/wiki/WikiProject_Belgium/Building_and_address_import.*", | ||||
|       "dismissable": false, | ||||
|       "title": { | ||||
|         "render": { | ||||
|           "en": "Profile mention obligated", | ||||
|           "nl": "Link op profiel verplicht" | ||||
|         } | ||||
|       }, | ||||
|       "body": [ | ||||
|         { | ||||
|           "render": { | ||||
|             "special": { | ||||
|               "type": "link", | ||||
|               "href": "https://www.openstreetmap.org/profile/edit", | ||||
|               "text": { | ||||
|                 "en": "Edit your user profile", | ||||
|                 "nl": "Pas je profiel aan" | ||||
|               } | ||||
|             }, | ||||
|             "after": { | ||||
|               "en": "to include the link <span class='literal-code'>https://wiki.openstreetmap.org/wiki/WikiProject_Belgium/Building_and_address_import</code>", | ||||
|               "nl": " en voeg deze link toe: <span class='literal-code'>https://wiki.openstreetmap.org/wiki/WikiProject_Belgium/Building_and_address_import</code>" | ||||
|             } | ||||
|           } | ||||
|         }, | ||||
|         { | ||||
|           "id": "reload_profile", | ||||
|           "render": { | ||||
|             "special": { | ||||
|               "type": "login_button", | ||||
|               "force": "yes", | ||||
|               "message": { | ||||
|                 "en": "Reload your profile", | ||||
|                 "nl": "Herlaad je profiel" | ||||
|               } | ||||
|             } | ||||
|           } | ||||
|         } | ||||
|       ] | ||||
|     } | ||||
|   ], | ||||
|   "icon": "./assets/themes/grb/logo.svg", | ||||
|   "startZoom": 9, | ||||
|   "startLat": 51.0249, | ||||
|  |  | |||
|  | @ -623,6 +623,30 @@ | |||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         }, | ||||
|         "popup": { | ||||
|             "0": { | ||||
|                 "body": { | ||||
|                     "0": { | ||||
|                         "render": { | ||||
|                             "special": { | ||||
|                                 "after": "to include the link <span class='literal-code'>https://wiki.openstreetmap.org/wiki/WikiProject_Belgium/Building_and_address_import</code>", | ||||
|                                 "text": "Edit your user profile" | ||||
|                             } | ||||
|                         } | ||||
|                     }, | ||||
|                     "1": { | ||||
|                         "render": { | ||||
|                             "special": { | ||||
|                                 "msg": "Reload your profile" | ||||
|                             } | ||||
|                         } | ||||
|                     } | ||||
|                 }, | ||||
|                 "title": { | ||||
|                     "render": "Profile mention obligated" | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     }, | ||||
|     "guideposts": { | ||||
|  |  | |||
|  | @ -669,6 +669,30 @@ | |||
|                 } | ||||
|             } | ||||
|         }, | ||||
|         "popup": { | ||||
|             "0": { | ||||
|                 "body": { | ||||
|                     "0": { | ||||
|                         "render": { | ||||
|                             "special": { | ||||
|                                 "after": " en voeg deze link toe: <span class='literal-code'>https://wiki.openstreetmap.org/wiki/WikiProject_Belgium/Building_and_address_import</code>", | ||||
|                                 "text": "Pas je profiel aan" | ||||
|                             } | ||||
|                         } | ||||
|                     }, | ||||
|                     "1": { | ||||
|                         "render": { | ||||
|                             "special": { | ||||
|                                 "msg": "Herlaad je profiel" | ||||
|                             } | ||||
|                         } | ||||
|                     } | ||||
|                 }, | ||||
|                 "title": { | ||||
|                     "render": "Link op profiel verplicht" | ||||
|                 } | ||||
|             } | ||||
|         }, | ||||
|         "shortDescription": "Grb import helper tool", | ||||
|         "title": "GRB import helper" | ||||
|     }, | ||||
|  |  | |||
|  | @ -1,16 +1,6 @@ | |||
| import { | ||||
|     Concat, | ||||
|     Conversion, | ||||
|     DesugaringContext, | ||||
|     DesugaringStep, | ||||
|     Each, | ||||
|     Fuse, | ||||
|     On, | ||||
|     Pass, | ||||
|     SetDefault, | ||||
| } from "./Conversion" | ||||
| import { Concat, Conversion, DesugaringContext, DesugaringStep, Each, Fuse, On, Pass, SetDefault } from "./Conversion" | ||||
| import { ThemeConfigJson } from "../Json/ThemeConfigJson" | ||||
| import { PrepareLayer } from "./PrepareLayer" | ||||
| import { PrepareLayer, RewriteSpecial } from "./PrepareLayer" | ||||
| import { LayerConfigJson } from "../Json/LayerConfigJson" | ||||
| import { Utils } from "../../../Utils" | ||||
| import Constants from "../../Constants" | ||||
|  | @ -40,7 +30,7 @@ class SubstituteLayer extends Conversion<string | LayerConfigJson, LayerConfigJs | |||
|             const knownLayers = Array.from(state.sharedLayers.keys()) | ||||
|             const withDistance: [string, number][] = knownLayers.map((lname) => [ | ||||
|                 lname, | ||||
|                 Utils.levenshteinDistance(name, lname), | ||||
|                 Utils.levenshteinDistance(name, lname) | ||||
|             ]) | ||||
|             withDistance.sort((a, b) => a[1] - b[1]) | ||||
|             const ids = withDistance.map((n) => n[0]) | ||||
|  | @ -130,9 +120,9 @@ class SubstituteLayer extends Conversion<string | LayerConfigJson, LayerConfigJs | |||
|                             usedLabels.add(labels[forbiddenLabel]) | ||||
|                             context.info( | ||||
|                                 "Dropping tagRendering " + | ||||
|                                     tr["id"] + | ||||
|                                     " as it has a forbidden label: " + | ||||
|                                     labels[forbiddenLabel] | ||||
|                                 tr["id"] + | ||||
|                                 " as it has a forbidden label: " + | ||||
|                                 labels[forbiddenLabel] | ||||
|                             ) | ||||
|                             continue | ||||
|                         } | ||||
|  | @ -150,10 +140,10 @@ class SubstituteLayer extends Conversion<string | LayerConfigJson, LayerConfigJs | |||
|                         usedLabels.add(tr["group"]) | ||||
|                         context.info( | ||||
|                             "Dropping tagRendering " + | ||||
|                                 tr["id"] + | ||||
|                                 " as its group `" + | ||||
|                                 tr["group"] + | ||||
|                                 "` is a forbidden label" | ||||
|                             tr["id"] + | ||||
|                             " as its group `" + | ||||
|                             tr["group"] + | ||||
|                             "` is a forbidden label" | ||||
|                         ) | ||||
|                         continue | ||||
|                     } | ||||
|  | @ -164,8 +154,8 @@ class SubstituteLayer extends Conversion<string | LayerConfigJson, LayerConfigJs | |||
|                 if (unused.length > 0) { | ||||
|                     context.err( | ||||
|                         "This theme specifies that certain tagrenderings have to be removed based on forbidden layers. One or more of these layers did not match any tagRenderings and caused no deletions: " + | ||||
|                             unused.join(", ") + | ||||
|                             "\n   This means that this label can be removed or that the original tagRendering that should be deleted does not have this label anymore" | ||||
|                         unused.join(", ") + | ||||
|                         "\n   This means that this label can be removed or that the original tagRendering that should be deleted does not have this label anymore" | ||||
|                     ) | ||||
|                 } | ||||
|                 found.tagRenderings = filtered | ||||
|  | @ -205,10 +195,10 @@ export class AddDefaultLayers extends DesugaringStep<ThemeConfigJson> { | |||
|             if (alreadyLoaded.has(v.id)) { | ||||
|                 context.warn( | ||||
|                     "Layout " + | ||||
|                         context + | ||||
|                         " already has a layer with name " + | ||||
|                         v.id + | ||||
|                         "; skipping inclusion of this builtin layer" | ||||
|                     context + | ||||
|                     " already has a layer with name " + | ||||
|                     v.id + | ||||
|                     "; skipping inclusion of this builtin layer" | ||||
|                 ) | ||||
|                 continue | ||||
|             } | ||||
|  | @ -352,10 +342,10 @@ class AddDependencyLayersToTheme extends DesugaringStep<ThemeConfigJson> { | |||
|                             .enters("layer dependency") | ||||
|                             .err( | ||||
|                                 "Layer " + | ||||
|                                     dependency.neededLayer + | ||||
|                                     " is loaded because " + | ||||
|                                     dependency.reason + | ||||
|                                     "; so it must specify a `snapName`. This is used in the sentence `move this point to snap it to {snapName}`" | ||||
|                                 dependency.neededLayer + | ||||
|                                 " is loaded because " + | ||||
|                                 dependency.reason + | ||||
|                                 "; so it must specify a `snapName`. This is used in the sentence `move this point to snap it to {snapName}`" | ||||
|                             ) | ||||
|                     } | ||||
|                 } | ||||
|  | @ -380,12 +370,12 @@ class AddDependencyLayersToTheme extends DesugaringStep<ThemeConfigJson> { | |||
|                 if (dep === undefined) { | ||||
|                     const message = [ | ||||
|                         "Loading a dependency failed: layer " + | ||||
|                             unmetDependency.neededLayer + | ||||
|                             " is not found, neither as layer of " + | ||||
|                             themeId + | ||||
|                             " nor as builtin layer.", | ||||
|                         unmetDependency.neededLayer + | ||||
|                         " is not found, neither as layer of " + | ||||
|                         themeId + | ||||
|                         " nor as builtin layer.", | ||||
|                         reason, | ||||
|                         "Loaded layers are: " + alreadyLoaded.map((l) => l.id).join(","), | ||||
|                         "Loaded layers are: " + alreadyLoaded.map((l) => l.id).join(",") | ||||
|                     ] | ||||
|                     throw message.join("\n\t") | ||||
|                 } | ||||
|  | @ -395,7 +385,7 @@ class AddDependencyLayersToTheme extends DesugaringStep<ThemeConfigJson> { | |||
|                 dep.description = reason | ||||
|                 dependenciesToAdd.unshift({ | ||||
|                     config: dep, | ||||
|                     reason, | ||||
|                     reason | ||||
|                 }) | ||||
|                 loadedLayerIds.add(dep.id) | ||||
|                 unmetDependencies = unmetDependencies.filter( | ||||
|  | @ -440,7 +430,7 @@ class AddDependencyLayersToTheme extends DesugaringStep<ThemeConfigJson> { | |||
| 
 | ||||
|         return { | ||||
|             ...theme, | ||||
|             layers: layers, | ||||
|             layers: layers | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | @ -510,10 +500,10 @@ class WarnForUnsubstitutedLayersInTheme extends DesugaringStep<ThemeConfigJson> | |||
| 
 | ||||
|             context.warn( | ||||
|                 "The theme " + | ||||
|                     json.id + | ||||
|                     " has an inline layer: " + | ||||
|                     layer["id"] + | ||||
|                     ". This is discouraged." | ||||
|                 json.id + | ||||
|                 " has an inline layer: " + | ||||
|                 layer["id"] + | ||||
|                 ". This is discouraged." | ||||
|             ) | ||||
|         } | ||||
|         return json | ||||
|  | @ -555,12 +545,12 @@ class PostvalidateTheme extends DesugaringStep<ThemeConfigJson> { | |||
|             if (minZoomAll < layer.minzoom) { | ||||
|                 context.err( | ||||
|                     "There are multiple layers based on " + | ||||
|                         basedOn + | ||||
|                         ". The layer with id " + | ||||
|                         layer.id + | ||||
|                         " has a minzoom of " + | ||||
|                         layer.minzoom + | ||||
|                         ", and has a name set. Another similar layer has a lower minzoom. As such, the layer selection might show 'zoom in to see features' even though some of the features are already visible. Set `\"name\": null` for this layer and eventually remove the 'name':null for the other layer." | ||||
|                     basedOn + | ||||
|                     ". The layer with id " + | ||||
|                     layer.id + | ||||
|                     " has a minzoom of " + | ||||
|                     layer.minzoom + | ||||
|                     ", and has a name set. Another similar layer has a lower minzoom. As such, the layer selection might show 'zoom in to see features' even though some of the features are already visible. Set `\"name\": null` for this layer and eventually remove the 'name':null for the other layer." | ||||
|                 ) | ||||
|             } | ||||
|         } | ||||
|  | @ -586,11 +576,11 @@ class PostvalidateTheme extends DesugaringStep<ThemeConfigJson> { | |||
|                     .enters("layers", config.id, "filter", "sameAs") | ||||
|                     .err( | ||||
|                         "The layer " + | ||||
|                             config.id + | ||||
|                             " follows the filter state of layer " + | ||||
|                             sameAs + | ||||
|                             ", but no layer with this name was found.\n\tDid you perhaps mean one of: " + | ||||
|                             closeLayers.slice(0, 3).join(", ") | ||||
|                         config.id + | ||||
|                         " follows the filter state of layer " + | ||||
|                         sameAs + | ||||
|                         ", but no layer with this name was found.\n\tDid you perhaps mean one of: " + | ||||
|                         closeLayers.slice(0, 3).join(", ") | ||||
|                     ) | ||||
|             } | ||||
|         } | ||||
|  | @ -618,6 +608,13 @@ export class PrepareTheme extends Fuse<ThemeConfigJson> { | |||
|             new SetDefault("socialImage", "assets/SocialImage.png", true), | ||||
|             // We expand all tagrenderings first...
 | ||||
|             new On("layers", new Each(new PrepareLayer(state))), | ||||
|             new On("popup", new Each( | ||||
|                 new Fuse("Prepare popups", | ||||
|                     new On("body", new Each(new RewriteSpecial())), | ||||
|                     new On("title", new RewriteSpecial()) | ||||
|                 ) | ||||
|             )), | ||||
| 
 | ||||
|             // Then we apply the override all. We must first expand everything in case that we override something in an expanded tag
 | ||||
|             // Note that it'll cheat with tagRenderings+
 | ||||
|             new ApplyOverrideAll(), | ||||
|  |  | |||
|  | @ -3,6 +3,8 @@ import ExtraLinkConfigJson from "./ExtraLinkConfigJson" | |||
| 
 | ||||
| import { RasterLayerProperties } from "../../RasterLayerProperties" | ||||
| import { Translatable } from "./Translatable" | ||||
| import { TagConfigJson } from "./TagConfigJson" | ||||
| import { TagRenderingConfigJson } from "./TagRenderingConfigJson" | ||||
| 
 | ||||
| /** | ||||
|  * Defines the entire theme. | ||||
|  | @ -468,4 +470,24 @@ export interface ThemeConfigJson { | |||
|      * group: hidden | ||||
|      */ | ||||
|     _usedImages?: string[] | ||||
| 
 | ||||
|     /** | ||||
|      * If set, an _additional_ popup will be shown under the theme introduction page. | ||||
|      * | ||||
|      * The embedded tagRenderingConfigs will be run against the settings-state of the contributor. | ||||
|      * If multiple popups are set, the first popup of the list will be rendered on top (and thus be seen first). | ||||
|      */ | ||||
|     popup?: { | ||||
|         /** | ||||
|          * ifset: the user can dismiss this message | ||||
|          */ | ||||
|         dismissible?: boolean | ||||
|         condition?: TagConfigJson | ||||
|         title: TagRenderingConfigJson, | ||||
|         body: TagRenderingConfigJson[], | ||||
|         /** | ||||
|          * id of the popup, mostly to keep the translations in check | ||||
|          */ | ||||
|         id: string, | ||||
|     }[] | ||||
| } | ||||
|  |  | |||
|  | @ -9,6 +9,10 @@ import LanguageUtils from "../../Utils/LanguageUtils" | |||
| 
 | ||||
| import { RasterLayerProperties } from "../RasterLayerProperties" | ||||
| import { Translatable } from "./Json/Translatable" | ||||
| import { TagsFilter } from "../../Logic/Tags/TagsFilter" | ||||
| import TagRenderingConfig from "./TagRenderingConfig" | ||||
| import { TagUtils } from "../../Logic/Tags/TagUtils" | ||||
| import { TagRenderingConfigJson } from "./Json/TagRenderingConfigJson" | ||||
| 
 | ||||
| /** | ||||
|  * Minimal information about a theme | ||||
|  | @ -93,6 +97,14 @@ export default class ThemeConfig implements ThemeInformation { | |||
|     public readonly source: ThemeConfigJson | ||||
|     public readonly enableCache: boolean | ||||
| 
 | ||||
|     public readonly popups: Readonly<{ | ||||
|         id: string, | ||||
|         dismissible?: boolean, | ||||
|         condition: TagsFilter, | ||||
|         title: TagRenderingConfig, | ||||
|         body: TagRenderingConfig[] | ||||
|     }>[] | ||||
| 
 | ||||
|     constructor( | ||||
|         json: ThemeConfigJson, | ||||
|         official = true, | ||||
|  | @ -193,11 +205,26 @@ export default class ThemeConfig implements ThemeInformation { | |||
|                 icon: "./assets/svg/pop-out.svg", | ||||
|                 href: "https://{basepath}/{theme}.html?lat={lat}&lon={lon}&z={zoom}&language={language}", | ||||
|                 newTab: true, | ||||
|                 requirements: ["iframe", "no-welcome-message"], | ||||
|                 requirements: ["iframe", "no-welcome-message"] | ||||
|             }, | ||||
|             context + ".extraLink" | ||||
|         ) | ||||
| 
 | ||||
|         this.popups = (json.popup ?? []).map((p, i) => { | ||||
|             const ctx = context + ".popup." + i | ||||
|             if (!p.id) { | ||||
|                 throw (ctx + ": an id is required") | ||||
|             } | ||||
|             const body: TagRenderingConfigJson[] = Array.isArray(p.body) ? p.body : [p.body] | ||||
|             return { | ||||
|                 id: p.id, | ||||
|                 dismissible: p.dismissible ?? false, | ||||
|                 condition: TagUtils.Tag(p.condition), | ||||
|                 title: new TagRenderingConfig(p.title, ctx + ".title"), | ||||
|                 body: body.map((body, i) => new TagRenderingConfig(body, ctx + ".body." + i)) | ||||
|             } | ||||
|         }) | ||||
| 
 | ||||
|         this.hideFromOverview = json.hideFromOverview ?? false | ||||
|         this.lockLocation = <[[number, number], [number, number]]>json.lockLocation ?? undefined | ||||
|         this.enableUserBadge = json.enableUserBadge ?? true | ||||
|  | @ -351,7 +378,7 @@ export default class ThemeConfig implements ThemeInformation { | |||
|         // The 'favourite'-layer contains pretty much all images as it bundles all layers, so we exclude it
 | ||||
|         const jsonNoFavourites = { | ||||
|             ...json, | ||||
|             layers: json.layers.filter((l) => l["id"] !== "favourite"), | ||||
|             layers: json.layers.filter((l) => l["id"] !== "favourite") | ||||
|         } | ||||
|         const usedImages = jsonNoFavourites._usedImages | ||||
|         usedImages.sort() | ||||
|  |  | |||
|  | @ -12,7 +12,7 @@ export default class WithContextLoader { | |||
|         this._context = context | ||||
|     } | ||||
| 
 | ||||
|     /** Given a key, gets the corresponding property from the json (or the default if not found | ||||
|     /** Given a key, gets the corresponding property from the json (or the default if not found) | ||||
|      * | ||||
|      * The found value is interpreted as a tagrendering and fetched/parsed | ||||
|      * */ | ||||
|  |  | |||
|  | @ -6,18 +6,26 @@ | |||
| 
 | ||||
|   export let osmConnection: OsmConnection | ||||
|   export let clss: string | undefined = undefined | ||||
| 
 | ||||
|   /** | ||||
|    * Show the button, even though we are logged in | ||||
|    */ | ||||
|   export let forceShow: boolean = false | ||||
|   export let msg: String = undefined | ||||
|   if (osmConnection === undefined) { | ||||
|     console.error("No osmConnection passed into loginButton") | ||||
|   } | ||||
|   let isLoggedIn = osmConnection.isLoggedIn | ||||
| </script> | ||||
| 
 | ||||
| {#if !$isLoggedIn} | ||||
| {#if !$isLoggedIn || forceShow} | ||||
|   <button class={clss} on:click={() => osmConnection.AttemptLogin()} style="margin-left: 0"> | ||||
|     <ArrowLeftOnRectangle class="m-1 w-12" /> | ||||
|     <slot> | ||||
|       <Tr t={Translations.t.general.loginWithOpenStreetMap} /> | ||||
|       {#if msg} | ||||
|         {msg} | ||||
|       {:else} | ||||
|         <Tr t={Translations.t.general.loginWithOpenStreetMap} /> | ||||
|       {/if} | ||||
|     </slot> | ||||
|   </button> | ||||
| {/if} | ||||
|  |  | |||
|  | @ -42,8 +42,8 @@ | |||
| 
 | ||||
| <Modal | ||||
|   open={_shown} | ||||
|   on:close={() => shown.set(false)} | ||||
|   outsideclose | ||||
|   on:close={() =>shown.set(false)} | ||||
|   outsideclose={dismissable} | ||||
|   size="xl" | ||||
|   {dismissable} | ||||
|   {defaultClass} | ||||
|  |  | |||
|  | @ -111,12 +111,27 @@ export class SettingsVisualisations { | |||
|             }, | ||||
|             { | ||||
|                 funcName: "login_button", | ||||
|                 args: [], | ||||
|                 args: [{ | ||||
|                     name: "force", | ||||
|                     doc: "Always show this button, even if logged in" | ||||
|                 }, { | ||||
|                     name: "message", | ||||
|                     doc: "Message to display on the button" | ||||
|                 }], | ||||
|                 docs: "Show a login button", | ||||
|                 needsUrls: [], | ||||
|                 group: "settings", | ||||
|                 constr(state: SpecialVisualizationState): SvelteUIElement { | ||||
|                     return new SvelteUIElement(LoginButton, { osmConnection: state.osmConnection }) | ||||
|                 constr(state: SpecialVisualizationState, _, args): SvelteUIElement { | ||||
|                     const force = args[0].toLowerCase() | ||||
|                     let msg = args[1] | ||||
|                     if (msg === "") { | ||||
|                         msg = undefined | ||||
|                     } | ||||
|                     return new SvelteUIElement(LoginButton, { | ||||
|                         osmConnection: state.osmConnection, | ||||
|                         msg, | ||||
|                         forceShow: force === "yes" || force === "true" | ||||
|                     }) | ||||
|                 }, | ||||
|             }, | ||||
| 
 | ||||
|  |  | |||
|  | @ -49,6 +49,8 @@ | |||
|   import Loading from "./Base/Loading.svelte" | ||||
|   import { WithSearchState } from "../Models/ThemeViewState/WithSearchState" | ||||
|   import TitleHandler from "../Logic/Actors/TitleHandler" | ||||
|   import Popup from "./Base/Popup.svelte" | ||||
|   import TagRenderingAnswer from "./Popup/TagRendering/TagRenderingAnswer.svelte" | ||||
| 
 | ||||
|   export let state: WithSearchState | ||||
|   new TitleHandler(state.selectedElement, state) | ||||
|  | @ -76,6 +78,7 @@ | |||
|   let mapproperties: MapProperties = state.mapProperties | ||||
|   let searchOpened = state.searchState.showSearchDrawer | ||||
| 
 | ||||
|   let metatags = state.userRelatedState.preferencesAsTags | ||||
|   Orientation.singleton.startMeasurements() | ||||
| 
 | ||||
|   let slideDuration = 150 // ms | ||||
|  | @ -148,7 +151,7 @@ | |||
|     const bottomRight = mlmap.unproject([rect.right, rect.bottom]) | ||||
|     const bbox = new BBox([ | ||||
|       [topLeft.lng, topLeft.lat], | ||||
|       [bottomRight.lng, bottomRight.lat], | ||||
|       [bottomRight.lng, bottomRight.lat] | ||||
|     ]) | ||||
|     state.visualFeedbackViewportBounds.setData(bbox) | ||||
|   } | ||||
|  | @ -500,5 +503,24 @@ | |||
|     {/if} | ||||
|   {/if} | ||||
| 
 | ||||
|   {#each theme.popups as popup} | ||||
|     {#if popup.condition.matchesProperties($metatags)} | ||||
|       <Popup shown={new UIEventSource(true)} dismissable={popup.dismissible}> | ||||
|         <TagRenderingAnswer slot="header" config={popup.title} {state} | ||||
|                             tags={metatags} | ||||
|                             layer={undefined} | ||||
|                             selectedElement={({type: "Feature", properties: $metatags, geometry: {type: "Point", coordinates: [0,0]}})} /> | ||||
|         <div class="flex flex-col"> | ||||
|           {#each popup.body as body} | ||||
|             <TagRenderingAnswer config={body} {state} | ||||
|                                 tags={metatags} | ||||
|                                 layer={undefined} | ||||
|                                 selectedElement={({type: "Feature", properties: $metatags, geometry: {type: "Point", coordinates: [0,0]}})} /> | ||||
|           {/each} | ||||
|           <span class="subtle">{popup.id}</span> | ||||
|         </div> | ||||
|       </Popup> | ||||
|     {/if} | ||||
|   {/each} | ||||
|   <MenuDrawer onlyLink={false} {state} /> | ||||
| </main> | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue