forked from MapComplete/MapComplete
		
	Fix loading of relative images in custom themes
This commit is contained in:
		
							parent
							
								
									8d79d94e7b
								
							
						
					
					
						commit
						a3b32a3697
					
				
					 7 changed files with 346 additions and 235 deletions
				
			
		|  | @ -10,16 +10,17 @@ import {UIEventSource} from "./UIEventSource"; | |||
| import {LocalStorageSource} from "./Web/LocalStorageSource"; | ||||
| import LZString from "lz-string"; | ||||
| import * as personal from "../assets/themes/personal/personal.json"; | ||||
| import {FixLegacyTheme} from "../Models/ThemeConfig/Conversion/LegacyJsonConvert"; | ||||
| import {FixImages, FixLegacyTheme} from "../Models/ThemeConfig/Conversion/LegacyJsonConvert"; | ||||
| import {LayerConfigJson} from "../Models/ThemeConfig/Json/LayerConfigJson"; | ||||
| import SharedTagRenderings from "../Customizations/SharedTagRenderings"; | ||||
| import * as known_layers from "../assets/generated/known_layers.json" | ||||
| import {LayoutConfigJson} from "../Models/ThemeConfig/Json/LayoutConfigJson"; | ||||
| import {PrepareTheme} from "../Models/ThemeConfig/Conversion/PrepareTheme"; | ||||
| import {Layer} from "leaflet"; | ||||
| 
 | ||||
| import * as licenses from "../assets/generated/license_info.json" | ||||
| export default class DetermineLayout { | ||||
| 
 | ||||
|     private static readonly _knownImages =new Set( Array.from(licenses).map(l => l.path)) | ||||
|      | ||||
|     /** | ||||
|      * Gets the correct layout for this website | ||||
|      */ | ||||
|  | @ -144,6 +145,7 @@ export default class DetermineLayout { | |||
|             sharedLayers: knownLayersDict | ||||
|         } | ||||
|         json = new FixLegacyTheme().convertStrict(json, "While loading a dynamic theme") | ||||
|         json = new FixImages(DetermineLayout._knownImages).convertStrict(json, "While fixing the images") | ||||
|         json = new PrepareTheme(converState).convertStrict(json, "While preparing a dynamic theme") | ||||
|         console.log("The layoutconfig is ", json) | ||||
|         return json | ||||
|  |  | |||
|  | @ -2,7 +2,8 @@ import {LayoutConfigJson} from "../Json/LayoutConfigJson"; | |||
| import {Utils} from "../../../Utils"; | ||||
| import LineRenderingConfigJson from "../Json/LineRenderingConfigJson"; | ||||
| import {LayerConfigJson} from "../Json/LayerConfigJson"; | ||||
| import {DesugaringContext, DesugaringStep, Fuse, OnEvery} from "./Conversion"; | ||||
| import {DesugaringStep, Fuse, OnEvery} from "./Conversion"; | ||||
| import * as metapaths from "../../../assets/layoutconfigmeta.json" | ||||
| 
 | ||||
| export class UpdateLegacyLayer extends DesugaringStep<LayerConfigJson | string | { builtin, override }> { | ||||
| 
 | ||||
|  | @ -157,3 +158,103 @@ export class FixLegacyTheme extends Fuse<LayoutConfigJson> { | |||
|         ); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| export class FixImages extends DesugaringStep<LayoutConfigJson> { | ||||
|     private readonly _knownImages: Set<string>; | ||||
| 
 | ||||
|     constructor(knownImages: Set<string>) { | ||||
|         super("Walks over the entire theme and replaces images to the relative URL. Only works if the ID of the theme is an URL"); | ||||
|         this._knownImages = knownImages; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Walks the path into the object till the end. | ||||
|      * | ||||
|      * If a list is encountered, this is tranparently walked recursively on every object. | ||||
|      * | ||||
|      * The leaf objects are replaced | ||||
|      */ | ||||
|     private static WalkPath(path: string[], object: any, replaceLeaf: ((leaf: any) => any)) { | ||||
|         const head = path[0] | ||||
|         if (path.length === 1) { | ||||
|             // We have reached the leaf
 | ||||
|             const leaf = object[head]; | ||||
|             if (leaf !== undefined) { | ||||
|                 object[head] = replaceLeaf(leaf) | ||||
|             } | ||||
|             return | ||||
| 
 | ||||
|         } | ||||
|         const sub = object[head] | ||||
|         if (sub === undefined) { | ||||
|             return; | ||||
|         } | ||||
|         if (typeof sub !== "object") { | ||||
|             return; | ||||
|         } | ||||
|         if (sub["forEach"] !== undefined) { | ||||
|             sub.forEach(el => FixImages.WalkPath(path.slice(1), el, replaceLeaf)) | ||||
|             return; | ||||
|         } | ||||
|         FixImages.WalkPath(path.slice(1), sub, replaceLeaf) | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     convert(json: LayoutConfigJson, context: string): { result: LayoutConfigJson; errors?: string[]; warnings?: string[] } { | ||||
|         let url: URL; | ||||
|         console.log("Fixing images!") | ||||
|         try { | ||||
|             url = new URL(json.id) | ||||
|         } catch (e) { | ||||
|             // Not a URL, we don't rewrite
 | ||||
|             return {result: json} | ||||
|         } | ||||
| 
 | ||||
|         const absolute =  url.protocol +"//"+url.host | ||||
|         let relative = url.protocol +"//"+ url.host + url.pathname  | ||||
|         relative = relative.substring(0, relative.lastIndexOf("/")) | ||||
|         const self = this; | ||||
| 
 | ||||
|         function replaceString(leaf: string) { | ||||
|             if (self._knownImages.has(leaf)) { | ||||
|                 return leaf; | ||||
|             } | ||||
|             if (leaf.startsWith("./")) { | ||||
|                 return relative + leaf.substring(1) | ||||
|             } | ||||
|             if (leaf.startsWith("/")) { | ||||
|                 return absolute + leaf | ||||
|             } | ||||
|             return leaf; | ||||
|         } | ||||
| 
 | ||||
|         json = Utils.Clone(json) | ||||
| 
 | ||||
|         let paths = metapaths["default"] ?? metapaths | ||||
| 
 | ||||
|         for (const metapath of paths) { | ||||
|             if (metapath.typeHint !== "image" && metapath.typeHint !== "icon") { | ||||
|                 continue | ||||
|             } | ||||
|             FixImages.WalkPath(metapath.path, json, leaf => { | ||||
|                 console.log("Detected leaf: ", leaf) | ||||
|                 if (typeof leaf === "string") { | ||||
|                     return replaceString(leaf) | ||||
|                 } | ||||
| 
 | ||||
|                 if (metapath.type["some"] !== undefined && (<any[]>metapath.type).some(t => t["$ref"] == "\"#/definitions/TagRenderingConfigJson\"")) { | ||||
|                     console.log("Possibly found a tagrendering") | ||||
|                      | ||||
|                 } | ||||
| 
 | ||||
|                 return leaf; | ||||
|             }) | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
|         return { | ||||
|             result: json | ||||
|         }; | ||||
|     } | ||||
| } | ||||
|  | @ -37,7 +37,8 @@ export default interface PointRenderingConfigJson { | |||
|      * | ||||
|      * Note: strings are interpreted as icons, so layering and substituting is supported. You can use `circle:white;./my_icon.svg` to add a background circle | ||||
|      */ | ||||
|     iconBadges?: { if: string | AndOrTagConfigJson,  | ||||
|     iconBadges?: {  | ||||
|         if: string | AndOrTagConfigJson,  | ||||
|         /** | ||||
|          * Badge to show | ||||
|          * Type: icon | ||||
|  |  | |||
|  | @ -119,45 +119,64 @@ export default class MoreScreen extends Combine { | |||
|         ]).SetClass("flex flex-col border border-gray-300 p-2 rounded-lg") | ||||
|     } | ||||
| 
 | ||||
|     private static createButtonFor(state: UserRelatedState, id: string): BaseUIElement { | ||||
|         const allPreferences = state.osmConnection.preferencesHandler.preferences.data; | ||||
|         const length = Number(allPreferences[id + "-combined-length"]) | ||||
|         let str = ""; | ||||
|         for (let i = 0; i < length; i++) { | ||||
|             str += allPreferences[id + "-" + i] | ||||
|         } | ||||
|         try { | ||||
|             const value: { | ||||
|                 id: string | ||||
|                 icon: string, | ||||
|                 title: any, | ||||
|                 shortDescription: any | ||||
|             } = JSON.parse(str) | ||||
| 
 | ||||
|             return MoreScreen.createLinkButton(state, value, true) | ||||
|         } catch (e) { | ||||
|             console.debug("Could not parse unofficial theme information for " + id, e) | ||||
|             return undefined | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private static createUnofficialThemeList(buttonClass: string, state: UserRelatedState, themeListClasses): BaseUIElement { | ||||
|         const prefix = "mapcomplete-unofficial-theme-"; | ||||
|         return new VariableUiElement(state.osmConnection.preferencesHandler.preferences.map(allPreferences => { | ||||
|             console.log("All preferences are ", allPreferences) | ||||
|             const allThemes: BaseUIElement[] = [] | ||||
|             for (const key in allPreferences) { | ||||
|                 if (key.startsWith(prefix) && key.endsWith("-combined-length")) { | ||||
|                     const id = key.substring(0, key.length - "-length".length) | ||||
|                     const length = Number(allPreferences[key]) | ||||
| 
 | ||||
|                     let str = ""; | ||||
|                     for (let i = 0; i < length; i++) { | ||||
|                         str += allPreferences[id + "-" + i] | ||||
|                     } | ||||
|                     console.log("Theme " + id + " has settings ", str) | ||||
|                     try { | ||||
|                         const value: { | ||||
|                             id: string | ||||
|                             icon: string, | ||||
|                             title: any, | ||||
|                             shortDescription: any | ||||
|                         } = JSON.parse(str) | ||||
|         var currentIds: UIEventSource<string[]> = state.osmConnection.preferencesHandler.preferences | ||||
|             .map(allPreferences => { | ||||
|                 const ids: string[] = [] | ||||
| 
 | ||||
|                         const link = MoreScreen.createLinkButton(state, value, true).SetClass(buttonClass) | ||||
|                         allThemes.push(link) | ||||
|                     } catch (e) { | ||||
|                         console.error("Could not parse unofficial theme information for " + id, e) | ||||
|                 for (const key in allPreferences) { | ||||
|                     if (key.startsWith(prefix) && key.endsWith("-combined-length")) { | ||||
|                         const id = key.substring(0, key.length - "-length".length) | ||||
|                         ids.push(id) | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             if (allThemes.length <= 0) { | ||||
|                 return undefined; | ||||
|             } | ||||
|             return new Combine([ | ||||
|                 Translations.t.general.customThemeIntro.Clone(), | ||||
|                 new Combine(allThemes).SetClass(themeListClasses) | ||||
|             ]); | ||||
|         })); | ||||
|                 return ids | ||||
|             }); | ||||
|         var stableIds = UIEventSource.ListStabilized<string>(currentIds) | ||||
| 
 | ||||
|         return new VariableUiElement( | ||||
|             stableIds.map(ids => { | ||||
|                 const allThemes: BaseUIElement[] = [] | ||||
|                 for (const id of ids) { | ||||
|                     const link = this.createButtonFor(state, id) | ||||
|                     if (link !== undefined) { | ||||
|                         allThemes.push(link.SetClass(buttonClass)) | ||||
|                     } | ||||
|                 } | ||||
| 
 | ||||
|                 if (allThemes.length <= 0) { | ||||
|                     return undefined; | ||||
|                 } | ||||
|                 return new Combine([ | ||||
|                     Translations.t.general.customThemeIntro.Clone(), | ||||
|                     new Combine(allThemes).SetClass(themeListClasses) | ||||
|                 ]); | ||||
|             })); | ||||
|     } | ||||
| 
 | ||||
|     private static createPreviouslyVistedHiddenList(state: UserRelatedState, buttonClass: string, themeListStyle: string) { | ||||
|  |  | |||
|  | @ -134,7 +134,7 @@ function main() { | |||
|         return {typeHint: type.substr("type: ".length), type: schemePart.type ?? schemePart.anyOf} | ||||
|     }, themeSchema) | ||||
|      | ||||
|    // writeFileSync("./assets/layoutconfigmeta.json",JSON.stringify(withTypes.map(({path, t}) => ({path, ...t})), null, "  "))
 | ||||
|     writeFileSync("./assets/layoutconfigmeta.json",JSON.stringify(withTypes.map(({path, t}) => ({path, ...t})), null, "  ")) | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,10 +1,10 @@ | |||
| import T from "./TestHelper"; | ||||
| import {FixLegacyTheme} from "../Models/ThemeConfig/Conversion/LegacyJsonConvert"; | ||||
| import {FixImages, FixLegacyTheme} from "../Models/ThemeConfig/Conversion/LegacyJsonConvert"; | ||||
| import LayoutConfig from "../Models/ThemeConfig/LayoutConfig"; | ||||
| import {LayerConfigJson} from "../Models/ThemeConfig/Json/LayerConfigJson"; | ||||
| import {TagRenderingConfigJson} from "../Models/ThemeConfig/Json/TagRenderingConfigJson"; | ||||
| import {AddMiniMap} from "../Models/ThemeConfig/Conversion/PrepareTheme"; | ||||
| import {DetectShadowedMappings} from "../Models/ThemeConfig/Conversion/Validation"; | ||||
| import * as Assert from "assert"; | ||||
| 
 | ||||
| export default class LegacyThemeLoaderSpec extends T { | ||||
| 
 | ||||
|  | @ -143,215 +143,203 @@ export default class LegacyThemeLoaderSpec extends T { | |||
|         ] | ||||
|     } | ||||
| 
 | ||||
|     private static readonly organic_waste_theme = { | ||||
|         "id": "recycling-organic", | ||||
|     private static readonly verkeerde_borden ={ | ||||
|         "id": "https://raw.githubusercontent.com/seppesantens/MapComplete-Themes/main/VerkeerdeBordenDatabank/VerkeerdeBordenDatabank.json", | ||||
|         "title": { | ||||
|             "nl": "Inzamelpunt organisch alval" | ||||
|         }, | ||||
|         "shortDescription": { | ||||
|             "nl": "Inzamelpunt organisch alval" | ||||
|             "nl": "VerkeerdeBordenDatabank", | ||||
|             "en": "Erratic Signs Database" | ||||
|         }, | ||||
|         "maintainer": "Seppe Santens", | ||||
|         "icon": "https://upload.wikimedia.org/wikipedia/commons/b/bc/Belgian_traffic_sign_A51.svg", | ||||
|         "description": { | ||||
|             "nl": "Op deze kaart vindt u inzamelpunten voor organisch afval. Beheer deze naar goeddunken en vermogen." | ||||
|             "nl": "Een kaart om verkeerde of ontbrekende verkeersborden te tonen en te editeren.", | ||||
|             "en": "A map to show and edit incorrect or missing traffic signs." | ||||
|         }, | ||||
|         "version": "2021-09-16", | ||||
|         "startLat": 51.08881, | ||||
|         "startLon": 3.447282, | ||||
|         "startZoom": 15, | ||||
|         "clustering": { | ||||
|             "maxZoom": 8 | ||||
|         }, | ||||
|         "language": [ | ||||
|             "nl" | ||||
|         ], | ||||
|         "maintainer": "", | ||||
|         "icon": "https://upload.wikimedia.org/wikipedia/commons/1/15/Compost_…able_waste_-_biodegradable_waste_-_biological_waste_icon.png", | ||||
|         "version": "0", | ||||
|         "startLat": 0, | ||||
|         "startLon": 0, | ||||
|         "startZoom": 1, | ||||
|         "widenFactor": 0.05, | ||||
|         "socialImage": "", | ||||
|         "layers": [ | ||||
|             { | ||||
|                 "id": "recycling-organic", | ||||
|                 "id": "trafficsign", | ||||
|                 "name": { | ||||
|                     "nl": "Inzamelpunt organisch alval" | ||||
|                     "nl": "verkeersbord", | ||||
|                     "en": "traffic sign" | ||||
|                 }, | ||||
|                 "minzoom": 12, | ||||
|                 "title": { | ||||
|                     "render": { | ||||
|                         "nl": "Inzamelpunt organisch alval" | ||||
|                     }, | ||||
|                     "mappings": [ | ||||
|                         { | ||||
|                             "if": { | ||||
|                                 "and": [ | ||||
|                                     "name~*" | ||||
|                                 ] | ||||
|                             }, | ||||
|                             "then": { | ||||
|                                 "nl": "{name}" | ||||
|                             } | ||||
|                         } | ||||
|                     ] | ||||
|                 }, | ||||
|                 "allowMove": true, | ||||
|                 "deletion": {}, | ||||
|                 "tagRenderings": [ | ||||
|                     "images", | ||||
|                     { | ||||
|                         "freeform": { | ||||
|                             "key": "opening_hours", | ||||
|                             "type": "opening_hours", | ||||
|                             "addExtraTags": [] | ||||
|                         }, | ||||
|                         "question": { | ||||
|                             "nl": "Wat zijn de openingsuren?" | ||||
|                         }, | ||||
|                         "render": { | ||||
|                             "nl": "{opening_hours_table()}" | ||||
|                         }, | ||||
|                         "mappings": [ | ||||
|                             { | ||||
|                                 "if": { | ||||
|                                     "and": [ | ||||
|                                         "opening_hours=\"by appointment\"" | ||||
|                                     ] | ||||
|                                 }, | ||||
|                                 "then": { | ||||
|                                     "nl": "Op afspraak" | ||||
|                                 } | ||||
|                             } | ||||
|                         ], | ||||
|                         "id": "Composthoekjes-opening_hours" | ||||
|                     }, | ||||
|                     { | ||||
|                         "question": { | ||||
|                             "nl": "Wat is de website voor meer informatie?" | ||||
|                         }, | ||||
|                         "freeform": { | ||||
|                             "key": "website", | ||||
|                             "type": "url" | ||||
|                         }, | ||||
|                         "render": { | ||||
|                             "nl": "<a href=\"{website}\">{website}</a>" | ||||
|                         }, | ||||
|                         "id": "Composthoekjes-website" | ||||
|                     }, | ||||
|                     { | ||||
|                         "question": { | ||||
|                             "nl": "Wat is het type inzamelpunt?" | ||||
|                         }, | ||||
|                         "mappings": [ | ||||
|                             { | ||||
|                                 "if": "recycling_type=container", | ||||
|                                 "then": "Container of vat" | ||||
|                             }, | ||||
|                             { | ||||
|                                 "if": "recycling_type=centre", | ||||
|                                 "then": "Verwerkingsplaats of containerpark" | ||||
|                             }, | ||||
|                             { | ||||
|                                 "if": "recycling_type=dump", | ||||
|                                 "then": "Composthoop" | ||||
|                             } | ||||
| 
 | ||||
|                         ], | ||||
|                         "id": "Composthoekjes-type" | ||||
|                     }, | ||||
|                     { | ||||
|                         "question": { | ||||
|                             "nl": "Wie mag hier organisch afval bezorgen?" | ||||
|                         }, | ||||
|                         "mappings": [ | ||||
|                             { | ||||
|                                 "if": "access=yes", | ||||
|                                 "then": "Publiek toegankelijk" | ||||
|                             }, | ||||
|                             { | ||||
|                                 "if": "access=no", | ||||
|                                 "then": "Privaat" | ||||
|                             }, | ||||
|                             { | ||||
|                                 "if": "access=permessive", | ||||
|                                 "then": "Mogelijks toegelaten tot nader order" | ||||
|                             }, | ||||
|                             { | ||||
|                                 "if": "access=", | ||||
|                                 "then": "Waarschijnlijk publiek toegankelijk", | ||||
|                                 "hideInAnswer": true | ||||
|                             }, | ||||
|                             { | ||||
|                                 "if": "access=residents", | ||||
|                                 "then": "Bewoners van gemeente", | ||||
|                                 "hideInAnswer": "recycling_type!=centre" | ||||
|                             } | ||||
| 
 | ||||
|                         ], | ||||
|                         "id": "Composthoekjes-voor-wie" | ||||
|                     }, | ||||
|                     { | ||||
|                         "question": { | ||||
|                             "nl": "Wat is de naam van dit compost-inzamelpunt?" | ||||
|                         }, | ||||
|                         "freeform": { | ||||
|                             "key": "name", | ||||
|                             "addExtraTags": ["noname="] | ||||
|                         }, | ||||
|                         "render": { | ||||
|                             "nl": "De naam van dit compost-inzamelpunt is {name}" | ||||
|                         }, | ||||
|                         "mappings": [ | ||||
|                             { | ||||
|                                 "if": {"and": ["noname=yes", "name="]}, | ||||
|                                 "then": "Heeft geen naam" | ||||
|                             }, | ||||
|                             { | ||||
|                                 "if": "name=", | ||||
|                                 "then": "Geen naam bekend", | ||||
|                                 "hideInAnswer": true | ||||
|                             } | ||||
|                         ], | ||||
|                         "id": "Composthoekjes-name" | ||||
|                     }], | ||||
|                 "presets": [ | ||||
|                     { | ||||
|                         "tags": [ | ||||
|                             "amenity=recycling", | ||||
|                             "recycling:organic=yes" | ||||
|                         ], | ||||
|                         "title": { | ||||
|                             "nl": "een inzamelpunt voor organisch afval" | ||||
|                         } | ||||
|                     } | ||||
|                 ], | ||||
|                 "source": { | ||||
|                     "osmTags": { | ||||
|                         "and": [ | ||||
|                             "recycling:organic~*" | ||||
|                             "traffic_sign~*", | ||||
|                             "traffic_sign:issue~*" | ||||
|                         ] | ||||
|                     } | ||||
|                 }, | ||||
|                 "mapRendering": [ | ||||
|                 "minzoom": 10, | ||||
|                 "title": { | ||||
|                     "render": { | ||||
|                         "nl": "verkeersbord", | ||||
|                         "en": "traffic sign" | ||||
|                     } | ||||
|                 }, | ||||
|                 "tagRenderings": [ | ||||
|                     "images", | ||||
|                     { | ||||
|                         "icon": { | ||||
|                             "render": "circle:white;https://upload.wikimedia.org/wikipedia/commons/1/15/Compost_…able_waste_-_biodegradable_waste_-_biological_waste_icon.png" | ||||
|                         "render": { | ||||
|                             "nl": "ID verkeersbord: {traffic_sign}", | ||||
|                             "en": "traffic sign ID: {traffic_sign}" | ||||
|                         }, | ||||
|                         "iconSize": { | ||||
|                             "render": "40,40,center" | ||||
|                         "question": { | ||||
|                             "nl": "Wat is het ID voor dit verkeersbord?", | ||||
|                             "en": "What is ID for this traffic sign?" | ||||
|                         }, | ||||
|                         "location": [ | ||||
|                             "point" | ||||
|                         ] | ||||
|                         "freeform": { | ||||
|                             "key": "traffic_sign" | ||||
|                         }, | ||||
|                         "id": "trafficsign-traffic_sign" | ||||
|                     }, | ||||
|                     { | ||||
|                         "color": { | ||||
|                             "render": "#00f" | ||||
|                         "render": { | ||||
|                             "nl": "Probleem bij dit verkeersbord: {traffic_sign:issue}", | ||||
|                             "en": "Issue with this traffic sign: {traffic_sign:issue}" | ||||
|                         }, | ||||
|                         "width": { | ||||
|                             "render": "8" | ||||
|                         } | ||||
|                         "question": { | ||||
|                             "nl": "Wat is het probleem met dit verkeersbord?", | ||||
|                             "en": "What is the issue with this traffic sign?" | ||||
|                         }, | ||||
|                         "freeform": { | ||||
|                             "key": "traffic_sign:issue" | ||||
|                         }, | ||||
|                         "id": "trafficsign-traffic_sign:issue" | ||||
|                     }, | ||||
|                     { | ||||
|                         "question": { | ||||
|                             "nl": "Wanneer werd dit verkeersbord laatst gesurveyed?", | ||||
|                             "en": "When was this traffic sign last surveyed?" | ||||
|                         }, | ||||
|                         "render": { | ||||
|                             "nl": "Dit verkeersbord werd laatst gesurveyed op {survey:date}", | ||||
|                             "en": "This traffic sign was last surveyed on {survey:date}" | ||||
|                         }, | ||||
|                         "freeform": { | ||||
|                             "key": "survey:date", | ||||
|                             "type": "date" | ||||
|                         }, | ||||
|                         "mappings": [ | ||||
|                             { | ||||
|                                 "if": "survey:date:={_now:date}", | ||||
|                                 "then": "Vandaag gesurveyed!" | ||||
|                             } | ||||
|                         ], | ||||
|                         "id": "trafficsign-survey:date" | ||||
|                     } | ||||
|                 ], | ||||
|                 "mapRendering": [ | ||||
|                     { | ||||
|                         "icon": "./TS_bolt.svg", | ||||
|                         "location": [ | ||||
|                             "point", | ||||
|                             "centroid" | ||||
|                         ] | ||||
|                     } | ||||
|                 ] | ||||
|             }, | ||||
|             { | ||||
|                 "id": "notrafficsign", | ||||
|                 "name": { | ||||
|                     "nl": "geen verkeersbord", | ||||
|                     "en": "no traffic sign" | ||||
|                 }, | ||||
|                 "source": { | ||||
|                     "osmTags": { | ||||
|                         "and": [ | ||||
|                             { | ||||
|                                 "or": [ | ||||
|                                     "no:traffic_sign~*", | ||||
|                                     "not:traffic_sign~*" | ||||
|                                 ] | ||||
|                             }, | ||||
|                             "traffic_sign:issue~*" | ||||
|                         ] | ||||
|                     } | ||||
|                 }, | ||||
|                 "minzoom": 10, | ||||
|                 "title": { | ||||
|                     "render": { | ||||
|                         "nl": "ontbrekend verkeersbord", | ||||
|                         "en": "missing traffic sign" | ||||
|                     } | ||||
|                 }, | ||||
|                 "tagRenderings": [ | ||||
|                     "images", | ||||
|                     { | ||||
|                         "render": { | ||||
|                             "nl": "ID ontbrekend verkeersbord: {no:traffic_sign}", | ||||
|                             "en": "missing traffic sign ID: {no:traffic_sign}" | ||||
|                         }, | ||||
|                         "question": { | ||||
|                             "nl": "Wat is het ID voor het ontbrekende verkeersbord?", | ||||
|                             "en": "What is ID for the missing traffic sign?" | ||||
|                         }, | ||||
|                         "freeform": { | ||||
|                             "key": "no:traffic_sign" | ||||
|                         }, | ||||
|                         "id": "notrafficsign-no:traffic_sign" | ||||
|                     }, | ||||
|                     { | ||||
|                         "render": { | ||||
|                             "nl": "Probleem bij deze situatie: {traffic_sign:issue}", | ||||
|                             "en": "Issue with this situation: {traffic_sign:issue}" | ||||
|                         }, | ||||
|                         "question": { | ||||
|                             "nl": "Wat is er mis met deze situatie?", | ||||
|                             "en": "What is the issue with this situation?" | ||||
|                         }, | ||||
|                         "freeform": { | ||||
|                             "key": "traffic_sign:issue" | ||||
|                         }, | ||||
|                         "id": "notrafficsign-traffic_sign:issue" | ||||
|                     }, | ||||
|                     { | ||||
|                         "question": { | ||||
|                             "nl": "Wanneer werd deze situatie laatst gesurveyed?", | ||||
|                             "en": "When was this situation last surveyed?" | ||||
|                         }, | ||||
|                         "render": { | ||||
|                             "nl": "Deze situatie werd laatst gesurveyed op {survey:date}", | ||||
|                             "en": "This situation was last surveyed on {survey:date}" | ||||
|                         }, | ||||
|                         "freeform": { | ||||
|                             "key": "survey:date", | ||||
|                             "type": "date" | ||||
|                         }, | ||||
|                         "mappings": [ | ||||
|                             { | ||||
|                                 "if": "survey:date:={_now:date}", | ||||
|                                 "then": "Vandaag gesurveyed!" | ||||
|                             } | ||||
|                         ], | ||||
|                         "id": "notrafficsign-survey:date" | ||||
|                     } | ||||
|                 ], | ||||
|                 "mapRendering": [ | ||||
|                     { | ||||
|                         "icon": "./TS_questionmark.svg", | ||||
|                         "location": [ | ||||
|                             "point", | ||||
|                             "centroid" | ||||
|                         ] | ||||
|                     } | ||||
|                 ] | ||||
|             } | ||||
|         ] | ||||
|         ], | ||||
|         "defaultBackgroundId": "Stamen.TonerLite" | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|     constructor() { | ||||
|         super([ | ||||
|                 ["Walking_node_theme", () => { | ||||
|  | @ -439,8 +427,13 @@ export default class LegacyThemeLoaderSpec extends T { | |||
|                         }, "test"); | ||||
|                         T.isTrue(r0.errors.length > 0, "Failing case is not detected") | ||||
|                     } | ||||
| 
 | ||||
|                 ] | ||||
|                 ], | ||||
|             ["Images are rewritten", () => { | ||||
|                 const fixed =   new FixImages(new Set<string>()).convertStrict(LegacyThemeLoaderSpec.verkeerde_borden, "test") | ||||
|                 const fixedValue = fixed.layers[0]["mapRendering"][0].icon | ||||
|                 Assert.equal("https://raw.githubusercontent.com/seppesantens/MapComplete-Themes/main/VerkeerdeBordenDatabank/TS_bolt.svg", | ||||
|                     fixedValue) | ||||
|             } ] | ||||
|             ] | ||||
|         ); | ||||
|     } | ||||
|  |  | |||
							
								
								
									
										15
									
								
								theme.html
									
										
									
									
									
								
							
							
						
						
									
										15
									
								
								theme.html
									
										
									
									
									
								
							|  | @ -37,14 +37,7 @@ | |||
|     <link href="./assets/generated/svg_mapcomplete_logo72.png" rel="apple-touch-icon" sizes="72x72"> | ||||
| 
 | ||||
|     <!-- THEME-SPECIFIC-END--> | ||||
| 
 | ||||
|     <style> | ||||
|         #decoration-desktop img { | ||||
|             width: 100%; | ||||
|             height: 100%; | ||||
|         } | ||||
|     </style> | ||||
| 
 | ||||
|      | ||||
| </head> | ||||
| <body> | ||||
| 
 | ||||
|  | @ -70,13 +63,15 @@ | |||
| 
 | ||||
| <div class="clutter absolute h-24 left-24 right-24 top-56 text-xl text-center" | ||||
|      id="centermessage" style="z-index: 4000"> | ||||
|     <h2>Loading MapComplete, hang on...</h2> | ||||
|     <p>Powered by OpenStreetMap</p> | ||||
|     <h1>Loading MapComplete, hang on...</h1> | ||||
|     <p class="subtle">Powered by OpenStreetMap</p> | ||||
| </div> | ||||
| 
 | ||||
| <span class="absolute" id="belowmap" style="z-index: -1">Below</span> | ||||
| <div id="leafletDiv"></div> | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| <script src="./index.ts"></script> | ||||
| <script async data-goatcounter="https://pietervdvn.goatcounter.com/count" src="//gc.zgo.at/count.js"></script> | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue