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": { |   "shortDescription": { | ||||||
|     "nl": "Grb import helper tool" |     "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", |   "icon": "./assets/themes/grb/logo.svg", | ||||||
|   "startZoom": 9, |   "startZoom": 9, | ||||||
|   "startLat": 51.0249, |   "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": { |     "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", |         "shortDescription": "Grb import helper tool", | ||||||
|         "title": "GRB import helper" |         "title": "GRB import helper" | ||||||
|     }, |     }, | ||||||
|  |  | ||||||
|  | @ -1,16 +1,6 @@ | ||||||
| import { | import { Concat, Conversion, DesugaringContext, DesugaringStep, Each, Fuse, On, Pass, SetDefault } from "./Conversion" | ||||||
|     Concat, |  | ||||||
|     Conversion, |  | ||||||
|     DesugaringContext, |  | ||||||
|     DesugaringStep, |  | ||||||
|     Each, |  | ||||||
|     Fuse, |  | ||||||
|     On, |  | ||||||
|     Pass, |  | ||||||
|     SetDefault, |  | ||||||
| } from "./Conversion" |  | ||||||
| import { ThemeConfigJson } from "../Json/ThemeConfigJson" | import { ThemeConfigJson } from "../Json/ThemeConfigJson" | ||||||
| import { PrepareLayer } from "./PrepareLayer" | import { PrepareLayer, RewriteSpecial } from "./PrepareLayer" | ||||||
| import { LayerConfigJson } from "../Json/LayerConfigJson" | import { LayerConfigJson } from "../Json/LayerConfigJson" | ||||||
| import { Utils } from "../../../Utils" | import { Utils } from "../../../Utils" | ||||||
| import Constants from "../../Constants" | import Constants from "../../Constants" | ||||||
|  | @ -40,7 +30,7 @@ class SubstituteLayer extends Conversion<string | LayerConfigJson, LayerConfigJs | ||||||
|             const knownLayers = Array.from(state.sharedLayers.keys()) |             const knownLayers = Array.from(state.sharedLayers.keys()) | ||||||
|             const withDistance: [string, number][] = knownLayers.map((lname) => [ |             const withDistance: [string, number][] = knownLayers.map((lname) => [ | ||||||
|                 lname, |                 lname, | ||||||
|                 Utils.levenshteinDistance(name, lname), |                 Utils.levenshteinDistance(name, lname) | ||||||
|             ]) |             ]) | ||||||
|             withDistance.sort((a, b) => a[1] - b[1]) |             withDistance.sort((a, b) => a[1] - b[1]) | ||||||
|             const ids = withDistance.map((n) => n[0]) |             const ids = withDistance.map((n) => n[0]) | ||||||
|  | @ -385,7 +375,7 @@ class AddDependencyLayersToTheme extends DesugaringStep<ThemeConfigJson> { | ||||||
|                         themeId + |                         themeId + | ||||||
|                         " nor as builtin layer.", |                         " nor as builtin layer.", | ||||||
|                         reason, |                         reason, | ||||||
|                         "Loaded layers are: " + alreadyLoaded.map((l) => l.id).join(","), |                         "Loaded layers are: " + alreadyLoaded.map((l) => l.id).join(",") | ||||||
|                     ] |                     ] | ||||||
|                     throw message.join("\n\t") |                     throw message.join("\n\t") | ||||||
|                 } |                 } | ||||||
|  | @ -395,7 +385,7 @@ class AddDependencyLayersToTheme extends DesugaringStep<ThemeConfigJson> { | ||||||
|                 dep.description = reason |                 dep.description = reason | ||||||
|                 dependenciesToAdd.unshift({ |                 dependenciesToAdd.unshift({ | ||||||
|                     config: dep, |                     config: dep, | ||||||
|                     reason, |                     reason | ||||||
|                 }) |                 }) | ||||||
|                 loadedLayerIds.add(dep.id) |                 loadedLayerIds.add(dep.id) | ||||||
|                 unmetDependencies = unmetDependencies.filter( |                 unmetDependencies = unmetDependencies.filter( | ||||||
|  | @ -440,7 +430,7 @@ class AddDependencyLayersToTheme extends DesugaringStep<ThemeConfigJson> { | ||||||
| 
 | 
 | ||||||
|         return { |         return { | ||||||
|             ...theme, |             ...theme, | ||||||
|             layers: layers, |             layers: layers | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | @ -618,6 +608,13 @@ export class PrepareTheme extends Fuse<ThemeConfigJson> { | ||||||
|             new SetDefault("socialImage", "assets/SocialImage.png", true), |             new SetDefault("socialImage", "assets/SocialImage.png", true), | ||||||
|             // We expand all tagrenderings first...
 |             // We expand all tagrenderings first...
 | ||||||
|             new On("layers", new Each(new PrepareLayer(state))), |             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
 |             // 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+
 |             // Note that it'll cheat with tagRenderings+
 | ||||||
|             new ApplyOverrideAll(), |             new ApplyOverrideAll(), | ||||||
|  |  | ||||||
|  | @ -3,6 +3,8 @@ import ExtraLinkConfigJson from "./ExtraLinkConfigJson" | ||||||
| 
 | 
 | ||||||
| import { RasterLayerProperties } from "../../RasterLayerProperties" | import { RasterLayerProperties } from "../../RasterLayerProperties" | ||||||
| import { Translatable } from "./Translatable" | import { Translatable } from "./Translatable" | ||||||
|  | import { TagConfigJson } from "./TagConfigJson" | ||||||
|  | import { TagRenderingConfigJson } from "./TagRenderingConfigJson" | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Defines the entire theme. |  * Defines the entire theme. | ||||||
|  | @ -468,4 +470,24 @@ export interface ThemeConfigJson { | ||||||
|      * group: hidden |      * group: hidden | ||||||
|      */ |      */ | ||||||
|     _usedImages?: string[] |     _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 { RasterLayerProperties } from "../RasterLayerProperties" | ||||||
| import { Translatable } from "./Json/Translatable" | 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 |  * Minimal information about a theme | ||||||
|  | @ -93,6 +97,14 @@ export default class ThemeConfig implements ThemeInformation { | ||||||
|     public readonly source: ThemeConfigJson |     public readonly source: ThemeConfigJson | ||||||
|     public readonly enableCache: boolean |     public readonly enableCache: boolean | ||||||
| 
 | 
 | ||||||
|  |     public readonly popups: Readonly<{ | ||||||
|  |         id: string, | ||||||
|  |         dismissible?: boolean, | ||||||
|  |         condition: TagsFilter, | ||||||
|  |         title: TagRenderingConfig, | ||||||
|  |         body: TagRenderingConfig[] | ||||||
|  |     }>[] | ||||||
|  | 
 | ||||||
|     constructor( |     constructor( | ||||||
|         json: ThemeConfigJson, |         json: ThemeConfigJson, | ||||||
|         official = true, |         official = true, | ||||||
|  | @ -193,11 +205,26 @@ export default class ThemeConfig implements ThemeInformation { | ||||||
|                 icon: "./assets/svg/pop-out.svg", |                 icon: "./assets/svg/pop-out.svg", | ||||||
|                 href: "https://{basepath}/{theme}.html?lat={lat}&lon={lon}&z={zoom}&language={language}", |                 href: "https://{basepath}/{theme}.html?lat={lat}&lon={lon}&z={zoom}&language={language}", | ||||||
|                 newTab: true, |                 newTab: true, | ||||||
|                 requirements: ["iframe", "no-welcome-message"], |                 requirements: ["iframe", "no-welcome-message"] | ||||||
|             }, |             }, | ||||||
|             context + ".extraLink" |             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.hideFromOverview = json.hideFromOverview ?? false | ||||||
|         this.lockLocation = <[[number, number], [number, number]]>json.lockLocation ?? undefined |         this.lockLocation = <[[number, number], [number, number]]>json.lockLocation ?? undefined | ||||||
|         this.enableUserBadge = json.enableUserBadge ?? true |         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
 |         // The 'favourite'-layer contains pretty much all images as it bundles all layers, so we exclude it
 | ||||||
|         const jsonNoFavourites = { |         const jsonNoFavourites = { | ||||||
|             ...json, |             ...json, | ||||||
|             layers: json.layers.filter((l) => l["id"] !== "favourite"), |             layers: json.layers.filter((l) => l["id"] !== "favourite") | ||||||
|         } |         } | ||||||
|         const usedImages = jsonNoFavourites._usedImages |         const usedImages = jsonNoFavourites._usedImages | ||||||
|         usedImages.sort() |         usedImages.sort() | ||||||
|  |  | ||||||
|  | @ -12,7 +12,7 @@ export default class WithContextLoader { | ||||||
|         this._context = context |         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 |      * The found value is interpreted as a tagrendering and fetched/parsed | ||||||
|      * */ |      * */ | ||||||
|  |  | ||||||
|  | @ -6,18 +6,26 @@ | ||||||
| 
 | 
 | ||||||
|   export let osmConnection: OsmConnection |   export let osmConnection: OsmConnection | ||||||
|   export let clss: string | undefined = undefined |   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) { |   if (osmConnection === undefined) { | ||||||
|     console.error("No osmConnection passed into loginButton") |     console.error("No osmConnection passed into loginButton") | ||||||
|   } |   } | ||||||
|   let isLoggedIn = osmConnection.isLoggedIn |   let isLoggedIn = osmConnection.isLoggedIn | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
| {#if !$isLoggedIn} | {#if !$isLoggedIn || forceShow} | ||||||
|   <button class={clss} on:click={() => osmConnection.AttemptLogin()} style="margin-left: 0"> |   <button class={clss} on:click={() => osmConnection.AttemptLogin()} style="margin-left: 0"> | ||||||
|     <ArrowLeftOnRectangle class="m-1 w-12" /> |     <ArrowLeftOnRectangle class="m-1 w-12" /> | ||||||
|     <slot> |     <slot> | ||||||
|  |       {#if msg} | ||||||
|  |         {msg} | ||||||
|  |       {:else} | ||||||
|         <Tr t={Translations.t.general.loginWithOpenStreetMap} /> |         <Tr t={Translations.t.general.loginWithOpenStreetMap} /> | ||||||
|  |       {/if} | ||||||
|     </slot> |     </slot> | ||||||
|   </button> |   </button> | ||||||
| {/if} | {/if} | ||||||
|  |  | ||||||
|  | @ -42,8 +42,8 @@ | ||||||
| 
 | 
 | ||||||
| <Modal | <Modal | ||||||
|   open={_shown} |   open={_shown} | ||||||
|   on:close={() => shown.set(false)} |   on:close={() =>shown.set(false)} | ||||||
|   outsideclose |   outsideclose={dismissable} | ||||||
|   size="xl" |   size="xl" | ||||||
|   {dismissable} |   {dismissable} | ||||||
|   {defaultClass} |   {defaultClass} | ||||||
|  |  | ||||||
|  | @ -111,12 +111,27 @@ export class SettingsVisualisations { | ||||||
|             }, |             }, | ||||||
|             { |             { | ||||||
|                 funcName: "login_button", |                 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", |                 docs: "Show a login button", | ||||||
|                 needsUrls: [], |                 needsUrls: [], | ||||||
|                 group: "settings", |                 group: "settings", | ||||||
|                 constr(state: SpecialVisualizationState): SvelteUIElement { |                 constr(state: SpecialVisualizationState, _, args): SvelteUIElement { | ||||||
|                     return new SvelteUIElement(LoginButton, { osmConnection: state.osmConnection }) |                     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 Loading from "./Base/Loading.svelte" | ||||||
|   import { WithSearchState } from "../Models/ThemeViewState/WithSearchState" |   import { WithSearchState } from "../Models/ThemeViewState/WithSearchState" | ||||||
|   import TitleHandler from "../Logic/Actors/TitleHandler" |   import TitleHandler from "../Logic/Actors/TitleHandler" | ||||||
|  |   import Popup from "./Base/Popup.svelte" | ||||||
|  |   import TagRenderingAnswer from "./Popup/TagRendering/TagRenderingAnswer.svelte" | ||||||
| 
 | 
 | ||||||
|   export let state: WithSearchState |   export let state: WithSearchState | ||||||
|   new TitleHandler(state.selectedElement, state) |   new TitleHandler(state.selectedElement, state) | ||||||
|  | @ -76,6 +78,7 @@ | ||||||
|   let mapproperties: MapProperties = state.mapProperties |   let mapproperties: MapProperties = state.mapProperties | ||||||
|   let searchOpened = state.searchState.showSearchDrawer |   let searchOpened = state.searchState.showSearchDrawer | ||||||
| 
 | 
 | ||||||
|  |   let metatags = state.userRelatedState.preferencesAsTags | ||||||
|   Orientation.singleton.startMeasurements() |   Orientation.singleton.startMeasurements() | ||||||
| 
 | 
 | ||||||
|   let slideDuration = 150 // ms |   let slideDuration = 150 // ms | ||||||
|  | @ -148,7 +151,7 @@ | ||||||
|     const bottomRight = mlmap.unproject([rect.right, rect.bottom]) |     const bottomRight = mlmap.unproject([rect.right, rect.bottom]) | ||||||
|     const bbox = new BBox([ |     const bbox = new BBox([ | ||||||
|       [topLeft.lng, topLeft.lat], |       [topLeft.lng, topLeft.lat], | ||||||
|       [bottomRight.lng, bottomRight.lat], |       [bottomRight.lng, bottomRight.lat] | ||||||
|     ]) |     ]) | ||||||
|     state.visualFeedbackViewportBounds.setData(bbox) |     state.visualFeedbackViewportBounds.setData(bbox) | ||||||
|   } |   } | ||||||
|  | @ -500,5 +503,24 @@ | ||||||
|     {/if} |     {/if} | ||||||
|   {/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} /> |   <MenuDrawer onlyLink={false} {state} /> | ||||||
| </main> | </main> | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue