forked from MapComplete/MapComplete
		
	Merge refactoring/no-central-theme-overview
This commit is contained in:
		
						commit
						11a0d1ed54
					
				
					 67 changed files with 1776 additions and 9910 deletions
				
			
		
							
								
								
									
										56
									
								
								404.html
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								404.html
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,56 @@ | ||||||
|  | <!DOCTYPE html> | ||||||
|  | <html lang="en"> | ||||||
|  | <head> | ||||||
|  |     <meta charset="UTF-8"> | ||||||
|  |     <meta content="width=device-width, initial-scale=1.0, user-scalable=no" name="viewport"> | ||||||
|  |     <link href="./css/mobile.css" rel="stylesheet"/> | ||||||
|  |     <link href="./css/tagrendering.css" rel="stylesheet"/> | ||||||
|  |     <link href="./css/index-tailwind-output.css" rel="stylesheet"/> | ||||||
|  |     <meta content="website" property="og:type"> | ||||||
|  | 
 | ||||||
|  |     <title>MapComplete - page not found</title> | ||||||
|  |     <link href="./index.manifest" rel="manifest"> | ||||||
|  |     <link href="./assets/svg/add.svg" rel="icon" sizes="any" type="image/svg+xml"> | ||||||
|  |     <meta content="MapComplete - Page not found" property="og:title"> | ||||||
|  |     <meta content="MapComplete is a platform to visualize OpenStreetMap on a specific topic and to easily contribute data back to it." | ||||||
|  |           property="og:description"> | ||||||
|  | 
 | ||||||
|  |     <link href="./assets/generated/svg_mapcomplete_logo512.png" rel="apple-touch-icon" sizes="512x512"> | ||||||
|  |     <link href="./assets/generated/svg_mapcomplete_logo384.png" rel="apple-touch-icon" sizes="384x384"> | ||||||
|  |     <link href="./assets/generated/svg_mapcomplete_logo192.png" rel="apple-touch-icon" sizes="192x192"> | ||||||
|  |     <link href="./assets/generated/svg_mapcomplete_logo180.png" rel="apple-touch-icon" sizes="180x180"> | ||||||
|  |     <link href="./assets/generated/svg_mapcomplete_logo152.png" rel="apple-touch-icon" sizes="152x152"> | ||||||
|  |     <link href="./assets/generated/svg_mapcomplete_logo144.png" rel="apple-touch-icon" sizes="144x144"> | ||||||
|  |     <link href="./assets/generated/svg_mapcomplete_logo128.png" rel="apple-touch-icon" sizes="128x128"> | ||||||
|  |     <link href="./assets/generated/svg_mapcomplete_logo120.png" rel="apple-touch-icon" sizes="120x120"> | ||||||
|  |     <link href="./assets/generated/svg_mapcomplete_logo96.png" rel="apple-touch-icon" sizes="96x96"> | ||||||
|  |     <link href="./assets/generated/svg_mapcomplete_logo72.png" rel="apple-touch-icon" sizes="72x72"> | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     <style> | ||||||
|  |         #decoration-desktop img { | ||||||
|  |             width: 100%; | ||||||
|  |             height: 100%; | ||||||
|  |         } | ||||||
|  |     </style> | ||||||
|  | 
 | ||||||
|  | </head> | ||||||
|  | <body> | ||||||
|  | 
 | ||||||
|  | <div id="decoration-desktop" style="position: fixed; left: 1em; bottom: 1em; width:35vh; height:35vh;"> | ||||||
|  |     <!-- A nice decoration while loading or on errors --> | ||||||
|  |     <!-- DECORATION 0 START --> | ||||||
|  |     <img src="./assets/svg/add.svg"/> | ||||||
|  |     <!-- DECORATION 0 END --> | ||||||
|  | </div> | ||||||
|  | 
 | ||||||
|  | <div class="clutter absolute h-24 left-24 right-24 top-56 text-xl text-center" | ||||||
|  |      id="maindiv" style="z-index: 4000"> | ||||||
|  |     Not found... | ||||||
|  | </div> | ||||||
|  | 
 | ||||||
|  | <script src="./notfound.ts"></script> | ||||||
|  | <script async data-goatcounter="https://pietervdvn.goatcounter.com/count" src="//gc.zgo.at/count.js"></script> | ||||||
|  | 
 | ||||||
|  | </body> | ||||||
|  | </html> | ||||||
|  | @ -1,131 +0,0 @@ | ||||||
| import * as known_layers from "../assets/generated/known_layers_and_themes.json" |  | ||||||
| import {Utils} from "../Utils"; |  | ||||||
| import LayerConfig from "../Models/ThemeConfig/LayerConfig"; |  | ||||||
| import {TagRenderingConfigJson} from "../Models/ThemeConfig/Json/TagRenderingConfigJson"; |  | ||||||
| import SharedTagRenderings from "./SharedTagRenderings"; |  | ||||||
| import {LayerConfigJson} from "../Models/ThemeConfig/Json/LayerConfigJson"; |  | ||||||
| import WithContextLoader from "../Models/ThemeConfig/WithContextLoader"; |  | ||||||
| 
 |  | ||||||
| export default class AllKnownLayers { |  | ||||||
| 
 |  | ||||||
|     public static inited = (_ => { |  | ||||||
|         WithContextLoader.getKnownTagRenderings = (id => AllKnownLayers.getTagRendering(id)) |  | ||||||
|         return true |  | ||||||
|     })() |  | ||||||
| 
 |  | ||||||
|     public static runningGenerateScript = false; |  | ||||||
| 
 |  | ||||||
|     // Must be below the list...
 |  | ||||||
|     public static sharedLayers: Map<string, LayerConfig> = AllKnownLayers.getSharedLayers(); |  | ||||||
|     public static sharedLayersJson: Map<string, any> = AllKnownLayers.getSharedLayersJson(); |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|     public static added_by_default: string[] = ["gps_location", "gps_location_history", "home_location", "gps_track"] |  | ||||||
|     public static no_include: string[] = ["conflation", "left_right_style", "split_point","current_view","matchpoint"] |  | ||||||
|     /** |  | ||||||
|      * Layer IDs of layers which have special properties through built-in hooks |  | ||||||
|      */ |  | ||||||
|     public static priviliged_layers: string[] = [...AllKnownLayers.added_by_default, "type_node", ...AllKnownLayers.no_include] |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Gets the appropriate tagRenderingJSON |  | ||||||
|      * Allows to steal them from other layers. |  | ||||||
|      * This will add the tags of the layer to the configuration though! |  | ||||||
|      * @param renderingId |  | ||||||
|      */ |  | ||||||
|     static getTagRendering(renderingId: string): TagRenderingConfigJson[] { |  | ||||||
|         if (renderingId.indexOf(".") < 0) { |  | ||||||
|             const found = SharedTagRenderings.SharedTagRenderingJson.get(renderingId) |  | ||||||
|             if(found === undefined){ |  | ||||||
|                 return [] |  | ||||||
|             } |  | ||||||
|             return [found] |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         const [layerId, id] = renderingId.split(".") |  | ||||||
|         const layer = AllKnownLayers.getSharedLayersJson().get(layerId) |  | ||||||
|         if (layer === undefined) { |  | ||||||
|             if (AllKnownLayers.runningGenerateScript) { |  | ||||||
|                 // Probably generating the layer overview
 |  | ||||||
|                 return <TagRenderingConfigJson[]>[{ |  | ||||||
|                     id: "dummy" |  | ||||||
|                 }] |  | ||||||
|             } |  | ||||||
|             throw "Builtin layer " + layerId + " not found" |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         const renderings = layer?.tagRenderings ?? [] |  | ||||||
|         if (id === "*") { |  | ||||||
|             return <TagRenderingConfigJson[]>JSON.parse(JSON.stringify(renderings)) |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         const selectByGroup = id.startsWith("*") |  | ||||||
|         const expectedGroupName = id.substring(1) |  | ||||||
| 
 |  | ||||||
|         const allValidValues = [] |  | ||||||
|         for (const rendering of renderings) { |  | ||||||
|             if ((!selectByGroup && rendering["id"] === id) || (selectByGroup && rendering["group"] === expectedGroupName)) { |  | ||||||
|                 const found = <TagRenderingConfigJson>JSON.parse(JSON.stringify(rendering)) |  | ||||||
|                 if (found.condition === undefined) { |  | ||||||
|                     found.condition = layer.source.osmTags |  | ||||||
|                 } else { |  | ||||||
|                     found.condition = {and: [found.condition, layer.source.osmTags]} |  | ||||||
|                 } |  | ||||||
|                 allValidValues.push(found) |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         if(allValidValues.length === 0){ |  | ||||||
|              |  | ||||||
|         throw `The rendering with id ${id} was not found in the builtin layer ${layerId}. Try one of ${Utils.NoNull(renderings.map(r => r["id"])).join(", ")}` |  | ||||||
|         } |  | ||||||
|         return allValidValues |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     private static getSharedLayers(): Map<string, LayerConfig> { |  | ||||||
|         const sharedLayers = new Map<string, LayerConfig>(); |  | ||||||
|         for (const layer of known_layers.layers) { |  | ||||||
|             try { |  | ||||||
|                 // @ts-ignore
 |  | ||||||
|                 const parsed = new LayerConfig(layer, "shared_layers") |  | ||||||
|                 sharedLayers.set(layer.id, parsed); |  | ||||||
|             } catch (e) { |  | ||||||
|                 if (!Utils.runningFromConsole) { |  | ||||||
|                     console.error("CRITICAL: Could not parse a layer configuration!", layer.id, " due to", e) |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         for (const layout of known_layers.themes) { |  | ||||||
|             for (const layer of layout.layers) { |  | ||||||
|                 if (typeof layer === "string") { |  | ||||||
|                     continue; |  | ||||||
|                 } |  | ||||||
|                 if (layer.builtin !== undefined) { |  | ||||||
|                     // This is a builtin layer of which stuff is overridden - skip
 |  | ||||||
|                     continue; |  | ||||||
|                 } |  | ||||||
|                 try { |  | ||||||
|                     const parsed = new LayerConfig(layer, "shared_layer_in_theme") |  | ||||||
|                     sharedLayers.set(layer.id, parsed); |  | ||||||
|                     sharedLayers[layer.id] = parsed; |  | ||||||
|                 } catch (e) { |  | ||||||
|                     if (!Utils.runningFromConsole) { |  | ||||||
|                         console.error("Could not parse a layer in theme ", layout.id, "due to", e) |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         return sharedLayers; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     private static getSharedLayersJson(): Map<string, LayerConfigJson> { |  | ||||||
|         const sharedLayers = new Map<string, any>(); |  | ||||||
|         for (const layer of known_layers.layers) { |  | ||||||
|             sharedLayers.set(layer.id, layer); |  | ||||||
|             sharedLayers[layer.id] = layer; |  | ||||||
|         } |  | ||||||
|         return sharedLayers; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
| } |  | ||||||
|  | @ -1,4 +1,3 @@ | ||||||
| import AllKnownLayers from "./AllKnownLayers"; |  | ||||||
| import * as known_themes from "../assets/generated/known_layers_and_themes.json" | import * as known_themes from "../assets/generated/known_layers_and_themes.json" | ||||||
| import LayoutConfig from "../Models/ThemeConfig/LayoutConfig"; | import LayoutConfig from "../Models/ThemeConfig/LayoutConfig"; | ||||||
| import LayerConfig from "../Models/ThemeConfig/LayerConfig"; | import LayerConfig from "../Models/ThemeConfig/LayerConfig"; | ||||||
|  | @ -7,9 +6,29 @@ import Combine from "../UI/Base/Combine"; | ||||||
| import Title from "../UI/Base/Title"; | import Title from "../UI/Base/Title"; | ||||||
| import List from "../UI/Base/List"; | import List from "../UI/Base/List"; | ||||||
| import DependencyCalculator from "../Models/ThemeConfig/DependencyCalculator"; | import DependencyCalculator from "../Models/ThemeConfig/DependencyCalculator"; | ||||||
|  | import Constants from "../Models/Constants"; | ||||||
|  | import {Utils} from "../Utils"; | ||||||
| 
 | 
 | ||||||
| export class AllKnownLayouts { | export class AllKnownLayouts { | ||||||
|  |     // Must be below the list...
 | ||||||
|  |     private static sharedLayers: Map<string, LayerConfig> = AllKnownLayouts.getSharedLayers(); | ||||||
| 
 | 
 | ||||||
|  |     private static getSharedLayers(): Map<string, LayerConfig> { | ||||||
|  |         const sharedLayers = new Map<string, LayerConfig>(); | ||||||
|  |         for (const layer of known_themes.layers) { | ||||||
|  |             try { | ||||||
|  |                 // @ts-ignore
 | ||||||
|  |                 const parsed = new LayerConfig(layer, "shared_layers") | ||||||
|  |                 sharedLayers.set(layer.id, parsed); | ||||||
|  |             } catch (e) { | ||||||
|  |                 if (!Utils.runningFromConsole) { | ||||||
|  |                     console.error("CRITICAL: Could not parse a layer configuration!", layer.id, " due to", e) | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return sharedLayers; | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|     public static allKnownLayouts: Map<string, LayoutConfig> = AllKnownLayouts.AllLayouts(); |     public static allKnownLayouts: Map<string, LayoutConfig> = AllKnownLayouts.AllLayouts(); | ||||||
|     public static layoutsList: LayoutConfig[] = AllKnownLayouts.GenerateOrderedList(AllKnownLayouts.allKnownLayouts); |     public static layoutsList: LayoutConfig[] = AllKnownLayouts.GenerateOrderedList(AllKnownLayouts.allKnownLayouts); | ||||||
|  | @ -35,14 +54,14 @@ export class AllKnownLayouts { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public static GenLayerOverviewText(): BaseUIElement { |     public static GenLayerOverviewText(): BaseUIElement { | ||||||
|         for (const id of AllKnownLayers.priviliged_layers) { |         for (const id of Constants.priviliged_layers) { | ||||||
|             if (!AllKnownLayers.sharedLayers.has(id)) { |             if (!AllKnownLayouts.sharedLayers.has(id)) { | ||||||
|                 throw "Priviliged layer definition not found: " + id |                 throw "Priviliged layer definition not found: " + id | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         const allLayers: LayerConfig[] = Array.from(AllKnownLayers.sharedLayers.values()) |         const allLayers: LayerConfig[] = Array.from(AllKnownLayouts.sharedLayers.values()) | ||||||
|             .filter(layer => AllKnownLayers.priviliged_layers.indexOf(layer.id) < 0) |             .filter(layer => Constants.priviliged_layers.indexOf(layer.id) < 0) | ||||||
| 
 | 
 | ||||||
|         const builtinLayerIds: Set<string> = new Set<string>() |         const builtinLayerIds: Set<string> = new Set<string>() | ||||||
|         allLayers.forEach(l => builtinLayerIds.add(l.id)) |         allLayers.forEach(l => builtinLayerIds.add(l.id)) | ||||||
|  | @ -89,10 +108,10 @@ export class AllKnownLayouts { | ||||||
|             new Title("Special and other useful layers", 1), |             new Title("Special and other useful layers", 1), | ||||||
|             "MapComplete has a few data layers available in the theme which have special properties through builtin-hooks. Furthermore, there are some normal layers (which are built from normal Theme-config files) but are so general that they get a mention here.", |             "MapComplete has a few data layers available in the theme which have special properties through builtin-hooks. Furthermore, there are some normal layers (which are built from normal Theme-config files) but are so general that they get a mention here.", | ||||||
|             new Title("Priviliged layers", 1), |             new Title("Priviliged layers", 1), | ||||||
|             new List(AllKnownLayers.priviliged_layers.map(id => "[" + id + "](#" + id + ")")), |             new List(Constants.priviliged_layers.map(id => "[" + id + "](#" + id + ")")), | ||||||
|             ...AllKnownLayers.priviliged_layers |             ...Constants.priviliged_layers | ||||||
|                 .map(id => AllKnownLayers.sharedLayers.get(id)) |                 .map(id => AllKnownLayouts.sharedLayers.get(id)) | ||||||
|                 .map((l) => l.GenerateDocumentation(themesPerLayer.get(l.id), layerIsNeededBy, DependencyCalculator.getLayerDependencies(l),AllKnownLayers.added_by_default.indexOf(l.id) >= 0, AllKnownLayers.no_include.indexOf(l.id) < 0)), |                 .map((l) => l.GenerateDocumentation(themesPerLayer.get(l.id), layerIsNeededBy, DependencyCalculator.getLayerDependencies(l),Constants.added_by_default.indexOf(l.id) >= 0, Constants.no_include.indexOf(l.id) < 0)), | ||||||
|             new Title("Normal layers", 1), |             new Title("Normal layers", 1), | ||||||
|             "The following layers are included in MapComplete", |             "The following layers are included in MapComplete", | ||||||
|             new Title("Frequently reused layers", 2), |             new Title("Frequently reused layers", 2), | ||||||
|  | @ -108,53 +127,26 @@ export class AllKnownLayouts { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private static GenerateOrderedList(allKnownLayouts: Map<string, LayoutConfig>): LayoutConfig[] { |     private static GenerateOrderedList(allKnownLayouts: Map<string, LayoutConfig>): LayoutConfig[] { | ||||||
|         const keys = ["personal", "cyclofix", "hailhydrant", "bookcases", "toilets", "aed"] |  | ||||||
|         const list = [] |         const list = [] | ||||||
|         for (const key of keys) { |  | ||||||
|             list.push(allKnownLayouts.get(key)) |  | ||||||
|         } |  | ||||||
|         allKnownLayouts.forEach((layout, key) => { |         allKnownLayouts.forEach((layout, key) => { | ||||||
|             if (keys.indexOf(key) < 0) { |             list.push(layout) | ||||||
|                 list.push(layout) |  | ||||||
|             } |  | ||||||
|         }) |         }) | ||||||
|         return list; |         return list; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private static AddGhostBikes(layout: LayoutConfig): LayoutConfig { |  | ||||||
|         const now = new Date(); |  | ||||||
|         const m = now.getMonth() + 1; |  | ||||||
|         const day = new Date().getDate() + 1; |  | ||||||
|         const date = day + "/" + m; |  | ||||||
|         if (date === "31/10" || date === "1/11" || date === "2/11") { |  | ||||||
|             console.log("The current date is ", date, ", which means we remember our dead") |  | ||||||
|             // Around Halloween/Fiesta de muerte/Allerzielen, we remember the dead
 |  | ||||||
|             layout.layers.push( |  | ||||||
|                 AllKnownLayers.sharedLayers.get("ghost_bike") |  | ||||||
|             ); |  | ||||||
| 
 |  | ||||||
|         } |  | ||||||
|         return layout; |  | ||||||
| 
 |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     private static AllLayouts(): Map<string, LayoutConfig> { |     private static AllLayouts(): Map<string, LayoutConfig> { | ||||||
|         const dict: Map<string, LayoutConfig> = new Map(); |         const dict: Map<string, LayoutConfig> = new Map(); | ||||||
|         for (const layoutConfigJson of known_themes.themes) { |         for (const layoutConfigJson of known_themes.themes) { | ||||||
|             // @ts-ignore
 |             // @ts-ignore
 | ||||||
|             const layout = new LayoutConfig(layoutConfigJson, true) |             const layout = new LayoutConfig(layoutConfigJson, true) | ||||||
| 
 |  | ||||||
|             if (layout.id === "cyclofix") { |  | ||||||
|                 AllKnownLayouts.AddGhostBikes(layout) |  | ||||||
|             } |  | ||||||
|             dict.set(layout.id, layout) |             dict.set(layout.id, layout) | ||||||
| 
 | 
 | ||||||
|             for (let i = 0; i < layout.layers.length; i++) { |             for (let i = 0; i < layout.layers.length; i++) { | ||||||
|                 let layer = layout.layers[i]; |                 let layer = layout.layers[i]; | ||||||
|                 if (typeof (layer) === "string") { |                 if (typeof (layer) === "string") { | ||||||
|                     layer = layout.layers[i] = AllKnownLayers.sharedLayers.get(layer); |                     layer = layout.layers[i] = AllKnownLayouts.sharedLayers.get(layer); | ||||||
|                     if (layer === undefined) { |                     if (layer === undefined) { | ||||||
|                         console.log("Defined layers are ", AllKnownLayers.sharedLayers.keys()) |                         console.log("Defined layers are ", AllKnownLayouts.sharedLayers.keys()) | ||||||
|                         throw `Layer ${layer} was not found or defined - probably a type was made` |                         throw `Layer ${layer} was not found or defined - probably a type was made` | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
|  | @ -38,7 +38,9 @@ export default class SharedTagRenderings { | ||||||
|             dict.set(key, <TagRenderingConfigJson>icons[key]) |             dict.set(key, <TagRenderingConfigJson>icons[key]) | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         dict.forEach((value, key) => value.id = key) |         dict.forEach((value, key) => { | ||||||
|  |             value.id = value.id ?? key; | ||||||
|  |         }) | ||||||
| 
 | 
 | ||||||
|         return dict; |         return dict; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | @ -29,8 +29,6 @@ | ||||||
|       * [Themes using this layer](#themes-using-this-layer) |       * [Themes using this layer](#themes-using-this-layer) | ||||||
|     + [walls_and_buildings](#walls_and_buildings) |     + [walls_and_buildings](#walls_and_buildings) | ||||||
|       * [Themes using this layer](#themes-using-this-layer) |       * [Themes using this layer](#themes-using-this-layer) | ||||||
|     + [all_streets](#all_streets) |  | ||||||
|       * [Themes using this layer](#themes-using-this-layer) |  | ||||||
|     + [ambulancestation](#ambulancestation) |     + [ambulancestation](#ambulancestation) | ||||||
|       * [Themes using this layer](#themes-using-this-layer) |       * [Themes using this layer](#themes-using-this-layer) | ||||||
|     + [artwork](#artwork) |     + [artwork](#artwork) | ||||||
|  | @ -113,36 +111,6 @@ | ||||||
|       * [Themes using this layer](#themes-using-this-layer) |       * [Themes using this layer](#themes-using-this-layer) | ||||||
|     + [waste_basket](#waste_basket) |     + [waste_basket](#waste_basket) | ||||||
|       * [Themes using this layer](#themes-using-this-layer) |       * [Themes using this layer](#themes-using-this-layer) | ||||||
|     + [caravansites](#caravansites) |  | ||||||
|       * [Themes using this layer](#themes-using-this-layer) |  | ||||||
|     + [dumpstations](#dumpstations) |  | ||||||
|       * [Themes using this layer](#themes-using-this-layer) |  | ||||||
|     + [climbing_club](#climbing_club) |  | ||||||
|       * [Themes using this layer](#themes-using-this-layer) |  | ||||||
|     + [climbing_gym](#climbing_gym) |  | ||||||
|       * [Themes using this layer](#themes-using-this-layer) |  | ||||||
|     + [climbing_route](#climbing_route) |  | ||||||
|       * [Themes using this layer](#themes-using-this-layer) |  | ||||||
|     + [climbing](#climbing) |  | ||||||
|       * [Themes using this layer](#themes-using-this-layer) |  | ||||||
|     + [maybe_climbing](#maybe_climbing) |  | ||||||
|       * [Themes using this layer](#themes-using-this-layer) |  | ||||||
|     + [fietsstraat](#fietsstraat) |  | ||||||
|       * [Themes using this layer](#themes-using-this-layer) |  | ||||||
|     + [toekomstige_fietsstraat](#toekomstige_fietsstraat) |  | ||||||
|       * [Themes using this layer](#themes-using-this-layer) |  | ||||||
|     + [facadegardens](#facadegardens) |  | ||||||
|       * [Themes using this layer](#themes-using-this-layer) |  | ||||||
|     + [hackerspaces](#hackerspaces) |  | ||||||
|       * [Themes using this layer](#themes-using-this-layer) |  | ||||||
|     + [windturbine](#windturbine) |  | ||||||
|       * [Themes using this layer](#themes-using-this-layer) |  | ||||||
|     + [postboxes](#postboxes) |  | ||||||
|       * [Themes using this layer](#themes-using-this-layer) |  | ||||||
|     + [postoffices](#postoffices) |  | ||||||
|       * [Themes using this layer](#themes-using-this-layer) |  | ||||||
|     + [lit_streets](#lit_streets) |  | ||||||
|       * [Themes using this layer](#themes-using-this-layer) |  | ||||||
| 
 | 
 | ||||||
|  MapComplete has a few data layers available in the theme which have special properties through builtin-hooks. Furthermore, there are some normal layers (which are built from normal Theme-config files) but are so general that they get a mention here.  |  MapComplete has a few data layers available in the theme which have special properties through builtin-hooks. Furthermore, there are some normal layers (which are built from normal Theme-config files) but are so general that they get a mention here.  | ||||||
| 
 | 
 | ||||||
|  | @ -235,7 +203,6 @@ This is a priviliged meta_layer which exports _every_ point in OSM. This only wo | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|   - Not rendered on the map by default. If you want to rendering this on the map, override `mapRenderings` |   - Not rendered on the map by default. If you want to rendering this on the map, override `mapRenderings` | ||||||
|   - This layer is needed as dependency for layer [GRB](#GRB) |  | ||||||
|   |   | ||||||
| 
 | 
 | ||||||
| ### conflation  | ### conflation  | ||||||
|  | @ -320,7 +287,6 @@ The default rendering for a locationInput which snaps onto another object | ||||||
|   - [food](#food) |   - [food](#food) | ||||||
|   - [map](#map) |   - [map](#map) | ||||||
|   - [walls_and_buildings](#walls_and_buildings) |   - [walls_and_buildings](#walls_and_buildings) | ||||||
|   - [all_streets](#all_streets) |  | ||||||
|   |   | ||||||
| 
 | 
 | ||||||
| ### bicycle_library  | ### bicycle_library  | ||||||
|  | @ -456,29 +422,6 @@ Special builtin layer providing all walls and buildings. This layer is useful in | ||||||
|   - [surveillance](https://mapcomplete.osm.be/surveillance) |   - [surveillance](https://mapcomplete.osm.be/surveillance) | ||||||
|   |   | ||||||
| 
 | 
 | ||||||
| ### all_streets  |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| [Go to the source code](../assets/layers/all_streets/all_streets.json) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|   - Not rendered on the map by default. If you want to rendering this on the map, override `mapRenderings` |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| #### Themes using this layer  |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|   - [cyclestreets](https://mapcomplete.osm.be/cyclestreets) |  | ||||||
|   - [street_lighting](https://mapcomplete.osm.be/street_lighting) |  | ||||||
|   |  | ||||||
| 
 |  | ||||||
|   - [ambulancestation](#ambulancestation) |   - [ambulancestation](#ambulancestation) | ||||||
|   - [artwork](#artwork) |   - [artwork](#artwork) | ||||||
|   - [barrier](#barrier) |   - [barrier](#barrier) | ||||||
|  | @ -520,21 +463,6 @@ Special builtin layer providing all walls and buildings. This layer is useful in | ||||||
|   - [toilet](#toilet) |   - [toilet](#toilet) | ||||||
|   - [tree_node](#tree_node) |   - [tree_node](#tree_node) | ||||||
|   - [waste_basket](#waste_basket) |   - [waste_basket](#waste_basket) | ||||||
|   - [caravansites](#caravansites) |  | ||||||
|   - [dumpstations](#dumpstations) |  | ||||||
|   - [climbing_club](#climbing_club) |  | ||||||
|   - [climbing_gym](#climbing_gym) |  | ||||||
|   - [climbing_route](#climbing_route) |  | ||||||
|   - [climbing](#climbing) |  | ||||||
|   - [maybe_climbing](#maybe_climbing) |  | ||||||
|   - [fietsstraat](#fietsstraat) |  | ||||||
|   - [toekomstige_fietsstraat](#toekomstige_fietsstraat) |  | ||||||
|   - [facadegardens](#facadegardens) |  | ||||||
|   - [hackerspaces](#hackerspaces) |  | ||||||
|   - [windturbine](#windturbine) |  | ||||||
|   - [postboxes](#postboxes) |  | ||||||
|   - [postoffices](#postoffices) |  | ||||||
|   - [lit_streets](#lit_streets) |  | ||||||
|   |   | ||||||
| 
 | 
 | ||||||
| ### ambulancestation  | ### ambulancestation  | ||||||
|  | @ -985,7 +913,6 @@ A layer showing defibrillators which can be used in case of emergency. This cont | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|   - This layer will automatically load  [walls_and_buildings](#walls_and_buildings)  into the layout as it depends on it:  a preset snaps to this layer (presets[1]) |   - This layer will automatically load  [walls_and_buildings](#walls_and_buildings)  into the layout as it depends on it:  a preset snaps to this layer (presets[1]) | ||||||
|   - This layer is needed as dependency for layer [Brugge](#Brugge) |  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -1418,7 +1345,7 @@ A layer showing street lights | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|   - This layer is needed as dependency for layer [Assen](#Assen) | 
 | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -1528,358 +1455,4 @@ This is a public waste basket, thrash can, where you can throw away your thrash. | ||||||
|   - [waste_basket](https://mapcomplete.osm.be/waste_basket) |   - [waste_basket](https://mapcomplete.osm.be/waste_basket) | ||||||
|   |   | ||||||
| 
 | 
 | ||||||
| ### caravansites  |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| camper sites |  | ||||||
| 
 |  | ||||||
| [Go to the source code](../assets/layers/caravansites/caravansites.json) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| #### Themes using this layer  |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|   - [campersite](https://mapcomplete.osm.be/campersite) |  | ||||||
|   |  | ||||||
| 
 |  | ||||||
| ### dumpstations  |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| Sanitary dump stations |  | ||||||
| 
 |  | ||||||
| [Go to the source code](../assets/layers/dumpstations/dumpstations.json) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| #### Themes using this layer  |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|   - [campersite](https://mapcomplete.osm.be/campersite) |  | ||||||
|   |  | ||||||
| 
 |  | ||||||
| ### climbing_club  |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| A climbing club or organisations |  | ||||||
| 
 |  | ||||||
| [Go to the source code](../assets/layers/climbing_club/climbing_club.json) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| #### Themes using this layer  |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|   - [climbing](https://mapcomplete.osm.be/climbing) |  | ||||||
|   |  | ||||||
| 
 |  | ||||||
| ### climbing_gym  |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| A climbing gym |  | ||||||
| 
 |  | ||||||
| [Go to the source code](../assets/layers/climbing_gym/climbing_gym.json) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| #### Themes using this layer  |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|   - [climbing](https://mapcomplete.osm.be/climbing) |  | ||||||
|   |  | ||||||
| 
 |  | ||||||
| ### climbing_route  |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| [Go to the source code](../assets/layers/climbing_route/climbing_route.json) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|   - This layer is needed as dependency for layer [climbing](#climbing) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| #### Themes using this layer  |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|   - [climbing](https://mapcomplete.osm.be/climbing) |  | ||||||
|   |  | ||||||
| 
 |  | ||||||
| ### climbing  |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| A climbing opportunity |  | ||||||
| 
 |  | ||||||
| [Go to the source code](../assets/layers/climbing/climbing.json) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|   - This layer will automatically load  [climbing_route](#climbing_route)  into the layout as it depends on it:  A calculated tag loads features from this layer (calculatedTag[0] which calculates the value for _contained_climbing_routes_properties) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| #### Themes using this layer  |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|   - [climbing](https://mapcomplete.osm.be/climbing) |  | ||||||
|   |  | ||||||
| 
 |  | ||||||
| ### maybe_climbing  |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| A climbing opportunity? |  | ||||||
| 
 |  | ||||||
| [Go to the source code](../assets/layers/maybe_climbing/maybe_climbing.json) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| #### Themes using this layer  |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|   - [climbing](https://mapcomplete.osm.be/climbing) |  | ||||||
|   |  | ||||||
| 
 |  | ||||||
| ### fietsstraat  |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| A cyclestreet is a street where motorized traffic is not allowed to overtake a cyclist |  | ||||||
| 
 |  | ||||||
| [Go to the source code](../assets/layers/fietsstraat/fietsstraat.json) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| #### Themes using this layer  |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|   - [cyclestreets](https://mapcomplete.osm.be/cyclestreets) |  | ||||||
|   |  | ||||||
| 
 |  | ||||||
| ### toekomstige_fietsstraat  |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| This street will become a cyclestreet soon |  | ||||||
| 
 |  | ||||||
| [Go to the source code](../assets/layers/toekomstige_fietsstraat/toekomstige_fietsstraat.json) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| #### Themes using this layer  |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|   - [cyclestreets](https://mapcomplete.osm.be/cyclestreets) |  | ||||||
|   |  | ||||||
| 
 |  | ||||||
| ### facadegardens  |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| Facade gardens |  | ||||||
| 
 |  | ||||||
| [Go to the source code](../assets/layers/facadegardens/facadegardens.json) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| #### Themes using this layer  |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|   - [facadegardens](https://mapcomplete.osm.be/facadegardens) |  | ||||||
|   |  | ||||||
| 
 |  | ||||||
| ### hackerspaces  |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| Hackerspace |  | ||||||
| 
 |  | ||||||
| [Go to the source code](../assets/layers/hackerspaces/hackerspaces.json) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| #### Themes using this layer  |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|   - [hackerspaces](https://mapcomplete.osm.be/hackerspaces) |  | ||||||
|   |  | ||||||
| 
 |  | ||||||
| ### windturbine  |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| [Go to the source code](../assets/layers/windturbine/windturbine.json) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| #### Themes using this layer  |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|   - [openwindpowermap](https://mapcomplete.osm.be/openwindpowermap) |  | ||||||
|   |  | ||||||
| 
 |  | ||||||
| ### postboxes  |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| The layer showing postboxes. |  | ||||||
| 
 |  | ||||||
| [Go to the source code](../assets/layers/postboxes/postboxes.json) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| #### Themes using this layer  |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|   - [postboxes](https://mapcomplete.osm.be/postboxes) |  | ||||||
|   |  | ||||||
| 
 |  | ||||||
| ### postoffices  |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| A layer showing post offices. |  | ||||||
| 
 |  | ||||||
| [Go to the source code](../assets/layers/postoffices/postoffices.json) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| #### Themes using this layer  |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|   - [postboxes](https://mapcomplete.osm.be/postboxes) |  | ||||||
|   |  | ||||||
| 
 |  | ||||||
| ### lit_streets  |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| [Go to the source code](../assets/layers/lit_streets/lit_streets.json) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|   - Not rendered on the map by default. If you want to rendering this on the map, override `mapRenderings` |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| #### Themes using this layer  |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|   - [street_lighting](https://mapcomplete.osm.be/street_lighting) |  | ||||||
|   |  | ||||||
| 
 |  | ||||||
| This document is autogenerated from AllKnownLayers.ts | This document is autogenerated from AllKnownLayers.ts | ||||||
|  | @ -1,61 +0,0 @@ | ||||||
| import {UIEventSource} from "../UIEventSource"; |  | ||||||
| import {OsmConnection} from "../Osm/OsmConnection"; |  | ||||||
| import {Utils} from "../../Utils"; |  | ||||||
| import LZString from "lz-string"; |  | ||||||
| import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"; |  | ||||||
| 
 |  | ||||||
| export default class InstalledThemes { |  | ||||||
|     public installedThemes: UIEventSource<{ layout: LayoutConfig; definition: string }[]>; |  | ||||||
| 
 |  | ||||||
|     constructor(osmConnection: OsmConnection) { |  | ||||||
|         this.installedThemes = osmConnection.preferencesHandler.preferences.map<{ layout: LayoutConfig, definition: string }[]>(allPreferences => { |  | ||||||
|             const installedThemes: { layout: LayoutConfig, definition: string }[] = []; |  | ||||||
|             if (allPreferences === undefined) { |  | ||||||
|                 console.log("All prefs is undefined"); |  | ||||||
|                 return installedThemes; |  | ||||||
|             } |  | ||||||
|             const invalidThemes = [] |  | ||||||
|             for (const allPreferencesKey in allPreferences) { |  | ||||||
|                 const themename = allPreferencesKey.match(/^mapcomplete-installed-theme-(.*)-combined-length$/); |  | ||||||
|                 if (themename && themename[1] !== "") { |  | ||||||
|                     const customLayout = osmConnection.GetLongPreference("installed-theme-" + themename[1]); |  | ||||||
|                     if (customLayout.data === undefined) { |  | ||||||
|                         console.log("No data defined for ", themename[1]); |  | ||||||
|                         continue; |  | ||||||
|                     } |  | ||||||
|                     try { |  | ||||||
|                         let layoutJson; |  | ||||||
|                         try { |  | ||||||
|                             layoutJson = JSON.parse(atob(customLayout.data)) |  | ||||||
|                         } catch (e) { |  | ||||||
|                             layoutJson = JSON.parse(Utils.UnMinify(LZString.decompressFromBase64(customLayout.data))) |  | ||||||
|                         } |  | ||||||
|                         const layout = new LayoutConfig(layoutJson, false); |  | ||||||
|                         installedThemes.push({ |  | ||||||
|                             layout: layout, |  | ||||||
|                             definition: customLayout.data |  | ||||||
|                         }); |  | ||||||
|                     } catch (e) { |  | ||||||
|                         console.warn("Could not parse custom layout from preferences - deleting: ", allPreferencesKey, e, customLayout.data); |  | ||||||
|                         invalidThemes.push(themename[1]) |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             InstalledThemes.DeleteInvalid(osmConnection, invalidThemes); |  | ||||||
| 
 |  | ||||||
|             return installedThemes; |  | ||||||
| 
 |  | ||||||
|         }); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     private static DeleteInvalid(osmConnection: OsmConnection, invalidThemes: any[]) { |  | ||||||
|         for (const invalid of invalidThemes) { |  | ||||||
|             console.error("Attempting to remove ", invalid) |  | ||||||
|             osmConnection.GetLongPreference( |  | ||||||
|                 "installed-theme-" + invalid |  | ||||||
|             ).setData(null); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
| } |  | ||||||
|  | @ -10,7 +10,7 @@ import RelationsTracker from "../Osm/RelationsTracker"; | ||||||
| import {BBox} from "../BBox"; | import {BBox} from "../BBox"; | ||||||
| import Loc from "../../Models/Loc"; | import Loc from "../../Models/Loc"; | ||||||
| import LayerConfig from "../../Models/ThemeConfig/LayerConfig"; | import LayerConfig from "../../Models/ThemeConfig/LayerConfig"; | ||||||
| import AllKnownLayers from "../../Customizations/AllKnownLayers"; | import Constants from "../../Models/Constants"; | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| export default class OverpassFeatureSource implements FeatureSource { | export default class OverpassFeatureSource implements FeatureSource { | ||||||
|  | @ -122,7 +122,7 @@ export default class OverpassFeatureSource implements FeatureSource { | ||||||
|             if (typeof (layer) === "string") { |             if (typeof (layer) === "string") { | ||||||
|                 throw "A layer was not expanded!" |                 throw "A layer was not expanded!" | ||||||
|             } |             } | ||||||
|             if(AllKnownLayers.priviliged_layers.indexOf(layer.id) >= 0){ |             if(Constants.priviliged_layers.indexOf(layer.id) >= 0){ | ||||||
|                 continue |                 continue | ||||||
|             } |             } | ||||||
|             if (this.state.locationControl.data.zoom < layer.minzoom) { |             if (this.state.locationControl.data.zoom < layer.minzoom) { | ||||||
|  |  | ||||||
|  | @ -29,7 +29,7 @@ export default class TitleHandler { | ||||||
|                     } |                     } | ||||||
|                     if (layer.source.osmTags.matchesProperties(tags)) { |                     if (layer.source.osmTags.matchesProperties(tags)) { | ||||||
|                         const tagsSource = state.allElements.getEventSourceById(tags.id) ?? new UIEventSource<any>(tags) |                         const tagsSource = state.allElements.getEventSourceById(tags.id) ?? new UIEventSource<any>(tags) | ||||||
|                         const title = new TagRenderingAnswer(tagsSource, layer.title) |                         const title = new TagRenderingAnswer(tagsSource, layer.title, {}) | ||||||
|                         return new Combine([defaultTitle, " | ", title]).ConstructElement()?.innerText ?? defaultTitle; |                         return new Combine([defaultTitle, " | ", title]).ConstructElement()?.innerText ?? defaultTitle; | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
|  | @ -10,30 +10,29 @@ import {UIEventSource} from "./UIEventSource"; | ||||||
| import {LocalStorageSource} from "./Web/LocalStorageSource"; | import {LocalStorageSource} from "./Web/LocalStorageSource"; | ||||||
| import LZString from "lz-string"; | import LZString from "lz-string"; | ||||||
| import * as personal from "../assets/themes/personal/personal.json"; | import * as personal from "../assets/themes/personal/personal.json"; | ||||||
| import LegacyJsonConvert from "../Models/ThemeConfig/LegacyJsonConvert"; | import {FixLegacyTheme, PrepareTheme} from "../Models/ThemeConfig/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"; | ||||||
| 
 | 
 | ||||||
| export default class DetermineLayout { | export default class DetermineLayout { | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * Gets the correct layout for this website |      * Gets the correct layout for this website | ||||||
|      */ |      */ | ||||||
|     public static async GetLayout(): Promise<[LayoutConfig, string]> { |     public static async GetLayout(): Promise<LayoutConfig> { | ||||||
| 
 | 
 | ||||||
|         const loadCustomThemeParam = QueryParameters.GetQueryParameter("userlayout", "false", "If not 'false', a custom (non-official) theme is loaded. This custom layout can be done in multiple ways: \n\n- The hash of the URL contains a base64-encoded .json-file containing the theme definition\n- The hash of the URL contains a lz-compressed .json-file, as generated by the custom theme generator\n- The parameter itself is an URL, in which case that URL will be downloaded. It should point to a .json of a theme") |         const loadCustomThemeParam = QueryParameters.GetQueryParameter("userlayout", "false", "If not 'false', a custom (non-official) theme is loaded. This custom layout can be done in multiple ways: \n\n- The hash of the URL contains a base64-encoded .json-file containing the theme definition\n- The hash of the URL contains a lz-compressed .json-file, as generated by the custom theme generator\n- The parameter itself is an URL, in which case that URL will be downloaded. It should point to a .json of a theme") | ||||||
|         const layoutFromBase64 = decodeURIComponent(loadCustomThemeParam.data); |         const layoutFromBase64 = decodeURIComponent(loadCustomThemeParam.data); | ||||||
| 
 | 
 | ||||||
|         if (layoutFromBase64.startsWith("http")) { |         if (layoutFromBase64.startsWith("http")) { | ||||||
|             const layout = await DetermineLayout.LoadRemoteTheme(layoutFromBase64) |             return await DetermineLayout.LoadRemoteTheme(layoutFromBase64) | ||||||
|             return [layout, undefined] |  | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         if (layoutFromBase64 !== "false") { |         if (layoutFromBase64 !== "false") { | ||||||
|             // We have to load something from the hash (or from disk)
 |             // We have to load something from the hash (or from disk)
 | ||||||
|             let loaded = DetermineLayout.LoadLayoutFromHash(loadCustomThemeParam); |             return DetermineLayout.LoadLayoutFromHash(loadCustomThemeParam) | ||||||
|             if (loaded === null) { |  | ||||||
|                 return [null, undefined] |  | ||||||
|             } |  | ||||||
|             return loaded |  | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         let layoutId: string = undefined |         let layoutId: string = undefined | ||||||
|  | @ -43,7 +42,7 @@ export default class DetermineLayout { | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|         const path = window.location.pathname.split("/").slice(-1)[0]; |         const path = window.location.pathname.split("/").slice(-1)[0]; | ||||||
|         if (path !== "index.html" && path !== "") { |         if (path !== "theme.html" && path !== "") { | ||||||
|             layoutId = path; |             layoutId = path; | ||||||
|             if (path.endsWith(".html")) { |             if (path.endsWith(".html")) { | ||||||
|                 layoutId = path.substr(0, path.length - 5); |                 layoutId = path.substr(0, path.length - 5); | ||||||
|  | @ -61,12 +60,28 @@ export default class DetermineLayout { | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         return [layoutToUse, undefined] |         return layoutToUse | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private static prepCustomTheme(json: any): LayoutConfigJson{ | ||||||
|  |         const knownLayersDict = new Map<string, LayerConfigJson>() | ||||||
|  |         for (const key in known_layers["default"]) { | ||||||
|  |             const layer = known_layers["default"][key] | ||||||
|  |             knownLayersDict.set(layer.id, layer) | ||||||
|  |         } | ||||||
|  |         const converState = { | ||||||
|  |             tagRenderings: SharedTagRenderings.SharedTagRenderingJson, | ||||||
|  |             sharedLayers: knownLayersDict | ||||||
|  |         } | ||||||
|  |         json = new FixLegacyTheme().convertStrict(converState, json, "While loading a dynamic theme") | ||||||
|  |         json = new PrepareTheme().convertStrict(converState, json, "While preparing a dynamic theme") | ||||||
|  |         console.log("The layoutconfig is ", json) | ||||||
|  |         return json | ||||||
|     } |     } | ||||||
|      |      | ||||||
|     public static LoadLayoutFromHash( |     public static LoadLayoutFromHash( | ||||||
|         userLayoutParam: UIEventSource<string> |         userLayoutParam: UIEventSource<string> | ||||||
|     ): [LayoutConfig, string] | null { |     ): LayoutConfig | null { | ||||||
|         let hash = location.hash.substr(1); |         let hash = location.hash.substr(1); | ||||||
|         try { |         try { | ||||||
|             // layoutFromBase64 contains the name of the theme. This is partly to do tracking with goat counter
 |             // layoutFromBase64 contains the name of the theme. This is partly to do tracking with goat counter
 | ||||||
|  | @ -104,10 +119,9 @@ export default class DetermineLayout { | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             LegacyJsonConvert.fixThemeConfig(json) |             const layoutToUse = DetermineLayout.prepCustomTheme(json) | ||||||
|             const layoutToUse = new LayoutConfig(json, false); |  | ||||||
|             userLayoutParam.setData(layoutToUse.id); |             userLayoutParam.setData(layoutToUse.id); | ||||||
|             return [layoutToUse, btoa(Utils.MinifyJSON(JSON.stringify(json)))]; |             return new LayoutConfig(layoutToUse, false); | ||||||
|         } catch (e) { |         } catch (e) { | ||||||
|             console.error(e) |             console.error(e) | ||||||
|             if (hash === undefined || hash.length < 10) { |             if (hash === undefined || hash.length < 10) { | ||||||
|  | @ -141,12 +155,20 @@ export default class DetermineLayout { | ||||||
| 
 | 
 | ||||||
|         try { |         try { | ||||||
| 
 | 
 | ||||||
|             const parsed = await Utils.downloadJson(link) |             let parsed = await Utils.downloadJson(link) | ||||||
|             console.log("Got ", parsed) |             console.log("Got ", parsed) | ||||||
|             LegacyJsonConvert.fixThemeConfig(parsed) |             parsed = new FixLegacyTheme().convertStrict({ | ||||||
|             try { |                 tagRenderings: SharedTagRenderings.SharedTagRenderingJson, | ||||||
|  |                 sharedLayers: new Map<string, LayerConfigJson>() // FIXME: actually add the layers
 | ||||||
|  |             }, parsed, "While loading a dynamic theme") | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|                 parsed.id = link; |                 parsed.id = link; | ||||||
|                 return new LayoutConfig(parsed, false).patchImages(link, JSON.stringify(parsed)); | 
 | ||||||
|  | 
 | ||||||
|  |             try { | ||||||
|  |                 const layoutToUse = DetermineLayout.prepCustomTheme(parsed) | ||||||
|  |                 return new LayoutConfig(layoutToUse,false).patchImages(link, JSON.stringify(layoutToUse)); | ||||||
|             } catch (e) { |             } catch (e) { | ||||||
|                 console.error(e) |                 console.error(e) | ||||||
|                 DetermineLayout.ShowErrorOnCustomTheme( |                 DetermineLayout.ShowErrorOnCustomTheme( | ||||||
|  |  | ||||||
|  | @ -10,7 +10,6 @@ import {LocalStorageSource} from "../Web/LocalStorageSource"; | ||||||
| import {Utils} from "../../Utils"; | import {Utils} from "../../Utils"; | ||||||
| import ChangeToElementsActor from "../Actors/ChangeToElementsActor"; | import ChangeToElementsActor from "../Actors/ChangeToElementsActor"; | ||||||
| import PendingChangesUploader from "../Actors/PendingChangesUploader"; | import PendingChangesUploader from "../Actors/PendingChangesUploader"; | ||||||
| import TitleHandler from "../Actors/TitleHandler"; |  | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * The part of the state keeping track of where the elements, loading them, configuring the feature pipeline etc |  * The part of the state keeping track of where the elements, loading them, configuring the feature pipeline etc | ||||||
|  | @ -90,7 +89,6 @@ export default class ElementsState extends FeatureSwitchState { | ||||||
| 
 | 
 | ||||||
|         new ChangeToElementsActor(this.changes, this.allElements) |         new ChangeToElementsActor(this.changes, this.allElements) | ||||||
|         new PendingChangesUploader(this.changes, this.selectedElement); |         new PendingChangesUploader(this.changes, this.selectedElement); | ||||||
|         new TitleHandler(this); |  | ||||||
| 
 | 
 | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | @ -17,6 +17,7 @@ import {FeatureSourceForLayer, Tiled} from "../FeatureSource/FeatureSource"; | ||||||
| import SimpleFeatureSource from "../FeatureSource/Sources/SimpleFeatureSource"; | import SimpleFeatureSource from "../FeatureSource/Sources/SimpleFeatureSource"; | ||||||
| import {LocalStorageSource} from "../Web/LocalStorageSource"; | import {LocalStorageSource} from "../Web/LocalStorageSource"; | ||||||
| import {GeoOperations} from "../GeoOperations"; | import {GeoOperations} from "../GeoOperations"; | ||||||
|  | import TitleHandler from "../Actors/TitleHandler"; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Contains all the leaflet-map related state |  * Contains all the leaflet-map related state | ||||||
|  | @ -130,6 +131,8 @@ export default class MapState extends UserRelatedState { | ||||||
|         this.initGpsLocation() |         this.initGpsLocation() | ||||||
|         this.initUserLocationTrail() |         this.initUserLocationTrail() | ||||||
|         this.initCurrentView() |         this.initCurrentView() | ||||||
|  | 
 | ||||||
|  |         new TitleHandler(this); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public AddAllOverlaysToMap(leafletMap: UIEventSource<any>) { |     public AddAllOverlaysToMap(leafletMap: UIEventSource<any>) { | ||||||
|  |  | ||||||
|  | @ -3,12 +3,12 @@ import {OsmConnection} from "../Osm/OsmConnection"; | ||||||
| import {MangroveIdentity} from "../Web/MangroveReviews"; | import {MangroveIdentity} from "../Web/MangroveReviews"; | ||||||
| import {UIEventSource} from "../UIEventSource"; | import {UIEventSource} from "../UIEventSource"; | ||||||
| import {QueryParameters} from "../Web/QueryParameters"; | import {QueryParameters} from "../Web/QueryParameters"; | ||||||
| import InstalledThemes from "../Actors/InstalledThemes"; |  | ||||||
| import {LocalStorageSource} from "../Web/LocalStorageSource"; | import {LocalStorageSource} from "../Web/LocalStorageSource"; | ||||||
| import {Utils} from "../../Utils"; | import {Utils} from "../../Utils"; | ||||||
| import Locale from "../../UI/i18n/Locale"; | import Locale from "../../UI/i18n/Locale"; | ||||||
| import ElementsState from "./ElementsState"; | import ElementsState from "./ElementsState"; | ||||||
| import SelectedElementTagsUpdater from "../Actors/SelectedElementTagsUpdater"; | import SelectedElementTagsUpdater from "../Actors/SelectedElementTagsUpdater"; | ||||||
|  | import {log} from "util"; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * The part of the state which keeps track of user-related stuff, e.g. the OSM-connection, |  * The part of the state which keeps track of user-related stuff, e.g. the OSM-connection, | ||||||
|  | @ -33,7 +33,10 @@ export default class UserRelatedState extends ElementsState { | ||||||
|     /** |     /** | ||||||
|      * WHich other themes the user previously visited |      * WHich other themes the user previously visited | ||||||
|      */ |      */ | ||||||
|     public installedThemes: UIEventSource<{ layout: LayoutConfig; definition: string }[]>; |     public installedThemes: UIEventSource<{ id: string, // The id doubles as the URL
 | ||||||
|  |         icon: string, | ||||||
|  |         title: any, | ||||||
|  |         shortDescription: any}[]>; | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|     constructor(layoutToUse: LayoutConfig, options?:{attemptLogin : true | boolean}) { |     constructor(layoutToUse: LayoutConfig, options?:{attemptLogin : true | boolean}) { | ||||||
|  | @ -69,9 +72,47 @@ export default class UserRelatedState extends ElementsState { | ||||||
|             }) |             }) | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         this.installedThemes = new InstalledThemes( |         this.installedThemes = this.osmConnection.GetLongPreference("installed-themes").map( | ||||||
|             this.osmConnection |             str => { | ||||||
|         ).installedThemes; |                 if(str === undefined || str === ""){ | ||||||
|  |                     return [] | ||||||
|  |                 } | ||||||
|  |                 try{ | ||||||
|  |                     return JSON.parse(str) | ||||||
|  |                 }catch(e){ | ||||||
|  |                     console.warn("Could not parse preference with installed themes due to ", e,"\nThe offending string is",str) | ||||||
|  |                     return [] | ||||||
|  |                 } | ||||||
|  |             }, [],(installed => JSON.stringify(installed)) | ||||||
|  |         ) | ||||||
|  |          | ||||||
|  |          | ||||||
|  |         const self = this; | ||||||
|  |         this.osmConnection.isLoggedIn.addCallbackAndRunD(loggedIn => { | ||||||
|  |             if(!loggedIn){ | ||||||
|  |                 return | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             if(this.layoutToUse.id.startsWith("http")){ | ||||||
|  |                 if(!this.installedThemes.data.some(installed => installed.id === this.layoutToUse.id)){ | ||||||
|  | 
 | ||||||
|  |                     this.installedThemes.data.push({ | ||||||
|  |                         id: this.layoutToUse.id, | ||||||
|  |                         icon: this.layoutToUse.icon, | ||||||
|  |                         title: this.layoutToUse.title.translations, | ||||||
|  |                         shortDescription: this.layoutToUse.shortDescription.translations | ||||||
|  |                     }) | ||||||
|  |                 } | ||||||
|  |                 this.installedThemes.ping() | ||||||
|  |                 console.log("Registered "+this.layoutToUse.id+" as installed themes") | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |             return true; | ||||||
|  |         }) | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
|         // Important: the favourite layers are initialized _after_ the installed themes, as these might contain an installedTheme
 |         // Important: the favourite layers are initialized _after_ the installed themes, as these might contain an installedTheme
 | ||||||
|         this.favouriteLayers = LocalStorageSource.Get("favouriteLayers") |         this.favouriteLayers = LocalStorageSource.Get("favouriteLayers") | ||||||
|  | @ -82,7 +123,6 @@ export default class UserRelatedState extends ElementsState { | ||||||
|                 (layers) => Utils.Dedup(layers)?.join(";") |                 (layers) => Utils.Dedup(layers)?.join(";") | ||||||
|             ); |             ); | ||||||
|          |          | ||||||
| 
 |  | ||||||
|         this.InitializeLanguage(); |         this.InitializeLanguage(); | ||||||
|         new SelectedElementTagsUpdater(this) |         new SelectedElementTagsUpdater(this) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -2,7 +2,7 @@ import {Utils} from "../Utils"; | ||||||
| 
 | 
 | ||||||
| export default class Constants { | export default class Constants { | ||||||
| 
 | 
 | ||||||
|     public static vNumber = "0.13.0"; |     public static vNumber = "0.14.0-alpha-1"; | ||||||
|     public static ImgurApiKey = '7070e7167f0a25a' |     public static ImgurApiKey = '7070e7167f0a25a' | ||||||
|     public static readonly mapillary_client_token_v4 = "MLY|4441509239301885|b40ad2d3ea105435bd40c7e76993ae85" |     public static readonly mapillary_client_token_v4 = "MLY|4441509239301885|b40ad2d3ea105435bd40c7e76993ae85" | ||||||
| 
 | 
 | ||||||
|  | @ -17,6 +17,14 @@ export default class Constants { | ||||||
|     ] |     ] | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  |     public static readonly added_by_default: string[] = ["gps_location", "gps_location_history", "home_location", "gps_track"] | ||||||
|  |     public static readonly no_include: string[] = ["conflation", "left_right_style", "split_point","current_view","matchpoint"] | ||||||
|  |     /** | ||||||
|  |      * Layer IDs of layers which have special properties through built-in hooks | ||||||
|  |      */ | ||||||
|  |     public static readonly priviliged_layers: string[] = [...Constants.added_by_default, "type_node", ...Constants.no_include] | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|     // The user journey states thresholds when a new feature gets unlocked
 |     // The user journey states thresholds when a new feature gets unlocked
 | ||||||
|     public static userJourney = { |     public static userJourney = { | ||||||
|         moreScreenUnlock: 1, |         moreScreenUnlock: 1, | ||||||
|  | @ -51,6 +59,7 @@ export default class Constants { | ||||||
|      * For every bin, the totals are uploaded as metadata |      * For every bin, the totals are uploaded as metadata | ||||||
|      */ |      */ | ||||||
|     static distanceToChangeObjectBins = [25,50,100,500,1000,5000, Number.MAX_VALUE] |     static distanceToChangeObjectBins = [25,50,100,500,1000,5000, Number.MAX_VALUE] | ||||||
|  |     static themeOrder = ["personal", "cyclofix", "hailhydrant", "bookcases", "toilets", "aed"]; | ||||||
| 
 | 
 | ||||||
|     private static isRetina(): boolean { |     private static isRetina(): boolean { | ||||||
|         if (Utils.runningFromConsole) { |         if (Utils.runningFromConsole) { | ||||||
|  |  | ||||||
|  | @ -18,7 +18,7 @@ export default interface LineRenderingConfigJson { | ||||||
|     /** |     /** | ||||||
|      * The stroke-width for way-elements |      * The stroke-width for way-elements | ||||||
|      */ |      */ | ||||||
|     width?: string | TagRenderingConfigJson; |     width?: string | number | TagRenderingConfigJson; | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * A dasharray, e.g. "5 6" |      * A dasharray, e.g. "5 6" | ||||||
|  |  | ||||||
|  | @ -22,7 +22,7 @@ import Title from "../../UI/Base/Title"; | ||||||
| import List from "../../UI/Base/List"; | import List from "../../UI/Base/List"; | ||||||
| import Link from "../../UI/Base/Link"; | import Link from "../../UI/Base/Link"; | ||||||
| import {Utils} from "../../Utils"; | import {Utils} from "../../Utils"; | ||||||
| import * as icons from "../../assets/tagRenderings/icons.json" | import {tag} from "@turf/turf"; | ||||||
| 
 | 
 | ||||||
| export default class LayerConfig extends WithContextLoader { | export default class LayerConfig extends WithContextLoader { | ||||||
| 
 | 
 | ||||||
|  | @ -236,26 +236,14 @@ export default class LayerConfig extends WithContextLoader { | ||||||
|             throw "Missing ids in tagrenderings" |             throw "Missing ids in tagrenderings" | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         this.tagRenderings = this.ExtractLayerTagRenderings(json, official) |         this.tagRenderings = (json.tagRenderings ?? []).map((tr, i) => new TagRenderingConfig(<TagRenderingConfigJson>tr, this.id + ".tagRenderings[" + i + "]")) | ||||||
|         if (official) { |  | ||||||
| 
 |  | ||||||
|             const emptyIds = this.tagRenderings.filter(tr => tr.id === ""); |  | ||||||
|             if (emptyIds.length > 0) { |  | ||||||
|                 throw `Some tagrendering-ids are empty or have an emtpy string; this is not allowed (at ${context})` |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             const duplicateIds = Utils.Dupicates(this.tagRenderings.map(f => f.id).filter(id => id !== "questions")) |  | ||||||
|             if (duplicateIds.length > 0) { |  | ||||||
|                 throw `Some tagRenderings have a duplicate id: ${duplicateIds} (at ${context}.tagRenderings)` |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
| 
 | 
 | ||||||
|         this.filters = (json.filter ?? []).map((option, i) => { |         this.filters = (json.filter ?? []).map((option, i) => { | ||||||
|             return new FilterConfig(option, `${context}.filter-[${i}]`) |             return new FilterConfig(option, `${context}.filter-[${i}]`) | ||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
|         { |         { | ||||||
|             const duplicateIds = Utils.Dupicates(this.filters.map(f => f.id)) |             const duplicateIds = Utils.Dupiclates(this.filters.map(f => f.id)) | ||||||
|             if (duplicateIds.length > 0) { |             if (duplicateIds.length > 0) { | ||||||
|                 throw `Some filters have a duplicate id: ${duplicateIds} (at ${context}.filters)` |                 throw `Some filters have a duplicate id: ${duplicateIds} (at ${context}.filters)` | ||||||
|             } |             } | ||||||
|  | @ -265,17 +253,8 @@ export default class LayerConfig extends WithContextLoader { | ||||||
|             throw "Error in " + context + ": use 'filter' instead of 'filters'" |             throw "Error in " + context + ": use 'filter' instead of 'filters'" | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         const titleIcons = []; |  | ||||||
|         const defaultIcons = icons.defaultIcons; |  | ||||||
|         for (const icon of json.titleIcons ?? defaultIcons) { |  | ||||||
|             if (icon === "defaults") { |  | ||||||
|                 titleIcons.push(...defaultIcons); |  | ||||||
|             } else { |  | ||||||
|                 titleIcons.push(icon); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
| 
 | 
 | ||||||
|         this.titleIcons = this.ParseTagRenderings(titleIcons, { |         this.titleIcons = this.ParseTagRenderings((<TagRenderingConfigJson[]> json.titleIcons), { | ||||||
|             readOnlyMode: true |             readOnlyMode: true | ||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
|  | @ -320,109 +299,6 @@ export default class LayerConfig extends WithContextLoader { | ||||||
|         const defaultTags = new UIEventSource(TagUtils.changeAsProperties(this.source.osmTags.asChange({id: "node/-1"}))) |         const defaultTags = new UIEventSource(TagUtils.changeAsProperties(this.source.osmTags.asChange({id: "node/-1"}))) | ||||||
|         return mapRendering.GenerateLeafletStyle(defaultTags, false, {noSize: true}).html |         return mapRendering.GenerateLeafletStyle(defaultTags, false, {noSize: true}).html | ||||||
|     } |     } | ||||||
| 
 |  | ||||||
|     public ExtractLayerTagRenderings(json: LayerConfigJson, official: boolean): TagRenderingConfig[] { |  | ||||||
| 
 |  | ||||||
|         if (json.tagRenderings === undefined) { |  | ||||||
|             return [] |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         const normalTagRenderings: (string | { builtin: string, override: any } | TagRenderingConfigJson)[] = [] |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|         const renderingsToRewrite: ({ |  | ||||||
|             rewrite: { |  | ||||||
|                 sourceString: string, |  | ||||||
|                 into: string[] |  | ||||||
|             }, renderings: (string | { builtin: string, override: any } | TagRenderingConfigJson)[] |  | ||||||
|         })[] = [] |  | ||||||
|         for (let i = 0; i < json.tagRenderings.length; i++) { |  | ||||||
|             const tr = json.tagRenderings[i]; |  | ||||||
|             const rewriteDefined = tr["rewrite"] !== undefined |  | ||||||
|             const renderingsDefined = tr["renderings"] |  | ||||||
| 
 |  | ||||||
|             if (!rewriteDefined && !renderingsDefined) { |  | ||||||
|                 // @ts-ignore
 |  | ||||||
|                 normalTagRenderings.push(tr) |  | ||||||
|                 continue |  | ||||||
|             } |  | ||||||
|             if (rewriteDefined && renderingsDefined) { |  | ||||||
|                 // @ts-ignore
 |  | ||||||
|                 renderingsToRewrite.push(tr) |  | ||||||
|                 continue |  | ||||||
|             } |  | ||||||
|             throw `Error in ${this._context}.tagrenderings[${i}]: got a value which defines either \`rewrite\` or \`renderings\`, but not both. Either define both or move the \`renderings\`  out of this scope` |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         const allRenderings = this.ParseTagRenderings(normalTagRenderings, |  | ||||||
|             { |  | ||||||
|                 requiresId: official |  | ||||||
|             }); |  | ||||||
| 
 |  | ||||||
|         if (renderingsToRewrite.length === 0) { |  | ||||||
|             return allRenderings |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         /* Used for left|right group creation and replacement */ |  | ||||||
|         function prepConfig(keyToRewrite: string, target: string, tr: TagRenderingConfigJson) { |  | ||||||
| 
 |  | ||||||
|             function replaceRecursive(transl: string | any) { |  | ||||||
|                 if (typeof transl === "string") { |  | ||||||
|                     return transl.replace(keyToRewrite, target) |  | ||||||
|                 } |  | ||||||
|                 if (transl.map !== undefined) { |  | ||||||
|                     return transl.map(o => replaceRecursive(o)) |  | ||||||
|                 } |  | ||||||
|                 transl = {...transl} |  | ||||||
|                 for (const key in transl) { |  | ||||||
|                     transl[key] = replaceRecursive(transl[key]) |  | ||||||
|                 } |  | ||||||
|                 return transl |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             const orig = tr; |  | ||||||
|             tr = replaceRecursive(tr) |  | ||||||
| 
 |  | ||||||
|             tr.id = target + "-" + orig.id |  | ||||||
|             tr.group = target |  | ||||||
|             return tr |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         const rewriteGroups: Map<string, TagRenderingConfig[]> = new Map<string, TagRenderingConfig[]>() |  | ||||||
|         for (const rewriteGroup of renderingsToRewrite) { |  | ||||||
| 
 |  | ||||||
|             const tagRenderings = rewriteGroup.renderings |  | ||||||
|             const textToReplace = rewriteGroup.rewrite.sourceString |  | ||||||
|             const targets = rewriteGroup.rewrite.into |  | ||||||
|             for (const target of targets) { |  | ||||||
|                 const parsedRenderings = this.ParseTagRenderings(tagRenderings,  { |  | ||||||
|                     prepConfig: tr => prepConfig(textToReplace, target, tr) |  | ||||||
|                 }) |  | ||||||
| 
 |  | ||||||
|                 if (!rewriteGroups.has(target)) { |  | ||||||
|                     rewriteGroups.set(target, []) |  | ||||||
|                 } |  | ||||||
|                 rewriteGroups.get(target).push(...parsedRenderings) |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|         rewriteGroups.forEach((group, groupName) => { |  | ||||||
|             group.push(new TagRenderingConfig({ |  | ||||||
|                 id: "questions", |  | ||||||
|                 group: groupName |  | ||||||
|             })) |  | ||||||
|         }) |  | ||||||
| 
 |  | ||||||
|         rewriteGroups.forEach(group => { |  | ||||||
|             allRenderings.push(...group) |  | ||||||
|         }) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|         return allRenderings; |  | ||||||
| 
 |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public GenerateDocumentation(usedInThemes: string[], layerIsNeededBy: Map<string, string[]>, dependencies: { |     public GenerateDocumentation(usedInThemes: string[], layerIsNeededBy: Map<string, string[]>, dependencies: { | ||||||
|         context?: string; |         context?: string; | ||||||
|         reason: string; |         reason: string; | ||||||
|  | @ -512,5 +388,4 @@ export default class LayerConfig extends WithContextLoader { | ||||||
|     public isLeftRightSensitive(): boolean { |     public isLeftRightSensitive(): boolean { | ||||||
|         return this.lineRendering.some(lr => lr.leftRightSensitive) |         return this.lineRendering.some(lr => lr.leftRightSensitive) | ||||||
|     } |     } | ||||||
| 
 |  | ||||||
| } | } | ||||||
|  | @ -1,12 +1,9 @@ | ||||||
| import {Translation} from "../../UI/i18n/Translation"; | import {Translation} from "../../UI/i18n/Translation"; | ||||||
| import {LayoutConfigJson} from "./Json/LayoutConfigJson"; | import {LayoutConfigJson} from "./Json/LayoutConfigJson"; | ||||||
| import AllKnownLayers from "../../Customizations/AllKnownLayers"; |  | ||||||
| import {Utils} from "../../Utils"; |  | ||||||
| import LayerConfig from "./LayerConfig"; | import LayerConfig from "./LayerConfig"; | ||||||
| import {LayerConfigJson} from "./Json/LayerConfigJson"; | import {LayerConfigJson} from "./Json/LayerConfigJson"; | ||||||
| import Constants from "../Constants"; | import Constants from "../Constants"; | ||||||
| import TilesourceConfig from "./TilesourceConfig"; | import TilesourceConfig from "./TilesourceConfig"; | ||||||
| import DependencyCalculator from "./DependencyCalculator"; |  | ||||||
| 
 | 
 | ||||||
| export default class LayoutConfig { | export default class LayoutConfig { | ||||||
|     public readonly id: string; |     public readonly id: string; | ||||||
|  | @ -15,7 +12,7 @@ export default class LayoutConfig { | ||||||
|     public readonly version: string; |     public readonly version: string; | ||||||
|     public readonly language: string[]; |     public readonly language: string[]; | ||||||
|     public readonly title: Translation; |     public readonly title: Translation; | ||||||
|     public readonly shortDescription?: Translation; |     public readonly shortDescription: Translation; | ||||||
|     public readonly description: Translation; |     public readonly description: Translation; | ||||||
|     public readonly descriptionTail?: Translation; |     public readonly descriptionTail?: Translation; | ||||||
|     public readonly icon: string; |     public readonly icon: string; | ||||||
|  | @ -53,7 +50,6 @@ export default class LayoutConfig { | ||||||
|     public readonly overpassMaxZoom: number |     public readonly overpassMaxZoom: number | ||||||
|     public readonly osmApiTileSize: number |     public readonly osmApiTileSize: number | ||||||
|     public readonly official: boolean; |     public readonly official: boolean; | ||||||
|     public readonly trackAllNodes: boolean; |  | ||||||
| 
 | 
 | ||||||
|     constructor(json: LayoutConfigJson, official = true, context?: string) { |     constructor(json: LayoutConfigJson, official = true, context?: string) { | ||||||
|         this.official = official; |         this.official = official; | ||||||
|  | @ -75,14 +71,28 @@ export default class LayoutConfig { | ||||||
|         } else { |         } else { | ||||||
|             this.language = json.language; |             this.language = json.language; | ||||||
|         } |         } | ||||||
|         if (this.language.length == 0) { |         { | ||||||
|             throw `No languages defined. Define at least one language. (${context}.languages)` |             if (this.language.length == 0) { | ||||||
|         } |                 throw `No languages defined. Define at least one language. (${context}.languages)` | ||||||
|         if (json.title === undefined) { |             } | ||||||
|             throw "Title not defined in " + this.id; |             if (json.title === undefined) { | ||||||
|         } |                 throw "Title not defined in " + this.id; | ||||||
|         if (json.description === undefined) { |             } | ||||||
|             throw "Description not defined in " + this.id; |             if (json.description === undefined) { | ||||||
|  |                 throw "Description not defined in " + this.id; | ||||||
|  |             } | ||||||
|  |             if (json.widenFactor <= 0) { | ||||||
|  |                 throw "Widenfactor too small, shoud be > 0" | ||||||
|  |             } | ||||||
|  |             if (json.widenFactor > 20) { | ||||||
|  |                 throw "Widenfactor is very big, use a value between 1 and 5 (current value is " + json.widenFactor + ") at " + context | ||||||
|  |             } | ||||||
|  |             if (json["hideInOverview"]) { | ||||||
|  |                 throw "The json for " + this.id + " contains a 'hideInOverview'. Did you mean hideFromOverview instead?" | ||||||
|  |             } | ||||||
|  |             if (json.layers === undefined) { | ||||||
|  |                 throw "Got undefined layers for " + json.id + " at " + context | ||||||
|  |             } | ||||||
|         } |         } | ||||||
|         this.title = new Translation(json.title, context + ".title"); |         this.title = new Translation(json.title, context + ".title"); | ||||||
|         this.description = new Translation(json.description, context + ".description"); |         this.description = new Translation(json.description, context + ".description"); | ||||||
|  | @ -93,19 +103,12 @@ export default class LayoutConfig { | ||||||
|         this.startZoom = json.startZoom; |         this.startZoom = json.startZoom; | ||||||
|         this.startLat = json.startLat; |         this.startLat = json.startLat; | ||||||
|         this.startLon = json.startLon; |         this.startLon = json.startLon; | ||||||
|         if (json.widenFactor <= 0) { |  | ||||||
|             throw "Widenfactor too small, shoud be > 0" |  | ||||||
|         } |  | ||||||
|         if (json.widenFactor > 20) { |  | ||||||
|             throw "Widenfactor is very big, use a value between 1 and 5 (current value is " + json.widenFactor + ") at " + context |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         this.widenFactor = json.widenFactor ?? 1.5; |         this.widenFactor = json.widenFactor ?? 1.5; | ||||||
| 
 | 
 | ||||||
|         this.defaultBackgroundId = json.defaultBackgroundId; |         this.defaultBackgroundId = json.defaultBackgroundId; | ||||||
|         this.tileLayerSources = (json.tileLayerSources ?? []).map((config, i) => new TilesourceConfig(config, `${this.id}.tileLayerSources[${i}]`)) |         this.tileLayerSources = (json.tileLayerSources ?? []).map((config, i) => new TilesourceConfig(config, `${this.id}.tileLayerSources[${i}]`)) | ||||||
|         const layerInfo = LayoutConfig.ExtractLayers(json, official, context); |         // At this point, layers should be expanded and validated either by the generateScript or the LegacyJsonConvert
 | ||||||
|         this.layers = layerInfo.layers |         this.layers = json.layers.map(lyrJson => new LayerConfig(<LayerConfigJson>lyrJson, json.id + ".layers." + lyrJson["id"], official)); | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|         this.clustering = { |         this.clustering = { | ||||||
|  | @ -125,10 +128,6 @@ export default class LayoutConfig { | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         this.hideFromOverview = json.hideFromOverview ?? false; |         this.hideFromOverview = json.hideFromOverview ?? false; | ||||||
|         // @ts-ignore
 |  | ||||||
|         if (json.hideInOverview) { |  | ||||||
|             throw "The json for " + this.id + " contains a 'hideInOverview'. Did you mean hideFromOverview instead?" |  | ||||||
|         } |  | ||||||
|         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; | ||||||
|         this.enableShareScreen = json.enableShareScreen ?? true; |         this.enableShareScreen = json.enableShareScreen ?? true; | ||||||
|  | @ -157,120 +156,6 @@ export default class LayoutConfig { | ||||||
| 
 | 
 | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private static ExtractLayers(json: LayoutConfigJson, official: boolean, context: string): { layers: LayerConfig[], extractAllNodes: boolean } { |  | ||||||
|         const result: LayerConfig[] = [] |  | ||||||
|         let exportAllNodes = false |  | ||||||
|         if(json.layers === undefined){ |  | ||||||
|             throw "Got undefined layers for "+json.id+" at "+context |  | ||||||
|         } |  | ||||||
|         json.layers.forEach((layer, i) => { |  | ||||||
| 
 |  | ||||||
|             if (typeof layer === "string") { |  | ||||||
|                 if (AllKnownLayers.sharedLayersJson.get(layer) !== undefined) { |  | ||||||
|                     if (json.overrideAll !== undefined) { |  | ||||||
|                         let lyr = JSON.parse(JSON.stringify(AllKnownLayers.sharedLayersJson.get(layer))); |  | ||||||
|                         const newLayer = new LayerConfig(Utils.Merge(json.overrideAll, lyr), `${json.id}+overrideAll.layers[${i}]`, official) |  | ||||||
|                         result.push(newLayer) |  | ||||||
|                         return |  | ||||||
|                     } else { |  | ||||||
|                         const shared = AllKnownLayers.sharedLayers.get(layer) |  | ||||||
|                         if (shared === undefined) { |  | ||||||
|                             throw `Shared layer ${layer} not found (at ${context}.layers[${i}])` |  | ||||||
|                         } |  | ||||||
|                         result.push(shared) |  | ||||||
|                         return |  | ||||||
|                     } |  | ||||||
|                 } else { |  | ||||||
|                     console.log("Layer ", layer, " not kown, try one of", Array.from(AllKnownLayers.sharedLayers.keys()).join(", ")) |  | ||||||
|                     throw `Unknown builtin layer ${layer} at ${context}.layers[${i}]`; |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             if (layer["builtin"] === undefined) { |  | ||||||
|                 if (json.overrideAll !== undefined) { |  | ||||||
|                     layer = Utils.Merge(json.overrideAll, layer); |  | ||||||
|                 } |  | ||||||
|                 // @ts-ignore
 |  | ||||||
|                 result.push(new LayerConfig(layer, `${json.id}.layers[${i}]`, official)) |  | ||||||
|                 return |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             // @ts-ignore
 |  | ||||||
|             let names = layer.builtin; |  | ||||||
|             if (typeof names === "string") { |  | ||||||
|                 names = [names] |  | ||||||
|             } |  | ||||||
|              |  | ||||||
|             // This is a very special layer which triggers special behaviour
 |  | ||||||
|             exportAllNodes = names.some(name => name === "type_node"); |  | ||||||
|              |  | ||||||
|             names.forEach(name => { |  | ||||||
|                 const shared = AllKnownLayers.sharedLayersJson.get(name); |  | ||||||
|                 if (shared === undefined) { |  | ||||||
|                     throw `Unknown shared/builtin layer ${name} at ${context}.layers[${i}]. Available layers are ${Array.from(AllKnownLayers.sharedLayersJson.keys()).join(", ")}`; |  | ||||||
|                 } |  | ||||||
|                 let newLayer: LayerConfigJson = Utils.Merge(layer["override"], JSON.parse(JSON.stringify(shared))); // We make a deep copy of the shared layer, in order to protect it from changes
 |  | ||||||
|                 if (json.overrideAll !== undefined) { |  | ||||||
|                     newLayer = Utils.Merge(json.overrideAll, newLayer); |  | ||||||
|                 } |  | ||||||
|                 result.push(new LayerConfig(newLayer, `${json.id}.layers[${i}]`, official)) |  | ||||||
|                 return |  | ||||||
|             }) |  | ||||||
| 
 |  | ||||||
|         }); |  | ||||||
| 
 |  | ||||||
|         // Some special layers which are always included by default
 |  | ||||||
|         for (const defaultLayer of AllKnownLayers.added_by_default) { |  | ||||||
|             if (result.some(l => l?.id === defaultLayer)) { |  | ||||||
|                 continue; // Already added
 |  | ||||||
|             } |  | ||||||
|             const sharedLayer = AllKnownLayers.sharedLayers.get(defaultLayer) |  | ||||||
|             if (sharedLayer !== undefined) { |  | ||||||
|                 result.push(sharedLayer) |  | ||||||
|             }else if(!AllKnownLayers.runningGenerateScript){ |  | ||||||
|                 throw "SharedLayer "+defaultLayer+" not found" |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         if(AllKnownLayers.runningGenerateScript){ |  | ||||||
|             return {layers: result, extractAllNodes: exportAllNodes} |  | ||||||
|         } |  | ||||||
|         // Verify cross-dependencies
 |  | ||||||
|         let unmetDependencies: { neededLayer: string, neededBy: string, reason: string, context?: string }[] = [] |  | ||||||
|         do { |  | ||||||
|             const dependencies: { neededLayer: string, reason: string, context?: string, neededBy: string }[] = [] |  | ||||||
| 
 |  | ||||||
|             for (const layerConfig of result) { |  | ||||||
|                 const layerDeps = DependencyCalculator.getLayerDependencies(layerConfig) |  | ||||||
|                 dependencies.push(...layerDeps) |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             const loadedLayers = new Set(result.map(r => r.id)) |  | ||||||
|             // During the generate script, builtin layers are verified but not loaded - so we have to add them manually here
 |  | ||||||
|             // Their existance is checked elsewhere, so this is fine
 |  | ||||||
|             unmetDependencies = dependencies.filter(dep => !loadedLayers.has(dep.neededLayer)) |  | ||||||
|             for (const unmetDependency of unmetDependencies) { |  | ||||||
|                 const dep = AllKnownLayers.sharedLayers.get(unmetDependency.neededLayer) |  | ||||||
|                 if (dep === undefined) { |  | ||||||
|                    |  | ||||||
|                     const message =  |  | ||||||
|                         ["Loading a dependency failed: layer "+unmetDependency.neededLayer+" is not found, neither as layer of "+json.id+" nor as builtin layer.", |  | ||||||
|                             "This layer is needed by "+unmetDependency.neededBy, |  | ||||||
|                             unmetDependency.reason+" (at "+unmetDependency.context+")", |  | ||||||
|                             "Loaded layers are: "+result.map(l => l.id).join(",") |  | ||||||
|                          |  | ||||||
|                     ] |  | ||||||
|                     throw message.join("\n\t"); |  | ||||||
|                 } |  | ||||||
|                 result.unshift(dep) |  | ||||||
|                 unmetDependencies = unmetDependencies.filter(d => d.neededLayer  !== unmetDependency.neededLayer) |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|         } while (unmetDependencies.length > 0) |  | ||||||
|          |  | ||||||
|         return {layers: result, extractAllNodes: exportAllNodes} |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public CustomCodeSnippets(): string[] { |     public CustomCodeSnippets(): string[] { | ||||||
|         if (this.official) { |         if (this.official) { | ||||||
|             return []; |             return []; | ||||||
|  |  | ||||||
|  | @ -1,14 +1,404 @@ | ||||||
| import LineRenderingConfigJson from "./Json/LineRenderingConfigJson"; | import LineRenderingConfigJson from "./Json/LineRenderingConfigJson"; | ||||||
| import PointRenderingConfig from "./PointRenderingConfig"; | import LayerConfig from "./LayerConfig"; | ||||||
|  | import Constants from "../Constants"; | ||||||
|  | import {LayoutConfigJson} from "./Json/LayoutConfigJson"; | ||||||
|  | import {LayerConfigJson} from "./Json/LayerConfigJson"; | ||||||
|  | import DependencyCalculator from "./DependencyCalculator"; | ||||||
|  | import {TagRenderingConfigJson} from "./Json/TagRenderingConfigJson"; | ||||||
|  | import {Utils} from "../../Utils"; | ||||||
|  | import LayoutConfig from "./LayoutConfig"; | ||||||
|  | import {Translation} from "../../UI/i18n/Translation"; | ||||||
| 
 | 
 | ||||||
| export default class LegacyJsonConvert { | export interface DesugaringContext { | ||||||
|  |     tagRenderings: Map<string, TagRenderingConfigJson> | ||||||
|  |     sharedLayers: Map<string, LayerConfigJson> | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | abstract class Conversion<TIn, TOut> { | ||||||
|  |     protected readonly doc: string; | ||||||
|  |     public readonly modifiedAttributes: string[]; | ||||||
|  | 
 | ||||||
|  |     constructor(doc: string, modifiedAttributes: string[] = []) { | ||||||
|  |         this.modifiedAttributes = modifiedAttributes; | ||||||
|  |         this.doc = doc + "\n\nModified attributes are\n" + modifiedAttributes.join(", "); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public convertStrict(state: DesugaringContext, json: TIn, context: string): TOut { | ||||||
|  |         const fixed = this.convert(state, json, context) | ||||||
|  |         return DesugaringStep.strict(fixed) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public static strict<T>(fixed: { errors: string[], warnings: string[], result?: T }): T { | ||||||
|  |         if (fixed.errors?.length > 0) { | ||||||
|  |             throw fixed.errors.join("\n"); | ||||||
|  |         } | ||||||
|  |         fixed.warnings?.forEach(w => console.warn(w)) | ||||||
|  |         return fixed.result; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     abstract convert(state: DesugaringContext, json: TIn, context: string): { result: TOut, errors: string[], warnings: string[] } | ||||||
|  | 
 | ||||||
|  |     public convertAll(state: DesugaringContext, jsons: TIn[], context: string): { result: TOut[], errors: string[], warnings: string[] } { | ||||||
|  |         const result = [] | ||||||
|  |         const errors = [] | ||||||
|  |         const warnings = [] | ||||||
|  |         for (let i = 0; i < jsons.length; i++) { | ||||||
|  |             const json = jsons[i]; | ||||||
|  |             const r = this.convert(state, json, context + "[" + i + "]") | ||||||
|  |             result.push(r.result) | ||||||
|  |             errors.push(...r.errors) | ||||||
|  |             warnings.push(...r.warnings) | ||||||
|  |         } | ||||||
|  |         return { | ||||||
|  |             result, | ||||||
|  |             errors, | ||||||
|  |             warnings | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | abstract class DesugaringStep<T> extends Conversion<T, T> { | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | class OnEvery<X, T> extends DesugaringStep<T> { | ||||||
|  |     private readonly key: string; | ||||||
|  |     private readonly step: DesugaringStep<X>; | ||||||
|  | 
 | ||||||
|  |     constructor(key: string, step: DesugaringStep<X>) { | ||||||
|  |         super("Applies " + step.constructor.name + " onto every object of the list `key`", [key]); | ||||||
|  |         this.step = step; | ||||||
|  |         this.key = key; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     convert(state: DesugaringContext, json: T, context: string): { result: T; errors: string[]; warnings: string[] } { | ||||||
|  |         json = {...json} | ||||||
|  |         const step = this.step | ||||||
|  |         const key = this.key; | ||||||
|  |         const r = step.convertAll(state, (<X[]>json[key]), context + "." + key) | ||||||
|  |         json[key] = r.result | ||||||
|  |         return { | ||||||
|  |             result: json, | ||||||
|  |             errors: r.errors, | ||||||
|  |             warnings: r.warnings | ||||||
|  |         }; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | class OnEveryConcat<X, T> extends DesugaringStep<T> { | ||||||
|  |     private readonly key: string; | ||||||
|  |     private readonly step: Conversion<X, X[]>; | ||||||
|  | 
 | ||||||
|  |     constructor(key: string, step: Conversion<X, X[]>) { | ||||||
|  |         super(`Applies ${step.constructor.name} onto every object of the list \`${key}\``, [key]); | ||||||
|  |         this.step = step; | ||||||
|  |         this.key = key; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     convert(state: DesugaringContext, json: T, context: string): { result: T; errors: string[]; warnings: string[] } { | ||||||
|  |         json = {...json} | ||||||
|  |         const step = this.step | ||||||
|  |         const key = this.key; | ||||||
|  |         const values = json[key] | ||||||
|  |         if (values === undefined) { | ||||||
|  |             // Move on - nothing to see here!
 | ||||||
|  |             return { | ||||||
|  |                 result: json, | ||||||
|  |                 errors: [], | ||||||
|  |                 warnings: [] | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         const r = step.convertAll(state, (<X[]>values), context + "." + key) | ||||||
|  |         const vals: X[][] = r.result | ||||||
|  |         json[key] = [].concat(...vals) | ||||||
|  |         return { | ||||||
|  |             result: json, | ||||||
|  |             errors: r.errors, | ||||||
|  |             warnings: r.warnings | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | class Fuse<T> extends DesugaringStep<T> { | ||||||
|  |     private readonly steps: DesugaringStep<T>[]; | ||||||
|  | 
 | ||||||
|  |     constructor(doc: string, ...steps: DesugaringStep<T>[]) { | ||||||
|  |         super((doc ?? "") + "This fused pipeline of the following steps: " + steps.map(s => s.constructor.name).join(", "), | ||||||
|  |             Utils.Dedup([].concat(...steps.map(step => step.modifiedAttributes))) | ||||||
|  |         ); | ||||||
|  |         this.steps = steps; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     convert(state: DesugaringContext, json: T, context: string): { result: T; errors: string[]; warnings: string[] } { | ||||||
|  |         const errors = [] | ||||||
|  |         const warnings = [] | ||||||
|  |         for (let i = 0; i < this.steps.length; i++){ | ||||||
|  |             const step = this.steps[i]; | ||||||
|  |             let r = step.convert(state, json, context + "(fusion "+this.constructor.name+"."+i+")") | ||||||
|  |             errors.push(...r.errors) | ||||||
|  |             warnings.push(...r.warnings) | ||||||
|  |             json = r.result | ||||||
|  |             if (errors.length > 0) { | ||||||
|  |                 break; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         return { | ||||||
|  |             result: json, | ||||||
|  |             errors, | ||||||
|  |             warnings | ||||||
|  |         }; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | class ExpandTagRendering extends Conversion<string | TagRenderingConfigJson | { builtin: string | string[], override: any }, TagRenderingConfigJson[]> { | ||||||
|  |     constructor() { | ||||||
|  |         super("Converts a tagRenderingSpec into the full tagRendering", []); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private lookup(state: DesugaringContext, name: string): TagRenderingConfigJson[] { | ||||||
|  |         if (state.tagRenderings.has(name)) { | ||||||
|  |             return [state.tagRenderings.get(name)] | ||||||
|  |         } | ||||||
|  |         if (name.indexOf(".") >= 0) { | ||||||
|  |             const spl = name.split("."); | ||||||
|  |             const layer = state.sharedLayers.get(spl[0]) | ||||||
|  |             if (spl.length === 2 && layer !== undefined) { | ||||||
|  |                 const id = spl[1]; | ||||||
|  | 
 | ||||||
|  |                 const layerTrs = <TagRenderingConfigJson[]>layer.tagRenderings.filter(tr => tr["id"] !== undefined) | ||||||
|  |                 let matchingTrs: TagRenderingConfigJson[] | ||||||
|  |                 if (id === "*") { | ||||||
|  |                     matchingTrs = layerTrs | ||||||
|  |                 } else if (id.startsWith("*")) { | ||||||
|  |                     const id_ = id.substring(1) | ||||||
|  |                     matchingTrs = layerTrs.filter(tr => tr.group === id_) | ||||||
|  |                 } else { | ||||||
|  |                     matchingTrs = layerTrs.filter(tr => tr.id === id) | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |                 for (let i = 0; i < matchingTrs.length; i++) { | ||||||
|  |                     // The matched tagRenderings are 'stolen' from another layer. This means that they must match the layer condition before being shown
 | ||||||
|  |                     const found = Utils.Clone(matchingTrs[i]); | ||||||
|  |                     if (found.condition === undefined) { | ||||||
|  |                         found.condition = layer.source.osmTags | ||||||
|  |                     } else { | ||||||
|  |                         found.condition = {and: [found.condition, layer.source.osmTags]} | ||||||
|  |                     } | ||||||
|  |                     matchingTrs[i] = found | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 if (matchingTrs.length !== 0) { | ||||||
|  |                     return matchingTrs | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         return undefined; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private convertOnce(state: DesugaringContext, tr: string | any, warnings: string[], errors: string[], ctx: string): TagRenderingConfigJson[] { | ||||||
|  |         if (tr === "questions") { | ||||||
|  |             return [{ | ||||||
|  |                 id: "questions" | ||||||
|  |             }] | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |         if (typeof tr === "string") { | ||||||
|  |             const lookup = this.lookup(state, tr); | ||||||
|  |             if (lookup !== undefined) { | ||||||
|  |                 return lookup | ||||||
|  |             } | ||||||
|  |             warnings.push(ctx + "A literal rendering was detected: " + tr) | ||||||
|  |             return [{ | ||||||
|  |                 render: tr, | ||||||
|  |                 id: tr.replace(/![a-zA-Z0-9]/g, "") | ||||||
|  |             }] | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if (tr["builtin"] !== undefined) { | ||||||
|  |             let names = tr["builtin"] | ||||||
|  |             if (typeof names === "string") { | ||||||
|  |                 names = [names] | ||||||
|  |             } | ||||||
|  |             const trs: TagRenderingConfigJson[] = [] | ||||||
|  |             for (const name of names) { | ||||||
|  |                 const lookup = this.lookup(state, name) | ||||||
|  |                 if (lookup === undefined) { | ||||||
|  |                     errors.push(ctx + ": The tagRendering with identifier " + name + " was not found.\n\tDid you mean one of " + Array.from(state.tagRenderings.keys()).join(", ") + "?") | ||||||
|  |                     continue | ||||||
|  |                 } | ||||||
|  |                 for (let tr of lookup) { | ||||||
|  |                     tr = Utils.Clone<any>(tr) | ||||||
|  |                     Utils.Merge(tr["override"] ?? {}, tr) | ||||||
|  |                     trs.push(tr) | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             return trs; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return [tr] | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private convertUntilStable(state: DesugaringContext, spec: string | any, warnings: string[], errors: string[], ctx: string): TagRenderingConfigJson[] { | ||||||
|  |         const trs = this.convertOnce(state, spec, warnings, errors, ctx); | ||||||
|  | 
 | ||||||
|  |         const result = [] | ||||||
|  |         for (const tr of trs) { | ||||||
|  |             if (tr["builtin"] !== undefined) { | ||||||
|  |                 const stable = this.convertUntilStable(state, tr, warnings, errors, ctx + "(RECURSIVE RESOLVE)") | ||||||
|  |                 result.push(...stable) | ||||||
|  |             } else { | ||||||
|  |                 result.push(tr) | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return result; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     convert(state: DesugaringContext, json: string | TagRenderingConfigJson | { builtin: string | string[]; override: any }, context: string): { result: TagRenderingConfigJson[]; errors: string[]; warnings: string[] } { | ||||||
|  |         const errors = [] | ||||||
|  |         const warnings = [] | ||||||
|  | 
 | ||||||
|  |         return { | ||||||
|  |             result: this.convertUntilStable(state, json, warnings, errors, context), | ||||||
|  |             errors, warnings | ||||||
|  |         }; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | class ExpandGroupRewrite extends Conversion<{ | ||||||
|  |     rewrite: { | ||||||
|  |         sourceString: string, | ||||||
|  |         into: string[] | ||||||
|  |     }[], | ||||||
|  |     renderings: (string | { builtin: string, override: any } | TagRenderingConfigJson)[] | ||||||
|  | } | TagRenderingConfigJson, TagRenderingConfigJson[]> { | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     private static expandSubTagRenderings = new ExpandTagRendering() | ||||||
|  | 
 | ||||||
|  |     constructor() { | ||||||
|  |         super( | ||||||
|  |             "Converts a rewrite config for tagRenderings into the expanded form" | ||||||
|  |         ); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /* Used for left|right group creation and replacement */ | ||||||
|  |     private prepConfig(keyToRewrite: string, target: string, tr: TagRenderingConfigJson) { | ||||||
|  | 
 | ||||||
|  |         function replaceRecursive(transl: string | any) { | ||||||
|  |             if (typeof transl === "string") { | ||||||
|  |                 return transl.replace(keyToRewrite, target) | ||||||
|  |             } | ||||||
|  |             if (transl.map !== undefined) { | ||||||
|  |                 return transl.map(o => replaceRecursive(o)) | ||||||
|  |             } | ||||||
|  |             transl = {...transl} | ||||||
|  |             for (const key in transl) { | ||||||
|  |                 transl[key] = replaceRecursive(transl[key]) | ||||||
|  |             } | ||||||
|  |             return transl | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         const orig = tr; | ||||||
|  |         tr = replaceRecursive(tr) | ||||||
|  | 
 | ||||||
|  |         tr.id = target + "-" + orig.id | ||||||
|  |         tr.group = target | ||||||
|  |         return tr | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     convert(state: DesugaringContext, json: | ||||||
|  |         { | ||||||
|  |             rewrite: | ||||||
|  |                 { sourceString: string; into: string[] }[]; renderings: (string | { builtin: string; override: any } | TagRenderingConfigJson)[] | ||||||
|  |         } | TagRenderingConfigJson, context: string): { result: TagRenderingConfigJson[]; errors: string[]; warnings: string[] } { | ||||||
|  | 
 | ||||||
|  |         if (json["rewrite"] === undefined) { | ||||||
|  |             return {result: [<TagRenderingConfigJson>json], errors: [], warnings: []} | ||||||
|  |         } | ||||||
|  |         let config = <{ | ||||||
|  |             rewrite: | ||||||
|  |                 { sourceString: string; into: string[] }[]; | ||||||
|  |             renderings: (string | { builtin: string; override: any } | TagRenderingConfigJson)[] | ||||||
|  |         }>json; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |         const subRenderingsRes = ExpandGroupRewrite.expandSubTagRenderings.convertAll(state, config.renderings, context); | ||||||
|  |         const subRenderings: TagRenderingConfigJson[] = [].concat(subRenderingsRes.result); | ||||||
|  |         const errors = subRenderingsRes.errors; | ||||||
|  |         const warnings = subRenderingsRes.warnings; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |         const rewrittenPerGroup = new Map<string, TagRenderingConfigJson[]>() | ||||||
|  | 
 | ||||||
|  |         // The actual rewriting
 | ||||||
|  |         for (const rewrite of config.rewrite) { | ||||||
|  |             const source = rewrite.sourceString; | ||||||
|  |             for (const target of rewrite.into) { | ||||||
|  |                 const groupName = target; | ||||||
|  |                 const trs: TagRenderingConfigJson[] = [] | ||||||
|  | 
 | ||||||
|  |                 for (const tr of subRenderings) { | ||||||
|  |                    trs.push( this.prepConfig(source, target, tr)) | ||||||
|  |                 } | ||||||
|  |                 if(rewrittenPerGroup.has(groupName)){ | ||||||
|  |                     rewrittenPerGroup.get(groupName).push(...trs) | ||||||
|  | 
 | ||||||
|  |                 }else{ | ||||||
|  |                 rewrittenPerGroup.set(groupName, trs) | ||||||
|  |                      | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // Add questions box for this category
 | ||||||
|  |         rewrittenPerGroup.forEach((group, groupName) => { | ||||||
|  |             group.push(<TagRenderingConfigJson>{ | ||||||
|  |                 id: "questions", | ||||||
|  |                 group: groupName | ||||||
|  |             }) | ||||||
|  |         }) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |         rewrittenPerGroup.forEach((group, groupName) => { | ||||||
|  |             group.forEach(tr => { | ||||||
|  |                 if(tr.id === undefined || tr.id === ""){ | ||||||
|  |                     errors.push("A tagrendering has an empty ID after expanding the tag") | ||||||
|  |                 } | ||||||
|  |             }) | ||||||
|  |         }) | ||||||
|  | 
 | ||||||
|  |         return { | ||||||
|  |             result: [].concat(...Array.from(rewrittenPerGroup.values())), | ||||||
|  |             errors, warnings | ||||||
|  |         }; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | export class UpdateLegacyLayer extends DesugaringStep<LayerConfigJson | string | { builtin, override }> { | ||||||
|  | 
 | ||||||
|  |     constructor() { | ||||||
|  |         super("Updates various attributes from the old data format to the new to provide backwards compatibility with the formats", | ||||||
|  |             ["overpassTags", "source.osmtags", "tagRenderings[*].id", "mapRendering"]); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     convert(state: {}, json: LayerConfigJson, context: string): { result: LayerConfigJson; errors: string[]; warnings: string[] } { | ||||||
|  |         const warnings = [] | ||||||
|  |         if (typeof json === "string") { | ||||||
|  |             return json | ||||||
|  |         } | ||||||
|  |         if (json["builtin"] !== undefined) { | ||||||
|  |             // @ts-ignore
 | ||||||
|  |             return json; | ||||||
|  |         } | ||||||
|  |         let config: any = {...json}; | ||||||
| 
 | 
 | ||||||
|     /** |  | ||||||
|      * Updates the config file in-place |  | ||||||
|      * @param config |  | ||||||
|      * @private |  | ||||||
|      */ |  | ||||||
|     public static fixLayerConfig(config: any): void { |  | ||||||
|         if (config["overpassTags"]) { |         if (config["overpassTags"]) { | ||||||
|             config.source = config.source ?? {} |             config.source = config.source ?? {} | ||||||
|             config.source.osmTags = config["overpassTags"] |             config.source.osmTags = config["overpassTags"] | ||||||
|  | @ -16,7 +406,12 @@ export default class LegacyJsonConvert { | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         if (config.tagRenderings !== undefined) { |         if (config.tagRenderings !== undefined) { | ||||||
|  |             let i =0; | ||||||
|             for (const tagRendering of config.tagRenderings) { |             for (const tagRendering of config.tagRenderings) { | ||||||
|  |                 i++; | ||||||
|  |                 if(typeof tagRendering === "string" || tagRendering["builtin"] !== undefined){ | ||||||
|  |                     continue | ||||||
|  |                 } | ||||||
|                 if (tagRendering["id"] === undefined) { |                 if (tagRendering["id"] === undefined) { | ||||||
| 
 | 
 | ||||||
|                     if (tagRendering["#"] !== undefined) { |                     if (tagRendering["#"] !== undefined) { | ||||||
|  | @ -24,11 +419,14 @@ export default class LegacyJsonConvert { | ||||||
|                         delete tagRendering["#"] |                         delete tagRendering["#"] | ||||||
|                     } else if (tagRendering["freeform"]?.key !== undefined) { |                     } else if (tagRendering["freeform"]?.key !== undefined) { | ||||||
|                         tagRendering["id"] = config.id + "-" + tagRendering["freeform"]["key"] |                         tagRendering["id"] = config.id + "-" + tagRendering["freeform"]["key"] | ||||||
|  |                     }else{ | ||||||
|  |                         tagRendering["id"] = "tr-"+i | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
|         if (config.mapRendering === undefined) { |         if (config.mapRendering === undefined) { | ||||||
|             config.mapRendering = [] |             config.mapRendering = [] | ||||||
|             // This is a legacy format, lets create a pointRendering
 |             // This is a legacy format, lets create a pointRendering
 | ||||||
|  | @ -37,19 +435,18 @@ export default class LegacyJsonConvert { | ||||||
|             if (wayHandling !== 0) { |             if (wayHandling !== 0) { | ||||||
|                 location = ["point", "centroid"] |                 location = ["point", "centroid"] | ||||||
|             } |             } | ||||||
|            if(config["icon"] ?? config["label"] !== undefined){ |             if (config["icon"] ?? config["label"] !== undefined) { | ||||||
| 
 | 
 | ||||||
|             const pointConfig =   { |                 const pointConfig = { | ||||||
|                 icon: config["icon"], |                     icon: config["icon"], | ||||||
|                 iconBadges: config["iconOverlays"], |                     iconBadges: config["iconOverlays"], | ||||||
|                 label: config["label"], |                     label: config["label"], | ||||||
|                 iconSize: config["iconSize"], |                     iconSize: config["iconSize"], | ||||||
|                 location, |                     location, | ||||||
|                 rotation: config["rotation"] |                     rotation: config["rotation"] | ||||||
|  |                 } | ||||||
|  |                 config.mapRendering.push(pointConfig) | ||||||
|             } |             } | ||||||
|             config.mapRendering.push(pointConfig) |  | ||||||
|            } |  | ||||||
|              |  | ||||||
| 
 | 
 | ||||||
|             if (wayHandling !== 1) { |             if (wayHandling !== 1) { | ||||||
|                 const lineRenderConfig = <LineRenderingConfigJson>{ |                 const lineRenderConfig = <LineRenderingConfigJson>{ | ||||||
|  | @ -61,12 +458,13 @@ export default class LegacyJsonConvert { | ||||||
|                     config.mapRendering.push(lineRenderConfig) |                     config.mapRendering.push(lineRenderConfig) | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|             if(config.mapRendering.length === 0){ |             if (config.mapRendering.length === 0) { | ||||||
|                 throw "Could not convert the legacy theme into a new theme: no renderings defined for layer "+config.id |                 throw "Could not convert the legacy theme into a new theme: no renderings defined for layer " + config.id | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
|         delete config["color"] |         delete config["color"] | ||||||
|         delete config["width"] |         delete config["width"] | ||||||
|         delete config["dashArray"] |         delete config["dashArray"] | ||||||
|  | @ -85,32 +483,463 @@ export default class LegacyJsonConvert { | ||||||
|             } |             } | ||||||
|             for (const overlay of mapRenderingElement["iconBadges"] ?? []) { |             for (const overlay of mapRenderingElement["iconBadges"] ?? []) { | ||||||
|                 if (overlay["badge"] !== true) { |                 if (overlay["badge"] !== true) { | ||||||
|                     console.log("Warning: non-overlay element for ", config.id) |                     warnings.push("Warning: non-overlay element for ", config.id) | ||||||
|                 } |                 } | ||||||
|                 delete overlay["badge"] |                 delete overlay["badge"] | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  |         return { | ||||||
|  |             result: config, | ||||||
|  |             errors: [], | ||||||
|  |             warnings | ||||||
|  |         }; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| 
 | } | ||||||
|     /** | 
 | ||||||
|      * Given an old (parsed) JSON-config, will (in place) fix some issues | class UpdateLegacyTheme extends DesugaringStep<LayoutConfigJson> { | ||||||
|      * @param oldThemeConfig: the config to update to the latest format |     constructor() { | ||||||
|      */ |         super("Small fixes in the theme config", ["roamingRenderings"]); | ||||||
|     public static fixThemeConfig(oldThemeConfig: any): void { |     } | ||||||
|         for (const layerConfig of oldThemeConfig.layers ?? []) { | 
 | ||||||
|             if (typeof layerConfig === "string" || layerConfig["builtin"] !== undefined) { |     convert(state: DesugaringContext, json: LayoutConfigJson, context: string): { result: LayoutConfigJson; errors: string[]; warnings: string[] } { | ||||||
|                 continue |         const oldThemeConfig = {...json} | ||||||
|             } |         if (oldThemeConfig["roamingRenderings"] !== undefined) { | ||||||
|             // @ts-ignore
 | 
 | ||||||
|             LegacyJsonConvert.fixLayerConfig(layerConfig) |             if (oldThemeConfig["roamingRenderings"].length == 0) { | ||||||
|         } |                 delete oldThemeConfig["roamingRenderings"] | ||||||
| 
 |             } else { | ||||||
|         if (oldThemeConfig["roamingRenderings"] !== undefined && oldThemeConfig["roamingRenderings"].length == 0) { |                 return { | ||||||
|             delete oldThemeConfig["roamingRenderings"] |                     result: null, | ||||||
|         } |                     errors: [context + ": The theme contains roamingRenderings. These are not supported anymore"], | ||||||
|     } |                     warnings: [] | ||||||
| 
 |                 } | ||||||
| 
 |             } | ||||||
|  |         } | ||||||
|  |         return { | ||||||
|  |             errors: [], | ||||||
|  |             warnings: [], | ||||||
|  |             result: oldThemeConfig | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export class FixLegacyTheme extends Fuse<LayoutConfigJson> { | ||||||
|  |     constructor() { | ||||||
|  |         super( | ||||||
|  |             "Fixes a legacy theme to the modern JSON format geared to humans. Syntactic sugars are kept (i.e. no tagRenderings are expandend, no dependencies are automatically gathered)", | ||||||
|  |             new UpdateLegacyTheme(), | ||||||
|  |             new OnEvery("layers", new UpdateLegacyLayer()) | ||||||
|  |         ); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export class ValidateLayer extends DesugaringStep<LayerConfigJson> { | ||||||
|  |     /** | ||||||
|  |      * The paths where this layer is originally saved. Triggers some extra checks | ||||||
|  |      * @private | ||||||
|  |      */ | ||||||
|  |     private readonly _path?: string; | ||||||
|  |     private readonly knownImagePaths?: Set<string>; | ||||||
|  |     private readonly _isBuiltin: boolean; | ||||||
|  | 
 | ||||||
|  |     constructor(knownImagePaths: Set<string>, path: string, isBuiltin: boolean) { | ||||||
|  |         super("Doesn't change anything, but emits warnings and errors", []); | ||||||
|  |         this.knownImagePaths = knownImagePaths; | ||||||
|  |         this._path = path; | ||||||
|  |         this._isBuiltin = isBuiltin; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     convert(state: DesugaringContext, json: LayerConfigJson, context: string): { result: LayerConfigJson; errors: string[]; warnings: string[] } { | ||||||
|  |         const errors = [] | ||||||
|  |         const warnings = [] | ||||||
|  | 
 | ||||||
|  |         if (typeof json === "string") { | ||||||
|  |             errors.push(context + ": This layer hasn't been expanded: " + json) | ||||||
|  |             return { | ||||||
|  |                 result: null, | ||||||
|  |                 warnings: [], | ||||||
|  |                 errors | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if (json["builtin"] !== undefined) { | ||||||
|  |             errors.push(context + ": This layer hasn't been expanded: " + json) | ||||||
|  |             return { | ||||||
|  |                 result: null, | ||||||
|  |                 warnings: [], | ||||||
|  |                 errors | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         try { | ||||||
|  |             { | ||||||
|  |                 // Some checks for legacy elements
 | ||||||
|  | 
 | ||||||
|  |                 if (json["overpassTags"] !== undefined) { | ||||||
|  |                     errors.push("Layer " + json.id + "still uses the old 'overpassTags'-format. Please use \"source\": {\"osmTags\": <tags>}' instead of \"overpassTags\": <tags> (note: this isn't your fault, the custom theme generator still spits out the old format)") | ||||||
|  |                 } | ||||||
|  |                 const forbiddenTopLevel = ["icon", "wayHandling", "roamingRenderings", "roamingRendering", "label", "width", "color", "colour", "iconOverlays"] | ||||||
|  |                 for (const forbiddenKey of forbiddenTopLevel) { | ||||||
|  |                     if (json[forbiddenKey] !== undefined) | ||||||
|  |                         errors.push(context + ": layer " + json.id + " still has a forbidden key " + forbiddenKey) | ||||||
|  |                 } | ||||||
|  |                 if (json["hideUnderlayingFeaturesMinPercentage"] !== undefined) { | ||||||
|  |                     errors.push(context + ": layer " + json.id + " contains an old 'hideUnderlayingFeaturesMinPercentage'") | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             { | ||||||
|  |                 const layer = new LayerConfig(json, "test", true) | ||||||
|  |                 const images = Array.from(layer.ExtractImages()) | ||||||
|  |                 const remoteImages = images.filter(img => img.indexOf("http") == 0) | ||||||
|  |                 for (const remoteImage of remoteImages) { | ||||||
|  |                     errors.push("Found a remote image: " + remoteImage + " in layer " + layer.id + ", please download it. You can use the fixTheme script to automate this") | ||||||
|  |                 } | ||||||
|  |                 for (const image of images) { | ||||||
|  |                     if (image.indexOf("{") >= 0) { | ||||||
|  |                         warnings.push("Ignoring image with { in the path: ", image) | ||||||
|  |                         continue | ||||||
|  |                     } | ||||||
|  | 
 | ||||||
|  |                     if (this.knownImagePaths !== undefined && !this.knownImagePaths.has(image)) { | ||||||
|  |                         const ctx = context === undefined ? "" : ` in a layer defined in the theme ${context}` | ||||||
|  |                         errors.push(`Image with path ${image} not found or not attributed; it is used in ${layer.id}${ctx}`) | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |             } | ||||||
|  |             { | ||||||
|  |                 // CHeck location
 | ||||||
|  |                 const expected: string = `assets/layers/${json.id}/${json.id}.json` | ||||||
|  |                 if (this._path != undefined && this._path.indexOf(expected) < 0) { | ||||||
|  |                     errors.push("Layer is in an incorrect place. The path is " + this._path + ", but expected " + expected) | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |             if (this._isBuiltin ) { | ||||||
|  |                 if (json.tagRenderings?.some(tr => tr["id"] === "")) { | ||||||
|  |                     const emptyIndexes : number[] = [] | ||||||
|  |                     for (let i = 0; i < json.tagRenderings.length; i++){ | ||||||
|  |                         const tagRendering = json.tagRenderings[i]; | ||||||
|  |                         if(tagRendering["id"] === ""){ | ||||||
|  |                             emptyIndexes.push(i) | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                     errors.push(`Some tagrendering-ids are empty or have an emtpy string; this is not allowed (at ${context}.tagRenderings.[${emptyIndexes.join(",")}])`) | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 const duplicateIds = Utils.Dupiclates((json.tagRenderings ?? [])?.map(f => f["id"]).filter(id => id !== "questions")) | ||||||
|  |                 if (duplicateIds.length > 0 && !Utils.runningFromConsole) { | ||||||
|  |                     errors.push(`Some tagRenderings have a duplicate id: ${duplicateIds} (at ${context}.tagRenderings)`) | ||||||
|  |                 } | ||||||
|  |                  | ||||||
|  |                  | ||||||
|  |                 if(json.description === undefined){ | ||||||
|  |                      | ||||||
|  |                 if (Constants.priviliged_layers.indexOf(json.id) >= 0) { | ||||||
|  |                     errors.push( | ||||||
|  |                         context + ": A priviliged layer must have a description" | ||||||
|  |                     ) | ||||||
|  |                 } else { | ||||||
|  |                     warnings.push( | ||||||
|  |                         context + ": A builtin layer should have a description" | ||||||
|  |                     ) | ||||||
|  |                 }} | ||||||
|  |             } | ||||||
|  |         } catch (e) { | ||||||
|  |             errors.push(e) | ||||||
|  |         } | ||||||
|  |         return { | ||||||
|  |             result: undefined, | ||||||
|  |             errors, | ||||||
|  |             warnings | ||||||
|  |         }; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | class ValidateLanguageCompleteness extends DesugaringStep<any> { | ||||||
|  |     private readonly _languages: string[]; | ||||||
|  | 
 | ||||||
|  |     constructor(...languages: string[]) { | ||||||
|  |         super("Checks that the given object is fully translated in the specified languages", []); | ||||||
|  |         this._languages = languages; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     convert(state: DesugaringContext, obj: any, context: string): { result: LayerConfig; errors: string[]; warnings: string[] } { | ||||||
|  |         const errors = [] | ||||||
|  |         const translations = Translation.ExtractAllTranslationsFrom( | ||||||
|  |             obj | ||||||
|  |         ) | ||||||
|  |         for (const neededLanguage of this._languages) { | ||||||
|  |             translations | ||||||
|  |                 .filter(t => t.tr.translations[neededLanguage] === undefined && t.tr.translations["*"] === undefined) | ||||||
|  |                 .forEach(missing => { | ||||||
|  |                     errors.push(context + "A theme should be translation-complete for " + neededLanguage + ", but it lacks a translation for " + missing.context + ".\n\tThe english translation is " + missing.tr.textFor('en')) | ||||||
|  |                 }) | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return { | ||||||
|  |             result: obj, | ||||||
|  |             warnings: [], errors | ||||||
|  |         }; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | class ValidateTheme extends DesugaringStep<LayoutConfigJson> { | ||||||
|  |     /** | ||||||
|  |      * The paths where this layer is originally saved. Triggers some extra checks | ||||||
|  |      * @private | ||||||
|  |      */ | ||||||
|  |     private readonly _path?: string; | ||||||
|  |     private readonly knownImagePaths: Set<string>; | ||||||
|  |     private readonly _isBuiltin: boolean; | ||||||
|  | 
 | ||||||
|  |     constructor(knownImagePaths: Set<string>, path: string, isBuiltin: boolean) { | ||||||
|  |         super("Doesn't change anything, but emits warnings and errors", []); | ||||||
|  |         this.knownImagePaths = knownImagePaths; | ||||||
|  |         this._path = path; | ||||||
|  |         this._isBuiltin = isBuiltin; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     convert(state: DesugaringContext, json: LayoutConfigJson, context: string): { result: LayoutConfigJson; errors: string[]; warnings: string[] } { | ||||||
|  |         const errors = [] | ||||||
|  |         const warnings = [] | ||||||
|  |         { | ||||||
|  |             // Legacy format checks  
 | ||||||
|  |             if (this._isBuiltin) { | ||||||
|  |                 if (typeof json.language === "string") { | ||||||
|  |                     errors.push("The theme " + json.id + " has a string as language. Please use a list of strings") | ||||||
|  |                 } | ||||||
|  |                 if (json["units"] !== undefined) { | ||||||
|  |                     errors.push("The theme " + json.id + " has units defined - these should be defined on the layer instead. (Hint: use overrideAll: { '+units': ... }) ") | ||||||
|  |                 } | ||||||
|  |                 if (json["roamingRenderings"] !== undefined) { | ||||||
|  |                     errors.push("Theme " + json.id + " contains an old 'roamingRenderings'. Use an 'overrideAll' instead") | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         try { | ||||||
|  |             const theme = new LayoutConfig(json, true, "test") | ||||||
|  |             if (theme.id !== theme.id.toLowerCase()) { | ||||||
|  |                 errors.push("Theme ids should be in lowercase, but it is " + theme.id) | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             const filename = this._path.substring(this._path.lastIndexOf("/") + 1, this._path.length - 5) | ||||||
|  |             if (theme.id !== filename) { | ||||||
|  |                 errors.push("Theme ids should be the same as the name.json, but we got id: " + theme.id + " and filename " + filename + " (" + this._path + ")") | ||||||
|  |             } | ||||||
|  |             if (!this.knownImagePaths.has(theme.icon)) { | ||||||
|  |                 errors.push("The theme image " + theme.icon + " is not attributed or not saved locally") | ||||||
|  |             } | ||||||
|  |             const dups = Utils.Dupiclates(json.layers.map(layer => layer["id"])) | ||||||
|  |             if (dups.length > 0) { | ||||||
|  |                 errors.push(`The theme ${json.id} defines multiple layers with id ${dups.join(", ")}`) | ||||||
|  |             } | ||||||
|  |             if (json["mustHaveLanguage"] !== undefined) { | ||||||
|  |                 const checked = new ValidateLanguageCompleteness(...json["mustHaveLanguage"]) | ||||||
|  |                     .convert(state, theme, theme.id) | ||||||
|  |                 errors.push(...checked.errors) | ||||||
|  |                 warnings.push(...checked.warnings) | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |         } catch (e) { | ||||||
|  |             errors.push(e) | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return { | ||||||
|  |             result: json, | ||||||
|  |             errors, | ||||||
|  |             warnings | ||||||
|  |         }; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export class ValidateThemeAndLayers extends Fuse<LayoutConfigJson> { | ||||||
|  |     constructor(knownImagePaths: Set<string>, path: string, isBuiltin: boolean) { | ||||||
|  |         super("Validates a theme and the contained layers", | ||||||
|  |             new ValidateTheme(knownImagePaths, path, isBuiltin), | ||||||
|  |             new OnEvery("layers", new ValidateLayer(knownImagePaths, undefined, false)) | ||||||
|  |         ); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | class AddDependencyLayersToTheme extends DesugaringStep<LayoutConfigJson> { | ||||||
|  |     constructor() { | ||||||
|  |         super("If a layer has a dependency on another layer, these layers are added automatically on the theme. (For example: defibrillator depends on 'walls_and_buildings' to snap onto. This layer is added automatically)", ["layers"]); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private static CalculateDependencies(alreadyLoaded: LayerConfigJson[], allKnownLayers: Map<string, LayerConfigJson>, themeId: string): LayerConfigJson[] { | ||||||
|  |         const dependenciesToAdd: LayerConfigJson[] = [] | ||||||
|  |         const loadedLayerIds: Set<string> = new Set<string>(alreadyLoaded.map(l => l.id)); | ||||||
|  |          | ||||||
|  |         // Verify cross-dependencies
 | ||||||
|  |         let unmetDependencies: { neededLayer: string, neededBy: string, reason: string, context?: string }[] = [] | ||||||
|  |         do { | ||||||
|  |             const dependencies: { neededLayer: string, reason: string, context?: string, neededBy: string }[] = [] | ||||||
|  | 
 | ||||||
|  |             for (const layerConfig of alreadyLoaded) { | ||||||
|  |                 const layerDeps = DependencyCalculator.getLayerDependencies(new LayerConfig(layerConfig)) | ||||||
|  |                 dependencies.push(...layerDeps) | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             // During the generate script, builtin layers are verified but not loaded - so we have to add them manually here
 | ||||||
|  |             // Their existance is checked elsewhere, so this is fine
 | ||||||
|  |             unmetDependencies = dependencies.filter(dep => !loadedLayerIds.has(dep.neededLayer)) | ||||||
|  |             for (const unmetDependency of unmetDependencies) { | ||||||
|  |                 if(loadedLayerIds.has(unmetDependency.neededLayer)){ | ||||||
|  |                     continue | ||||||
|  |                 } | ||||||
|  |                 const dep = allKnownLayers.get(unmetDependency.neededLayer) | ||||||
|  |                 if (dep === undefined) { | ||||||
|  |                     const message = | ||||||
|  |                         ["Loading a dependency failed: layer " + unmetDependency.neededLayer + " is not found, neither as layer of " + themeId + " nor as builtin layer.", | ||||||
|  |                             "This layer is needed by " + unmetDependency.neededBy, | ||||||
|  |                             unmetDependency.reason + " (at " + unmetDependency.context + ")", | ||||||
|  |                             "Loaded layers are: " + alreadyLoaded.map(l => l.id).join(",") | ||||||
|  | 
 | ||||||
|  |                         ] | ||||||
|  |                     throw message.join("\n\t"); | ||||||
|  |                 } | ||||||
|  |                 dependenciesToAdd.unshift(dep) | ||||||
|  |                 loadedLayerIds.add(dep.id); | ||||||
|  |                 unmetDependencies = unmetDependencies.filter(d => d.neededLayer !== unmetDependency.neededLayer) | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |         } while (unmetDependencies.length > 0) | ||||||
|  | 
 | ||||||
|  |         return dependenciesToAdd; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     convert(state: DesugaringContext, theme: LayoutConfigJson, context: string): { result: LayoutConfigJson; errors: string[]; warnings: string[] } { | ||||||
|  |         const allKnownLayers: Map<string, LayerConfigJson> = state.sharedLayers; | ||||||
|  |         const knownTagRenderings: Map<string, TagRenderingConfigJson> = state.tagRenderings; | ||||||
|  |         const errors = []; | ||||||
|  |         const warnings = []; | ||||||
|  |         const layers: LayerConfigJson[] = <LayerConfigJson[]>theme.layers; // Layers should be expanded at this point
 | ||||||
|  | 
 | ||||||
|  |         knownTagRenderings.forEach((value, key) => { | ||||||
|  |             value.id = key; | ||||||
|  |         }) | ||||||
|  | 
 | ||||||
|  |         const dependencies = AddDependencyLayersToTheme.CalculateDependencies(layers, allKnownLayers, theme.id); | ||||||
|  |         if(dependencies.length > 0){ | ||||||
|  |              | ||||||
|  |             warnings.push(context+": added "+dependencies.map(d => d.id).join(", ")+" to the theme as they are needed") | ||||||
|  |         } | ||||||
|  |         layers.unshift(...dependencies); | ||||||
|  | 
 | ||||||
|  |         return { | ||||||
|  |             result: { | ||||||
|  |                 ...theme, | ||||||
|  |                 layers: layers | ||||||
|  |             }, | ||||||
|  |             errors, | ||||||
|  |             warnings | ||||||
|  |         }; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export class PrepareLayer extends Fuse<LayerConfigJson> { | ||||||
|  |     constructor() { | ||||||
|  |         super( | ||||||
|  |             "Fully prepares and expands a layer for the LayerConfig.", | ||||||
|  |             new OnEveryConcat("tagRenderings", new ExpandGroupRewrite()), | ||||||
|  |             new OnEveryConcat("tagRenderings", new ExpandTagRendering()), | ||||||
|  |             new OnEveryConcat("titleIcons", new ExpandTagRendering()) | ||||||
|  |         ); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | class SubstituteLayer extends Conversion<(string | LayerConfigJson), LayerConfigJson[]> { | ||||||
|  |     constructor() { | ||||||
|  |         super("Converts the identifier of a builtin layer into the actual layer, or converts a 'builtin' syntax with override in the fully expanded form", []); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     convert(state: DesugaringContext, json: string | LayerConfigJson, context: string): { result: LayerConfigJson[]; errors: string[]; warnings: string[] } { | ||||||
|  |         const errors = [] | ||||||
|  |         const warnings = [] | ||||||
|  |         if (typeof json === "string") { | ||||||
|  |             const found = state.sharedLayers.get(json) | ||||||
|  |             if (found === undefined) { | ||||||
|  |                 return { | ||||||
|  |                     result: null, | ||||||
|  |                     errors: [context + ": The layer with name " + json + " was not found as a builtin layer"], | ||||||
|  |                     warnings | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             return { | ||||||
|  |                 result: [found], | ||||||
|  |                 errors, warnings | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if (json["builtin"] !== undefined) { | ||||||
|  |             let names = json["builtin"] | ||||||
|  |             if (typeof names === "string") { | ||||||
|  |                 names = [names] | ||||||
|  |             } | ||||||
|  |             const layers = [] | ||||||
|  |             for (const name of names) { | ||||||
|  |                 const found = Utils.Clone(state.sharedLayers.get(name)) | ||||||
|  |                 if (found === undefined) { | ||||||
|  |                     errors.push(context + ": The layer with name " + json + " was not found as a builtin layer") | ||||||
|  |                     continue | ||||||
|  |                 } | ||||||
|  |                 Utils.Merge(json["override"], found); | ||||||
|  |                 layers.push(found) | ||||||
|  |             } | ||||||
|  |             return { | ||||||
|  |                 result: layers, | ||||||
|  |                 errors, warnings | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return { | ||||||
|  |             result: [json], | ||||||
|  |             errors, warnings | ||||||
|  |         }; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | class AddDefaultLayers extends  DesugaringStep<LayoutConfigJson>{ | ||||||
|  |      | ||||||
|  |     constructor() { | ||||||
|  |         super("Adds the default layers, namely: "+Constants.added_by_default.join(", "),["layers"]); | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     convert(state: DesugaringContext, json: LayoutConfigJson, context: string): { result: LayoutConfigJson; errors: string[]; warnings: string[] } { | ||||||
|  |         const errors = [] | ||||||
|  |         json.layers = [...json.layers] | ||||||
|  |         for (const layerName of Constants.added_by_default) { | ||||||
|  |             const v = state.sharedLayers.get(layerName) | ||||||
|  |             if(v === undefined){ | ||||||
|  |                 errors.push("Default layer "+layerName+" not found") | ||||||
|  |             } | ||||||
|  |             json.layers.push(v) | ||||||
|  |         } | ||||||
|  |         return { | ||||||
|  |             result: json, | ||||||
|  |             errors, | ||||||
|  |             warnings: [] | ||||||
|  |         }; | ||||||
|  |     } | ||||||
|  |      | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export class PrepareTheme extends Fuse<LayoutConfigJson> { | ||||||
|  |     constructor() { | ||||||
|  |         super( | ||||||
|  |             "Fully prepares and expands a theme", | ||||||
|  |             new OnEveryConcat("layers", new SubstituteLayer()), | ||||||
|  |             new AddDefaultLayers(), | ||||||
|  |             new AddDependencyLayersToTheme(), | ||||||
|  |             new OnEvery("layers", new PrepareLayer()), | ||||||
|  | 
 | ||||||
|  |         ); | ||||||
|  |     } | ||||||
| } | } | ||||||
|  | @ -40,7 +40,6 @@ export default class TagRenderingConfig { | ||||||
|         readonly hideInAnswer: boolean | TagsFilter |         readonly hideInAnswer: boolean | TagsFilter | ||||||
|         readonly addExtraTags: Tag[] |         readonly addExtraTags: Tag[] | ||||||
|     }[] |     }[] | ||||||
| 
 |  | ||||||
|     constructor(json: string | TagRenderingConfigJson, context?: string) { |     constructor(json: string | TagRenderingConfigJson, context?: string) { | ||||||
|         if (json === undefined) { |         if (json === undefined) { | ||||||
|             throw "Initing a TagRenderingConfig with undefined in " + context; |             throw "Initing a TagRenderingConfig with undefined in " + context; | ||||||
|  | @ -69,7 +68,7 @@ export default class TagRenderingConfig { | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|         this.id = json.id ?? ""; |         this.id = json.id ?? ""; // Some tagrenderings - especially for the map rendering - don't need an ID
 | ||||||
|         if (this.id.match(/^[a-zA-Z0-9 ()?\/=:;,_-]*$/) === null) { |         if (this.id.match(/^[a-zA-Z0-9 ()?\/=:;,_-]*$/) === null) { | ||||||
|             throw "Invalid ID in " + context + ": an id can only contain [a-zA-Z0-0_-] as characters. The offending id is: " + this.id |             throw "Invalid ID in " + context + ": an id can only contain [a-zA-Z0-0_-] as characters. The offending id is: " + this.id | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  | @ -1,21 +1,11 @@ | ||||||
| import TagRenderingConfig from "./TagRenderingConfig"; | import TagRenderingConfig from "./TagRenderingConfig"; | ||||||
| import SharedTagRenderings from "../../Customizations/SharedTagRenderings"; | import SharedTagRenderings from "../../Customizations/SharedTagRenderings"; | ||||||
| import {TagRenderingConfigJson} from "./Json/TagRenderingConfigJson"; | import {TagRenderingConfigJson} from "./Json/TagRenderingConfigJson"; | ||||||
| import {Utils} from "../../Utils"; |  | ||||||
| 
 | 
 | ||||||
| export default class WithContextLoader { | export default class WithContextLoader { | ||||||
|     protected readonly _context: string; |     protected readonly _context: string; | ||||||
|     private readonly _json: any; |     private readonly _json: any; | ||||||
| 
 | 
 | ||||||
|     public static getKnownTagRenderings : ((id: string) => TagRenderingConfigJson[])=  function(id)  { |  | ||||||
|         const found = SharedTagRenderings.SharedTagRenderingJson.get(id) |  | ||||||
|         if(found !== undefined){ |  | ||||||
|             return [found] |  | ||||||
|         }else{ |  | ||||||
|             return [] |  | ||||||
|         } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
|     constructor(json: any, context: string) { |     constructor(json: any, context: string) { | ||||||
|         this._json = json; |         this._json = json; | ||||||
|         this._context = context; |         this._context = context; | ||||||
|  | @ -53,8 +43,8 @@ export default class WithContextLoader { | ||||||
|      * A string is interpreted as a name to call |      * A string is interpreted as a name to call | ||||||
|      */ |      */ | ||||||
|     public ParseTagRenderings( |     public ParseTagRenderings( | ||||||
|         tagRenderings: (string | { builtin: string, override: any } | TagRenderingConfigJson)[], |         tagRenderings: TagRenderingConfigJson[], | ||||||
|         options?:{ |         options?: { | ||||||
|             /** |             /** | ||||||
|              * Throw an error if 'question' is defined |              * Throw an error if 'question' is defined | ||||||
|              */ |              */ | ||||||
|  | @ -73,59 +63,14 @@ export default class WithContextLoader { | ||||||
|         if (options.prepConfig === undefined) { |         if (options.prepConfig === undefined) { | ||||||
|             options.prepConfig = c => c |             options.prepConfig = c => c | ||||||
|         } |         } | ||||||
|         const preparedConfigs : TagRenderingConfigJson[] = [] |  | ||||||
|         for (let i = 0; i < tagRenderings.length; i++) { |  | ||||||
|             let renderingJson = tagRenderings[i] |  | ||||||
|             if(renderingJson === "questions"){ |  | ||||||
|                 renderingJson = { |  | ||||||
|                     id: "questions" |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|             if (typeof renderingJson === "string") { |  | ||||||
|                 renderingJson = {builtin: renderingJson, override: undefined} |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             if (renderingJson["builtin"] === undefined) { |  | ||||||
|                 const patchedConfig = options.prepConfig(<TagRenderingConfigJson>renderingJson) |  | ||||||
|                 preparedConfigs.push(patchedConfig) |  | ||||||
|                 continue |  | ||||||
|              |  | ||||||
|             }  |  | ||||||
|              |  | ||||||
|              |  | ||||||
|             const renderingId = renderingJson["builtin"] |  | ||||||
|             let sharedJsons = [] |  | ||||||
|             if(typeof renderingId === "string"){ |  | ||||||
|                 sharedJsons = WithContextLoader.getKnownTagRenderings(renderingId) |  | ||||||
|             }else{ |  | ||||||
|                 sharedJsons = [].concat( ...(<string[]>renderingId).map(id => WithContextLoader.getKnownTagRenderings(id) ) ) |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             if (sharedJsons.length === 0) { |  | ||||||
|                 const keys = Array.from(SharedTagRenderings.SharedTagRenderingJson.keys()); |  | ||||||
|                 throw `Predefined tagRendering ${renderingId} not found in ${context}.\n    Try one of ${keys.join( |  | ||||||
|                     ", " |  | ||||||
|                 )}\n    If you intent to output this text literally, use {\"render\": <your text>} instead"}`;
 |  | ||||||
|             } |  | ||||||
|             for (let sharedJson of sharedJsons) { |  | ||||||
|                 if (renderingJson["override"] !== undefined) { |  | ||||||
|                     sharedJson = Utils.Merge(renderingJson["override"], JSON.parse(JSON.stringify(sharedJson))) |  | ||||||
|                 } |  | ||||||
|      |  | ||||||
|                 const patchedConfig = options.prepConfig(<TagRenderingConfigJson>sharedJson) |  | ||||||
|                 preparedConfigs.push(patchedConfig) |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         const renderings: TagRenderingConfig[] = [] |         const renderings: TagRenderingConfig[] = [] | ||||||
|         for (let i = 0; i < preparedConfigs.length; i++){ |         for (let i = 0; i < tagRenderings.length; i++) { | ||||||
|             const preparedConfig = preparedConfigs[i]; |             const preparedConfig = tagRenderings[i]; | ||||||
|             const tr = new TagRenderingConfig(preparedConfig, `${context}.tagrendering[${i}]`); |             const tr = new TagRenderingConfig(preparedConfig, `${context}.tagrendering[${i}]`); | ||||||
|             if(options.readOnlyMode && tr.question !== undefined){ |             if (options.readOnlyMode && tr.question !== undefined) { | ||||||
|                 throw "A question is defined for "+`${context}.tagrendering[${i}], but this is not allowed at this position - probably because this rendering is an icon, badge or label` |                 throw "A question is defined for " + `${context}.tagrendering[${i}], but this is not allowed at this position - probably because this rendering is an icon, badge or label` | ||||||
|             } |             } | ||||||
|             if(options.requiresId && tr.id === ""){ |             if (options.requiresId && tr.id === "") { | ||||||
|                 throw `${context}.tagrendering[${i}] has an invalid ID - make sure it is defined and not empty` |                 throw `${context}.tagrendering[${i}] has an invalid ID - make sure it is defined and not empty` | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,9 +1,9 @@ | ||||||
|  | import UserRelatedState from "../Logic/State/UserRelatedState"; | ||||||
| import {FixedUiElement} from "./Base/FixedUiElement"; | import {FixedUiElement} from "./Base/FixedUiElement"; | ||||||
| import Combine from "./Base/Combine"; | import Combine from "./Base/Combine"; | ||||||
| import MoreScreen from "./BigComponents/MoreScreen"; | import MoreScreen from "./BigComponents/MoreScreen"; | ||||||
| import Translations from "./i18n/Translations"; | import Translations from "./i18n/Translations"; | ||||||
| import Constants from "../Models/Constants"; | import Constants from "../Models/Constants"; | ||||||
| import UserRelatedState from "../Logic/State/UserRelatedState"; |  | ||||||
| import {Utils} from "../Utils"; | import {Utils} from "../Utils"; | ||||||
| import LanguagePicker from "./LanguagePicker"; | import LanguagePicker from "./LanguagePicker"; | ||||||
| import IndexText from "./BigComponents/IndexText"; | import IndexText from "./BigComponents/IndexText"; | ||||||
|  | @ -13,7 +13,6 @@ import {SubtleButton} from "./Base/SubtleButton"; | ||||||
| 
 | 
 | ||||||
| export default class AllThemesGui { | export default class AllThemesGui { | ||||||
|     constructor() { |     constructor() { | ||||||
| 
 |  | ||||||
|         try { |         try { | ||||||
| 
 | 
 | ||||||
|             new FixedUiElement("").AttachTo("centermessage") |             new FixedUiElement("").AttachTo("centermessage") | ||||||
|  | @ -41,6 +40,7 @@ export default class AllThemesGui { | ||||||
|                 .SetStyle("pointer-events: all;") |                 .SetStyle("pointer-events: all;") | ||||||
|                 .AttachTo("topleft-tools"); |                 .AttachTo("topleft-tools"); | ||||||
|         } catch (e) { |         } catch (e) { | ||||||
|  |             console.error(">>>> CRITICAL", e) | ||||||
|             new FixedUiElement("Seems like no layers are compiled - check the output of `npm run generate:layeroverview`. Is this visible online? Contact pietervdvn immediately!").SetClass("alert") |             new FixedUiElement("Seems like no layers are compiled - check the output of `npm run generate:layeroverview`. Is this visible online? Contact pietervdvn immediately!").SetClass("alert") | ||||||
|                 .AttachTo("centermessage") |                 .AttachTo("centermessage") | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  | @ -27,6 +27,7 @@ import {QueryParameters} from "../Logic/Web/QueryParameters"; | ||||||
| import {SubstitutedTranslation} from "./SubstitutedTranslation"; | import {SubstitutedTranslation} from "./SubstitutedTranslation"; | ||||||
| import {AutoAction} from "./Popup/AutoApplyButton"; | import {AutoAction} from "./Popup/AutoApplyButton"; | ||||||
| import DynamicGeoJsonTileSource from "../Logic/FeatureSource/TiledFeatureSource/DynamicGeoJsonTileSource"; | import DynamicGeoJsonTileSource from "../Logic/FeatureSource/TiledFeatureSource/DynamicGeoJsonTileSource"; | ||||||
|  | import * as themeOverview from "../assets/generated/theme_overview.json" | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class AutomationPanel extends Combine{ | class AutomationPanel extends Combine{ | ||||||
|  | @ -177,7 +178,7 @@ class AutomationPanel extends Combine{ | ||||||
|                         const feature = ffs.feature |                         const feature = ffs.feature | ||||||
|                         const renderingTr = targetAction.GetRenderValue(feature.properties) |                         const renderingTr = targetAction.GetRenderValue(feature.properties) | ||||||
|                         const rendering = renderingTr.txt |                         const rendering = renderingTr.txt | ||||||
|                         log.push("<a href='https://openstreetmap.org/"+feature.properties.id+"' target='_blank'>"+feature.properties.id+"</a>: "+new SubstitutedTranslation(renderingTr, new UIEventSource<any>(feature.properties)).ConstructElement().innerText) |                         log.push("<a href='https://openstreetmap.org/"+feature.properties.id+"' target='_blank'>"+feature.properties.id+"</a>: "+new SubstitutedTranslation(renderingTr, new UIEventSource<any>(feature.properties), state).ConstructElement().innerText) | ||||||
|                         const actions = Utils.NoNull(SubstitutedTranslation.ExtractSpecialComponents(rendering) |                         const actions = Utils.NoNull(SubstitutedTranslation.ExtractSpecialComponents(rendering) | ||||||
|                             .map(obj => obj.special)) |                             .map(obj => obj.special)) | ||||||
|                         for (const action of actions) { |                         for (const action of actions) { | ||||||
|  | @ -251,7 +252,7 @@ class AutomatonGui { | ||||||
|     private static GenerateMainPanel(): BaseUIElement { |     private static GenerateMainPanel(): BaseUIElement { | ||||||
| 
 | 
 | ||||||
|         const themeSelect = new DropDown<string>("Select a theme", |         const themeSelect = new DropDown<string>("Select a theme", | ||||||
|             AllKnownLayouts.layoutsList.map(l => ({value: l.id, shown: l.id}))  |             Array.from(themeOverview).map(l => ({value: l.id, shown: l.id}))  | ||||||
|         ) |         ) | ||||||
| 
 | 
 | ||||||
|         LocalStorageSource.Get("automation-theme-id", "missing_streets").syncWith(themeSelect.GetValue()) |         LocalStorageSource.Get("automation-theme-id", "missing_streets").syncWith(themeSelect.GetValue()) | ||||||
|  |  | ||||||
|  | @ -8,32 +8,35 @@ import {UIElement} from "../UIElement"; | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| export class SubtleButton extends UIElement { | export class SubtleButton extends UIElement { | ||||||
|  |     private readonly imageUrl: string | BaseUIElement; | ||||||
|  |     private readonly message: string | BaseUIElement; | ||||||
|  |     private readonly linkTo: { url: string | UIEventSource<string>; newTab?: boolean }; | ||||||
| 
 | 
 | ||||||
|     private readonly _element: BaseUIElement |  | ||||||
| 
 | 
 | ||||||
|     constructor(imageUrl: string | BaseUIElement, message: string | BaseUIElement, linkTo: { url: string | UIEventSource<string>, newTab?: boolean } = undefined) { |     constructor(imageUrl: string | BaseUIElement, message: string | BaseUIElement, linkTo: { url: string | UIEventSource<string>, newTab?: boolean } = undefined) { | ||||||
|         super(); |         super(); | ||||||
|         this._element = SubtleButton.generateContent(imageUrl, message, linkTo) |         this.imageUrl = imageUrl; | ||||||
|         this.SetClass("block flex p-3 my-2 bg-blue-100 rounded-lg hover:shadow-xl hover:bg-blue-200 link-no-underline") |         this.message = message; | ||||||
| 
 |         this.linkTo = linkTo; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private static generateContent(imageUrl: string | BaseUIElement, messageT: string | BaseUIElement, linkTo: { url: string | UIEventSource<string>, newTab?: boolean } = undefined): BaseUIElement { |     protected InnerRender(): string | BaseUIElement { | ||||||
|         const message = Translations.W(messageT); |         const classes=  "block flex p-3 my-2 bg-blue-100 rounded-lg hover:shadow-xl hover:bg-blue-200 link-no-underline"; | ||||||
|         message |         const message = Translations.W(this.message); | ||||||
|         let img; |         let img; | ||||||
|         if ((imageUrl ?? "") === "") { |         if ((this.imageUrl ?? "") === "") { | ||||||
|             img = undefined; |             img = undefined; | ||||||
|         } else if (typeof (imageUrl) === "string") { |         } else if (typeof (this.imageUrl) === "string") { | ||||||
|             img = new Img(imageUrl) |             img = new Img(this.imageUrl) | ||||||
|         } else { |         } else { | ||||||
|             img = imageUrl; |             img = this.imageUrl; | ||||||
|         } |         } | ||||||
|         img?.SetClass("block flex items-center justify-center h-11 w-11 flex-shrink0 mr-4") |         img?.SetClass("block flex items-center justify-center h-11 w-11 flex-shrink0 mr-4") | ||||||
|         const image = new Combine([img]) |         const image = new Combine([img]) | ||||||
|             .SetClass("flex-shrink-0"); |             .SetClass("flex-shrink-0"); | ||||||
| 
 | 
 | ||||||
|         if (linkTo == undefined) { |         if (this.linkTo == undefined) { | ||||||
|  |             this.SetClass(classes) | ||||||
|             return new Combine([ |             return new Combine([ | ||||||
|                 image, |                 image, | ||||||
|                 message?.SetClass("block overflow-ellipsis"), |                 message?.SetClass("block overflow-ellipsis"), | ||||||
|  | @ -46,13 +49,10 @@ export class SubtleButton extends UIElement { | ||||||
|                 image, |                 image, | ||||||
|                 message?.SetClass("block overflow-ellipsis") |                 message?.SetClass("block overflow-ellipsis") | ||||||
|             ]).SetClass("flex group w-full"), |             ]).SetClass("flex group w-full"), | ||||||
|             linkTo.url, |             this.linkTo.url, | ||||||
|             linkTo.newTab ?? false |             this.linkTo.newTab ?? false | ||||||
|         ) |         ).SetClass(classes) | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|     protected InnerRender(): string | BaseUIElement { |  | ||||||
|         return this._element; |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -3,7 +3,7 @@ import * as welcome_messages from "../../assets/welcome_message.json" | ||||||
| import BaseUIElement from "../BaseUIElement"; | import BaseUIElement from "../BaseUIElement"; | ||||||
| import {FixedUiElement} from "../Base/FixedUiElement"; | import {FixedUiElement} from "../Base/FixedUiElement"; | ||||||
| import MoreScreen from "./MoreScreen"; | import MoreScreen from "./MoreScreen"; | ||||||
| import {AllKnownLayouts} from "../../Customizations/AllKnownLayouts"; | import * as themeOverview from "../../assets/generated/theme_overview.json" | ||||||
| import Translations from "../i18n/Translations"; | import Translations from "../i18n/Translations"; | ||||||
| import Title from "../Base/Title"; | import Title from "../Base/Title"; | ||||||
| 
 | 
 | ||||||
|  | @ -33,6 +33,12 @@ export default class FeaturedMessage extends Combine { | ||||||
| 
 | 
 | ||||||
|     public static WelcomeMessages(): { start_date: Date, end_date: Date, message: string, featured_theme?: string }[] { |     public static WelcomeMessages(): { start_date: Date, end_date: Date, message: string, featured_theme?: string }[] { | ||||||
|         const all_messages: { start_date: Date, end_date: Date, message: string, featured_theme?: string }[] = [] |         const all_messages: { start_date: Date, end_date: Date, message: string, featured_theme?: string }[] = [] | ||||||
|  |          | ||||||
|  |         const themesById = new Map<string, {id: string, title: any, shortDescription: any}>(); | ||||||
|  |         for (const theme of themeOverview["default"]) { | ||||||
|  |             themesById.set(theme.id, theme); | ||||||
|  |         } | ||||||
|  |          | ||||||
|         for (const i in welcome_messages) { |         for (const i in welcome_messages) { | ||||||
|             if (isNaN(Number(i))) { |             if (isNaN(Number(i))) { | ||||||
|                 continue |                 continue | ||||||
|  | @ -41,7 +47,8 @@ export default class FeaturedMessage extends Combine { | ||||||
|             if (wm === null) { |             if (wm === null) { | ||||||
|                 continue |                 continue | ||||||
|             } |             } | ||||||
|             if (AllKnownLayouts.allKnownLayouts.get(wm.featured_theme) === undefined) { |             if (themesById.get(wm.featured_theme) === undefined) { | ||||||
|  |                 console.log("THEMES BY ID:", themesById) | ||||||
|                 console.error("Unkown featured theme for ", wm) |                 console.error("Unkown featured theme for ", wm) | ||||||
|                 continue |                 continue | ||||||
|             } |             } | ||||||
|  | @ -71,7 +78,10 @@ export default class FeaturedMessage extends Combine { | ||||||
|         const msg = new FixedUiElement(welcome_message.message).SetClass("link-underline font-lg") |         const msg = new FixedUiElement(welcome_message.message).SetClass("link-underline font-lg") | ||||||
|         els.push(new Combine([title, msg]).SetClass("m-4")) |         els.push(new Combine([title, msg]).SetClass("m-4")) | ||||||
|         if (welcome_message.featured_theme !== undefined) { |         if (welcome_message.featured_theme !== undefined) { | ||||||
|             els.push(MoreScreen.createLinkButton({}, AllKnownLayouts.allKnownLayouts.get(welcome_message.featured_theme)) |              | ||||||
|  |             const theme = themeOverview["default"].filter(th => th.id === welcome_message.featured_theme)[0]; | ||||||
|  |              | ||||||
|  |             els.push(MoreScreen.createLinkButton({}, theme) | ||||||
|                 .SetClass("m-4 self-center md:w-160") |                 .SetClass("m-4 self-center md:w-160") | ||||||
|                 .SetStyle("height: min-content;")) |                 .SetStyle("height: min-content;")) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,18 +1,18 @@ | ||||||
| import {DropDown} from "../Input/DropDown"; | import {DropDown} from "../Input/DropDown"; | ||||||
| import Translations from "../i18n/Translations"; | import Translations from "../i18n/Translations"; | ||||||
| import State from "../../State"; |  | ||||||
| import {UIEventSource} from "../../Logic/UIEventSource"; | import {UIEventSource} from "../../Logic/UIEventSource"; | ||||||
|  | import {OsmConnection} from "../../Logic/Osm/OsmConnection"; | ||||||
| 
 | 
 | ||||||
| export default class LicensePicker extends DropDown<string> { | export default class LicensePicker extends DropDown<string> { | ||||||
| 
 | 
 | ||||||
|     constructor() { |     constructor(state: {osmConnection: OsmConnection}) { | ||||||
|         super(Translations.t.image.willBePublished.Clone(), |         super(Translations.t.image.willBePublished.Clone(), | ||||||
|             [ |             [ | ||||||
|                 {value: "CC0", shown: Translations.t.image.cco.Clone()}, |                 {value: "CC0", shown: Translations.t.image.cco.Clone()}, | ||||||
|                 {value: "CC-BY-SA 4.0", shown: Translations.t.image.ccbs.Clone()}, |                 {value: "CC-BY-SA 4.0", shown: Translations.t.image.ccbs.Clone()}, | ||||||
|                 {value: "CC-BY 4.0", shown: Translations.t.image.ccb.Clone()} |                 {value: "CC-BY 4.0", shown: Translations.t.image.ccb.Clone()} | ||||||
|             ], |             ], | ||||||
|             State.state?.osmConnection?.GetPreference("pictures-license") ?? new UIEventSource<string>("CC0") |             state?.osmConnection?.GetPreference("pictures-license") ?? new UIEventSource<string>("CC0") | ||||||
|         ) |         ) | ||||||
|         this.SetClass("flex flex-col sm:flex-row").SetStyle("float:left"); |         this.SetClass("flex flex-col sm:flex-row").SetStyle("float:left"); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | @ -1,5 +1,4 @@ | ||||||
| import {VariableUiElement} from "../Base/VariableUIElement"; | import {VariableUiElement} from "../Base/VariableUIElement"; | ||||||
| import {AllKnownLayouts} from "../../Customizations/AllKnownLayouts"; |  | ||||||
| import Svg from "../../Svg"; | import Svg from "../../Svg"; | ||||||
| import Combine from "../Base/Combine"; | import Combine from "../Base/Combine"; | ||||||
| import {SubtleButton} from "../Base/SubtleButton"; | import {SubtleButton} from "../Base/SubtleButton"; | ||||||
|  | @ -15,6 +14,8 @@ import UserRelatedState from "../../Logic/State/UserRelatedState"; | ||||||
| import Toggle from "../Input/Toggle"; | import Toggle from "../Input/Toggle"; | ||||||
| import {Utils} from "../../Utils"; | import {Utils} from "../../Utils"; | ||||||
| import Title from "../Base/Title"; | import Title from "../Base/Title"; | ||||||
|  | import * as themeOverview from "../../assets/generated/theme_overview.json" | ||||||
|  | import {Translation} from "../i18n/Translation"; | ||||||
| 
 | 
 | ||||||
| export default class MoreScreen extends Combine { | export default class MoreScreen extends Combine { | ||||||
| 
 | 
 | ||||||
|  | @ -47,7 +48,12 @@ export default class MoreScreen extends Combine { | ||||||
|         state: { |         state: { | ||||||
|             locationControl?: UIEventSource<Loc>, |             locationControl?: UIEventSource<Loc>, | ||||||
|             layoutToUse?: LayoutConfig |             layoutToUse?: LayoutConfig | ||||||
|         }, layout: LayoutConfig, customThemeDefinition: string = undefined |         }, layout: { | ||||||
|  |             id: string, | ||||||
|  |             icon: string, | ||||||
|  |             title: any, | ||||||
|  |             shortDescription: any | ||||||
|  |         }, isCustom: boolean = false | ||||||
|     ): |     ): | ||||||
|         BaseUIElement { |         BaseUIElement { | ||||||
|         if (layout === undefined) { |         if (layout === undefined) { | ||||||
|  | @ -73,14 +79,12 @@ export default class MoreScreen extends Combine { | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         let linkPrefix = `${path}/${layout.id.toLowerCase()}.html?` |         let linkPrefix = `${path}/${layout.id.toLowerCase()}.html?` | ||||||
|         let linkSuffix = "" |  | ||||||
|         if (location.hostname === "localhost" || location.hostname === "127.0.0.1") { |         if (location.hostname === "localhost" || location.hostname === "127.0.0.1") { | ||||||
|             linkPrefix = `${path}/index.html?layout=${layout.id}&` |             linkPrefix = `${path}/theme.html?layout=${layout.id}&` | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         if (customThemeDefinition) { |         if (isCustom) { | ||||||
|             linkPrefix = `${path}/index.html?userlayout=${layout.id}&` |             linkPrefix = `${path}/theme.html?userlayout=${layout.id}&` | ||||||
|             linkSuffix = `#${customThemeDefinition}` |  | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         const linkText = currentLocation?.map(currentLocation => { |         const linkText = currentLocation?.map(currentLocation => { | ||||||
|  | @ -91,17 +95,17 @@ export default class MoreScreen extends Combine { | ||||||
|             ].filter(part => part[1] !== undefined) |             ].filter(part => part[1] !== undefined) | ||||||
|                 .map(part => part[0] + "=" + part[1]) |                 .map(part => part[0] + "=" + part[1]) | ||||||
|                 .join("&") |                 .join("&") | ||||||
|             return `${linkPrefix}${params}${linkSuffix}`; |             return `${linkPrefix}${params}`; | ||||||
|         }) ?? new UIEventSource<string>(`${linkPrefix}${linkSuffix}`) |         }) ?? new UIEventSource<string>(`${linkPrefix}`) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|         return new SubtleButton(layout.icon, |         return new SubtleButton(layout.icon, | ||||||
|             new Combine([ |             new Combine([ | ||||||
|                 `<dt class='text-lg leading-6 font-medium text-gray-900 group-hover:text-blue-800'>`, |                 `<dt class='text-lg leading-6 font-medium text-gray-900 group-hover:text-blue-800'>`, | ||||||
|                 Translations.WT(layout.title), |                 new Translation(layout.title), | ||||||
|                 `</dt>`, |                 `</dt>`, | ||||||
|                 `<dd class='mt-1 text-base text-gray-500 group-hover:text-blue-900 overflow-ellipsis'>`, |                 `<dd class='mt-1 text-base text-gray-500 group-hover:text-blue-900 overflow-ellipsis'>`, | ||||||
|                 Translations.WT(layout.shortDescription)?.SetClass("subtle") ?? "", |                 new Translation(layout.shortDescription)?.SetClass("subtle") ?? "", | ||||||
|                 `</dd>`, |                 `</dd>`, | ||||||
|             ]), {url: linkText, newTab: false}); |             ]), {url: linkText, newTab: false}); | ||||||
|     } |     } | ||||||
|  | @ -111,7 +115,7 @@ export default class MoreScreen extends Combine { | ||||||
|             if (customThemes.length <= 0) { |             if (customThemes.length <= 0) { | ||||||
|                 return undefined; |                 return undefined; | ||||||
|             } |             } | ||||||
|             const customThemeButtons = customThemes.map(theme => MoreScreen.createLinkButton(state, theme.layout, theme.definition)?.SetClass(buttonClass)) |             const customThemeButtons = customThemes.map(theme => MoreScreen.createLinkButton(state, theme, true)?.SetClass(buttonClass)) | ||||||
|             return new Combine([ |             return new Combine([ | ||||||
|                 Translations.t.general.customThemeIntro.Clone(), |                 Translations.t.general.customThemeIntro.Clone(), | ||||||
|                 new Combine(customThemeButtons).SetClass(themeListClasses) |                 new Combine(customThemeButtons).SetClass(themeListClasses) | ||||||
|  | @ -122,27 +126,29 @@ export default class MoreScreen extends Combine { | ||||||
|     private static createPreviouslyVistedHiddenList(state: UserRelatedState, buttonClass: string, themeListStyle: string) { |     private static createPreviouslyVistedHiddenList(state: UserRelatedState, buttonClass: string, themeListStyle: string) { | ||||||
|         const t = Translations.t.general.morescreen |         const t = Translations.t.general.morescreen | ||||||
|         const prefix = "mapcomplete-hidden-theme-" |         const prefix = "mapcomplete-hidden-theme-" | ||||||
|         const hiddenTotal = AllKnownLayouts.layoutsList.filter(layout => layout.hideFromOverview).length |         const hiddenThemes = themeOverview["default"].filter(layout => layout.hideFromOverview) | ||||||
|  |         const hiddenTotal = hiddenThemes.length | ||||||
|  |          | ||||||
|         return new Toggle( |         return new Toggle( | ||||||
|             new VariableUiElement( |             new VariableUiElement( | ||||||
|                 state.osmConnection.preferencesHandler.preferences.map(allPreferences => { |                 state.osmConnection.preferencesHandler.preferences.map(allPreferences => { | ||||||
|                     const knownThemes = Utils.NoNull(Object.keys(allPreferences) |                     const knownThemes: Set<string> = new Set(Utils.NoNull(Object.keys(allPreferences) | ||||||
|                         .filter(key => key.startsWith(prefix)) |                         .filter(key => key.startsWith(prefix)) | ||||||
|                         .map(key => key.substring(prefix.length, key.length - "-enabled".length)) |                         .map(key => key.substring(prefix.length, key.length - "-enabled".length)))); | ||||||
|                         .map(theme => AllKnownLayouts.allKnownLayouts.get(theme))) |                      | ||||||
|                         .filter(theme => theme?.hideFromOverview) |                     if(knownThemes.size === 0){ | ||||||
|                     if (knownThemes.length === 0) { |  | ||||||
|                         return undefined |                         return undefined | ||||||
|                     } |                     } | ||||||
|                      |                      | ||||||
|                     const knownLayouts = new Combine(knownThemes.map(layout => |                      const knownThemeDescriptions = hiddenThemes.filter(theme => knownThemes.has(theme.id)) | ||||||
|                         MoreScreen.createLinkButton(state, layout)?.SetClass(buttonClass) |                          .map(theme => MoreScreen.createLinkButton(state, theme)?.SetClass(buttonClass)); | ||||||
|                     )).SetClass(themeListStyle) | 
 | ||||||
|  |                     const knownLayouts = new Combine(knownThemeDescriptions).SetClass(themeListStyle) | ||||||
| 
 | 
 | ||||||
|                     return new Combine([ |                     return new Combine([ | ||||||
|                         new Title(t.previouslyHiddenTitle), |                         new Title(t.previouslyHiddenTitle), | ||||||
|                         t.hiddenExplanation.Subs({ |                         t.hiddenExplanation.Subs({ | ||||||
|                             hidden_discovered: "" + knownThemes.length, |                             hidden_discovered: "" + knownThemes.size, | ||||||
|                             total_hidden: "" + hiddenTotal |                             total_hidden: "" + hiddenTotal | ||||||
|                         }), |                         }), | ||||||
|                         knownLayouts |                         knownLayouts | ||||||
|  | @ -158,7 +164,7 @@ export default class MoreScreen extends Combine { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private static createOfficialThemesList(state: { osmConnection: OsmConnection, locationControl?: UIEventSource<Loc> }, buttonClass: string): BaseUIElement { |     private static createOfficialThemesList(state: { osmConnection: OsmConnection, locationControl?: UIEventSource<Loc> }, buttonClass: string): BaseUIElement { | ||||||
|         let officialThemes = AllKnownLayouts.layoutsList |         let officialThemes = themeOverview["default"]; | ||||||
| 
 | 
 | ||||||
|         let buttons = officialThemes.map((layout) => { |         let buttons = officialThemes.map((layout) => { | ||||||
|             if (layout === undefined) { |             if (layout === undefined) { | ||||||
|  |  | ||||||
|  | @ -15,13 +15,14 @@ import LeftControls from "./BigComponents/LeftControls"; | ||||||
| import RightControls from "./BigComponents/RightControls"; | import RightControls from "./BigComponents/RightControls"; | ||||||
| import CenterMessageBox from "./CenterMessageBox"; | import CenterMessageBox from "./CenterMessageBox"; | ||||||
| import ShowDataLayer from "./ShowDataLayer/ShowDataLayer"; | import ShowDataLayer from "./ShowDataLayer/ShowDataLayer"; | ||||||
| import AllKnownLayers from "../Customizations/AllKnownLayers"; |  | ||||||
| import ScrollableFullScreen from "./Base/ScrollableFullScreen"; | import ScrollableFullScreen from "./Base/ScrollableFullScreen"; | ||||||
| import Translations from "./i18n/Translations"; | import Translations from "./i18n/Translations"; | ||||||
| import SimpleAddUI from "./BigComponents/SimpleAddUI"; | import SimpleAddUI from "./BigComponents/SimpleAddUI"; | ||||||
| import StrayClickHandler from "../Logic/Actors/StrayClickHandler"; | import StrayClickHandler from "../Logic/Actors/StrayClickHandler"; | ||||||
| import Lazy from "./Base/Lazy"; | import Lazy from "./Base/Lazy"; | ||||||
| import {DefaultGuiState} from "./DefaultGuiState"; | import {DefaultGuiState} from "./DefaultGuiState"; | ||||||
|  | import LayerConfig from "../Models/ThemeConfig/LayerConfig"; | ||||||
|  | import * as home_location_json from "../assets/layers/home_location/home_location.json"; | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  | @ -111,7 +112,7 @@ export default class DefaultGUI { | ||||||
| 
 | 
 | ||||||
|         new ShowDataLayer({ |         new ShowDataLayer({ | ||||||
|             leafletMap: state.leafletMap, |             leafletMap: state.leafletMap, | ||||||
|             layerToShow: AllKnownLayers.sharedLayers.get("home_location"), |             layerToShow: new LayerConfig(home_location_json, "all_known_layers", true), | ||||||
|             features: state.homeLocation, |             features: state.homeLocation, | ||||||
|             enablePopups: false, |             enablePopups: false, | ||||||
|         }) |         }) | ||||||
|  |  | ||||||
|  | @ -2,20 +2,21 @@ import {UIEventSource} from "../../Logic/UIEventSource"; | ||||||
| import Translations from "../i18n/Translations"; | import Translations from "../i18n/Translations"; | ||||||
| import Toggle from "../Input/Toggle"; | import Toggle from "../Input/Toggle"; | ||||||
| import Combine from "../Base/Combine"; | import Combine from "../Base/Combine"; | ||||||
| import State from "../../State"; |  | ||||||
| import Svg from "../../Svg"; | import Svg from "../../Svg"; | ||||||
| import {Tag} from "../../Logic/Tags/Tag"; | import {Tag} from "../../Logic/Tags/Tag"; | ||||||
| import ChangeTagAction from "../../Logic/Osm/Actions/ChangeTagAction"; | import ChangeTagAction from "../../Logic/Osm/Actions/ChangeTagAction"; | ||||||
|  | import {Changes} from "../../Logic/Osm/Changes"; | ||||||
|  | import {OsmConnection} from "../../Logic/Osm/OsmConnection"; | ||||||
| 
 | 
 | ||||||
| export default class DeleteImage extends Toggle { | export default class DeleteImage extends Toggle { | ||||||
| 
 | 
 | ||||||
|     constructor(key: string, tags: UIEventSource<any>) { |     constructor(key: string, tags: UIEventSource<any>, state: {changes?: Changes, osmConnection?: OsmConnection}) { | ||||||
|         const oldValue = tags.data[key] |         const oldValue = tags.data[key] | ||||||
|         const isDeletedBadge = Translations.t.image.isDeleted.Clone() |         const isDeletedBadge = Translations.t.image.isDeleted.Clone() | ||||||
|             .SetClass("rounded-full p-1") |             .SetClass("rounded-full p-1") | ||||||
|             .SetStyle("color:white;background:#ff8c8c") |             .SetStyle("color:white;background:#ff8c8c") | ||||||
|             .onClick(async () => { |             .onClick(async () => { | ||||||
|                 await State.state?.changes?.applyAction(new ChangeTagAction(tags.data.id, new Tag(key, oldValue), tags.data, { |                 await state?.changes?.applyAction(new ChangeTagAction(tags.data.id, new Tag(key, oldValue), tags.data, { | ||||||
|                     changeType: "answer", |                     changeType: "answer", | ||||||
|                     theme: "test" |                     theme: "test" | ||||||
|                 })) |                 })) | ||||||
|  | @ -25,7 +26,7 @@ export default class DeleteImage extends Toggle { | ||||||
|             .SetClass("block w-full pl-4 pr-4") |             .SetClass("block w-full pl-4 pr-4") | ||||||
|             .SetStyle("color:white;background:#ff8c8c; border-top-left-radius:30rem; border-top-right-radius: 30rem;") |             .SetStyle("color:white;background:#ff8c8c; border-top-left-radius:30rem; border-top-right-radius: 30rem;") | ||||||
|             .onClick(async () => { |             .onClick(async () => { | ||||||
|                 await State.state?.changes?.applyAction( |                 await state?.changes?.applyAction( | ||||||
|                     new ChangeTagAction(tags.data.id, new Tag(key, ""), tags.data, { |                     new ChangeTagAction(tags.data.id, new Tag(key, ""), tags.data, { | ||||||
|                         changeType: "answer", |                         changeType: "answer", | ||||||
|                         theme: "test" |                         theme: "test" | ||||||
|  | @ -53,7 +54,7 @@ export default class DeleteImage extends Toggle { | ||||||
|                 tags.map(tags => (tags[key] ?? "") !== "") |                 tags.map(tags => (tags[key] ?? "") !== "") | ||||||
|             ), |             ), | ||||||
|             undefined /*Login (and thus editing) is disabled*/, |             undefined /*Login (and thus editing) is disabled*/, | ||||||
|             State.state.osmConnection.isLoggedIn |             state.osmConnection.isLoggedIn | ||||||
|         ) |         ) | ||||||
|         this.SetClass("cursor-pointer") |         this.SetClass("cursor-pointer") | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | @ -6,12 +6,14 @@ import {AttributedImage} from "./AttributedImage"; | ||||||
| import BaseUIElement from "../BaseUIElement"; | import BaseUIElement from "../BaseUIElement"; | ||||||
| import Toggle from "../Input/Toggle"; | import Toggle from "../Input/Toggle"; | ||||||
| import ImageProvider from "../../Logic/ImageProviders/ImageProvider"; | import ImageProvider from "../../Logic/ImageProviders/ImageProvider"; | ||||||
|  | import {OsmConnection} from "../../Logic/Osm/OsmConnection"; | ||||||
|  | import {Changes} from "../../Logic/Osm/Changes"; | ||||||
| 
 | 
 | ||||||
| export class ImageCarousel extends Toggle { | export class ImageCarousel extends Toggle { | ||||||
| 
 | 
 | ||||||
|     constructor(images: UIEventSource<{ key: string, url: string, provider: ImageProvider }[]>, |     constructor(images: UIEventSource<{ key: string, url: string, provider: ImageProvider }[]>, | ||||||
|                 tags: UIEventSource<any>, |                 tags: UIEventSource<any>, | ||||||
|                 keys: string[]) { |                 state: {osmConnection?: OsmConnection, changes?: Changes}) { | ||||||
|         const uiElements = images.map((imageURLS: { key: string, url: string, provider: ImageProvider }[]) => { |         const uiElements = images.map((imageURLS: { key: string, url: string, provider: ImageProvider }[]) => { | ||||||
|             const uiElements: BaseUIElement[] = []; |             const uiElements: BaseUIElement[] = []; | ||||||
|             for (const url of imageURLS) { |             for (const url of imageURLS) { | ||||||
|  | @ -21,7 +23,7 @@ export class ImageCarousel extends Toggle { | ||||||
|                     if (url.key !== undefined) { |                     if (url.key !== undefined) { | ||||||
|                         image = new Combine([ |                         image = new Combine([ | ||||||
|                             image, |                             image, | ||||||
|                             new DeleteImage(url.key, tags).SetClass("delete-image-marker absolute top-0 left-0 pl-3") |                             new DeleteImage(url.key, tags, state).SetClass("delete-image-marker absolute top-0 left-0 pl-3") | ||||||
|                         ]).SetClass("relative"); |                         ]).SetClass("relative"); | ||||||
|                     } |                     } | ||||||
|                     image |                     image | ||||||
|  |  | ||||||
|  | @ -1,5 +1,4 @@ | ||||||
| import {UIEventSource} from "../../Logic/UIEventSource"; | import {UIEventSource} from "../../Logic/UIEventSource"; | ||||||
| import State from "../../State"; |  | ||||||
| import Combine from "../Base/Combine"; | import Combine from "../Base/Combine"; | ||||||
| import Translations from "../i18n/Translations"; | import Translations from "../i18n/Translations"; | ||||||
| import Svg from "../../Svg"; | import Svg from "../../Svg"; | ||||||
|  | @ -13,13 +12,23 @@ import ChangeTagAction from "../../Logic/Osm/Actions/ChangeTagAction"; | ||||||
| import LayerConfig from "../../Models/ThemeConfig/LayerConfig"; | import LayerConfig from "../../Models/ThemeConfig/LayerConfig"; | ||||||
| import {FixedUiElement} from "../Base/FixedUiElement"; | import {FixedUiElement} from "../Base/FixedUiElement"; | ||||||
| import {VariableUiElement} from "../Base/VariableUIElement"; | import {VariableUiElement} from "../Base/VariableUIElement"; | ||||||
|  | import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"; | ||||||
|  | import {OsmConnection} from "../../Logic/Osm/OsmConnection"; | ||||||
|  | import {Changes} from "../../Logic/Osm/Changes"; | ||||||
| 
 | 
 | ||||||
| export class ImageUploadFlow extends Toggle { | export class ImageUploadFlow extends Toggle { | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|     private static readonly uploadCountsPerId = new Map<string, UIEventSource<number>>() |     private static readonly uploadCountsPerId = new Map<string, UIEventSource<number>>() | ||||||
| 
 | 
 | ||||||
|     constructor(tagsSource: UIEventSource<any>, imagePrefix: string = "image", text: string = undefined) { |     constructor(tagsSource: UIEventSource<any>,  | ||||||
|  |                 state: { | ||||||
|  |                     osmConnection: OsmConnection; | ||||||
|  |                     layoutToUse: LayoutConfig; | ||||||
|  |                     changes: Changes, | ||||||
|  |                     featureSwitchUserbadge: UIEventSource<boolean>; | ||||||
|  |                 }, | ||||||
|  |                 imagePrefix: string = "image", text: string = undefined) { | ||||||
|         const perId = ImageUploadFlow.uploadCountsPerId |         const perId = ImageUploadFlow.uploadCountsPerId | ||||||
|         const id = tagsSource.data.id |         const id = tagsSource.data.id | ||||||
|         if (!perId.has(id)) { |         if (!perId.has(id)) { | ||||||
|  | @ -41,17 +50,17 @@ export class ImageUploadFlow extends Toggle { | ||||||
|             console.log("Adding image:" + key, url); |             console.log("Adding image:" + key, url); | ||||||
|             uploadedCount.data++ |             uploadedCount.data++ | ||||||
|             uploadedCount.ping() |             uploadedCount.ping() | ||||||
|             Promise.resolve(State.state.changes |             Promise.resolve(state.changes | ||||||
|                 .applyAction(new ChangeTagAction( |                 .applyAction(new ChangeTagAction( | ||||||
|                     tags.id, new Tag(key, url), tagsSource.data, |                     tags.id, new Tag(key, url), tagsSource.data, | ||||||
|                     { |                     { | ||||||
|                         changeType: "add-image", |                         changeType: "add-image", | ||||||
|                         theme: State.state.layoutToUse.id |                         theme: state.layoutToUse.id | ||||||
|                     } |                     } | ||||||
|                 ))) |                 ))) | ||||||
|         }) |         }) | ||||||
| 
 | 
 | ||||||
|         const licensePicker = new LicensePicker() |         const licensePicker = new LicensePicker(state) | ||||||
| 
 | 
 | ||||||
|         const t = Translations.t.image; |         const t = Translations.t.image; | ||||||
| 
 | 
 | ||||||
|  | @ -90,7 +99,7 @@ export class ImageUploadFlow extends Toggle { | ||||||
| 
 | 
 | ||||||
|             const tags = tagsSource.data; |             const tags = tagsSource.data; | ||||||
| 
 | 
 | ||||||
|             const layout = State.state?.layoutToUse |             const layout = state?.layoutToUse | ||||||
|             let matchingLayer: LayerConfig = undefined |             let matchingLayer: LayerConfig = undefined | ||||||
|             for (const layer of layout?.layers ?? []) { |             for (const layer of layout?.layers ?? []) { | ||||||
|                 if (layer.source.osmTags.matchesProperties(tags)) { |                 if (layer.source.osmTags.matchesProperties(tags)) { | ||||||
|  | @ -102,7 +111,7 @@ export class ImageUploadFlow extends Toggle { | ||||||
| 
 | 
 | ||||||
|             const title = matchingLayer?.title?.GetRenderValue(tags)?.ConstructElement()?.innerText ?? tags.name ?? "Unknown area"; |             const title = matchingLayer?.title?.GetRenderValue(tags)?.ConstructElement()?.innerText ?? tags.name ?? "Unknown area"; | ||||||
|             const description = [ |             const description = [ | ||||||
|                 "author:" + State.state.osmConnection.userDetails.data.name, |                 "author:" + state.osmConnection.userDetails.data.name, | ||||||
|                 "license:" + license, |                 "license:" + license, | ||||||
|                 "osmid:" + tags.id, |                 "osmid:" + tags.id, | ||||||
|             ].join("\n"); |             ].join("\n"); | ||||||
|  | @ -146,17 +155,17 @@ export class ImageUploadFlow extends Toggle { | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|         const pleaseLoginButton = t.pleaseLogin.Clone() |         const pleaseLoginButton = t.pleaseLogin.Clone() | ||||||
|             .onClick(() => State.state.osmConnection.AttemptLogin()) |             .onClick(() => state.osmConnection.AttemptLogin()) | ||||||
|             .SetClass("login-button-friendly"); |             .SetClass("login-button-friendly"); | ||||||
|         super( |         super( | ||||||
|             new Toggle( |             new Toggle( | ||||||
|                 /*We can show the actual upload button!*/ |                 /*We can show the actual upload button!*/ | ||||||
|                 uploadFlow, |                 uploadFlow, | ||||||
|                 /* User not logged in*/ pleaseLoginButton, |                 /* User not logged in*/ pleaseLoginButton, | ||||||
|                 State.state?.osmConnection?.isLoggedIn |                 state?.osmConnection?.isLoggedIn | ||||||
|             ), |             ), | ||||||
|             undefined /* Nothing as the user badge is disabled*/, |             undefined /* Nothing as the user badge is disabled*/, | ||||||
|             State.state.featureSwitchUserbadge |             state.featureSwitchUserbadge | ||||||
|         ) |         ) | ||||||
| 
 | 
 | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | @ -1,6 +1,5 @@ | ||||||
| import {UIEventSource} from "../../Logic/UIEventSource"; | import {UIEventSource} from "../../Logic/UIEventSource"; | ||||||
| import Combine from "../Base/Combine"; | import Combine from "../Base/Combine"; | ||||||
| import State from "../../State"; |  | ||||||
| import {FixedUiElement} from "../Base/FixedUiElement"; | import {FixedUiElement} from "../Base/FixedUiElement"; | ||||||
| import {OH} from "./OpeningHours"; | import {OH} from "./OpeningHours"; | ||||||
| import Translations from "../i18n/Translations"; | import Translations from "../i18n/Translations"; | ||||||
|  | @ -11,6 +10,7 @@ import Toggle from "../Input/Toggle"; | ||||||
| import {VariableUiElement} from "../Base/VariableUIElement"; | import {VariableUiElement} from "../Base/VariableUIElement"; | ||||||
| import Table from "../Base/Table"; | import Table from "../Base/Table"; | ||||||
| import {Translation} from "../i18n/Translation"; | import {Translation} from "../i18n/Translation"; | ||||||
|  | import {OsmConnection} from "../../Logic/Osm/OsmConnection"; | ||||||
| 
 | 
 | ||||||
| export default class OpeningHoursVisualization extends Toggle { | export default class OpeningHoursVisualization extends Toggle { | ||||||
|     private static readonly weekdays: Translation[] = [ |     private static readonly weekdays: Translation[] = [ | ||||||
|  | @ -23,7 +23,7 @@ export default class OpeningHoursVisualization extends Toggle { | ||||||
|         Translations.t.general.weekdays.abbreviations.sunday, |         Translations.t.general.weekdays.abbreviations.sunday, | ||||||
|     ] |     ] | ||||||
| 
 | 
 | ||||||
|     constructor(tags: UIEventSource<any>, key: string, prefix = "", postfix = "") { |     constructor(tags: UIEventSource<any>, state:{osmConnection?: OsmConnection}, key: string, prefix = "", postfix = "") { | ||||||
|         const tagsDirect = tags.data; |         const tagsDirect = tags.data; | ||||||
|         const ohTable = new VariableUiElement(tags |         const ohTable = new VariableUiElement(tags | ||||||
|             .map(tags => { |             .map(tags => { | ||||||
|  | @ -57,7 +57,7 @@ export default class OpeningHoursVisualization extends Toggle { | ||||||
|                             new Toggle( |                             new Toggle( | ||||||
|                                 new FixedUiElement(e).SetClass("subtle"), |                                 new FixedUiElement(e).SetClass("subtle"), | ||||||
|                                 undefined, |                                 undefined, | ||||||
|                                 State.state?.osmConnection?.userDetails.map(userdetails => userdetails.csCount >= Constants.userJourney.tagsVisibleAndWikiLinked) |                                 state?.osmConnection?.userDetails.map(userdetails => userdetails.csCount >= Constants.userJourney.tagsVisibleAndWikiLinked) | ||||||
|                             ) |                             ) | ||||||
|                         ]); |                         ]); | ||||||
|                     } |                     } | ||||||
|  |  | ||||||
|  | @ -42,7 +42,7 @@ export default class EditableTagRendering extends Toggle { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private static CreateRendering(tags: UIEventSource<any>, configuration: TagRenderingConfig, units: Unit[], editMode: UIEventSource<boolean>): BaseUIElement { |     private static CreateRendering(tags: UIEventSource<any>, configuration: TagRenderingConfig, units: Unit[], editMode: UIEventSource<boolean>): BaseUIElement { | ||||||
|         const answer: BaseUIElement = new TagRenderingAnswer(tags, configuration) |         const answer: BaseUIElement = new TagRenderingAnswer(tags, configuration, State.state) | ||||||
|         answer.SetClass("w-full") |         answer.SetClass("w-full") | ||||||
|         let rendering = answer; |         let rendering = answer; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -40,7 +40,7 @@ export default class FeatureInfoBox extends ScrollableFullScreen { | ||||||
| 
 | 
 | ||||||
|     private static GenerateTitleBar(tags: UIEventSource<any>, |     private static GenerateTitleBar(tags: UIEventSource<any>, | ||||||
|                                     layerConfig: LayerConfig): BaseUIElement { |                                     layerConfig: LayerConfig): BaseUIElement { | ||||||
|         const title = new TagRenderingAnswer(tags, layerConfig.title ?? new TagRenderingConfig("POI")) |         const title = new TagRenderingAnswer(tags, layerConfig.title ?? new TagRenderingConfig("POI"), State.state) | ||||||
|             .SetClass("break-words font-bold sm:p-0.5 md:p-1 sm:p-1.5 md:p-2"); |             .SetClass("break-words font-bold sm:p-0.5 md:p-1 sm:p-1.5 md:p-2"); | ||||||
|         const titleIcons = new Combine( |         const titleIcons = new Combine( | ||||||
|             layerConfig.titleIcons.map(icon => new TagRenderingAnswer(tags, icon, |             layerConfig.titleIcons.map(icon => new TagRenderingAnswer(tags, icon, | ||||||
|  | @ -88,7 +88,8 @@ export default class FeatureInfoBox extends ScrollableFullScreen { | ||||||
| 
 | 
 | ||||||
|                     if (tr.render !== undefined) { |                     if (tr.render !== undefined) { | ||||||
|                         questionBox.SetClass("text-sm") |                         questionBox.SetClass("text-sm") | ||||||
|                         const renderedQuestion = new TagRenderingAnswer(tags, tr, tr.group + " questions", "", { |                         const renderedQuestion = new TagRenderingAnswer(tags, tr,State.state, | ||||||
|  |                             tr.group + " questions", "", { | ||||||
|                             specialViz: new Map<string, BaseUIElement>([["questions", questionBox]]) |                             specialViz: new Map<string, BaseUIElement>([["questions", questionBox]]) | ||||||
|                         }) |                         }) | ||||||
|                         const possiblyHidden = new Toggle( |                         const possiblyHidden = new Toggle( | ||||||
|  | @ -163,7 +164,7 @@ export default class FeatureInfoBox extends ScrollableFullScreen { | ||||||
| 
 | 
 | ||||||
|         const hasMinimap = layerConfig.tagRenderings.some(tr => FeatureInfoBox.hasMinimap(tr)) |         const hasMinimap = layerConfig.tagRenderings.some(tr => FeatureInfoBox.hasMinimap(tr)) | ||||||
|         if (!hasMinimap) { |         if (!hasMinimap) { | ||||||
|             allRenderings.push(new TagRenderingAnswer(tags, SharedTagRenderings.SharedTagRendering.get("minimap"))) |             allRenderings.push(new TagRenderingAnswer(tags, SharedTagRenderings.SharedTagRendering.get("minimap"), State.state)) | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         editElements.push( |         editElements.push( | ||||||
|  | @ -177,7 +178,7 @@ export default class FeatureInfoBox extends ScrollableFullScreen { | ||||||
|                             return undefined |                             return undefined | ||||||
|                         } |                         } | ||||||
| 
 | 
 | ||||||
|                         return new TagRenderingAnswer(tags, SharedTagRenderings.SharedTagRendering.get("last_edit")); |                         return new TagRenderingAnswer(tags, SharedTagRenderings.SharedTagRendering.get("last_edit"), State.state); | ||||||
| 
 | 
 | ||||||
|                     }, [State.state.featureSwitchIsDebugging, State.state.featureSwitchIsTesting]) |                     }, [State.state.featureSwitchIsDebugging, State.state.featureSwitchIsTesting]) | ||||||
|             ) |             ) | ||||||
|  |  | ||||||
|  | @ -19,7 +19,6 @@ import Svg from "../../Svg"; | ||||||
| import {Utils} from "../../Utils"; | import {Utils} from "../../Utils"; | ||||||
| import Minimap from "../Base/Minimap"; | import Minimap from "../Base/Minimap"; | ||||||
| import ShowDataLayer from "../ShowDataLayer/ShowDataLayer"; | import ShowDataLayer from "../ShowDataLayer/ShowDataLayer"; | ||||||
| import AllKnownLayers from "../../Customizations/AllKnownLayers"; |  | ||||||
| import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource"; | import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource"; | ||||||
| import ShowDataMultiLayer from "../ShowDataLayer/ShowDataMultiLayer"; | import ShowDataMultiLayer from "../ShowDataLayer/ShowDataMultiLayer"; | ||||||
| import CreateWayWithPointReuseAction, {MergePointConfig} from "../../Logic/Osm/Actions/CreateWayWithPointReuseAction"; | import CreateWayWithPointReuseAction, {MergePointConfig} from "../../Logic/Osm/Actions/CreateWayWithPointReuseAction"; | ||||||
|  | @ -35,6 +34,8 @@ import ReplaceGeometryAction from "../../Logic/Osm/Actions/ReplaceGeometryAction | ||||||
| import CreateMultiPolygonWithPointReuseAction from "../../Logic/Osm/Actions/CreateMultiPolygonWithPointReuseAction"; | import CreateMultiPolygonWithPointReuseAction from "../../Logic/Osm/Actions/CreateMultiPolygonWithPointReuseAction"; | ||||||
| import {Tag} from "../../Logic/Tags/Tag"; | import {Tag} from "../../Logic/Tags/Tag"; | ||||||
| import TagApplyButton from "./TagApplyButton"; | import TagApplyButton from "./TagApplyButton"; | ||||||
|  | import LayerConfig from "../../Models/ThemeConfig/LayerConfig"; | ||||||
|  | import * as conflation_json from "../../assets/layers/conflation/conflation.json"; | ||||||
| import {GeoOperations} from "../../Logic/GeoOperations"; | import {GeoOperations} from "../../Logic/GeoOperations"; | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -256,7 +257,7 @@ ${Utils.special_visualizations_importRequirementDocs} | ||||||
|                 zoomToFeatures: false, |                 zoomToFeatures: false, | ||||||
|                 features: changePreview, |                 features: changePreview, | ||||||
|                 allElements: state.allElements, |                 allElements: state.allElements, | ||||||
|                 layerToShow: AllKnownLayers.sharedLayers.get("conflation") |                 layerToShow: new LayerConfig(conflation_json, "all_known_layers", true) | ||||||
|             }) |             }) | ||||||
|         }) |         }) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -12,6 +12,7 @@ import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig"; | ||||||
| export default class TagRenderingAnswer extends VariableUiElement { | export default class TagRenderingAnswer extends VariableUiElement { | ||||||
| 
 | 
 | ||||||
|     constructor(tagsSource: UIEventSource<any>, configuration: TagRenderingConfig, |     constructor(tagsSource: UIEventSource<any>, configuration: TagRenderingConfig, | ||||||
|  |                 state: any, | ||||||
|                 contentClasses: string = "", contentStyle: string = "", options?:{ |                 contentClasses: string = "", contentStyle: string = "", options?:{ | ||||||
|                     specialViz: Map<string, BaseUIElement> |                     specialViz: Map<string, BaseUIElement> | ||||||
|                 }) { |                 }) { | ||||||
|  | @ -37,7 +38,7 @@ export default class TagRenderingAnswer extends VariableUiElement { | ||||||
|                 return undefined; |                 return undefined; | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             const valuesToRender: BaseUIElement[] = trs.map(tr => new SubstitutedTranslation(tr, tagsSource, options?.specialViz)) |             const valuesToRender: BaseUIElement[] = trs.map(tr => new SubstitutedTranslation(tr, tagsSource, state, options?.specialViz)) | ||||||
|             if (valuesToRender.length === 1) { |             if (valuesToRender.length === 1) { | ||||||
|                 return valuesToRender[0]; |                 return valuesToRender[0]; | ||||||
|             } else if (valuesToRender.length > 1) { |             } else if (valuesToRender.length > 1) { | ||||||
|  |  | ||||||
|  | @ -71,7 +71,7 @@ export default class TagRenderingQuestion extends Combine { | ||||||
|         } |         } | ||||||
|         options = options ?? {} |         options = options ?? {} | ||||||
|         const applicableUnit = (options.units ?? []).filter(unit => unit.isApplicableToKey(configuration.freeform?.key))[0]; |         const applicableUnit = (options.units ?? []).filter(unit => unit.isApplicableToKey(configuration.freeform?.key))[0]; | ||||||
|         const question = new SubstitutedTranslation(configuration.question, tags) |         const question = new SubstitutedTranslation(configuration.question, tags, State.state) | ||||||
|             .SetClass("question-text"); |             .SetClass("question-text"); | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -352,7 +352,7 @@ export default class TagRenderingQuestion extends Combine { | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         return new FixedInputElement( |         return new FixedInputElement( | ||||||
|             new SubstitutedTranslation(mapping.then, tagsSource), |             new SubstitutedTranslation(mapping.then, tagsSource, State.state), | ||||||
|             tagging, |             tagging, | ||||||
|             (t0, t1) => t1.isEquivalent(t0)); |             (t0, t1) => t1.isEquivalent(t0)); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | @ -1,34 +0,0 @@ | ||||||
| import {FixedUiElement} from "./Base/FixedUiElement"; |  | ||||||
| import Combine from "./Base/Combine"; |  | ||||||
| import MoreScreen from "./BigComponents/MoreScreen"; |  | ||||||
| import Translations from "./i18n/Translations"; |  | ||||||
| import Constants from "../Models/Constants"; |  | ||||||
| import UserRelatedState from "../Logic/State/UserRelatedState"; |  | ||||||
| import {Utils} from "../Utils"; |  | ||||||
| import LanguagePicker from "./LanguagePicker"; |  | ||||||
| import IndexText from "./BigComponents/IndexText"; |  | ||||||
| import FeaturedMessage from "./BigComponents/FeaturedMessage"; |  | ||||||
| 
 |  | ||||||
| export default class Professional { |  | ||||||
|     constructor() { |  | ||||||
|         new FixedUiElement("").AttachTo("centermessage") |  | ||||||
|          |  | ||||||
|         const state = new UserRelatedState(undefined); |  | ||||||
|         const intro = new Combine([ |  | ||||||
|             LanguagePicker.CreateLanguagePicker(Translations.t.index.title.SupportedLanguages()) |  | ||||||
|                 .SetClass("absolute top-2 right-3"), |  | ||||||
|             new IndexText() |  | ||||||
|         ]); |  | ||||||
|         new Combine([ |  | ||||||
|             intro, |  | ||||||
|             new FeaturedMessage(), |  | ||||||
|             new MoreScreen(state, true), |  | ||||||
|             Translations.t.general.aboutMapcomplete |  | ||||||
|                 .Subs({"osmcha_link": Utils.OsmChaLinkFor(7)}) |  | ||||||
|                 .SetClass("link-underline"), |  | ||||||
|             new FixedUiElement("v" + Constants.vNumber) |  | ||||||
|         ]).SetClass("block m-5 lg:w-3/4 lg:ml-40") |  | ||||||
|             .SetStyle("pointer-events: all;") |  | ||||||
|             .AttachTo("topleft-tools"); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | @ -97,7 +97,7 @@ export default class ProfessionalGui { | ||||||
|             Svg.back_svg().SetStyle("height: 1.5rem;"), |             Svg.back_svg().SetStyle("height: 1.5rem;"), | ||||||
|             t.backToMapcomplete, |             t.backToMapcomplete, | ||||||
|             { |             { | ||||||
|                 url: window.location.host + "/index.html" |                 url: "./index.html" | ||||||
|             } |             } | ||||||
|         )]).SetClass("block") |         )]).SetClass("block") | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -8,8 +8,7 @@ import {Tiles} from "../../Models/TileRange"; | ||||||
| import * as clusterstyle from "../../assets/layers/cluster_style/cluster_style.json" | import * as clusterstyle from "../../assets/layers/cluster_style/cluster_style.json" | ||||||
| 
 | 
 | ||||||
| export default class ShowTileInfo { | export default class ShowTileInfo { | ||||||
|     public static readonly styling = new LayerConfig( |     public static readonly styling = new LayerConfig(clusterstyle, "ShowTileInfo", true) | ||||||
|         clusterstyle, "tileinfo", true) |  | ||||||
| 
 | 
 | ||||||
|     constructor(options: { |     constructor(options: { | ||||||
|         source: FeatureSource & Tiled, leafletMap: UIEventSource<any>, layer?: LayerConfig, |         source: FeatureSource & Tiled, leafletMap: UIEventSource<any>, layer?: LayerConfig, | ||||||
|  |  | ||||||
|  | @ -27,7 +27,6 @@ import AllImageProviders from "../Logic/ImageProviders/AllImageProviders"; | ||||||
| import WikipediaBox from "./Wikipedia/WikipediaBox"; | import WikipediaBox from "./Wikipedia/WikipediaBox"; | ||||||
| import SimpleMetaTagger from "../Logic/SimpleMetaTagger"; | import SimpleMetaTagger from "../Logic/SimpleMetaTagger"; | ||||||
| import MultiApply from "./Popup/MultiApply"; | import MultiApply from "./Popup/MultiApply"; | ||||||
| import AllKnownLayers from "../Customizations/AllKnownLayers"; |  | ||||||
| import ShowDataLayer from "./ShowDataLayer/ShowDataLayer"; | import ShowDataLayer from "./ShowDataLayer/ShowDataLayer"; | ||||||
| import {SubtleButton} from "./Base/SubtleButton"; | import {SubtleButton} from "./Base/SubtleButton"; | ||||||
| import {DefaultGuiState} from "./DefaultGuiState"; | import {DefaultGuiState} from "./DefaultGuiState"; | ||||||
|  | @ -37,6 +36,7 @@ import FeaturePipelineState from "../Logic/State/FeaturePipelineState"; | ||||||
| import {ConflateButton, ImportPointButton, ImportWayButton} from "./Popup/ImportButton"; | import {ConflateButton, ImportPointButton, ImportWayButton} from "./Popup/ImportButton"; | ||||||
| import TagApplyButton from "./Popup/TagApplyButton"; | import TagApplyButton from "./Popup/TagApplyButton"; | ||||||
| import AutoApplyButton from "./Popup/AutoApplyButton"; | import AutoApplyButton from "./Popup/AutoApplyButton"; | ||||||
|  | import * as left_right_style_json from "../assets/layers/left_right_style/left_right_style.json"; | ||||||
| import {OpenIdEditor} from "./BigComponents/CopyrightPanel"; | import {OpenIdEditor} from "./BigComponents/CopyrightPanel"; | ||||||
| 
 | 
 | ||||||
| export interface SpecialVisualization { | export interface SpecialVisualization { | ||||||
|  | @ -52,7 +52,6 @@ export default class SpecialVisualizations { | ||||||
| 
 | 
 | ||||||
|     public static specialVisualizations = SpecialVisualizations.init() |     public static specialVisualizations = SpecialVisualizations.init() | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
|     private static init(){ |     private static init(){ | ||||||
|       const  specialVisualizations: SpecialVisualization[] = |       const  specialVisualizations: SpecialVisualization[] = | ||||||
|             [ |             [ | ||||||
|  | @ -105,7 +104,7 @@ export default class SpecialVisualizations { | ||||||
|                         if (args.length > 0) { |                         if (args.length > 0) { | ||||||
|                             imagePrefixes = [].concat(...args.map(a => a.split(","))); |                             imagePrefixes = [].concat(...args.map(a => a.split(","))); | ||||||
|                         } |                         } | ||||||
|                         return new ImageCarousel(AllImageProviders.LoadImagesFor(tags, imagePrefixes), tags, imagePrefixes); |                         return new ImageCarousel(AllImageProviders.LoadImagesFor(tags, imagePrefixes), tags, state); | ||||||
|                     } |                     } | ||||||
|                 }, |                 }, | ||||||
|                 { |                 { | ||||||
|  | @ -121,7 +120,7 @@ export default class SpecialVisualizations { | ||||||
|                         defaultValue: "Add image" |                         defaultValue: "Add image" | ||||||
|                     }], |                     }], | ||||||
|                     constr: (state: State, tags, args) => { |                     constr: (state: State, tags, args) => { | ||||||
|                         return new ImageUploadFlow(tags, args[0], args[1]) |                         return new ImageUploadFlow(tags, state, args[0], args[1]) | ||||||
|                     } |                     } | ||||||
|                 }, |                 }, | ||||||
|                 { |                 { | ||||||
|  | @ -162,7 +161,7 @@ export default class SpecialVisualizations { | ||||||
|                         } |                         } | ||||||
|                     ], |                     ], | ||||||
|                     example: "`{minimap()}`, `{minimap(17, id, _list_of_embedded_feature_ids_calculated_by_calculated_tag):height:10rem; border: 2px solid black}`", |                     example: "`{minimap()}`, `{minimap(17, id, _list_of_embedded_feature_ids_calculated_by_calculated_tag):height:10rem; border: 2px solid black}`", | ||||||
|                     constr: (state, tagSource, args, defaultGuiState) => { |                     constr: (state, tagSource, args, _) => { | ||||||
| 
 | 
 | ||||||
|                         const keys = [...args] |                         const keys = [...args] | ||||||
|                         keys.splice(0, 1) |                         keys.splice(0, 1) | ||||||
|  | @ -268,7 +267,7 @@ export default class SpecialVisualizations { | ||||||
|                                 leafletMap: minimap["leafletMap"], |                                 leafletMap: minimap["leafletMap"], | ||||||
|                                 enablePopups: false, |                                 enablePopups: false, | ||||||
|                                 zoomToFeatures: true, |                                 zoomToFeatures: true, | ||||||
|                                 layerToShow: AllKnownLayers.sharedLayers.get("left_right_style"), |                                 layerToShow: new LayerConfig(left_right_style_json, "all_known_layers", true), | ||||||
|                                 features: new StaticFeatureSource([copy], false), |                                 features: new StaticFeatureSource([copy], false), | ||||||
|                                 allElements: State.state.allElements |                                 allElements: State.state.allElements | ||||||
|                             } |                             } | ||||||
|  | @ -325,7 +324,7 @@ export default class SpecialVisualizations { | ||||||
|                     }], |                     }], | ||||||
|                     example: "A normal opening hours table can be invoked with `{opening_hours_table()}`. A table for e.g. conditional access with opening hours can be `{opening_hours_table(access:conditional, no @ &LPARENS, &RPARENS)}`", |                     example: "A normal opening hours table can be invoked with `{opening_hours_table()}`. A table for e.g. conditional access with opening hours can be `{opening_hours_table(access:conditional, no @ &LPARENS, &RPARENS)}`", | ||||||
|                     constr: (state: State, tagSource: UIEventSource<any>, args) => { |                     constr: (state: State, tagSource: UIEventSource<any>, args) => { | ||||||
|                         return new OpeningHoursVisualization(tagSource, args[0], args[1], args[2]) |                         return new OpeningHoursVisualization(tagSource, state, args[0], args[1], args[2]) | ||||||
|                     } |                     } | ||||||
|                 }, |                 }, | ||||||
|                 { |                 { | ||||||
|  |  | ||||||
|  | @ -1,7 +1,6 @@ | ||||||
| import {UIEventSource} from "../Logic/UIEventSource"; | import {UIEventSource} from "../Logic/UIEventSource"; | ||||||
| import {Translation} from "./i18n/Translation"; | import {Translation} from "./i18n/Translation"; | ||||||
| import Locale from "./i18n/Locale"; | import Locale from "./i18n/Locale"; | ||||||
| import State from "../State"; |  | ||||||
| import {FixedUiElement} from "./Base/FixedUiElement"; | import {FixedUiElement} from "./Base/FixedUiElement"; | ||||||
| import SpecialVisualizations, {SpecialVisualization} from "./SpecialVisualizations"; | import SpecialVisualizations, {SpecialVisualization} from "./SpecialVisualizations"; | ||||||
| import {Utils} from "../Utils"; | import {Utils} from "../Utils"; | ||||||
|  | @ -15,6 +14,7 @@ export class SubstitutedTranslation extends VariableUiElement { | ||||||
|     public constructor( |     public constructor( | ||||||
|         translation: Translation, |         translation: Translation, | ||||||
|         tagsSource: UIEventSource<any>, |         tagsSource: UIEventSource<any>, | ||||||
|  |         state, | ||||||
|         mapping: Map<string, BaseUIElement> = undefined) { |         mapping: Map<string, BaseUIElement> = undefined) { | ||||||
| 
 | 
 | ||||||
|         const extraMappings: SpecialVisualization[] = []; |         const extraMappings: SpecialVisualization[] = []; | ||||||
|  | @ -50,7 +50,7 @@ export class SubstitutedTranslation extends VariableUiElement { | ||||||
|                         } |                         } | ||||||
|                         const viz = proto.special; |                         const viz = proto.special; | ||||||
|                         try { |                         try { | ||||||
|                             return viz.func.constr(State.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) { |                         } catch (e) { | ||||||
|                             console.error("SPECIALRENDERING FAILED for", tagsSource.data?.id, 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") |                             return new FixedUiElement(`Could not generate special rendering for ${viz.func.funcName}(${viz.args.join(", ")}) ${e}`).SetStyle("alert") | ||||||
|  |  | ||||||
|  | @ -13,6 +13,9 @@ export class Translation extends BaseUIElement { | ||||||
|         if (translations === undefined) { |         if (translations === undefined) { | ||||||
|             throw `Translation without content (${context})` |             throw `Translation without content (${context})` | ||||||
|         } |         } | ||||||
|  |         if(typeof translations === "string"){ | ||||||
|  |             translations = {"*": translations}; | ||||||
|  |         } | ||||||
|         let count = 0; |         let count = 0; | ||||||
|         for (const translationsKey in translations) { |         for (const translationsKey in translations) { | ||||||
|             if (!translations.hasOwnProperty(translationsKey)) { |             if (!translations.hasOwnProperty(translationsKey)) { | ||||||
|  |  | ||||||
							
								
								
									
										14
									
								
								Utils.ts
									
										
									
									
									
								
							
							
						
						
									
										14
									
								
								Utils.ts
									
										
									
									
									
								
							|  | @ -191,7 +191,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be | ||||||
|         return newArr; |         return newArr; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public static Dupicates(arr: string[]): string[] { |     public static Dupiclates(arr: string[]): string[] { | ||||||
|         if (arr === undefined) { |         if (arr === undefined) { | ||||||
|             return undefined; |             return undefined; | ||||||
|         } |         } | ||||||
|  | @ -618,5 +618,17 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be | ||||||
|             b: parseInt(hex.substr(5, 2), 16), |             b: parseInt(hex.substr(5, 2), 16), | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Deepclone an object by serializing and deserializing it | ||||||
|  |      * @param x | ||||||
|  |      * @constructor | ||||||
|  |      */ | ||||||
|  |     static Clone<T>(x: T): T { | ||||||
|  |         if(x === undefined){ | ||||||
|  |             return undefined; | ||||||
|  |         } | ||||||
|  |         return JSON.parse(JSON.stringify(x)); | ||||||
|  |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
							
								
								
									
										18
									
								
								all_themes_index.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								all_themes_index.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,18 @@ | ||||||
|  | import {Utils} from "./Utils"; | ||||||
|  | import AllThemesGui from "./UI/AllThemesGui"; | ||||||
|  | import {QueryParameters} from "./Logic/Web/QueryParameters"; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | const layout = QueryParameters.GetQueryParameter("layout", undefined).data ?? "" | ||||||
|  | const customLayout = QueryParameters.GetQueryParameter("userlayout", undefined).data ?? "" | ||||||
|  | const l = window.location; | ||||||
|  | if( layout !== ""){ | ||||||
|  |     window.location.replace(l.protocol + "//" + window.location.host+"/"+layout+".html"+ l.search + l.hash); | ||||||
|  | }else if (customLayout !== ""){ | ||||||
|  |     window.location.replace(l.protocol + "//" + window.location.host+"/theme.html"+ l.search + l.hash); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | Utils.DisableLongPresses() | ||||||
|  | document.getElementById("decoration-desktop").remove(); | ||||||
|  | new AllThemesGui(); | ||||||
|  | @ -1,11 +1,14 @@ | ||||||
| { | { | ||||||
|   "defaultIcons": ["phonelink", |   "defaults": { | ||||||
|     "emaillink", |     "builtin": [ | ||||||
|     "wikipedialink", |       "phonelink", | ||||||
|     "osmlink", |       "emaillink", | ||||||
|     "sharelink" |       "wikipedialink", | ||||||
|   ], |       "osmlink", | ||||||
|    |       "sharelink" | ||||||
|  |     ], | ||||||
|  |     "override": {} | ||||||
|  |   }, | ||||||
|   "wikipedialink": { |   "wikipedialink": { | ||||||
|     "render": "<a href='https://wikipedia.org/wiki/{wikipedia}' target='_blank'><img src='./assets/svg/wikipedia.svg' alt='WP'/></a>", |     "render": "<a href='https://wikipedia.org/wiki/{wikipedia}' target='_blank'><img src='./assets/svg/wikipedia.svg' alt='WP'/></a>", | ||||||
|     "condition": { |     "condition": { | ||||||
|  |  | ||||||
|  | @ -36,7 +36,7 @@ | ||||||
|   ], |   ], | ||||||
|   "maintainer": "MapComplete", |   "maintainer": "MapComplete", | ||||||
|   "credits": "Originally created during Open Summer of Code by Pieter Fiers, Thibault Declercq, Pierre Barban, Joost Schouppe and Pieter Vander Vennet", |   "credits": "Originally created during Open Summer of Code by Pieter Fiers, Thibault Declercq, Pierre Barban, Joost Schouppe and Pieter Vander Vennet", | ||||||
|   "icon": "assets/themes/cyclofix/logo.svg", |   "icon": "./assets/themes/cyclofix/logo.svg", | ||||||
|   "version": "0", |   "version": "0", | ||||||
|   "startLat": 0, |   "startLat": 0, | ||||||
|   "defaultBackgroundId": "CartoDB.Voyager", |   "defaultBackgroundId": "CartoDB.Voyager", | ||||||
|  |  | ||||||
|  | @ -80,6 +80,7 @@ | ||||||
|       "builtin": "nature_reserve", |       "builtin": "nature_reserve", | ||||||
|       "wayHandling": 1, |       "wayHandling": 1, | ||||||
|       "override": { |       "override": { | ||||||
|  |         "id": "nature_reserve_centerpoints", | ||||||
|         "source": { |         "source": { | ||||||
|           "osmTags": { |           "osmTags": { | ||||||
|             "+and": [ |             "+and": [ | ||||||
|  |  | ||||||
|  | @ -74,11 +74,11 @@ | ||||||
|           }, |           }, | ||||||
|           "renderings": [ |           "renderings": [ | ||||||
|             { |             { | ||||||
|               "id": "sidewalk_minimap", |               "id": "sidewalk_minimap_left|right", | ||||||
|               "render": "{sided_minimap(left|right):height:8rem;border-radius:0.5rem;overflow:hidden}" |               "render": "{sided_minimap(left|right):height:8rem;border-radius:0.5rem;overflow:hidden}" | ||||||
|             }, |             }, | ||||||
|             { |             { | ||||||
|               "id": "has_sidewalk", |               "id": "has_sidewalk_left|right", | ||||||
|               "question": "Is there a sidewalk on this side of the road?", |               "question": "Is there a sidewalk on this side of the road?", | ||||||
|               "mappings": [ |               "mappings": [ | ||||||
|                 { |                 { | ||||||
|  | @ -92,7 +92,7 @@ | ||||||
|               ] |               ] | ||||||
|             }, |             }, | ||||||
|             { |             { | ||||||
|               "id": "sidewalk_width", |               "id": "sidewalk_width_left|right", | ||||||
|               "question": "What is the width of the sidewalk on this side of the road?", |               "question": "What is the width of the sidewalk on this side of the road?", | ||||||
|               "render": "This sidewalk is {sidewalk:left|right:width}m wide", |               "render": "This sidewalk is {sidewalk:left|right:width}m wide", | ||||||
|               "condition": "sidewalk:left|right=yes", |               "condition": "sidewalk:left|right=yes", | ||||||
|  |  | ||||||
|  | @ -50,7 +50,6 @@ | ||||||
|           "geoJsonZoomLevel": 14, |           "geoJsonZoomLevel": 14, | ||||||
|           "isOsmCache": true |           "isOsmCache": true | ||||||
|         }, |         }, | ||||||
|         "icon": "./assets/themes/speelplekken/speelbos.svg", |  | ||||||
|         "minzoom": 12, |         "minzoom": 12, | ||||||
|         "calculatedTags": [ |         "calculatedTags": [ | ||||||
|           "_is_shadowed=feat.overlapWith('shadow').length > 0 ? 'yes': ''", |           "_is_shadowed=feat.overlapWith('shadow').length > 0 ? 'yes': ''", | ||||||
|  | @ -61,7 +60,6 @@ | ||||||
|     { |     { | ||||||
|       "builtin": "playground", |       "builtin": "playground", | ||||||
|       "override": { |       "override": { | ||||||
|         "icon": "./assets/themes/speelplekken/speeltuin.svg", |  | ||||||
|         "minzoom": 14, |         "minzoom": 14, | ||||||
|         "source": { |         "source": { | ||||||
|           "geoJsonLocal": "http://127.0.0.1:8080/speelplekken_{layer}_{z}_{x}_{y}.geojson", |           "geoJsonLocal": "http://127.0.0.1:8080/speelplekken_{layer}_{z}_{x}_{y}.geojson", | ||||||
|  | @ -78,7 +76,6 @@ | ||||||
|     { |     { | ||||||
|       "builtin": "village_green", |       "builtin": "village_green", | ||||||
|       "override": { |       "override": { | ||||||
|         "icon": "./assets/themes/speelplekken/speelweide.svg", |  | ||||||
|         "minzoom": 14, |         "minzoom": 14, | ||||||
|         "source": { |         "source": { | ||||||
|           "geoJsonLocal": "http://127.0.0.1:8080/speelplekken_{layer}_{z}_{x}_{y}.geojson", |           "geoJsonLocal": "http://127.0.0.1:8080/speelplekken_{layer}_{z}_{x}_{y}.geojson", | ||||||
|  | @ -95,7 +92,6 @@ | ||||||
|     { |     { | ||||||
|       "builtin": "grass_in_parks", |       "builtin": "grass_in_parks", | ||||||
|       "override": { |       "override": { | ||||||
|         "icon": "./assets/themes/speelplekken/speelweide.svg", |  | ||||||
|         "minzoom": 14, |         "minzoom": 14, | ||||||
|         "source": { |         "source": { | ||||||
|           "geoJsonLocal": "http://127.0.0.1:8080/speelplekken_{layer}_{z}_{x}_{y}.geojson", |           "geoJsonLocal": "http://127.0.0.1:8080/speelplekken_{layer}_{z}_{x}_{y}.geojson", | ||||||
|  |  | ||||||
							
								
								
									
										7406
									
								
								dependencies.svg
									
										
									
									
									
								
							
							
						
						
									
										7406
									
								
								dependencies.svg
									
										
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							| Before Width: | Height: | Size: 1.5 MiB | 
|  | @ -77,7 +77,7 @@ | ||||||
| <span class="absolute" id="belowmap" style="z-index: -1">Below</span> | <span class="absolute" id="belowmap" style="z-index: -1">Below</span> | ||||||
| <div id="leafletDiv"></div> | <div id="leafletDiv"></div> | ||||||
| 
 | 
 | ||||||
| <script src="./index.ts"></script> | <script src="./all_themes_index.ts"></script> | ||||||
| <script async data-goatcounter="https://pietervdvn.goatcounter.com/count" src="//gc.zgo.at/count.js"></script> | <script async data-goatcounter="https://pietervdvn.goatcounter.com/count" src="//gc.zgo.at/count.js"></script> | ||||||
| 
 | 
 | ||||||
| </body> | </body> | ||||||
|  |  | ||||||
							
								
								
									
										49
									
								
								index.ts
									
										
									
									
									
								
							
							
						
						
									
										49
									
								
								index.ts
									
										
									
									
									
								
							|  | @ -1,5 +1,4 @@ | ||||||
| import {FixedUiElement} from "./UI/Base/FixedUiElement"; | import {FixedUiElement} from "./UI/Base/FixedUiElement"; | ||||||
| import {QueryParameters} from "./Logic/Web/QueryParameters"; |  | ||||||
| import Combine from "./UI/Base/Combine"; | import Combine from "./UI/Base/Combine"; | ||||||
| import AvailableBaseLayers from "./Logic/Actors/AvailableBaseLayers"; | import AvailableBaseLayers from "./Logic/Actors/AvailableBaseLayers"; | ||||||
| import MinimapImplementation from "./UI/Base/MinimapImplementation"; | import MinimapImplementation from "./UI/Base/MinimapImplementation"; | ||||||
|  | @ -18,19 +17,10 @@ MinimapImplementation.initialize() | ||||||
| AvailableBaseLayers.implement(new AvailableBaseLayersImplementation()) | AvailableBaseLayers.implement(new AvailableBaseLayersImplementation()) | ||||||
| ShowOverlayLayerImplementation.Implement(); | ShowOverlayLayerImplementation.Implement(); | ||||||
| // Miscelleanous
 | // Miscelleanous
 | ||||||
| 
 |  | ||||||
| Utils.DisableLongPresses() | Utils.DisableLongPresses() | ||||||
| 
 | 
 | ||||||
| // --------------------- Special actions based on the parameters -----------------
 |  | ||||||
| // @ts-ignore
 |  | ||||||
| if (location.href.startsWith("http://buurtnatuur.be")) { |  | ||||||
|     // Reload the https version. This is important for the 'locate me' button
 |  | ||||||
|     window.location.replace("https://buurtnatuur.be"); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| class Init { | class Init { | ||||||
|     public static Init(layoutToUse: LayoutConfig, encoded: string) { |     public static Init(layoutToUse: LayoutConfig) { | ||||||
| 
 | 
 | ||||||
|         if (layoutToUse === null) { |         if (layoutToUse === null) { | ||||||
|             // Something went wrong, error message is already on screen
 |             // Something went wrong, error message is already on screen
 | ||||||
|  | @ -43,40 +33,15 @@ class Init { | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         // Workaround/legacy to keep the old paramters working as I renamed some of them
 |  | ||||||
|         if (layoutToUse?.id === "cyclofix") { |  | ||||||
|             const legacy = QueryParameters.GetQueryParameter("layer-bike_shops", "true", "Legacy - keep De Fietsambassade working"); |  | ||||||
|             const correct = QueryParameters.GetQueryParameter("layer-bike_shop", "true", "Legacy - keep De Fietsambassade working") |  | ||||||
|             if (legacy.data !== "true") { |  | ||||||
|                 correct.setData(legacy.data) |  | ||||||
|             } |  | ||||||
|             console.log("layer-bike_shop toggles: legacy:", legacy.data, "new:", correct.data) |  | ||||||
| 
 |  | ||||||
|             const legacyCafe = QueryParameters.GetQueryParameter("layer-bike_cafes", "true", "Legacy - keep De Fietsambassade working") |  | ||||||
|             const correctCafe = QueryParameters.GetQueryParameter("layer-bike_cafe", "true", "Legacy - keep De Fietsambassade working") |  | ||||||
|             if (legacyCafe.data !== "true") { |  | ||||||
|                 correctCafe.setData(legacy.data) |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|         const guiState = new DefaultGuiState() |         const guiState = new DefaultGuiState() | ||||||
|         State.state = new State(layoutToUse); |         State.state = new State(layoutToUse); | ||||||
|         DefaultGuiState.state = guiState; |         DefaultGuiState.state = guiState; | ||||||
|         // This 'leaks' the global state via the window object, useful for debugging
 |         // This 'leaks' the global state via the window object, useful for debugging
 | ||||||
|         // @ts-ignore
 |         // @ts-ignore
 | ||||||
|         window.mapcomplete_state = State.state; |         window.mapcomplete_state = State.state; | ||||||
| 
 |  | ||||||
|         new DefaultGUI(State.state, guiState) |         new DefaultGUI(State.state, guiState) | ||||||
| 
 | 
 | ||||||
|         if (encoded !== undefined && encoded.length > 10) { |         | ||||||
|             // We save the layout to the user settings and local storage
 |  | ||||||
|             State.state.osmConnection.OnLoggedIn(() => { |  | ||||||
|                 State.state.osmConnection |  | ||||||
|                     .GetLongPreference("installed-theme-" + layoutToUse.id) |  | ||||||
|                     .setData(encoded); |  | ||||||
|             }); |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -92,12 +57,12 @@ new Combine(["Initializing... <br/>", | ||||||
|         })]) |         })]) | ||||||
|     .AttachTo("centermessage"); // Add an initialization and reset button if something goes wrong
 |     .AttachTo("centermessage"); // Add an initialization and reset button if something goes wrong
 | ||||||
| 
 | 
 | ||||||
| 
 | // @ts-ignore
 | ||||||
| DetermineLayout.GetLayout().then(value => { | DetermineLayout.GetLayout().then(value => { | ||||||
|     console.log("Got ", value) |     console.log("Got ", value) | ||||||
|     Init.Init(value[0], value[1]) |     Init.Init(value) | ||||||
| }).catch(err => { |     }).catch(err => { | ||||||
|     console.error("Error while initializing: ", err, err.stack) |         console.error("Error while initializing: ", err, err.stack) | ||||||
| }) |     }) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
							
								
								
									
										65
									
								
								index_theme.ts.template
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								index_theme.ts.template
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,65 @@ | ||||||
|  | import {FixedUiElement} from "./UI/Base/FixedUiElement"; | ||||||
|  | import {QueryParameters} from "./Logic/Web/QueryParameters"; | ||||||
|  | import Combine from "./UI/Base/Combine"; | ||||||
|  | import AvailableBaseLayers from "./Logic/Actors/AvailableBaseLayers"; | ||||||
|  | import MinimapImplementation from "./UI/Base/MinimapImplementation"; | ||||||
|  | import {Utils} from "./Utils"; | ||||||
|  | import LayoutConfig from "./Models/ThemeConfig/LayoutConfig"; | ||||||
|  | import DefaultGUI from "./UI/DefaultGUI"; | ||||||
|  | import State from "./State"; | ||||||
|  | import AvailableBaseLayersImplementation from "./Logic/Actors/AvailableBaseLayersImplementation"; | ||||||
|  | import ShowOverlayLayerImplementation from "./UI/ShowDataLayer/ShowOverlayLayerImplementation"; | ||||||
|  | import {DefaultGuiState} from "./UI/DefaultGuiState"; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | document.getElementById("decoration-desktop").remove(); | ||||||
|  | new Combine(["Initializing... <br/>", | ||||||
|  |     new FixedUiElement("<a>If this message persist, something went wrong - click here to try again</a>") | ||||||
|  |         .SetClass("link-underline small") | ||||||
|  |         .onClick(() => { | ||||||
|  |             localStorage.clear(); | ||||||
|  |             window.location.reload(true); | ||||||
|  | 
 | ||||||
|  |         })]) | ||||||
|  |     .AttachTo("centermessage"); // Add an initialization and reset button if something goes wrong | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | // Workaround for a stupid crash: inject some functions which would give stupid circular dependencies or crash the other nodejs scripts running from console | ||||||
|  | MinimapImplementation.initialize() | ||||||
|  | AvailableBaseLayers.implement(new AvailableBaseLayersImplementation()) | ||||||
|  | ShowOverlayLayerImplementation.Implement(); | ||||||
|  | // Miscelleanous | ||||||
|  | Utils.DisableLongPresses() | ||||||
|  | 
 | ||||||
|  | const layoutToUse = new LayoutConfig(themeConfig["default"]) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | // Workaround/legacy to keep the old paramters working as I renamed some of them | ||||||
|  | if (layoutToUse?.id === "cyclofix") { | ||||||
|  |     const legacy = QueryParameters.GetQueryParameter("layer-bike_shops", "true", "Legacy - keep De Fietsambassade working"); | ||||||
|  |     const correct = QueryParameters.GetQueryParameter("layer-bike_shop", "true", "Legacy - keep De Fietsambassade working") | ||||||
|  |     if (legacy.data !== "true") { | ||||||
|  |         correct.setData(legacy.data) | ||||||
|  |     } | ||||||
|  |     console.log("layer-bike_shop toggles: legacy:", legacy.data, "new:", correct.data) | ||||||
|  | 
 | ||||||
|  |     const legacyCafe = QueryParameters.GetQueryParameter("layer-bike_cafes", "true", "Legacy - keep De Fietsambassade working") | ||||||
|  |     const correctCafe = QueryParameters.GetQueryParameter("layer-bike_cafe", "true", "Legacy - keep De Fietsambassade working") | ||||||
|  |     if (legacyCafe.data !== "true") { | ||||||
|  |         correctCafe.setData(legacy.data) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | const guiState = new DefaultGuiState() | ||||||
|  | State.state = new State(layoutToUse); | ||||||
|  | DefaultGuiState.state = guiState; | ||||||
|  | // This 'leaks' the global state via the window object, useful for debugging | ||||||
|  | // @ts-ignore | ||||||
|  | window.mapcomplete_state = State.state; | ||||||
|  | new DefaultGUI(State.state, guiState) | ||||||
							
								
								
									
										11
									
								
								notfound.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								notfound.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,11 @@ | ||||||
|  | import {FixedUiElement} from "./UI/Base/FixedUiElement"; | ||||||
|  | import Combine from "./UI/Base/Combine"; | ||||||
|  | import {SubtleButton} from "./UI/Base/SubtleButton"; | ||||||
|  | import Svg from "./Svg"; | ||||||
|  | 
 | ||||||
|  | new Combine([new FixedUiElement("This page is not found"), | ||||||
|  | new SubtleButton(Svg.back_svg(), "Back to index", { | ||||||
|  |     url: "./index.html", | ||||||
|  |     newTab: false | ||||||
|  | }) | ||||||
|  | ]).AttachTo("maindiv") | ||||||
							
								
								
									
										877
									
								
								package-lock.json
									
										
									
										generated
									
									
									
								
							
							
						
						
									
										877
									
								
								package-lock.json
									
										
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							
							
								
								
									
										23
									
								
								package.json
									
										
									
									
									
								
							
							
						
						
									
										23
									
								
								package.json
									
										
									
									
									
								
							|  | @ -11,7 +11,7 @@ | ||||||
|     "start": "npm run start:prepare && npm-run-all --parallel start:parallel:*", |     "start": "npm run start:prepare && npm-run-all --parallel start:parallel:*", | ||||||
|     "strt": "npm run start:prepare && npm run start:parallel:parcel", |     "strt": "npm run start:prepare && npm run start:parallel:parcel", | ||||||
|     "start:prepare": "ts-node scripts/generateLayerOverview.ts --no-fail && npm run increase-memory", |     "start:prepare": "ts-node scripts/generateLayerOverview.ts --no-fail && npm run increase-memory", | ||||||
|     "start:parallel:parcel": "node --max_old_space_size=12000 $(which parcel) serve *.html UI/** Logic/** assets/*.json assets/svg/* assets/generated/* assets/layers/*/*.svg assets/layers/*/*.jpg assets/layers/*/*.png assets/layers/*/*.css assets/tagRenderings/*.json assets/themes/*/*.svg assets/themes/*/*.css assets/themes/*/*.jpg assets/themes/*/*.png vendor/* vendor/*/*", |     "start:parallel:parcel": "parcel serve *.html UI/** Logic/** assets/*.json assets/svg/* assets/generated/* assets/layers/*/*.svg assets/layers/*/*.jpg assets/layers/*/*.png assets/layers/*/*.css assets/tagRenderings/*.json assets/themes/*/*.svg assets/themes/*/*.css assets/themes/*/*.jpg assets/themes/*/*.png vendor/* vendor/*/*", | ||||||
|     "start:parallel:tailwindcli": "tailwindcss -i index.css -o css/index-tailwind-output.css --watch", |     "start:parallel:tailwindcli": "tailwindcss -i index.css -o css/index-tailwind-output.css --watch", | ||||||
|     "generate:css": "tailwindcss -i index.css -o css/index-tailwind-output.css", |     "generate:css": "tailwindcss -i index.css -o css/index-tailwind-output.css", | ||||||
|     "test": "ts-node test/TestAll.ts", |     "test": "ts-node test/TestAll.ts", | ||||||
|  | @ -28,28 +28,21 @@ | ||||||
|     "generate:cache:speelplekken:mini": "ts-node scripts/generateCache.ts speelplekken 14 ../MapComplete-data/speelplekken_cache_mini/ 51.181710380278176 4.423413276672363 51.193007664772495 4.444141387939452", |     "generate:cache:speelplekken:mini": "ts-node scripts/generateCache.ts speelplekken 14 ../MapComplete-data/speelplekken_cache_mini/ 51.181710380278176 4.423413276672363 51.193007664772495 4.444141387939452", | ||||||
|     "generate:cache:speelplekken": "npm run generate:layeroverview && ts-node scripts/generateCache.ts speelplekken 14 ../MapComplete-data/speelplekken_cache/ 51.20 4.35 51.09 4.56", |     "generate:cache:speelplekken": "npm run generate:layeroverview && ts-node scripts/generateCache.ts speelplekken 14 ../MapComplete-data/speelplekken_cache/ 51.20 4.35 51.09 4.56", | ||||||
|     "generate:cache:natuurpunt": "npm run generate:layeroverview && ts-node scripts/generateCache.ts natuurpunt 12 ../MapComplete-data/natuurpunt_cache/ 50.40 2.1 51.54 6.4 --generate-point-overview nature_reserve,visitor_information_centre", |     "generate:cache:natuurpunt": "npm run generate:layeroverview && ts-node scripts/generateCache.ts natuurpunt 12 ../MapComplete-data/natuurpunt_cache/ 50.40 2.1 51.54 6.4 --generate-point-overview nature_reserve,visitor_information_centre", | ||||||
|     "generate:layeroverview": "echo {\\\"layers\\\":[], \\\"themes\\\":[]} > ./assets/generated/known_layers_and_themes.json && ts-node scripts/generateLayerOverview.ts --no-fail", |     "generate:layeroverview": "ts-node scripts/generateLayerOverview.ts --no-fail", | ||||||
|     "generate:licenses": "ts-node scripts/generateLicenseInfo.ts --no-fail", |     "generate:licenses": "ts-node scripts/generateLicenseInfo.ts --no-fail", | ||||||
|     "query:licenses": "ts-node scripts/generateLicenseInfo.ts --query", |     "query:licenses": "ts-node scripts/generateLicenseInfo.ts --query", | ||||||
|     "generate:report": "cd Docs/Tools && ./compileStats.sh && git commit . -m 'New statistics ands graphs' && git push", |     "generate:report": "cd Docs/Tools && ./compileStats.sh && git commit . -m 'New statistics ands graphs' && git push", | ||||||
|     "generate:contributor-list": "git log --pretty='%aN' | sort | uniq -c | sort -hr | sed 's/ *\\([0-9]*\\) \\(.*\\)$/{\"contributor\":\"\\2\", \"commits\":\\1}/' | tr '\\n' ',' | sed 's/^/{\"contributors\":[/' | sed 's/,$/]}/' | jq > assets/contributors.json", |     "generate:contributor-list": "git log --pretty='%aN' | sort | uniq -c | sort -hr | sed 's/ *\\([0-9]*\\) \\(.*\\)$/{\"contributor\":\"\\2\", \"commits\":\\1}/' | tr '\\n' ',' | sed 's/^/{\"contributors\":[/' | sed 's/,$/]}/' | jq > assets/contributors.json", | ||||||
|     "validate:layeroverview": "ts-node scripts/generateLayerOverview.ts --report", |  | ||||||
|     "validate:licenses": "ts-node scripts/generateLicenseInfo.ts --report", |  | ||||||
|     "generate:schemas": "ts2json-schema -p Models/ThemeConfig/Json/ -o Docs/Schemas/ -t tsconfig.json -R . -m \".*ConfigJson\" && ts-node scripts/fixSchemas.ts ", |     "generate:schemas": "ts2json-schema -p Models/ThemeConfig/Json/ -o Docs/Schemas/ -t tsconfig.json -R . -m \".*ConfigJson\" && ts-node scripts/fixSchemas.ts ", | ||||||
|     "optimize-images": "cd assets/generated/ &&  find -name '*.png' -exec optipng '{}' \\; && echo 'PNGs are optimized'", |     "optimize-images": "cd assets/generated/ &&  find -name '*.png' -exec optipng '{}' \\; && echo 'PNGs are optimized'", | ||||||
|     "reset:layeroverview": "echo {\\\"layers\\\":[], \\\"themes\\\":[]} > ./assets/generated/known_layers_and_themes.json", |     "reset:layeroverview": "echo {\\\"layers\\\":[], \\\"themes\\\":[]} > ./assets/generated/known_layers_and_themes.json", | ||||||
|     "generate": "mkdir -p ./assets/generated && npm run reset:layeroverview && npm run generate:images && npm run generate:charging-stations && npm run generate:translations && npm run generate:licenses && npm run validate:layeroverview", |     "generate": "mkdir -p ./assets/generated && npm run reset:layeroverview && npm run generate:images && npm run generate:charging-stations && npm run generate:translations && npm run generate:licenses && npm run generate:layeroverview", | ||||||
|     "build": "rm -rf dist/ && npm run generate && parcel build --public-url ./ *.html assets/** assets/**/** assets/**/**/** vendor/* vendor/*/*", |  | ||||||
|     "generate:charging-stations": "cd ./assets/layers/charging_station && ts-node csvToJson.ts && cd -", |     "generate:charging-stations": "cd ./assets/layers/charging_station && ts-node csvToJson.ts && cd -", | ||||||
|     "prepare-deploy": "npm run generate && npm run test && npm run generate:editor-layer-index && npm run generate:layouts && npm run build && rm -rf .cache", |     "prepare-deploy": "./scripts/build.sh", | ||||||
|     "deploy:staging": "npm run prepare-deploy && rm -rf ~/git/pietervdvn.github.io/Staging/* && cp -r dist/* ~/git/pietervdvn.github.io/Staging/ && cd ~/git/pietervdvn.github.io/ && git add * && git commit -m 'New MapComplete Version' && git push && cd - && npm run clean", |  | ||||||
|     "deploy:pietervdvn": "cd ~/git/pietervdvn.github.io/ && git pull && cd - && npm run prepare-deploy && rm -rf ~/git/pietervdvn.github.io/MapComplete/* && cp -r dist/* ~/git/pietervdvn.github.io/MapComplete/ && cd ~/git/pietervdvn.github.io/ && git add * && git commit -m 'New MapComplete Version' && git push && cd - && npm run clean", |  | ||||||
|     "deploy:production": "cd ~/git/mapcomplete.github.io/ && git pull && cd - && rm -rf ./assets/generated && npm run prepare-deploy && npm run optimize-images && rm -rf ~/git/mapcomplete.github.io/* && cp -r dist/* ~/git/mapcomplete.github.io/ && cd ~/git/mapcomplete.github.io/ && echo \"mapcomplete.osm.be\" > CNAME  && git add * && git commit -m 'New MapComplete Version' && git push && cd - && npm run clean && npm run gittag", |  | ||||||
|     "gittag": "ts-node scripts/printVersion.ts | bash", |     "gittag": "ts-node scripts/printVersion.ts | bash", | ||||||
|     "lint": "tslint --project . -c tslint.json '**.ts' ", |     "lint": "tslint --project . -c tslint.json '**.ts' ", | ||||||
|     "clean": "rm -rf .cache/ && (find *.html | grep -v \"\\(index\\|land\\|test\\|preferences\\|customGenerator\\|professional\\|automaton\\).html\" | xargs rm) && rm *.webmanifest", |     "clean": "rm -rf .cache/ && (find *.html | grep -v \"\\(404\\|index\\|land\\|test\\|preferences\\|customGenerator\\|professional\\|automaton\\|theme\\).html\" | xargs rm) && (ls | grep \"^index_[a-zA-Z_]\\+\\.ts$\" | xargs rm) && (ls | grep \".*.webmanifest$\" | xargs rm)", | ||||||
|     "generate:dependency-graph": "node_modules/.bin/depcruise --exclude \"^node_modules\" --output-type dot test/TestAll.ts > dependencies.dot && dot dependencies.dot -T svg -o dependencies.svg && rm dependencies.dot", |     "generate:dependency-graph": "node_modules/.bin/depcruise --exclude \"^node_modules\" --output-type dot Logic/State/MapState.ts > dependencies.dot && dot dependencies.dot -T svg -o dependencies.svg && rm dependencies.dot" | ||||||
|     "genPostal": " ts-node ./scripts/postal_code_tools/createRoutablePoint.ts /home/pietervdvn/Downloads/postal_codes/postal_codes_town_hall_points.geojson /home/pietervdvn/Downloads/31370/Postcodes.geojson\n" |  | ||||||
|   }, |   }, | ||||||
|   "keywords": [ |   "keywords": [ | ||||||
|     "OpenStreetMap", |     "OpenStreetMap", | ||||||
|  | @ -84,7 +77,7 @@ | ||||||
|     "leaflet-providers": "^1.13.0", |     "leaflet-providers": "^1.13.0", | ||||||
|     "leaflet-simple-map-screenshoter": "^0.4.4", |     "leaflet-simple-map-screenshoter": "^0.4.4", | ||||||
|     "leaflet.markercluster": "^1.4.1", |     "leaflet.markercluster": "^1.4.1", | ||||||
|     "libphonenumber": "0.0.10", |     "libphonenumber": "^0.0.9", | ||||||
|     "libphonenumber-js": "^1.7.55", |     "libphonenumber-js": "^1.7.55", | ||||||
|     "lz-string": "^1.4.4", |     "lz-string": "^1.4.4", | ||||||
|     "mangrove-reviews": "^0.1.3", |     "mangrove-reviews": "^0.1.3", | ||||||
|  | @ -92,7 +85,7 @@ | ||||||
|     "npm-run-all": "^4.1.5", |     "npm-run-all": "^4.1.5", | ||||||
|     "opening_hours": "^3.6.0", |     "opening_hours": "^3.6.0", | ||||||
|     "osm-auth": "^1.0.2", |     "osm-auth": "^1.0.2", | ||||||
|     "osmtogeojson": "^3.0.0-beta.4", |     "osmtogeojson": "^1.0.0", | ||||||
|     "parcel": "^1.2.4", |     "parcel": "^1.2.4", | ||||||
|     "prompt-sync": "^4.2.0", |     "prompt-sync": "^4.2.0", | ||||||
|     "svg-resizer": "github:vieron/svg-resizer", |     "svg-resizer": "github:vieron/svg-resizer", | ||||||
|  |  | ||||||
|  | @ -1,29 +0,0 @@ | ||||||
| <!DOCTYPE html> |  | ||||||
| <html> |  | ||||||
| <head> |  | ||||||
|     <link href="index.css" rel="stylesheet"/> |  | ||||||
|     <title>Preferences editor</title> |  | ||||||
| 
 |  | ||||||
|     <style> |  | ||||||
|         table { |  | ||||||
|             border-collapse: collapse; |  | ||||||
| 
 |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         table, th, td { |  | ||||||
|             border: 1px solid black; |  | ||||||
|         } |  | ||||||
|     </style> |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| </head> |  | ||||||
| <body> |  | ||||||
| <h1>Preferences editor - developers only</h1> |  | ||||||
| Only use if you know what you're doing. To prevent newbies to make mistakes here, editing a mapcomplete-preference is |  | ||||||
| only available if over 500 changes<br/> |  | ||||||
| Editing any preference -including non-mapcomplete ones- is available when you have more then 2500 changesets. Until that |  | ||||||
| point, only editing mapcomplete-preferences is possible. |  | ||||||
| <div id="maindiv">'maindiv' not attached</div> |  | ||||||
| <script src="./preferences.ts"></script> |  | ||||||
| </body> |  | ||||||
| </html> |  | ||||||
							
								
								
									
										154
									
								
								preferences.ts
									
										
									
									
									
								
							
							
						
						
									
										154
									
								
								preferences.ts
									
										
									
									
									
								
							|  | @ -1,154 +0,0 @@ | ||||||
| import {OsmConnection} from "./Logic/Osm/OsmConnection"; |  | ||||||
| import Combine from "./UI/Base/Combine"; |  | ||||||
| import {Button} from "./UI/Base/Button"; |  | ||||||
| import {TextField} from "./UI/Input/TextField"; |  | ||||||
| import {FixedUiElement} from "./UI/Base/FixedUiElement"; |  | ||||||
| import {Utils} from "./Utils"; |  | ||||||
| import {SubtleButton} from "./UI/Base/SubtleButton"; |  | ||||||
| import LZString from "lz-string"; |  | ||||||
| import BaseUIElement from "./UI/BaseUIElement"; |  | ||||||
| import Table from "./UI/Base/Table"; |  | ||||||
| import {LayoutConfigJson} from "./Models/ThemeConfig/Json/LayoutConfigJson"; |  | ||||||
| import {Changes} from "./Logic/Osm/Changes"; |  | ||||||
| import {ElementStorage} from "./Logic/ElementStorage"; |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| const connection = new OsmConnection({ |  | ||||||
|     osmConfiguration: 'osm', |  | ||||||
|     changes: new Changes(), |  | ||||||
|     layoutName: '', |  | ||||||
|     allElements: new ElementStorage() |  | ||||||
| }); |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| let rendered = false; |  | ||||||
| 
 |  | ||||||
| function salvageThemes(preferences: any) { |  | ||||||
|     const knownThemeNames = new Set<string>(); |  | ||||||
|     const correctThemeNames = [] |  | ||||||
|     for (const key in preferences) { |  | ||||||
|         try { |  | ||||||
|             if (!(typeof key === "string")) { |  | ||||||
|                 continue; |  | ||||||
|             } |  | ||||||
|             const prefix = "mapcomplete-installed-theme-"; |  | ||||||
|             // mapcomplete-installed-theme-arbres_llefia-combined-11
 |  | ||||||
|             //mapcomplete-installed-theme-1roadAlllanes-combined-length
 |  | ||||||
|             if (!key.startsWith(prefix)) { |  | ||||||
|                 continue; |  | ||||||
|             } |  | ||||||
|             const theme = key.substring(prefix.length, key.indexOf("-combined-")) |  | ||||||
| 
 |  | ||||||
|             if (key.endsWith("-length")) { |  | ||||||
|                 correctThemeNames.push(theme) |  | ||||||
|             } else { |  | ||||||
|                 knownThemeNames.add(theme); |  | ||||||
|             } |  | ||||||
|         } catch (e) { |  | ||||||
|             console.error(e) |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     for (const correctThemeName of correctThemeNames) { |  | ||||||
|         knownThemeNames.delete(correctThemeName); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     const missingValues = Array.from(knownThemeNames).map(failedTheme => { |  | ||||||
| 
 |  | ||||||
|         let i = 0; |  | ||||||
|         let foundValue = undefined |  | ||||||
|         let combined = "" |  | ||||||
|         do { |  | ||||||
|             const prefix = "mapcomplete-installed-theme-"; |  | ||||||
|             const key = prefix + failedTheme + "-combined-" + i; |  | ||||||
|             foundValue = preferences[key] |  | ||||||
|             console.log(key, "-->", foundValue) |  | ||||||
|             i++; |  | ||||||
|             combined += foundValue ?? "" |  | ||||||
|         } while (foundValue !== undefined); |  | ||||||
| 
 |  | ||||||
|         if (combined === "") { |  | ||||||
|             return null; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         console.log("COmbined value is", combined) |  | ||||||
|         let jsonObject; |  | ||||||
|         try { |  | ||||||
|             jsonObject = JSON.parse(atob(combined)); |  | ||||||
|         } catch (e) { |  | ||||||
|             try { |  | ||||||
| 
 |  | ||||||
|                 // We try to decode with lz-string
 |  | ||||||
|                 jsonObject = JSON.parse(Utils.UnMinify(LZString.decompressFromBase64(combined))) as LayoutConfigJson; |  | ||||||
|             } catch (e0) { |  | ||||||
|                 console.log("Could not salvage theme. Initial parsing failed due to:", e, "\nWith LZ failed due ", e0) |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         return { |  | ||||||
|             themeName: failedTheme, |  | ||||||
|             contents: JSON.stringify(jsonObject, null, "  ") |  | ||||||
|         } |  | ||||||
|     }) |  | ||||||
|     return Utils.NoNull(missingValues); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| function clearAll(preferences) { |  | ||||||
|     for (const key in preferences) { |  | ||||||
|         const pref = connection.GetPreference(key, ""); |  | ||||||
|         if (key.startsWith("mapcomplete")) { |  | ||||||
|             pref.setData("") |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| function SalvageButton(theme: { themeName: string, contents: string }) { |  | ||||||
|     return new SubtleButton("./assets/svg/bug.svg", "Download broken theme " + theme.themeName).onClick( |  | ||||||
|         () => { |  | ||||||
|             Utils.offerContentsAsDownloadableFile(theme.contents, theme.themeName + ".json") |  | ||||||
|         } |  | ||||||
|     ) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| function createTable(preferences: any) { |  | ||||||
|     if (rendered) { |  | ||||||
|         return; |  | ||||||
|     } |  | ||||||
|     rendered = true; |  | ||||||
|     const prefs: (BaseUIElement | string)[][] = []; |  | ||||||
|     for (const key in preferences) { |  | ||||||
|         if (!preferences.hasOwnProperty(key)) { |  | ||||||
|             continue; |  | ||||||
|         } |  | ||||||
|         const pref = connection.GetPreference(key, ""); |  | ||||||
| 
 |  | ||||||
|         let value: BaseUIElement = new FixedUiElement(pref.data); |  | ||||||
|         if (connection.userDetails.data.csCount > 500 && |  | ||||||
|             (key.startsWith("mapcomplete") || connection.userDetails.data.csCount > 2500)) { |  | ||||||
|             value = new TextField({ |  | ||||||
|                 value: pref |  | ||||||
|             }); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         const row = [ |  | ||||||
|             key, |  | ||||||
|             new Button("delete", () => pref.setData(null)), |  | ||||||
|             value |  | ||||||
|         ]; |  | ||||||
|         prefs.push(row); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     new Combine( |  | ||||||
|         [ |  | ||||||
|             ...salvageThemes(preferences).map(theme => SalvageButton(theme)), |  | ||||||
|             new Table( |  | ||||||
|                 ["Key", "", "Value"], |  | ||||||
|                 prefs |  | ||||||
|             ), |  | ||||||
|             new SubtleButton("./assets/svg/delete_icon.svg", "Delete all mapcomplete preferences (mangrove identies are preserved)").onClick(() => clearAll(preferences))] |  | ||||||
|     ).AttachTo("maindiv"); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| connection.preferencesHandler.preferences.addCallback((prefs) => createTable(prefs)) |  | ||||||
| 
 |  | ||||||
							
								
								
									
										41
									
								
								scripts/build.sh
									
										
									
									
									
										Executable file
									
								
							
							
						
						
									
										41
									
								
								scripts/build.sh
									
										
									
									
									
										Executable file
									
								
							|  | @ -0,0 +1,41 @@ | ||||||
|  | #! /bin/bash | ||||||
|  | echo "Starting build.Should" | ||||||
|  | # The build script; we build the application step by step as building everything at once takes too much RAM | ||||||
|  | # Should be run from the repository root | ||||||
|  | rm -rf dist/* | ||||||
|  | rm -rf .cache | ||||||
|  | mkdir dist 2> /dev/null | ||||||
|  | mkdir dist/assets 2> /dev/null | ||||||
|  | 
 | ||||||
|  | npm run generate | ||||||
|  | npm run test | ||||||
|  | npm run generate:editor-layer-index  | ||||||
|  | npm run generate:layouts | ||||||
|  | 
 | ||||||
|  | # Copy the layer files, as these might contain assets (e.g. svgs) | ||||||
|  | cp -r assets/layers/ dist/assets/layers/ | ||||||
|  | cp -r assets/themes/ dist/assets/themes/ | ||||||
|  | cp -r assets/svg/ dist/assets/svg/ | ||||||
|  | echo -e "\n\n   Building non-theme pages" | ||||||
|  | echo -e "  ==========================\n\n" | ||||||
|  | parcel build --public-url "./"  --no-source-maps "index.html" "404.html" "professional.html" "automaton.html" "land.html" "customGenerator.html" "theme.html" vendor | ||||||
|  | echo -e "\n\n   Building theme pages" | ||||||
|  | echo -e "  ======================\n\n" | ||||||
|  | 
 | ||||||
|  | for file in $(ls index_*.ts) | ||||||
|  | do | ||||||
|  |     theme=${file:6:-3} | ||||||
|  |     echo -e "\n\n  $theme" | ||||||
|  |     echo -e " ------------ \n\n" | ||||||
|  |     # Builds the necessary files for just one theme, e.g. 'bookcases.html' + 'index_bookcases.ts' + supporting file | ||||||
|  |     # npm run generate && node --max_old_space_size=12000 $(which parcel)  build  | ||||||
|  |     parcel build --public-url './' --no-source-maps "$theme.html"  | ||||||
|  | done | ||||||
|  | # At last: a workaround; parcel 1.x borks the link to social images; the public-URL (./) is setup incorrectly, so we fix those | ||||||
|  | cd dist | ||||||
|  | echo -e "Fixing social images..." | ||||||
|  | for file in $(ls *.html) | ||||||
|  | do | ||||||
|  |     sed -i 's!<meta content="\([^"]\+\)" property="og:image">!<meta content="./\1" property="og:image">!' $file | ||||||
|  |     sed -i 's!<meta property="og:image" content="\([^"]\+\)">!<meta content="./\1" property="og:image">!' $file | ||||||
|  | done | ||||||
|  | @ -1,27 +0,0 @@ | ||||||
| #! /bin/bash |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| # To run with crontab:  |  | ||||||
| # */1 * * * * /home/pietervdvn/git/MapComplete/scripts/deployIfChanged.sh >> /home/pietervdvn/auto_deploy_caching.log 2>&1 |  | ||||||
| 
 |  | ||||||
| PATH=/home/pietervdvn/.local/bin:/home/pietervdvn/.nvm/versions/node/v16.0.0/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin:/home/pietervdvn/.dotnet/tools |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| cd ~/git/MapComplete |  | ||||||
| 
 |  | ||||||
|  git fetch |  | ||||||
|  HEADHASH=$(git rev-parse HEAD) |  | ||||||
|  UPSTREAMHASH=$(git rev-parse master@{upstream}) |  | ||||||
| 
 |  | ||||||
|  if [ "$HEADHASH" != "$UPSTREAMHASH" ] |  | ||||||
|  then |  | ||||||
|    echo Not up to date with origin. Deploying! |  | ||||||
|    git pull |  | ||||||
|    npm run generate:translations |  | ||||||
|    git commit -am "Sync translations" |  | ||||||
|    git push |  | ||||||
|    npm run generate:docs |  | ||||||
|    git commit -am "Autgenerate docs and taginfo files" |  | ||||||
|     |  | ||||||
|    npm run deploy:production |  | ||||||
|  fi |  | ||||||
|  | @ -1,13 +1,19 @@ | ||||||
| import ScriptUtils from "./ScriptUtils"; | import ScriptUtils from "./ScriptUtils"; | ||||||
| import {writeFileSync} from "fs"; | import {existsSync, mkdirSync, writeFileSync} from "fs"; | ||||||
| import * as licenses from "../assets/generated/license_info.json" | import * as licenses from "../assets/generated/license_info.json" | ||||||
| import {LayoutConfigJson} from "../Models/ThemeConfig/Json/LayoutConfigJson"; | import {LayoutConfigJson} from "../Models/ThemeConfig/Json/LayoutConfigJson"; | ||||||
| import LayoutConfig from "../Models/ThemeConfig/LayoutConfig"; |  | ||||||
| import {LayerConfigJson} from "../Models/ThemeConfig/Json/LayerConfigJson"; | import {LayerConfigJson} from "../Models/ThemeConfig/Json/LayerConfigJson"; | ||||||
| import LayerConfig from "../Models/ThemeConfig/LayerConfig"; | import Constants from "../Models/Constants"; | ||||||
|  | import { | ||||||
|  |     DesugaringContext, | ||||||
|  |     PrepareLayer, PrepareTheme, | ||||||
|  |     ValidateLayer, | ||||||
|  |     ValidateThemeAndLayers | ||||||
|  | } from "../Models/ThemeConfig/LegacyJsonConvert"; | ||||||
| import {Translation} from "../UI/i18n/Translation"; | import {Translation} from "../UI/i18n/Translation"; | ||||||
| import {Utils} from "../Utils"; | import {TagRenderingConfigJson} from "../Models/ThemeConfig/Json/TagRenderingConfigJson"; | ||||||
| import AllKnownLayers from "../Customizations/AllKnownLayers"; | import * as questions from "../assets/tagRenderings/questions.json"; | ||||||
|  | import * as icons from "../assets/tagRenderings/icons.json"; | ||||||
| 
 | 
 | ||||||
| // This scripts scans 'assets/layers/*.json' for layer definition files and 'assets/themes/*.json' for theme definition files.
 | // This scripts scans 'assets/layers/*.json' for layer definition files and 'assets/themes/*.json' for theme definition files.
 | ||||||
| // It spits out an overview of those to be used to load them
 | // It spits out an overview of those to be used to load them
 | ||||||
|  | @ -20,218 +26,157 @@ interface LayersAndThemes { | ||||||
| 
 | 
 | ||||||
| class LayerOverviewUtils { | class LayerOverviewUtils { | ||||||
| 
 | 
 | ||||||
|     loadThemesAndLayers(): LayersAndThemes { |     writeSmallOverview(themes: { id: string, title: any, shortDescription: any, icon: string, hideFromOverview: boolean }[]) { | ||||||
|  |         const perId = new Map<string, any>(); | ||||||
|  |         for (const theme of themes) { | ||||||
|  |             const data = { | ||||||
|  |                 id: theme.id, | ||||||
|  |                 title: theme.title, | ||||||
|  |                 shortDescription: theme.shortDescription, | ||||||
|  |                 icon: theme.icon, | ||||||
|  |                 hideFromOverview: theme.hideFromOverview | ||||||
|  |             } | ||||||
|  |             perId.set(theme.id, data); | ||||||
|  |         } | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
|  |         const sorted = Constants.themeOrder.map(id => { | ||||||
|  |             if (!perId.has(id)) { | ||||||
|  |                 throw "Ordered theme id " + id + " not found" | ||||||
|  |             } | ||||||
|  |             return perId.get(id); | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |         perId.forEach((value) => { | ||||||
|  |             if (Constants.themeOrder.indexOf(value.id) >= 0) { | ||||||
|  |                 return; // actually a continue
 | ||||||
|  |             } | ||||||
|  |             sorted.push(value) | ||||||
|  |         }) | ||||||
|  | 
 | ||||||
|  |         writeFileSync("./assets/generated/theme_overview.json", JSON.stringify(sorted, null, "  "), "UTF8"); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     writeTheme(theme: LayoutConfigJson) { | ||||||
|  |         if (!existsSync("./assets/generated/themes")) { | ||||||
|  |             mkdirSync("./assets/generated/themes"); | ||||||
|  |         } | ||||||
|  |         writeFileSync(`./assets/generated/themes/${theme.id}.json`, JSON.stringify(theme, null, "  "), "UTF8"); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     writeLayer(layer: LayerConfigJson) { | ||||||
|  |         if (!existsSync("./assets/generated/layers")) { | ||||||
|  |             mkdirSync("./assets/generated/layers"); | ||||||
|  |         } | ||||||
|  |         writeFileSync(`./assets/generated/layers/${layer.id}.json`, JSON.stringify(layer, null, "  "), "UTF8"); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     getSharedTagRenderings(): Map<string, TagRenderingConfigJson> { | ||||||
|  |         const dict = new Map<string, TagRenderingConfigJson>(); | ||||||
|  | 
 | ||||||
|  |         for (const key in questions["default"]) { | ||||||
|  |             questions[key].id = key; | ||||||
|  |             dict.set(key, <TagRenderingConfigJson>questions[key]) | ||||||
|  |         } | ||||||
|  |         for (const key in icons["default"]) { | ||||||
|  |             if(typeof icons[key] !== "object"){ | ||||||
|  |                 continue | ||||||
|  |             } | ||||||
|  |             icons[key].id = key; | ||||||
|  |             dict.set(key, <TagRenderingConfigJson>icons[key]) | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         dict.forEach((value, key) => { | ||||||
|  |             value.id = value.id ?? key; | ||||||
|  |         }) | ||||||
|  | 
 | ||||||
|  |         return dict; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     private buildLayerIndex(knownImagePaths: Set<string>): Map<string, LayerConfigJson> { | ||||||
|  |         // First, we expand and validate all builtin layers. These are written to assets/generated/layers
 | ||||||
|  |         // At the same time, an index of available layers is built.
 | ||||||
|  |         console.log("   ---------- VALIDATING BUILTIN LAYERS ---------") | ||||||
|  | 
 | ||||||
|  |         const sharedTagRenderings = this.getSharedTagRenderings(); | ||||||
|         const layerFiles = ScriptUtils.getLayerFiles(); |         const layerFiles = ScriptUtils.getLayerFiles(); | ||||||
| 
 |         const sharedLayers = new Map<string, LayerConfigJson>() | ||||||
|         const themeFiles: LayoutConfigJson[] = ScriptUtils.getThemeFiles().map(x => x.parsed); |         const prepLayer = new PrepareLayer(); | ||||||
| 
 |         const state: DesugaringContext = { | ||||||
|         console.log("Discovered", layerFiles.length, "layers and", themeFiles.length, "themes\n") |             tagRenderings: sharedTagRenderings, | ||||||
|         if (layerFiles.length + themeFiles.length === 0) { |             sharedLayers | ||||||
|             throw "Panic: no themes and layers loaded!" |  | ||||||
|         } |         } | ||||||
|         return { |         for (const sharedLayerJson of layerFiles) { | ||||||
|             layers: layerFiles, |             const context = "While building builtin layer " + sharedLayerJson.path | ||||||
|             themes: themeFiles |             const fixed = prepLayer.convertStrict(state, sharedLayerJson.parsed, context) | ||||||
|         } |             const validator = new ValidateLayer(knownImagePaths, sharedLayerJson.path, true); | ||||||
|     } |             validator.convertStrict(state, fixed, context) | ||||||
| 
 | 
 | ||||||
|     writeFiles(lt: LayersAndThemes) { |             if (sharedLayers.has(fixed.id)) { | ||||||
|         writeFileSync("./assets/generated/known_layers_and_themes.json", JSON.stringify({ |                 throw "There are multiple layers with the id " + fixed.id | ||||||
|             "layers": lt.layers.map(l => l.parsed), |  | ||||||
|             "themes": lt.themes |  | ||||||
|         })) |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     validateLayer(layerJson: LayerConfigJson, path: string, knownPaths: Set<string>, context?: string): string[] { |  | ||||||
|         let errorCount = []; |  | ||||||
|         if (layerJson["overpassTags"] !== undefined) { |  | ||||||
|             errorCount.push("Layer " + layerJson.id + "still uses the old 'overpassTags'-format. Please use \"source\": {\"osmTags\": <tags>}' instead of \"overpassTags\": <tags> (note: this isn't your fault, the custom theme generator still spits out the old format)") |  | ||||||
|         } |  | ||||||
|         const forbiddenTopLevel = ["icon","wayHandling","roamingRenderings","roamingRendering","label","width","color","colour","iconOverlays"] |  | ||||||
|         for (const forbiddenKey of forbiddenTopLevel) { |  | ||||||
|             if(layerJson[forbiddenKey] !== undefined) |  | ||||||
|             errorCount.push("Layer "+layerJson.id+" still has a forbidden key "+forbiddenKey) |  | ||||||
|         } |  | ||||||
|         try { |  | ||||||
|             const layer = new LayerConfig(layerJson, "test", true) |  | ||||||
|             const images = Array.from(layer.ExtractImages()) |  | ||||||
|             const remoteImages = images.filter(img => img.indexOf("http") == 0) |  | ||||||
|             for (const remoteImage of remoteImages) { |  | ||||||
|                 errorCount.push("Found a remote image: " + remoteImage + " in layer " + layer.id + ", please download it. You can use the fixTheme script to automate this") |  | ||||||
|             } |  | ||||||
|             const expected: string = `assets/layers/${layer.id}/${layer.id}.json` |  | ||||||
|             if (path != undefined && path.indexOf(expected) < 0) { |  | ||||||
|                 errorCount.push("Layer is in an incorrect place. The path is " + path + ", but expected " + expected) |  | ||||||
|             } |  | ||||||
|             if (layerJson["hideUnderlayingFeaturesMinPercentage"] !== undefined) { |  | ||||||
|                 errorCount.push("Layer " + layer.id + " contains an old 'hideUnderlayingFeaturesMinPercentage'") |  | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|  |             sharedLayers.set(fixed.id, fixed) | ||||||
| 
 | 
 | ||||||
|             for (const image of images) { |             this.writeLayer(fixed) | ||||||
|                 if (image.indexOf("{") >= 0) { |  | ||||||
|                     console.warn("Ignoring image with { in the path: ", image) |  | ||||||
|                     continue |  | ||||||
|                 } |  | ||||||
| 
 | 
 | ||||||
|                 if (!knownPaths.has(image)) { |  | ||||||
|                     const ctx = context === undefined ? "" : ` in a layer defined in the theme ${context}` |  | ||||||
|                     errorCount.push(`Image with path ${image} not found or not attributed; it is used in ${layer.id}${ctx}`) |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|         } catch (e) { |  | ||||||
|             console.error(e) |  | ||||||
|             return [`Layer ${layerJson.id}` ?? JSON.stringify(layerJson).substring(0, 50) + " is invalid: " + e] |  | ||||||
|         } |         } | ||||||
|         return errorCount |         return sharedLayers; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|     main(args: string[]) { |     private buildThemeIndex(knownImagePaths: Set<string>, sharedLayers: Map<string, LayerConfigJson>): Map<string, LayoutConfigJson> { | ||||||
| 
 |         console.log("   ---------- VALIDATING BUILTIN THEMES ---------") | ||||||
|         AllKnownLayers.runningGenerateScript = true; |  | ||||||
|         const layerFiles = ScriptUtils.getLayerFiles(); |  | ||||||
|         const themeFiles = ScriptUtils.getThemeFiles(); |         const themeFiles = ScriptUtils.getThemeFiles(); | ||||||
|  |         const fixed = new Map<string, LayoutConfigJson>(); | ||||||
| 
 | 
 | ||||||
| 
 |         const convertState: DesugaringContext = { | ||||||
|         console.log("   ---------- VALIDATING ---------") |             sharedLayers, | ||||||
|         const licensePaths = [] |             tagRenderings: this.getSharedTagRenderings() | ||||||
|         for (const i in licenses) { |  | ||||||
|             licensePaths.push(licenses[i].path) |  | ||||||
|         } |         } | ||||||
|         const knownPaths = new Set<string>(licensePaths) |  | ||||||
| 
 |  | ||||||
|         let layerErrorCount = [] |  | ||||||
|         const knownLayerIds = new Map<string, LayerConfig>(); |  | ||||||
|         for (const layerFile of layerFiles) { |  | ||||||
| 
 |  | ||||||
|             if (knownLayerIds.has(layerFile.parsed.id)) { |  | ||||||
|                 throw "Duplicate identifier: " + layerFile.parsed.id + " in file " + layerFile.path |  | ||||||
|             } |  | ||||||
|             layerErrorCount.push(...this.validateLayer(layerFile.parsed, layerFile.path, knownPaths)) |  | ||||||
|             knownLayerIds.set(layerFile.parsed.id, new LayerConfig(layerFile.parsed)) |  | ||||||
|              |  | ||||||
|             if(layerFile.parsed.description === undefined){ |  | ||||||
|                 throw "The layer "+layerFile.parsed.id+" does not provide a description, but this is required for builtin themes" |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         let themeErrorCount = [] |  | ||||||
|         // used only for the reports
 |  | ||||||
|         let themeConfigs: LayoutConfig[] = [] |  | ||||||
|         for (const themeInfo of themeFiles) { |         for (const themeInfo of themeFiles) { | ||||||
|             const themeFile = themeInfo.parsed |             let themeFile = themeInfo.parsed | ||||||
|             const themePath = themeInfo.path |             const themePath = themeInfo.path | ||||||
|             if (typeof themeFile.language === "string") { |  | ||||||
|                 themeErrorCount.push("The theme " + themeFile.id + " has a string as language. Please use a list of strings") |  | ||||||
|             } |  | ||||||
|             if (themeFile["units"] !== undefined) { |  | ||||||
|                 themeErrorCount.push("The theme " + themeFile.id + " has units defined - these should be defined on the layer instead. (Hint: use overrideAll: { '+units': ... }) ") |  | ||||||
|             } |  | ||||||
|             if (themeFile["roamingRenderings"] !== undefined) { |  | ||||||
|                 themeErrorCount.push("Theme " + themeFile.id + " contains an old 'roamingRenderings'. Use an 'overrideAll' instead") |  | ||||||
|             } |  | ||||||
|             for (const layer of themeFile.layers) { |  | ||||||
|                 if (typeof layer === "string") { |  | ||||||
|                     if (!knownLayerIds.has(layer)) { |  | ||||||
|                         themeErrorCount.push(`Unknown layer id: ${layer} in theme ${themeFile.id}`) |  | ||||||
|                     } |  | ||||||
|                 } else if (layer["builtin"] !== undefined) { |  | ||||||
|                     let names = layer["builtin"]; |  | ||||||
|                     if (typeof names === "string") { |  | ||||||
|                         names = [names] |  | ||||||
|                     } |  | ||||||
|                     names.forEach(name => { |  | ||||||
|                         if (!knownLayerIds.has(name)) { |  | ||||||
|                             themeErrorCount.push("Unknown layer id: " + name + "(which uses inheritance)") |  | ||||||
|                         } |  | ||||||
|                         return |  | ||||||
|                     }) |  | ||||||
|                 } else { |  | ||||||
|                     layerErrorCount.push(...this.validateLayer(<LayerConfigJson>layer, undefined, knownPaths, themeFile.id)) |  | ||||||
|                     if (knownLayerIds.has(layer["id"])) { |  | ||||||
|                         throw `The theme ${themeFile.id} defines a layer with id ${layer["id"]}, which is the same as an already existing layer` |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|              |              | ||||||
|             const referencedLayers = Utils.NoNull([].concat(...themeFile.layers.map(layer => { |             themeFile = new PrepareTheme().convertStrict(convertState, themeFile, themePath) | ||||||
|                 if (typeof layer === "string") { |  | ||||||
|                     return layer |  | ||||||
|                 } |  | ||||||
|                 if (layer["builtin"] !== undefined) { |  | ||||||
|                     return layer["builtin"] |  | ||||||
|                 } |  | ||||||
|                 return undefined |  | ||||||
|             }).map(layerName => { |  | ||||||
|                 if (typeof layerName === "string") { |  | ||||||
|                     return [layerName] |  | ||||||
|                 } |  | ||||||
|                 return layerName |  | ||||||
|             }))) |  | ||||||
| 
 | 
 | ||||||
|             themeFile.layers = themeFile.layers |             new ValidateThemeAndLayers(knownImagePaths, themePath, true) | ||||||
|                 .filter(l => typeof l != "string") // We remove all the builtin layer references as they don't work with ts-node for some weird reason
 |                 .convertStrict(convertState, themeFile, themePath) | ||||||
|                 .filter(l => l["builtin"] === undefined) |  | ||||||
| 
 | 
 | ||||||
| 
 |             this.writeTheme(themeFile) | ||||||
|             try { |             fixed.set(themeFile.id, themeFile) | ||||||
|                 const theme = new LayoutConfig(themeFile, true, "test") |  | ||||||
|                 if (theme.id !== theme.id.toLowerCase()) { |  | ||||||
|                     themeErrorCount.push("Theme ids should be in lowercase, but it is " + theme.id) |  | ||||||
|                 } |  | ||||||
|                 let filename = themePath.substring(themePath.lastIndexOf("/") + 1, themePath.length - 5) |  | ||||||
|                 if (theme.id !== filename) { |  | ||||||
|                     themeErrorCount.push("Theme ids should be the same as the name.json, but we got id: " + theme.id + " and filename " + filename + " (" + themePath + ")") |  | ||||||
|                 } |  | ||||||
|                 const neededLanguages = themeFile["mustHaveLanguage"] |  | ||||||
|                 if (neededLanguages !== undefined) { |  | ||||||
|                     console.log("Checking language requirements for ", theme.id, "as it must have", neededLanguages.join(", ")) |  | ||||||
|                     const allTranslations = [].concat(Translation.ExtractAllTranslationsFrom(theme, theme.id), |  | ||||||
|                         ...referencedLayers.map(layerId => Translation.ExtractAllTranslationsFrom(knownLayerIds.get(layerId), theme.id + "->" + layerId))) |  | ||||||
|                     for (const neededLanguage of neededLanguages) { |  | ||||||
|                         allTranslations |  | ||||||
|                             .filter(t => t.tr.translations[neededLanguage] === undefined && t.tr.translations["*"] === undefined) |  | ||||||
|                             .forEach(missing => { |  | ||||||
|                                 themeErrorCount.push("The theme " + theme.id + " should be translation-complete for " + neededLanguage + ", but it lacks a translation for " + missing.context+".\n\tThe english translation is "+missing.tr.textFor('en')) |  | ||||||
|                             }) |  | ||||||
|                     } |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|                 } |  | ||||||
|                 themeConfigs.push(theme) |  | ||||||
|             } catch (e) { |  | ||||||
|                 themeErrorCount.push("Could not parse theme " + themeFile["id"] + " due to", e) |  | ||||||
|             } |  | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         if (layerErrorCount.length + themeErrorCount.length == 0) { |         this.writeSmallOverview(themeFiles.map(tf => { | ||||||
|             console.log("All good!") |             const t = tf.parsed; | ||||||
| 
 |             return { | ||||||
|             // We load again from disc, as modifications were made above
 |                 ...t, | ||||||
|             const lt = this.loadThemesAndLayers(); |                 hideFromOverview: t.hideFromOverview ?? false, | ||||||
|              |                 shortDescription: t.shortDescription ?? new Translation(t.description).FirstSentence().translations | ||||||
|              |  | ||||||
|             this.writeFiles(lt); |  | ||||||
|         } else { |  | ||||||
|             const errors = layerErrorCount.concat(themeErrorCount).join("\n") |  | ||||||
|             console.log(errors) |  | ||||||
|             const msg = (`Found ${layerErrorCount.length} errors in the layers; ${themeErrorCount.length} errors in the themes`) |  | ||||||
|             console.log("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!") |  | ||||||
| 
 |  | ||||||
|             console.log(msg) |  | ||||||
|             console.log("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!") |  | ||||||
| 
 |  | ||||||
|             if (args.indexOf("--report") >= 0) { |  | ||||||
|                 console.log("Writing report!") |  | ||||||
|                 writeFileSync("layer_report.txt", errors) |  | ||||||
|             } |  | ||||||
|             if (args.indexOf("--no-fail") < 0) { |  | ||||||
|                 throw msg; |  | ||||||
|             } |             } | ||||||
|  |         })); | ||||||
|  |         return fixed; | ||||||
|  | 
 | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     main(_: string[]) { | ||||||
|  | 
 | ||||||
|  |         const licensePaths = new Set<string>() | ||||||
|  |         for (const i in licenses) { | ||||||
|  |             licensePaths.add(licenses[i].path) | ||||||
|         } |         } | ||||||
|  | 
 | ||||||
|  |         const sharedLayers = this.buildLayerIndex(licensePaths); | ||||||
|  |         const sharedThemes = this.buildThemeIndex(licensePaths, sharedLayers) | ||||||
|  | 
 | ||||||
|  |         writeFileSync("./assets/generated/known_layers_and_themes.json", JSON.stringify({ | ||||||
|  |             "layers": Array.from(sharedLayers.values()), | ||||||
|  |             "themes": Array.from(sharedThemes.values()) | ||||||
|  |         })) | ||||||
|  | 
 | ||||||
|  |         writeFileSync("./assets/generated/known_layers.json", JSON.stringify(Array.from(sharedLayers.values()))) | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,4 +1,4 @@ | ||||||
| import {existsSync, mkdirSync, readFileSync, writeFile, writeFileSync} from "fs"; | import {appendFileSync, existsSync, mkdirSync, readFileSync, writeFile, writeFileSync} from "fs"; | ||||||
| import Locale from "../UI/i18n/Locale"; | import Locale from "../UI/i18n/Locale"; | ||||||
| import Translations from "../UI/i18n/Translations"; | import Translations from "../UI/i18n/Translations"; | ||||||
| import {Translation} from "../UI/i18n/Translation"; | import {Translation} from "../UI/i18n/Translation"; | ||||||
|  | @ -8,6 +8,8 @@ import {LayoutConfigJson} from "../Models/ThemeConfig/Json/LayoutConfigJson"; | ||||||
| import LayoutConfig from "../Models/ThemeConfig/LayoutConfig"; | import LayoutConfig from "../Models/ThemeConfig/LayoutConfig"; | ||||||
| 
 | 
 | ||||||
| const sharp = require('sharp'); | const sharp = require('sharp'); | ||||||
|  | const template = readFileSync("theme.html", "utf8"); | ||||||
|  | const codeTemplate = readFileSync("index_theme.ts.template", "utf8"); | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| function enc(str: string): string { | function enc(str: string): string { | ||||||
|  | @ -107,7 +109,6 @@ async function createManifest(layout: LayoutConfig) { | ||||||
|     }; |     }; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| const template = readFileSync("index.html", "utf8"); |  | ||||||
| 
 | 
 | ||||||
| async function createLandingPage(layout: LayoutConfig, manifest) { | async function createLandingPage(layout: LayoutConfig, manifest) { | ||||||
| 
 | 
 | ||||||
|  | @ -160,7 +161,8 @@ async function createLandingPage(layout: LayoutConfig, manifest) { | ||||||
| 
 | 
 | ||||||
|     let output = template |     let output = template | ||||||
|         .replace("Loading MapComplete, hang on...", `Loading MapComplete theme <i>${ogTitle}</i>...`) |         .replace("Loading MapComplete, hang on...", `Loading MapComplete theme <i>${ogTitle}</i>...`) | ||||||
|         .replace(/<!-- THEME-SPECIFIC -->.*<!-- THEME-SPECIFIC-END-->/s, themeSpecific); |         .replace(/<!-- THEME-SPECIFIC -->.*<!-- THEME-SPECIFIC-END-->/s, themeSpecific) | ||||||
|  |         .replace("<script src=\"./index.ts\"></script>", `<script src='./index_${layout.id}.ts'></script>`); | ||||||
| 
 | 
 | ||||||
|     try { |     try { | ||||||
|         output = output |         output = output | ||||||
|  | @ -173,12 +175,18 @@ async function createLandingPage(layout: LayoutConfig, manifest) { | ||||||
|     return output; |     return output; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | async function createIndexFor(theme: LayoutConfig){ | ||||||
|  |     const filename = "index_"+theme.id+".ts" | ||||||
|  |     writeFileSync(filename, `import * as themeConfig from "./assets/generated/themes/${theme.id}.json"\n`) | ||||||
|  |     appendFileSync(filename, codeTemplate) | ||||||
|  | } | ||||||
|  | 
 | ||||||
| const generatedDir = "./assets/generated"; | const generatedDir = "./assets/generated"; | ||||||
| if (!existsSync(generatedDir)) { | if (!existsSync(generatedDir)) { | ||||||
|     mkdirSync(generatedDir) |     mkdirSync(generatedDir) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| const blacklist = ["", "test", ".", "..", "manifest", "index", "land", "preferences", "account", "openstreetmap", "custom"] | const blacklist = ["", "test", ".", "..", "manifest", "index", "land", "preferences", "account", "openstreetmap", "custom","theme"] | ||||||
| // @ts-ignore
 | // @ts-ignore
 | ||||||
| const all: LayoutConfigJson[] = all_known_layouts.themes; | const all: LayoutConfigJson[] = all_known_layouts.themes; | ||||||
| for (const i in all) { | for (const i in all) { | ||||||
|  | @ -203,6 +211,7 @@ for (const i in all) { | ||||||
|         createLandingPage(layout, manifObj).then(landing => { |         createLandingPage(layout, manifObj).then(landing => { | ||||||
|             writeFile(enc(layout.id) + ".html", landing, err) |             writeFile(enc(layout.id) + ".html", landing, err) | ||||||
|         }); |         }); | ||||||
|  |         createIndexFor(layout) | ||||||
|     }).catch(e => console.log("Could not generate the manifest: ", e)) |     }).catch(e => console.log("Could not generate the manifest: ", e)) | ||||||
| 
 | 
 | ||||||
| } | } | ||||||
|  | @ -225,6 +234,4 @@ createManifest(new LayoutConfig({ | ||||||
| }) | }) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| console.log("Counting all translations") |  | ||||||
| Translations.CountTranslations(); |  | ||||||
| console.log("All done!"); | console.log("All done!"); | ||||||
|  | @ -1,22 +1,24 @@ | ||||||
| import {writeFile} from "fs"; | import {writeFile} from "fs"; | ||||||
| import Translations from "../UI/i18n/Translations"; | import Translations from "../UI/i18n/Translations"; | ||||||
| import {AllKnownLayouts} from "../Customizations/AllKnownLayouts"; | import * as themeOverview from "../assets/generated/theme_overview.json" | ||||||
| import LayoutConfig from "../Models/ThemeConfig/LayoutConfig"; |  | ||||||
| 
 | 
 | ||||||
| function generateWikiEntry(layout: LayoutConfig) { | function generateWikiEntry(layout: {hideFromOverview: boolean, id: string, shortDescription: any}) { | ||||||
|     if (layout.hideFromOverview) { |     if (layout.hideFromOverview) { | ||||||
|         return ""; |         return ""; | ||||||
|     } |     } | ||||||
|     const languages = layout.language.map(ln => `{{#language:${ln}|en}}`).join(", ") |      | ||||||
|     let auth = "Yes"; |     const languagesInDescr = [] | ||||||
|     if (layout.maintainer !== "" && layout.maintainer !== "MapComplete") { |     for (const shortDescriptionKey in layout.shortDescription) { | ||||||
|         auth = `Yes, by ${layout.maintainer};` |         languagesInDescr.push(shortDescriptionKey) | ||||||
|     } |     } | ||||||
|  |      | ||||||
|  |     const languages = languagesInDescr .map(ln => `{{#language:${ln}|en}}`).join(", ") | ||||||
|  |     let auth = "Yes"; | ||||||
|     return `{{service_item
 |     return `{{service_item
 | ||||||
| |name= [https://mapcomplete.osm.be/${layout.id} ${layout.id}]
 | |name= [https://mapcomplete.osm.be/${layout.id} ${layout.id}]
 | ||||||
| |region= Worldwide | |region= Worldwide | ||||||
| |lang= ${languages} | |lang= ${languages} | ||||||
| |descr= A MapComplete theme: ${Translations.WT(layout.description) | |descr= A MapComplete theme: ${Translations.WT(layout.shortDescription) | ||||||
|         .textFor("en") |         .textFor("en") | ||||||
|         .replace("<a href='", "[[") |         .replace("<a href='", "[[") | ||||||
|         .replace(/'>.*<\/a>/, "]]") |         .replace(/'>.*<\/a>/, "]]") | ||||||
|  | @ -31,7 +33,7 @@ let wikiPage = "{|class=\"wikitable sortable\"\n" + | ||||||
|     "! Name, link !! Genre !! Covered region !! Language !! Description !! Free materials !! Image\n" + |     "! Name, link !! Genre !! Covered region !! Language !! Description !! Free materials !! Image\n" + | ||||||
|     "|-"; |     "|-"; | ||||||
| 
 | 
 | ||||||
| for (const layout of AllKnownLayouts.layoutsList) { | for (const layout of themeOverview) { | ||||||
|     if (layout.hideFromOverview) { |     if (layout.hideFromOverview) { | ||||||
|         continue; |         continue; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| import ScriptUtils from "./ScriptUtils"; | import ScriptUtils from "./ScriptUtils"; | ||||||
| import {writeFileSync} from "fs"; | import {writeFileSync} from "fs"; | ||||||
| import LegacyJsonConvert from "../Models/ThemeConfig/LegacyJsonConvert"; | import {FixLegacyTheme, UpdateLegacyLayer} from "../Models/ThemeConfig/LegacyJsonConvert"; | ||||||
| 
 | 
 | ||||||
| /* | /* | ||||||
|  * This script reads all theme and layer files and reformats them inplace |  * This script reads all theme and layer files and reformats them inplace | ||||||
|  | @ -10,8 +10,9 @@ import LegacyJsonConvert from "../Models/ThemeConfig/LegacyJsonConvert"; | ||||||
| const layerFiles = ScriptUtils.getLayerFiles(); | const layerFiles = ScriptUtils.getLayerFiles(); | ||||||
| for (const layerFile of layerFiles) { | for (const layerFile of layerFiles) { | ||||||
|     try { |     try { | ||||||
|         LegacyJsonConvert.fixLayerConfig(layerFile.parsed) |         const state : any = undefined; // FIXME
 | ||||||
|         writeFileSync(layerFile.path, JSON.stringify(layerFile.parsed, null, "  ")) |         const fixed = new UpdateLegacyLayer().convertStrict(state,layerFile.parsed, "While linting "+layerFile.path); | ||||||
|  |         writeFileSync(layerFile.path, JSON.stringify(fixed, null, "  ")) | ||||||
|     } catch (e) { |     } catch (e) { | ||||||
|         console.error("COULD NOT LINT LAYER" + layerFile.path + ":\n\t" + e) |         console.error("COULD NOT LINT LAYER" + layerFile.path + ":\n\t" + e) | ||||||
|     } |     } | ||||||
|  | @ -20,8 +21,9 @@ for (const layerFile of layerFiles) { | ||||||
| const themeFiles = ScriptUtils.getThemeFiles() | const themeFiles = ScriptUtils.getThemeFiles() | ||||||
| for (const themeFile of themeFiles) { | for (const themeFile of themeFiles) { | ||||||
|     try { |     try { | ||||||
|         LegacyJsonConvert.fixThemeConfig(themeFile.parsed) |         const state : any = undefined; // FIXME
 | ||||||
|         writeFileSync(themeFile.path, JSON.stringify(themeFile.parsed, null, "  ")) |         const fixed = new FixLegacyTheme().convertStrict(state,themeFile.parsed, "While linting "+themeFile.path); | ||||||
|  |         writeFileSync(themeFile.path, JSON.stringify(fixed, null, "  ")) | ||||||
|     } catch (e) { |     } catch (e) { | ||||||
|         console.error("COULD NOT LINT THEME" + themeFile.path + ":\n\t" + e) |         console.error("COULD NOT LINT THEME" + themeFile.path + ":\n\t" + e) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | @ -1,5 +1,4 @@ | ||||||
| import T from "./TestHelper"; | import T from "./TestHelper"; | ||||||
| import {AllKnownLayouts} from "../Customizations/AllKnownLayouts"; |  | ||||||
| import SelectedElementTagsUpdater from "../Logic/Actors/SelectedElementTagsUpdater"; | import SelectedElementTagsUpdater from "../Logic/Actors/SelectedElementTagsUpdater"; | ||||||
| import UserRelatedState from "../Logic/State/UserRelatedState"; | import UserRelatedState from "../Logic/State/UserRelatedState"; | ||||||
| import {Utils} from "../Utils"; | import {Utils} from "../Utils"; | ||||||
|  | @ -7,6 +6,8 @@ import SelectedFeatureHandler from "../Logic/Actors/SelectedFeatureHandler"; | ||||||
| import {UIEventSource} from "../Logic/UIEventSource"; | import {UIEventSource} from "../Logic/UIEventSource"; | ||||||
| import {ElementStorage} from "../Logic/ElementStorage"; | import {ElementStorage} from "../Logic/ElementStorage"; | ||||||
| import Loc from "../Models/Loc"; | import Loc from "../Models/Loc"; | ||||||
|  | import * as bookcaseJson from "../assets/generated/themes/bookcases.json" | ||||||
|  | import LayoutConfig from "../Models/ThemeConfig/LayoutConfig"; | ||||||
| 
 | 
 | ||||||
| export default class ActorsSpec extends T { | export default class ActorsSpec extends T { | ||||||
| 
 | 
 | ||||||
|  | @ -52,7 +53,7 @@ export default class ActorsSpec extends T { | ||||||
|             [ |             [ | ||||||
|                 "download latest version", |                 "download latest version", | ||||||
|                 () => { |                 () => { | ||||||
|                     const state = new UserRelatedState(AllKnownLayouts.allKnownLayouts.get("bookcases")) |                     const state = new UserRelatedState(new LayoutConfig(bookcaseJson, true, "tests" )) | ||||||
|                     const feature = { |                     const feature = { | ||||||
|                         "type": "Feature", |                         "type": "Feature", | ||||||
|                         "id": "node/5568693115", |                         "id": "node/5568693115", | ||||||
|  |  | ||||||
|  | @ -1,6 +1,8 @@ | ||||||
| import T from "./TestHelper"; | import T from "./TestHelper"; | ||||||
| import LegacyJsonConvert from "../Models/ThemeConfig/LegacyJsonConvert"; | import {FixLegacyTheme} from "../Models/ThemeConfig/LegacyJsonConvert"; | ||||||
| import LayoutConfig from "../Models/ThemeConfig/LayoutConfig"; | import LayoutConfig from "../Models/ThemeConfig/LayoutConfig"; | ||||||
|  | import {LayerConfigJson} from "../Models/ThemeConfig/Json/LayerConfigJson"; | ||||||
|  | import {TagRenderingConfigJson} from "../Models/ThemeConfig/Json/TagRenderingConfigJson"; | ||||||
| 
 | 
 | ||||||
| export default class LegacyThemeLoaderSpec extends T { | export default class LegacyThemeLoaderSpec extends T { | ||||||
| 
 | 
 | ||||||
|  | @ -145,9 +147,12 @@ export default class LegacyThemeLoaderSpec extends T { | ||||||
|                 ["Walking_node_theme", () => { |                 ["Walking_node_theme", () => { | ||||||
| 
 | 
 | ||||||
|                     const config = LegacyThemeLoaderSpec.walking_node_theme |                     const config = LegacyThemeLoaderSpec.walking_node_theme | ||||||
|                     LegacyJsonConvert.fixThemeConfig(config) |                     const fixed = new FixLegacyTheme().convert({tagRenderings: new Map<string, TagRenderingConfigJson>(), sharedLayers: new Map<string, LayerConfigJson>()},  | ||||||
|                     // @ts-ignore
 |                         // @ts-ignore
 | ||||||
|                     const theme = new LayoutConfig(config) |                         config, | ||||||
|  |                         "While testing") | ||||||
|  |                     T.isTrue(fixed.errors.length === 0, "Could not fix the legacy theme") | ||||||
|  |                     const theme = new LayoutConfig(fixed.result) | ||||||
| 
 | 
 | ||||||
|                 }] |                 }] | ||||||
|             ] |             ] | ||||||
|  |  | ||||||
|  | @ -1,8 +1,13 @@ | ||||||
| import T from "./TestHelper"; | import T from "./TestHelper"; | ||||||
| import {Utils} from "../Utils"; |  | ||||||
| import * as assert from "assert"; | import * as assert from "assert"; | ||||||
| import {LayoutConfigJson} from "../Models/ThemeConfig/Json/LayoutConfigJson"; | import {LayoutConfigJson} from "../Models/ThemeConfig/Json/LayoutConfigJson"; | ||||||
| import LayoutConfig from "../Models/ThemeConfig/LayoutConfig"; | import LayoutConfig from "../Models/ThemeConfig/LayoutConfig"; | ||||||
|  | import * as bookcaseLayer from "../assets/generated/layers/public_bookcase.json" | ||||||
|  | import {PrepareLayer, PrepareTheme} from "../Models/ThemeConfig/LegacyJsonConvert"; | ||||||
|  | import {TagRenderingConfigJson} from "../Models/ThemeConfig/Json/TagRenderingConfigJson"; | ||||||
|  | import {LayerConfigJson} from "../Models/ThemeConfig/Json/LayerConfigJson"; | ||||||
|  | import LayerConfig from "../Models/ThemeConfig/LayerConfig"; | ||||||
|  | import Constants from "../Models/Constants"; | ||||||
| 
 | 
 | ||||||
| export default class ThemeSpec extends T { | export default class ThemeSpec extends T { | ||||||
|     constructor() { |     constructor() { | ||||||
|  | @ -10,7 +15,7 @@ export default class ThemeSpec extends T { | ||||||
|             [ |             [ | ||||||
|                 ["Nested overrides work", () => { |                 ["Nested overrides work", () => { | ||||||
| 
 | 
 | ||||||
|                     const themeConfigJson: LayoutConfigJson = { |                     let themeConfigJson: LayoutConfigJson = { | ||||||
|                         description: "Descr", |                         description: "Descr", | ||||||
|                         icon: "", |                         icon: "", | ||||||
|                         language: ["en"], |                         language: ["en"], | ||||||
|  | @ -34,7 +39,14 @@ export default class ThemeSpec extends T { | ||||||
|                         version: "", |                         version: "", | ||||||
|                         id: "test" |                         id: "test" | ||||||
|                     } |                     } | ||||||
| 
 |                     // TOtal cheat: disable the default layers:
 | ||||||
|  |                     Constants.added_by_default.splice(0, Constants.added_by_default.length) | ||||||
|  |                     const sharedLayers = new Map<string, LayerConfigJson>() | ||||||
|  |                     sharedLayers.set("public_bookcase", bookcaseLayer["default"]) | ||||||
|  |                     themeConfigJson = new PrepareTheme().convertStrict({ | ||||||
|  |                         tagRenderings: new Map<string, TagRenderingConfigJson>(), | ||||||
|  |                         sharedLayers: sharedLayers | ||||||
|  |                     }, themeConfigJson, "test") | ||||||
|                     const themeConfig = new LayoutConfig(themeConfigJson); |                     const themeConfig = new LayoutConfig(themeConfigJson); | ||||||
|                     assert.equal("xyz", themeConfig.layers[0].source.geojsonSource) |                     assert.equal("xyz", themeConfig.layers[0].source.geojsonSource) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
							
								
								
									
										84
									
								
								theme.html
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										84
									
								
								theme.html
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,84 @@ | ||||||
|  | <!DOCTYPE html> | ||||||
|  | <!-- WARNING: index.html serves as a template. If you want to change something, change it there --> | ||||||
|  | <html lang="en"> | ||||||
|  | <head> | ||||||
|  |     <meta charset="UTF-8"> | ||||||
|  |     <meta content="width=device-width, initial-scale=1.0, user-scalable=no" name="viewport"> | ||||||
|  |     <link href="./vendor/leaflet.css" rel="stylesheet"/> | ||||||
|  |     <link href="./css/userbadge.css" rel="stylesheet"/> | ||||||
|  |     <link href="./css/tabbedComponent.css" rel="stylesheet"/> | ||||||
|  |     <link href="./css/mobile.css" rel="stylesheet"/> | ||||||
|  |     <link href="./css/openinghourstable.css" rel="stylesheet"/> | ||||||
|  |     <link href="./css/tagrendering.css" rel="stylesheet"/> | ||||||
|  |     <link href="css/ReviewElement.css" rel="stylesheet"/> | ||||||
|  |     <link href="./css/index-tailwind-output.css" rel="stylesheet"/> | ||||||
|  |     <link href="./css/wikipedia.css" rel="stylesheet"/> | ||||||
|  |     <meta content="website" property="og:type"> | ||||||
|  | 
 | ||||||
|  |     <!-- THEME-SPECIFIC --> | ||||||
|  |     <!-- Every theme gets their own html page, this is created by a script; this part will be removed except for the index --> | ||||||
|  |     <title>MapComplete</title> | ||||||
|  |     <link href="./index.manifest" rel="manifest"> | ||||||
|  |     <link href="./assets/svg/add.svg" rel="icon" sizes="any" type="image/svg+xml"> | ||||||
|  |     <meta content="./assets/SocialImage.png" property="og:image"> | ||||||
|  |     <meta content="MapComplete - editable, thematic maps with OpenStreetMap" property="og:title"> | ||||||
|  |     <meta content="MapComplete is a platform to visualize OpenStreetMap on a specific topic and to easily contribute data back to it." | ||||||
|  |           property="og:description"> | ||||||
|  | 
 | ||||||
|  |     <link href="./assets/generated/svg_mapcomplete_logo512.png" rel="apple-touch-icon" sizes="512x512"> | ||||||
|  |     <link href="./assets/generated/svg_mapcomplete_logo384.png" rel="apple-touch-icon" sizes="384x384"> | ||||||
|  |     <link href="./assets/generated/svg_mapcomplete_logo192.png" rel="apple-touch-icon" sizes="192x192"> | ||||||
|  |     <link href="./assets/generated/svg_mapcomplete_logo180.png" rel="apple-touch-icon" sizes="180x180"> | ||||||
|  |     <link href="./assets/generated/svg_mapcomplete_logo152.png" rel="apple-touch-icon" sizes="152x152"> | ||||||
|  |     <link href="./assets/generated/svg_mapcomplete_logo144.png" rel="apple-touch-icon" sizes="144x144"> | ||||||
|  |     <link href="./assets/generated/svg_mapcomplete_logo128.png" rel="apple-touch-icon" sizes="128x128"> | ||||||
|  |     <link href="./assets/generated/svg_mapcomplete_logo120.png" rel="apple-touch-icon" sizes="120x120"> | ||||||
|  |     <link href="./assets/generated/svg_mapcomplete_logo96.png" rel="apple-touch-icon" sizes="96x96"> | ||||||
|  |     <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> | ||||||
|  | 
 | ||||||
|  | <div id="decoration-desktop" style="position: fixed; left: 1em; bottom: 1em; width:35vh; height:35vh;"> | ||||||
|  |     <!-- A nice decoration while loading or on errors --> | ||||||
|  |     <!-- DECORATION 0 START --> | ||||||
|  |     <img src="./assets/svg/add.svg"/> | ||||||
|  |     <!-- DECORATION 0 END --> | ||||||
|  | </div> | ||||||
|  | 
 | ||||||
|  | <div class="hidden md:hidden fixed inset-0 block z-above-controls" id="fullscreen"></div> | ||||||
|  | <div class="z-index-above-map pointer-events-none" id="topleft-tools"> | ||||||
|  |     <div class="p-3 flex flex-col items-end sm:items-start sm:flex-row sm:flex-wrap w-full sm:justify-between"> | ||||||
|  |         <div class="shadow rounded-full h-min w-full overflow-hidden sm:max-w-sm pointer-events-auto" | ||||||
|  |              id="searchbox"></div> | ||||||
|  |         <div class="m-1 pointer-events-auto" id="userbadge"></div> | ||||||
|  |     </div> | ||||||
|  |     <div class="rounded-3xl overflow-hidden ml-3" id="messagesbox"></div> | ||||||
|  | </div> | ||||||
|  | 
 | ||||||
|  | <div class="absolute bottom-3 left-3 rounded-3xl z-above-map" id="bottom-left"></div> | ||||||
|  | <div class="absolute bottom-3 right-2 rounded-3xl z-above-map" id="bottom-right"></div> | ||||||
|  | 
 | ||||||
|  | <div class="clutter absolute h-24 left-24 right-24 top-56 text-xl text-center" | ||||||
|  |      id="centermessage" style="z-index: 4000"> | ||||||
|  |     Loading MapComplete, hang on... | ||||||
|  | </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> | ||||||
|  | 
 | ||||||
|  | </body> | ||||||
|  | </html> | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue