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 LayoutConfig from "../Models/ThemeConfig/LayoutConfig"; | ||||
| import LayerConfig from "../Models/ThemeConfig/LayerConfig"; | ||||
|  | @ -7,9 +6,29 @@ import Combine from "../UI/Base/Combine"; | |||
| import Title from "../UI/Base/Title"; | ||||
| import List from "../UI/Base/List"; | ||||
| import DependencyCalculator from "../Models/ThemeConfig/DependencyCalculator"; | ||||
| import Constants from "../Models/Constants"; | ||||
| import {Utils} from "../Utils"; | ||||
| 
 | ||||
| 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 layoutsList: LayoutConfig[] = AllKnownLayouts.GenerateOrderedList(AllKnownLayouts.allKnownLayouts); | ||||
|  | @ -35,14 +54,14 @@ export class AllKnownLayouts { | |||
|     } | ||||
| 
 | ||||
|     public static GenLayerOverviewText(): BaseUIElement { | ||||
|         for (const id of AllKnownLayers.priviliged_layers) { | ||||
|             if (!AllKnownLayers.sharedLayers.has(id)) { | ||||
|         for (const id of Constants.priviliged_layers) { | ||||
|             if (!AllKnownLayouts.sharedLayers.has(id)) { | ||||
|                 throw "Priviliged layer definition not found: " + id | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         const allLayers: LayerConfig[] = Array.from(AllKnownLayers.sharedLayers.values()) | ||||
|             .filter(layer => AllKnownLayers.priviliged_layers.indexOf(layer.id) < 0) | ||||
|         const allLayers: LayerConfig[] = Array.from(AllKnownLayouts.sharedLayers.values()) | ||||
|             .filter(layer => Constants.priviliged_layers.indexOf(layer.id) < 0) | ||||
| 
 | ||||
|         const builtinLayerIds: Set<string> = new Set<string>() | ||||
|         allLayers.forEach(l => builtinLayerIds.add(l.id)) | ||||
|  | @ -89,10 +108,10 @@ export class AllKnownLayouts { | |||
|             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.", | ||||
|             new Title("Priviliged layers", 1), | ||||
|             new List(AllKnownLayers.priviliged_layers.map(id => "[" + id + "](#" + id + ")")), | ||||
|             ...AllKnownLayers.priviliged_layers | ||||
|                 .map(id => AllKnownLayers.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)), | ||||
|             new List(Constants.priviliged_layers.map(id => "[" + id + "](#" + id + ")")), | ||||
|             ...Constants.priviliged_layers | ||||
|                 .map(id => AllKnownLayouts.sharedLayers.get(id)) | ||||
|                 .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), | ||||
|             "The following layers are included in MapComplete", | ||||
|             new Title("Frequently reused layers", 2), | ||||
|  | @ -108,53 +127,26 @@ export class AllKnownLayouts { | |||
|     } | ||||
| 
 | ||||
|     private static GenerateOrderedList(allKnownLayouts: Map<string, LayoutConfig>): LayoutConfig[] { | ||||
|         const keys = ["personal", "cyclofix", "hailhydrant", "bookcases", "toilets", "aed"] | ||||
|         const list = [] | ||||
|         for (const key of keys) { | ||||
|             list.push(allKnownLayouts.get(key)) | ||||
|         } | ||||
|         allKnownLayouts.forEach((layout, key) => { | ||||
|             if (keys.indexOf(key) < 0) { | ||||
|             list.push(layout) | ||||
|             } | ||||
|         }) | ||||
|         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> { | ||||
|         const dict: Map<string, LayoutConfig> = new Map(); | ||||
|         for (const layoutConfigJson of known_themes.themes) { | ||||
|             // @ts-ignore
 | ||||
|             const layout = new LayoutConfig(layoutConfigJson, true) | ||||
| 
 | ||||
|             if (layout.id === "cyclofix") { | ||||
|                 AllKnownLayouts.AddGhostBikes(layout) | ||||
|             } | ||||
|             dict.set(layout.id, layout) | ||||
| 
 | ||||
|             for (let i = 0; i < layout.layers.length; i++) { | ||||
|                 let layer = layout.layers[i]; | ||||
|                 if (typeof (layer) === "string") { | ||||
|                     layer = layout.layers[i] = AllKnownLayers.sharedLayers.get(layer); | ||||
|                     layer = layout.layers[i] = AllKnownLayouts.sharedLayers.get(layer); | ||||
|                     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` | ||||
|                     } | ||||
|                 } | ||||
|  |  | |||
|  | @ -38,7 +38,9 @@ export default class SharedTagRenderings { | |||
|             dict.set(key, <TagRenderingConfigJson>icons[key]) | ||||
|         } | ||||
| 
 | ||||
|         dict.forEach((value, key) => value.id = key) | ||||
|         dict.forEach((value, key) => { | ||||
|             value.id = value.id ?? key; | ||||
|         }) | ||||
| 
 | ||||
|         return dict; | ||||
|     } | ||||
|  |  | |||
|  | @ -29,8 +29,6 @@ | |||
|       * [Themes using this layer](#themes-using-this-layer) | ||||
|     + [walls_and_buildings](#walls_and_buildings) | ||||
|       * [Themes using this layer](#themes-using-this-layer) | ||||
|     + [all_streets](#all_streets) | ||||
|       * [Themes using this layer](#themes-using-this-layer) | ||||
|     + [ambulancestation](#ambulancestation) | ||||
|       * [Themes using this layer](#themes-using-this-layer) | ||||
|     + [artwork](#artwork) | ||||
|  | @ -113,36 +111,6 @@ | |||
|       * [Themes using this layer](#themes-using-this-layer) | ||||
|     + [waste_basket](#waste_basket) | ||||
|       * [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.  | ||||
| 
 | ||||
|  | @ -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` | ||||
|   - This layer is needed as dependency for layer [GRB](#GRB) | ||||
|   | ||||
| 
 | ||||
| ### conflation  | ||||
|  | @ -320,7 +287,6 @@ The default rendering for a locationInput which snaps onto another object | |||
|   - [food](#food) | ||||
|   - [map](#map) | ||||
|   - [walls_and_buildings](#walls_and_buildings) | ||||
|   - [all_streets](#all_streets) | ||||
|   | ||||
| 
 | ||||
| ### 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) | ||||
|   | ||||
| 
 | ||||
| ### 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) | ||||
|   - [artwork](#artwork) | ||||
|   - [barrier](#barrier) | ||||
|  | @ -520,21 +463,6 @@ Special builtin layer providing all walls and buildings. This layer is useful in | |||
|   - [toilet](#toilet) | ||||
|   - [tree_node](#tree_node) | ||||
|   - [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  | ||||
|  | @ -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 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) | ||||
|   | ||||
| 
 | ||||
| ### 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 | ||||
|  | @ -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 Loc from "../../Models/Loc"; | ||||
| import LayerConfig from "../../Models/ThemeConfig/LayerConfig"; | ||||
| import AllKnownLayers from "../../Customizations/AllKnownLayers"; | ||||
| import Constants from "../../Models/Constants"; | ||||
| 
 | ||||
| 
 | ||||
| export default class OverpassFeatureSource implements FeatureSource { | ||||
|  | @ -122,7 +122,7 @@ export default class OverpassFeatureSource implements FeatureSource { | |||
|             if (typeof (layer) === "string") { | ||||
|                 throw "A layer was not expanded!" | ||||
|             } | ||||
|             if(AllKnownLayers.priviliged_layers.indexOf(layer.id) >= 0){ | ||||
|             if(Constants.priviliged_layers.indexOf(layer.id) >= 0){ | ||||
|                 continue | ||||
|             } | ||||
|             if (this.state.locationControl.data.zoom < layer.minzoom) { | ||||
|  |  | |||
|  | @ -29,7 +29,7 @@ export default class TitleHandler { | |||
|                     } | ||||
|                     if (layer.source.osmTags.matchesProperties(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; | ||||
|                     } | ||||
|                 } | ||||
|  |  | |||
|  | @ -10,30 +10,29 @@ import {UIEventSource} from "./UIEventSource"; | |||
| import {LocalStorageSource} from "./Web/LocalStorageSource"; | ||||
| import LZString from "lz-string"; | ||||
| 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 { | ||||
| 
 | ||||
|     /** | ||||
|      * 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 layoutFromBase64 = decodeURIComponent(loadCustomThemeParam.data); | ||||
| 
 | ||||
|         if (layoutFromBase64.startsWith("http")) { | ||||
|             const layout = await DetermineLayout.LoadRemoteTheme(layoutFromBase64) | ||||
|             return [layout, undefined] | ||||
|             return await DetermineLayout.LoadRemoteTheme(layoutFromBase64) | ||||
|         } | ||||
| 
 | ||||
|         if (layoutFromBase64 !== "false") { | ||||
|             // We have to load something from the hash (or from disk)
 | ||||
|             let loaded = DetermineLayout.LoadLayoutFromHash(loadCustomThemeParam); | ||||
|             if (loaded === null) { | ||||
|                 return [null, undefined] | ||||
|             } | ||||
|             return loaded | ||||
|             return DetermineLayout.LoadLayoutFromHash(loadCustomThemeParam) | ||||
|         } | ||||
| 
 | ||||
|         let layoutId: string = undefined | ||||
|  | @ -43,7 +42,7 @@ export default class DetermineLayout { | |||
| 
 | ||||
| 
 | ||||
|         const path = window.location.pathname.split("/").slice(-1)[0]; | ||||
|         if (path !== "index.html" && path !== "") { | ||||
|         if (path !== "theme.html" && path !== "") { | ||||
|             layoutId = path; | ||||
|             if (path.endsWith(".html")) { | ||||
|                 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( | ||||
|         userLayoutParam: UIEventSource<string> | ||||
|     ): [LayoutConfig, string] | null { | ||||
|     ): LayoutConfig | null { | ||||
|         let hash = location.hash.substr(1); | ||||
|         try { | ||||
|             // 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 = new LayoutConfig(json, false); | ||||
|             const layoutToUse = DetermineLayout.prepCustomTheme(json) | ||||
|             userLayoutParam.setData(layoutToUse.id); | ||||
|             return [layoutToUse, btoa(Utils.MinifyJSON(JSON.stringify(json)))]; | ||||
|             return new LayoutConfig(layoutToUse, false); | ||||
|         } catch (e) { | ||||
|             console.error(e) | ||||
|             if (hash === undefined || hash.length < 10) { | ||||
|  | @ -141,12 +155,20 @@ export default class DetermineLayout { | |||
| 
 | ||||
|         try { | ||||
| 
 | ||||
|             const parsed = await Utils.downloadJson(link) | ||||
|             let parsed = await Utils.downloadJson(link) | ||||
|             console.log("Got ", parsed) | ||||
|             LegacyJsonConvert.fixThemeConfig(parsed) | ||||
|             try { | ||||
|             parsed = new FixLegacyTheme().convertStrict({ | ||||
|                 tagRenderings: SharedTagRenderings.SharedTagRenderingJson, | ||||
|                 sharedLayers: new Map<string, LayerConfigJson>() // FIXME: actually add the layers
 | ||||
|             }, parsed, "While loading a dynamic theme") | ||||
| 
 | ||||
| 
 | ||||
|                 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) { | ||||
|                 console.error(e) | ||||
|                 DetermineLayout.ShowErrorOnCustomTheme( | ||||
|  |  | |||
|  | @ -10,7 +10,6 @@ import {LocalStorageSource} from "../Web/LocalStorageSource"; | |||
| import {Utils} from "../../Utils"; | ||||
| import ChangeToElementsActor from "../Actors/ChangeToElementsActor"; | ||||
| 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 | ||||
|  | @ -90,7 +89,6 @@ export default class ElementsState extends FeatureSwitchState { | |||
| 
 | ||||
|         new ChangeToElementsActor(this.changes, this.allElements) | ||||
|         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 {LocalStorageSource} from "../Web/LocalStorageSource"; | ||||
| import {GeoOperations} from "../GeoOperations"; | ||||
| import TitleHandler from "../Actors/TitleHandler"; | ||||
| 
 | ||||
| /** | ||||
|  * Contains all the leaflet-map related state | ||||
|  | @ -130,6 +131,8 @@ export default class MapState extends UserRelatedState { | |||
|         this.initGpsLocation() | ||||
|         this.initUserLocationTrail() | ||||
|         this.initCurrentView() | ||||
| 
 | ||||
|         new TitleHandler(this); | ||||
|     } | ||||
| 
 | ||||
|     public AddAllOverlaysToMap(leafletMap: UIEventSource<any>) { | ||||
|  |  | |||
|  | @ -3,12 +3,12 @@ import {OsmConnection} from "../Osm/OsmConnection"; | |||
| import {MangroveIdentity} from "../Web/MangroveReviews"; | ||||
| import {UIEventSource} from "../UIEventSource"; | ||||
| import {QueryParameters} from "../Web/QueryParameters"; | ||||
| import InstalledThemes from "../Actors/InstalledThemes"; | ||||
| import {LocalStorageSource} from "../Web/LocalStorageSource"; | ||||
| import {Utils} from "../../Utils"; | ||||
| import Locale from "../../UI/i18n/Locale"; | ||||
| import ElementsState from "./ElementsState"; | ||||
| 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, | ||||
|  | @ -33,7 +33,10 @@ export default class UserRelatedState extends ElementsState { | |||
|     /** | ||||
|      * 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}) { | ||||
|  | @ -69,9 +72,47 @@ export default class UserRelatedState extends ElementsState { | |||
|             }) | ||||
|         } | ||||
| 
 | ||||
|         this.installedThemes = new InstalledThemes( | ||||
|             this.osmConnection | ||||
|         ).installedThemes; | ||||
|         this.installedThemes = this.osmConnection.GetLongPreference("installed-themes").map( | ||||
|             str => { | ||||
|                 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
 | ||||
|         this.favouriteLayers = LocalStorageSource.Get("favouriteLayers") | ||||
|  | @ -82,7 +123,6 @@ export default class UserRelatedState extends ElementsState { | |||
|                 (layers) => Utils.Dedup(layers)?.join(";") | ||||
|             ); | ||||
|          | ||||
| 
 | ||||
|         this.InitializeLanguage(); | ||||
|         new SelectedElementTagsUpdater(this) | ||||
| 
 | ||||
|  |  | |||
|  | @ -2,7 +2,7 @@ import {Utils} from "../Utils"; | |||
| 
 | ||||
| export default class Constants { | ||||
| 
 | ||||
|     public static vNumber = "0.13.0"; | ||||
|     public static vNumber = "0.14.0-alpha-1"; | ||||
|     public static ImgurApiKey = '7070e7167f0a25a' | ||||
|     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
 | ||||
|     public static userJourney = { | ||||
|         moreScreenUnlock: 1, | ||||
|  | @ -51,6 +59,7 @@ export default class Constants { | |||
|      * For every bin, the totals are uploaded as metadata | ||||
|      */ | ||||
|     static distanceToChangeObjectBins = [25,50,100,500,1000,5000, Number.MAX_VALUE] | ||||
|     static themeOrder = ["personal", "cyclofix", "hailhydrant", "bookcases", "toilets", "aed"]; | ||||
| 
 | ||||
|     private static isRetina(): boolean { | ||||
|         if (Utils.runningFromConsole) { | ||||
|  |  | |||
|  | @ -18,7 +18,7 @@ export default interface LineRenderingConfigJson { | |||
|     /** | ||||
|      * The stroke-width for way-elements | ||||
|      */ | ||||
|     width?: string | TagRenderingConfigJson; | ||||
|     width?: string | number | TagRenderingConfigJson; | ||||
| 
 | ||||
|     /** | ||||
|      * A dasharray, e.g. "5 6" | ||||
|  |  | |||
|  | @ -22,7 +22,7 @@ import Title from "../../UI/Base/Title"; | |||
| import List from "../../UI/Base/List"; | ||||
| import Link from "../../UI/Base/Link"; | ||||
| import {Utils} from "../../Utils"; | ||||
| import * as icons from "../../assets/tagRenderings/icons.json" | ||||
| import {tag} from "@turf/turf"; | ||||
| 
 | ||||
| export default class LayerConfig extends WithContextLoader { | ||||
| 
 | ||||
|  | @ -236,26 +236,14 @@ export default class LayerConfig extends WithContextLoader { | |||
|             throw "Missing ids in tagrenderings" | ||||
|         } | ||||
| 
 | ||||
|         this.tagRenderings = this.ExtractLayerTagRenderings(json, official) | ||||
|         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.tagRenderings = (json.tagRenderings ?? []).map((tr, i) => new TagRenderingConfig(<TagRenderingConfigJson>tr, this.id + ".tagRenderings[" + i + "]")) | ||||
| 
 | ||||
|         this.filters = (json.filter ?? []).map((option, 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) { | ||||
|                 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'" | ||||
|         } | ||||
| 
 | ||||
|         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 | ||||
|         }); | ||||
| 
 | ||||
|  | @ -320,109 +299,6 @@ export default class LayerConfig extends WithContextLoader { | |||
|         const defaultTags = new UIEventSource(TagUtils.changeAsProperties(this.source.osmTags.asChange({id: "node/-1"}))) | ||||
|         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: { | ||||
|         context?: string; | ||||
|         reason: string; | ||||
|  | @ -512,5 +388,4 @@ export default class LayerConfig extends WithContextLoader { | |||
|     public isLeftRightSensitive(): boolean { | ||||
|         return this.lineRendering.some(lr => lr.leftRightSensitive) | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | @ -1,12 +1,9 @@ | |||
| import {Translation} from "../../UI/i18n/Translation"; | ||||
| import {LayoutConfigJson} from "./Json/LayoutConfigJson"; | ||||
| import AllKnownLayers from "../../Customizations/AllKnownLayers"; | ||||
| import {Utils} from "../../Utils"; | ||||
| import LayerConfig from "./LayerConfig"; | ||||
| import {LayerConfigJson} from "./Json/LayerConfigJson"; | ||||
| import Constants from "../Constants"; | ||||
| import TilesourceConfig from "./TilesourceConfig"; | ||||
| import DependencyCalculator from "./DependencyCalculator"; | ||||
| 
 | ||||
| export default class LayoutConfig { | ||||
|     public readonly id: string; | ||||
|  | @ -15,7 +12,7 @@ export default class LayoutConfig { | |||
|     public readonly version: string; | ||||
|     public readonly language: string[]; | ||||
|     public readonly title: Translation; | ||||
|     public readonly shortDescription?: Translation; | ||||
|     public readonly shortDescription: Translation; | ||||
|     public readonly description: Translation; | ||||
|     public readonly descriptionTail?: Translation; | ||||
|     public readonly icon: string; | ||||
|  | @ -53,7 +50,6 @@ export default class LayoutConfig { | |||
|     public readonly overpassMaxZoom: number | ||||
|     public readonly osmApiTileSize: number | ||||
|     public readonly official: boolean; | ||||
|     public readonly trackAllNodes: boolean; | ||||
| 
 | ||||
|     constructor(json: LayoutConfigJson, official = true, context?: string) { | ||||
|         this.official = official; | ||||
|  | @ -75,6 +71,7 @@ export default class LayoutConfig { | |||
|         } else { | ||||
|             this.language = json.language; | ||||
|         } | ||||
|         { | ||||
|             if (this.language.length == 0) { | ||||
|                 throw `No languages defined. Define at least one language. (${context}.languages)` | ||||
|             } | ||||
|  | @ -84,6 +81,19 @@ export default class LayoutConfig { | |||
|             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.description = new Translation(json.description, context + ".description"); | ||||
|         this.shortDescription = json.shortDescription === undefined ? this.description.FirstSentence() : new Translation(json.shortDescription, context + ".shortdescription"); | ||||
|  | @ -93,19 +103,12 @@ export default class LayoutConfig { | |||
|         this.startZoom = json.startZoom; | ||||
|         this.startLat = json.startLat; | ||||
|         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.defaultBackgroundId = json.defaultBackgroundId; | ||||
|         this.tileLayerSources = (json.tileLayerSources ?? []).map((config, i) => new TilesourceConfig(config, `${this.id}.tileLayerSources[${i}]`)) | ||||
|         const layerInfo = LayoutConfig.ExtractLayers(json, official, context); | ||||
|         this.layers = layerInfo.layers | ||||
|         // At this point, layers should be expanded and validated either by the generateScript or the LegacyJsonConvert
 | ||||
|         this.layers = json.layers.map(lyrJson => new LayerConfig(<LayerConfigJson>lyrJson, json.id + ".layers." + lyrJson["id"], official)); | ||||
| 
 | ||||
| 
 | ||||
|         this.clustering = { | ||||
|  | @ -125,10 +128,6 @@ export default class LayoutConfig { | |||
|         } | ||||
| 
 | ||||
|         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.enableUserBadge = json.enableUserBadge ?? 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[] { | ||||
|         if (this.official) { | ||||
|             return []; | ||||
|  |  | |||
|  | @ -1,14 +1,404 @@ | |||
| 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"]) { | ||||
|             config.source = config.source ?? {} | ||||
|             config.source.osmTags = config["overpassTags"] | ||||
|  | @ -16,7 +406,12 @@ export default class LegacyJsonConvert { | |||
|         } | ||||
| 
 | ||||
|         if (config.tagRenderings !== undefined) { | ||||
|             let i =0; | ||||
|             for (const tagRendering of config.tagRenderings) { | ||||
|                 i++; | ||||
|                 if(typeof tagRendering === "string" || tagRendering["builtin"] !== undefined){ | ||||
|                     continue | ||||
|                 } | ||||
|                 if (tagRendering["id"] === undefined) { | ||||
| 
 | ||||
|                     if (tagRendering["#"] !== undefined) { | ||||
|  | @ -24,11 +419,14 @@ export default class LegacyJsonConvert { | |||
|                         delete tagRendering["#"] | ||||
|                     } else if (tagRendering["freeform"]?.key !== undefined) { | ||||
|                         tagRendering["id"] = config.id + "-" + tagRendering["freeform"]["key"] | ||||
|                     }else{ | ||||
|                         tagRendering["id"] = "tr-"+i | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
|         if (config.mapRendering === undefined) { | ||||
|             config.mapRendering = [] | ||||
|             // This is a legacy format, lets create a pointRendering
 | ||||
|  | @ -50,7 +448,6 @@ export default class LegacyJsonConvert { | |||
|                 config.mapRendering.push(pointConfig) | ||||
|             } | ||||
| 
 | ||||
| 
 | ||||
|             if (wayHandling !== 1) { | ||||
|                 const lineRenderConfig = <LineRenderingConfigJson>{ | ||||
|                     color: config["color"], | ||||
|  | @ -67,6 +464,7 @@ export default class LegacyJsonConvert { | |||
| 
 | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
|         delete config["color"] | ||||
|         delete config["width"] | ||||
|         delete config["dashArray"] | ||||
|  | @ -85,32 +483,463 @@ export default class LegacyJsonConvert { | |||
|             } | ||||
|             for (const overlay of mapRenderingElement["iconBadges"] ?? []) { | ||||
|                 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"] | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return { | ||||
|             result: config, | ||||
|             errors: [], | ||||
|             warnings | ||||
|         }; | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| class UpdateLegacyTheme extends DesugaringStep<LayoutConfigJson> { | ||||
|     constructor() { | ||||
|         super("Small fixes in the theme config", ["roamingRenderings"]); | ||||
|     } | ||||
| 
 | ||||
|     convert(state: DesugaringContext, json: LayoutConfigJson, context: string): { result: LayoutConfigJson; errors: string[]; warnings: string[] } { | ||||
|         const oldThemeConfig = {...json} | ||||
|         if (oldThemeConfig["roamingRenderings"] !== undefined) { | ||||
| 
 | ||||
|             if (oldThemeConfig["roamingRenderings"].length == 0) { | ||||
|                 delete oldThemeConfig["roamingRenderings"] | ||||
|             } else { | ||||
|                 return { | ||||
|                     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> { | ||||
|     /** | ||||
|      * Given an old (parsed) JSON-config, will (in place) fix some issues | ||||
|      * @param oldThemeConfig: the config to update to the latest format | ||||
|      * The paths where this layer is originally saved. Triggers some extra checks | ||||
|      * @private | ||||
|      */ | ||||
|     public static fixThemeConfig(oldThemeConfig: any): void { | ||||
|         for (const layerConfig of oldThemeConfig.layers ?? []) { | ||||
|             if (typeof layerConfig === "string" || layerConfig["builtin"] !== undefined) { | ||||
|     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 | ||||
|                     } | ||||
|             // @ts-ignore
 | ||||
|             LegacyJsonConvert.fixLayerConfig(layerConfig) | ||||
| 
 | ||||
|                     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}`) | ||||
|                     } | ||||
|                 } | ||||
| 
 | ||||
|         if (oldThemeConfig["roamingRenderings"] !== undefined && oldThemeConfig["roamingRenderings"].length == 0) { | ||||
|             delete oldThemeConfig["roamingRenderings"] | ||||
|             } | ||||
|             { | ||||
|                 // 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 addExtraTags: Tag[] | ||||
|     }[] | ||||
| 
 | ||||
|     constructor(json: string | TagRenderingConfigJson, context?: string) { | ||||
|         if (json === undefined) { | ||||
|             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) { | ||||
|             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 SharedTagRenderings from "../../Customizations/SharedTagRenderings"; | ||||
| import {TagRenderingConfigJson} from "./Json/TagRenderingConfigJson"; | ||||
| import {Utils} from "../../Utils"; | ||||
| 
 | ||||
| export default class WithContextLoader { | ||||
|     protected readonly _context: string; | ||||
|     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) { | ||||
|         this._json = json; | ||||
|         this._context = context; | ||||
|  | @ -53,7 +43,7 @@ export default class WithContextLoader { | |||
|      * A string is interpreted as a name to call | ||||
|      */ | ||||
|     public ParseTagRenderings( | ||||
|         tagRenderings: (string | { builtin: string, override: any } | TagRenderingConfigJson)[], | ||||
|         tagRenderings: TagRenderingConfigJson[], | ||||
|         options?: { | ||||
|             /** | ||||
|              * Throw an error if 'question' is defined | ||||
|  | @ -73,54 +63,9 @@ export default class WithContextLoader { | |||
|         if (options.prepConfig === undefined) { | ||||
|             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[] = [] | ||||
|         for (let i = 0; i < preparedConfigs.length; i++){ | ||||
|             const preparedConfig = preparedConfigs[i]; | ||||
|         for (let i = 0; i < tagRenderings.length; i++) { | ||||
|             const preparedConfig = tagRenderings[i]; | ||||
|             const tr = new TagRenderingConfig(preparedConfig, `${context}.tagrendering[${i}]`); | ||||
|             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` | ||||
|  |  | |||
|  | @ -1,9 +1,9 @@ | |||
| import UserRelatedState from "../Logic/State/UserRelatedState"; | ||||
| 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"; | ||||
|  | @ -13,7 +13,6 @@ import {SubtleButton} from "./Base/SubtleButton"; | |||
| 
 | ||||
| export default class AllThemesGui { | ||||
|     constructor() { | ||||
| 
 | ||||
|         try { | ||||
| 
 | ||||
|             new FixedUiElement("").AttachTo("centermessage") | ||||
|  | @ -41,6 +40,7 @@ export default class AllThemesGui { | |||
|                 .SetStyle("pointer-events: all;") | ||||
|                 .AttachTo("topleft-tools"); | ||||
|         } 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") | ||||
|                 .AttachTo("centermessage") | ||||
|         } | ||||
|  |  | |||
|  | @ -27,6 +27,7 @@ import {QueryParameters} from "../Logic/Web/QueryParameters"; | |||
| import {SubstitutedTranslation} from "./SubstitutedTranslation"; | ||||
| import {AutoAction} from "./Popup/AutoApplyButton"; | ||||
| import DynamicGeoJsonTileSource from "../Logic/FeatureSource/TiledFeatureSource/DynamicGeoJsonTileSource"; | ||||
| import * as themeOverview from "../assets/generated/theme_overview.json" | ||||
| 
 | ||||
| 
 | ||||
| class AutomationPanel extends Combine{ | ||||
|  | @ -177,7 +178,7 @@ class AutomationPanel extends Combine{ | |||
|                         const feature = ffs.feature | ||||
|                         const renderingTr = targetAction.GetRenderValue(feature.properties) | ||||
|                         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) | ||||
|                             .map(obj => obj.special)) | ||||
|                         for (const action of actions) { | ||||
|  | @ -251,7 +252,7 @@ class AutomatonGui { | |||
|     private static GenerateMainPanel(): BaseUIElement { | ||||
| 
 | ||||
|         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()) | ||||
|  |  | |||
|  | @ -8,32 +8,35 @@ import {UIElement} from "../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) { | ||||
|         super(); | ||||
|         this._element = SubtleButton.generateContent(imageUrl, message, linkTo) | ||||
|         this.SetClass("block flex p-3 my-2 bg-blue-100 rounded-lg hover:shadow-xl hover:bg-blue-200 link-no-underline") | ||||
| 
 | ||||
|         this.imageUrl = imageUrl; | ||||
|         this.message = message; | ||||
|         this.linkTo = linkTo; | ||||
|     } | ||||
| 
 | ||||
|     private static generateContent(imageUrl: string | BaseUIElement, messageT: string | BaseUIElement, linkTo: { url: string | UIEventSource<string>, newTab?: boolean } = undefined): BaseUIElement { | ||||
|         const message = Translations.W(messageT); | ||||
|         message | ||||
|     protected InnerRender(): string | BaseUIElement { | ||||
|         const classes=  "block flex p-3 my-2 bg-blue-100 rounded-lg hover:shadow-xl hover:bg-blue-200 link-no-underline"; | ||||
|         const message = Translations.W(this.message); | ||||
|         let img; | ||||
|         if ((imageUrl ?? "") === "") { | ||||
|         if ((this.imageUrl ?? "") === "") { | ||||
|             img = undefined; | ||||
|         } else if (typeof (imageUrl) === "string") { | ||||
|             img = new Img(imageUrl) | ||||
|         } else if (typeof (this.imageUrl) === "string") { | ||||
|             img = new Img(this.imageUrl) | ||||
|         } else { | ||||
|             img = imageUrl; | ||||
|             img = this.imageUrl; | ||||
|         } | ||||
|         img?.SetClass("block flex items-center justify-center h-11 w-11 flex-shrink0 mr-4") | ||||
|         const image = new Combine([img]) | ||||
|             .SetClass("flex-shrink-0"); | ||||
| 
 | ||||
|         if (linkTo == undefined) { | ||||
|         if (this.linkTo == undefined) { | ||||
|             this.SetClass(classes) | ||||
|             return new Combine([ | ||||
|                 image, | ||||
|                 message?.SetClass("block overflow-ellipsis"), | ||||
|  | @ -46,13 +49,10 @@ export class SubtleButton extends UIElement { | |||
|                 image, | ||||
|                 message?.SetClass("block overflow-ellipsis") | ||||
|             ]).SetClass("flex group w-full"), | ||||
|             linkTo.url, | ||||
|             linkTo.newTab ?? false | ||||
|         ) | ||||
|     } | ||||
|             this.linkTo.url, | ||||
|             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 {FixedUiElement} from "../Base/FixedUiElement"; | ||||
| import MoreScreen from "./MoreScreen"; | ||||
| import {AllKnownLayouts} from "../../Customizations/AllKnownLayouts"; | ||||
| import * as themeOverview from "../../assets/generated/theme_overview.json" | ||||
| import Translations from "../i18n/Translations"; | ||||
| 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 }[] { | ||||
|         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) { | ||||
|             if (isNaN(Number(i))) { | ||||
|                 continue | ||||
|  | @ -41,7 +47,8 @@ export default class FeaturedMessage extends Combine { | |||
|             if (wm === null) { | ||||
|                 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) | ||||
|                 continue | ||||
|             } | ||||
|  | @ -71,7 +78,10 @@ export default class FeaturedMessage extends Combine { | |||
|         const msg = new FixedUiElement(welcome_message.message).SetClass("link-underline font-lg") | ||||
|         els.push(new Combine([title, msg]).SetClass("m-4")) | ||||
|         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") | ||||
|                 .SetStyle("height: min-content;")) | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,18 +1,18 @@ | |||
| import {DropDown} from "../Input/DropDown"; | ||||
| import Translations from "../i18n/Translations"; | ||||
| import State from "../../State"; | ||||
| import {UIEventSource} from "../../Logic/UIEventSource"; | ||||
| import {OsmConnection} from "../../Logic/Osm/OsmConnection"; | ||||
| 
 | ||||
| export default class LicensePicker extends DropDown<string> { | ||||
| 
 | ||||
|     constructor() { | ||||
|     constructor(state: {osmConnection: OsmConnection}) { | ||||
|         super(Translations.t.image.willBePublished.Clone(), | ||||
|             [ | ||||
|                 {value: "CC0", shown: Translations.t.image.cco.Clone()}, | ||||
|                 {value: "CC-BY-SA 4.0", shown: Translations.t.image.ccbs.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"); | ||||
|     } | ||||
|  |  | |||
|  | @ -1,5 +1,4 @@ | |||
| import {VariableUiElement} from "../Base/VariableUIElement"; | ||||
| import {AllKnownLayouts} from "../../Customizations/AllKnownLayouts"; | ||||
| import Svg from "../../Svg"; | ||||
| import Combine from "../Base/Combine"; | ||||
| import {SubtleButton} from "../Base/SubtleButton"; | ||||
|  | @ -15,6 +14,8 @@ import UserRelatedState from "../../Logic/State/UserRelatedState"; | |||
| import Toggle from "../Input/Toggle"; | ||||
| import {Utils} from "../../Utils"; | ||||
| 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 { | ||||
| 
 | ||||
|  | @ -47,7 +48,12 @@ export default class MoreScreen extends Combine { | |||
|         state: { | ||||
|             locationControl?: UIEventSource<Loc>, | ||||
|             layoutToUse?: LayoutConfig | ||||
|         }, layout: LayoutConfig, customThemeDefinition: string = undefined | ||||
|         }, layout: { | ||||
|             id: string, | ||||
|             icon: string, | ||||
|             title: any, | ||||
|             shortDescription: any | ||||
|         }, isCustom: boolean = false | ||||
|     ): | ||||
|         BaseUIElement { | ||||
|         if (layout === undefined) { | ||||
|  | @ -73,14 +79,12 @@ export default class MoreScreen extends Combine { | |||
|         } | ||||
| 
 | ||||
|         let linkPrefix = `${path}/${layout.id.toLowerCase()}.html?` | ||||
|         let linkSuffix = "" | ||||
|         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) { | ||||
|             linkPrefix = `${path}/index.html?userlayout=${layout.id}&` | ||||
|             linkSuffix = `#${customThemeDefinition}` | ||||
|         if (isCustom) { | ||||
|             linkPrefix = `${path}/theme.html?userlayout=${layout.id}&` | ||||
|         } | ||||
| 
 | ||||
|         const linkText = currentLocation?.map(currentLocation => { | ||||
|  | @ -91,17 +95,17 @@ export default class MoreScreen extends Combine { | |||
|             ].filter(part => part[1] !== undefined) | ||||
|                 .map(part => part[0] + "=" + part[1]) | ||||
|                 .join("&") | ||||
|             return `${linkPrefix}${params}${linkSuffix}`; | ||||
|         }) ?? new UIEventSource<string>(`${linkPrefix}${linkSuffix}`) | ||||
|             return `${linkPrefix}${params}`; | ||||
|         }) ?? new UIEventSource<string>(`${linkPrefix}`) | ||||
| 
 | ||||
| 
 | ||||
|         return new SubtleButton(layout.icon, | ||||
|             new Combine([ | ||||
|                 `<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>`, | ||||
|                 `<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>`, | ||||
|             ]), {url: linkText, newTab: false}); | ||||
|     } | ||||
|  | @ -111,7 +115,7 @@ export default class MoreScreen extends Combine { | |||
|             if (customThemes.length <= 0) { | ||||
|                 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([ | ||||
|                 Translations.t.general.customThemeIntro.Clone(), | ||||
|                 new Combine(customThemeButtons).SetClass(themeListClasses) | ||||
|  | @ -122,27 +126,29 @@ export default class MoreScreen extends Combine { | |||
|     private static createPreviouslyVistedHiddenList(state: UserRelatedState, buttonClass: string, themeListStyle: string) { | ||||
|         const t = Translations.t.general.morescreen | ||||
|         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( | ||||
|             new VariableUiElement( | ||||
|                 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)) | ||||
|                         .map(key => key.substring(prefix.length, key.length - "-enabled".length)) | ||||
|                         .map(theme => AllKnownLayouts.allKnownLayouts.get(theme))) | ||||
|                         .filter(theme => theme?.hideFromOverview) | ||||
|                     if (knownThemes.length === 0) { | ||||
|                         .map(key => key.substring(prefix.length, key.length - "-enabled".length)))); | ||||
|                      | ||||
|                     if(knownThemes.size === 0){ | ||||
|                         return undefined | ||||
|                     } | ||||
|                      | ||||
|                     const knownLayouts = new Combine(knownThemes.map(layout => | ||||
|                         MoreScreen.createLinkButton(state, layout)?.SetClass(buttonClass) | ||||
|                     )).SetClass(themeListStyle) | ||||
|                      const knownThemeDescriptions = hiddenThemes.filter(theme => knownThemes.has(theme.id)) | ||||
|                          .map(theme => MoreScreen.createLinkButton(state, theme)?.SetClass(buttonClass)); | ||||
| 
 | ||||
|                     const knownLayouts = new Combine(knownThemeDescriptions).SetClass(themeListStyle) | ||||
| 
 | ||||
|                     return new Combine([ | ||||
|                         new Title(t.previouslyHiddenTitle), | ||||
|                         t.hiddenExplanation.Subs({ | ||||
|                             hidden_discovered: "" + knownThemes.length, | ||||
|                             hidden_discovered: "" + knownThemes.size, | ||||
|                             total_hidden: "" + hiddenTotal | ||||
|                         }), | ||||
|                         knownLayouts | ||||
|  | @ -158,7 +164,7 @@ export default class MoreScreen extends Combine { | |||
|     } | ||||
| 
 | ||||
|     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) => { | ||||
|             if (layout === undefined) { | ||||
|  |  | |||
|  | @ -15,13 +15,14 @@ import LeftControls from "./BigComponents/LeftControls"; | |||
| import RightControls from "./BigComponents/RightControls"; | ||||
| import CenterMessageBox from "./CenterMessageBox"; | ||||
| import ShowDataLayer from "./ShowDataLayer/ShowDataLayer"; | ||||
| import AllKnownLayers from "../Customizations/AllKnownLayers"; | ||||
| import ScrollableFullScreen from "./Base/ScrollableFullScreen"; | ||||
| import Translations from "./i18n/Translations"; | ||||
| import SimpleAddUI from "./BigComponents/SimpleAddUI"; | ||||
| import StrayClickHandler from "../Logic/Actors/StrayClickHandler"; | ||||
| import Lazy from "./Base/Lazy"; | ||||
| 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({ | ||||
|             leafletMap: state.leafletMap, | ||||
|             layerToShow: AllKnownLayers.sharedLayers.get("home_location"), | ||||
|             layerToShow: new LayerConfig(home_location_json, "all_known_layers", true), | ||||
|             features: state.homeLocation, | ||||
|             enablePopups: false, | ||||
|         }) | ||||
|  |  | |||
|  | @ -2,20 +2,21 @@ import {UIEventSource} from "../../Logic/UIEventSource"; | |||
| import Translations from "../i18n/Translations"; | ||||
| import Toggle from "../Input/Toggle"; | ||||
| import Combine from "../Base/Combine"; | ||||
| import State from "../../State"; | ||||
| import Svg from "../../Svg"; | ||||
| import {Tag} from "../../Logic/Tags/Tag"; | ||||
| 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 { | ||||
| 
 | ||||
|     constructor(key: string, tags: UIEventSource<any>) { | ||||
|     constructor(key: string, tags: UIEventSource<any>, state: {changes?: Changes, osmConnection?: OsmConnection}) { | ||||
|         const oldValue = tags.data[key] | ||||
|         const isDeletedBadge = Translations.t.image.isDeleted.Clone() | ||||
|             .SetClass("rounded-full p-1") | ||||
|             .SetStyle("color:white;background:#ff8c8c") | ||||
|             .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", | ||||
|                     theme: "test" | ||||
|                 })) | ||||
|  | @ -25,7 +26,7 @@ export default class DeleteImage extends Toggle { | |||
|             .SetClass("block w-full pl-4 pr-4") | ||||
|             .SetStyle("color:white;background:#ff8c8c; border-top-left-radius:30rem; border-top-right-radius: 30rem;") | ||||
|             .onClick(async () => { | ||||
|                 await State.state?.changes?.applyAction( | ||||
|                 await state?.changes?.applyAction( | ||||
|                     new ChangeTagAction(tags.data.id, new Tag(key, ""), tags.data, { | ||||
|                         changeType: "answer", | ||||
|                         theme: "test" | ||||
|  | @ -53,7 +54,7 @@ export default class DeleteImage extends Toggle { | |||
|                 tags.map(tags => (tags[key] ?? "") !== "") | ||||
|             ), | ||||
|             undefined /*Login (and thus editing) is disabled*/, | ||||
|             State.state.osmConnection.isLoggedIn | ||||
|             state.osmConnection.isLoggedIn | ||||
|         ) | ||||
|         this.SetClass("cursor-pointer") | ||||
|     } | ||||
|  |  | |||
|  | @ -6,12 +6,14 @@ import {AttributedImage} from "./AttributedImage"; | |||
| import BaseUIElement from "../BaseUIElement"; | ||||
| import Toggle from "../Input/Toggle"; | ||||
| import ImageProvider from "../../Logic/ImageProviders/ImageProvider"; | ||||
| import {OsmConnection} from "../../Logic/Osm/OsmConnection"; | ||||
| import {Changes} from "../../Logic/Osm/Changes"; | ||||
| 
 | ||||
| export class ImageCarousel extends Toggle { | ||||
| 
 | ||||
|     constructor(images: UIEventSource<{ key: string, url: string, provider: ImageProvider }[]>, | ||||
|                 tags: UIEventSource<any>, | ||||
|                 keys: string[]) { | ||||
|                 state: {osmConnection?: OsmConnection, changes?: Changes}) { | ||||
|         const uiElements = images.map((imageURLS: { key: string, url: string, provider: ImageProvider }[]) => { | ||||
|             const uiElements: BaseUIElement[] = []; | ||||
|             for (const url of imageURLS) { | ||||
|  | @ -21,7 +23,7 @@ export class ImageCarousel extends Toggle { | |||
|                     if (url.key !== undefined) { | ||||
|                         image = new Combine([ | ||||
|                             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"); | ||||
|                     } | ||||
|                     image | ||||
|  |  | |||
|  | @ -1,5 +1,4 @@ | |||
| import {UIEventSource} from "../../Logic/UIEventSource"; | ||||
| import State from "../../State"; | ||||
| import Combine from "../Base/Combine"; | ||||
| import Translations from "../i18n/Translations"; | ||||
| import Svg from "../../Svg"; | ||||
|  | @ -13,13 +12,23 @@ import ChangeTagAction from "../../Logic/Osm/Actions/ChangeTagAction"; | |||
| import LayerConfig from "../../Models/ThemeConfig/LayerConfig"; | ||||
| import {FixedUiElement} from "../Base/FixedUiElement"; | ||||
| 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 { | ||||
| 
 | ||||
| 
 | ||||
|     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 id = tagsSource.data.id | ||||
|         if (!perId.has(id)) { | ||||
|  | @ -41,17 +50,17 @@ export class ImageUploadFlow extends Toggle { | |||
|             console.log("Adding image:" + key, url); | ||||
|             uploadedCount.data++ | ||||
|             uploadedCount.ping() | ||||
|             Promise.resolve(State.state.changes | ||||
|             Promise.resolve(state.changes | ||||
|                 .applyAction(new ChangeTagAction( | ||||
|                     tags.id, new Tag(key, url), tagsSource.data, | ||||
|                     { | ||||
|                         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; | ||||
| 
 | ||||
|  | @ -90,7 +99,7 @@ export class ImageUploadFlow extends Toggle { | |||
| 
 | ||||
|             const tags = tagsSource.data; | ||||
| 
 | ||||
|             const layout = State.state?.layoutToUse | ||||
|             const layout = state?.layoutToUse | ||||
|             let matchingLayer: LayerConfig = undefined | ||||
|             for (const layer of layout?.layers ?? []) { | ||||
|                 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 description = [ | ||||
|                 "author:" + State.state.osmConnection.userDetails.data.name, | ||||
|                 "author:" + state.osmConnection.userDetails.data.name, | ||||
|                 "license:" + license, | ||||
|                 "osmid:" + tags.id, | ||||
|             ].join("\n"); | ||||
|  | @ -146,17 +155,17 @@ export class ImageUploadFlow extends Toggle { | |||
| 
 | ||||
| 
 | ||||
|         const pleaseLoginButton = t.pleaseLogin.Clone() | ||||
|             .onClick(() => State.state.osmConnection.AttemptLogin()) | ||||
|             .onClick(() => state.osmConnection.AttemptLogin()) | ||||
|             .SetClass("login-button-friendly"); | ||||
|         super( | ||||
|             new Toggle( | ||||
|                 /*We can show the actual upload button!*/ | ||||
|                 uploadFlow, | ||||
|                 /* User not logged in*/ pleaseLoginButton, | ||||
|                 State.state?.osmConnection?.isLoggedIn | ||||
|                 state?.osmConnection?.isLoggedIn | ||||
|             ), | ||||
|             undefined /* Nothing as the user badge is disabled*/, | ||||
|             State.state.featureSwitchUserbadge | ||||
|             state.featureSwitchUserbadge | ||||
|         ) | ||||
| 
 | ||||
|     } | ||||
|  |  | |||
|  | @ -1,6 +1,5 @@ | |||
| import {UIEventSource} from "../../Logic/UIEventSource"; | ||||
| import Combine from "../Base/Combine"; | ||||
| import State from "../../State"; | ||||
| import {FixedUiElement} from "../Base/FixedUiElement"; | ||||
| import {OH} from "./OpeningHours"; | ||||
| import Translations from "../i18n/Translations"; | ||||
|  | @ -11,6 +10,7 @@ import Toggle from "../Input/Toggle"; | |||
| import {VariableUiElement} from "../Base/VariableUIElement"; | ||||
| import Table from "../Base/Table"; | ||||
| import {Translation} from "../i18n/Translation"; | ||||
| import {OsmConnection} from "../../Logic/Osm/OsmConnection"; | ||||
| 
 | ||||
| export default class OpeningHoursVisualization extends Toggle { | ||||
|     private static readonly weekdays: Translation[] = [ | ||||
|  | @ -23,7 +23,7 @@ export default class OpeningHoursVisualization extends Toggle { | |||
|         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 ohTable = new VariableUiElement(tags | ||||
|             .map(tags => { | ||||
|  | @ -57,7 +57,7 @@ export default class OpeningHoursVisualization extends Toggle { | |||
|                             new Toggle( | ||||
|                                 new FixedUiElement(e).SetClass("subtle"), | ||||
|                                 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 { | ||||
|         const answer: BaseUIElement = new TagRenderingAnswer(tags, configuration) | ||||
|         const answer: BaseUIElement = new TagRenderingAnswer(tags, configuration, State.state) | ||||
|         answer.SetClass("w-full") | ||||
|         let rendering = answer; | ||||
| 
 | ||||
|  |  | |||
|  | @ -40,7 +40,7 @@ export default class FeatureInfoBox extends ScrollableFullScreen { | |||
| 
 | ||||
|     private static GenerateTitleBar(tags: UIEventSource<any>, | ||||
|                                     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"); | ||||
|         const titleIcons = new Combine( | ||||
|             layerConfig.titleIcons.map(icon => new TagRenderingAnswer(tags, icon, | ||||
|  | @ -88,7 +88,8 @@ export default class FeatureInfoBox extends ScrollableFullScreen { | |||
| 
 | ||||
|                     if (tr.render !== undefined) { | ||||
|                         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]]) | ||||
|                         }) | ||||
|                         const possiblyHidden = new Toggle( | ||||
|  | @ -163,7 +164,7 @@ export default class FeatureInfoBox extends ScrollableFullScreen { | |||
| 
 | ||||
|         const hasMinimap = layerConfig.tagRenderings.some(tr => FeatureInfoBox.hasMinimap(tr)) | ||||
|         if (!hasMinimap) { | ||||
|             allRenderings.push(new TagRenderingAnswer(tags, SharedTagRenderings.SharedTagRendering.get("minimap"))) | ||||
|             allRenderings.push(new TagRenderingAnswer(tags, SharedTagRenderings.SharedTagRendering.get("minimap"), State.state)) | ||||
|         } | ||||
| 
 | ||||
|         editElements.push( | ||||
|  | @ -177,7 +178,7 @@ export default class FeatureInfoBox extends ScrollableFullScreen { | |||
|                             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]) | ||||
|             ) | ||||
|  |  | |||
|  | @ -19,7 +19,6 @@ import Svg from "../../Svg"; | |||
| import {Utils} from "../../Utils"; | ||||
| import Minimap from "../Base/Minimap"; | ||||
| import ShowDataLayer from "../ShowDataLayer/ShowDataLayer"; | ||||
| import AllKnownLayers from "../../Customizations/AllKnownLayers"; | ||||
| import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource"; | ||||
| import ShowDataMultiLayer from "../ShowDataLayer/ShowDataMultiLayer"; | ||||
| 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 {Tag} from "../../Logic/Tags/Tag"; | ||||
| 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"; | ||||
| 
 | ||||
| 
 | ||||
|  | @ -256,7 +257,7 @@ ${Utils.special_visualizations_importRequirementDocs} | |||
|                 zoomToFeatures: false, | ||||
|                 features: changePreview, | ||||
|                 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 { | ||||
| 
 | ||||
|     constructor(tagsSource: UIEventSource<any>, configuration: TagRenderingConfig, | ||||
|                 state: any, | ||||
|                 contentClasses: string = "", contentStyle: string = "", options?:{ | ||||
|                     specialViz: Map<string, BaseUIElement> | ||||
|                 }) { | ||||
|  | @ -37,7 +38,7 @@ export default class TagRenderingAnswer extends VariableUiElement { | |||
|                 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) { | ||||
|                 return valuesToRender[0]; | ||||
|             } else if (valuesToRender.length > 1) { | ||||
|  |  | |||
|  | @ -71,7 +71,7 @@ export default class TagRenderingQuestion extends Combine { | |||
|         } | ||||
|         options = options ?? {} | ||||
|         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"); | ||||
| 
 | ||||
| 
 | ||||
|  | @ -352,7 +352,7 @@ export default class TagRenderingQuestion extends Combine { | |||
|         } | ||||
| 
 | ||||
|         return new FixedInputElement( | ||||
|             new SubstitutedTranslation(mapping.then, tagsSource), | ||||
|             new SubstitutedTranslation(mapping.then, tagsSource, State.state), | ||||
|             tagging, | ||||
|             (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;"), | ||||
|             t.backToMapcomplete, | ||||
|             { | ||||
|                 url: window.location.host + "/index.html" | ||||
|                 url: "./index.html" | ||||
|             } | ||||
|         )]).SetClass("block") | ||||
| 
 | ||||
|  |  | |||
|  | @ -8,8 +8,7 @@ import {Tiles} from "../../Models/TileRange"; | |||
| import * as clusterstyle from "../../assets/layers/cluster_style/cluster_style.json" | ||||
| 
 | ||||
| export default class ShowTileInfo { | ||||
|     public static readonly styling = new LayerConfig( | ||||
|         clusterstyle, "tileinfo", true) | ||||
|     public static readonly styling = new LayerConfig(clusterstyle, "ShowTileInfo", true) | ||||
| 
 | ||||
|     constructor(options: { | ||||
|         source: FeatureSource & Tiled, leafletMap: UIEventSource<any>, layer?: LayerConfig, | ||||
|  |  | |||
|  | @ -27,7 +27,6 @@ import AllImageProviders from "../Logic/ImageProviders/AllImageProviders"; | |||
| import WikipediaBox from "./Wikipedia/WikipediaBox"; | ||||
| import SimpleMetaTagger from "../Logic/SimpleMetaTagger"; | ||||
| import MultiApply from "./Popup/MultiApply"; | ||||
| import AllKnownLayers from "../Customizations/AllKnownLayers"; | ||||
| import ShowDataLayer from "./ShowDataLayer/ShowDataLayer"; | ||||
| import {SubtleButton} from "./Base/SubtleButton"; | ||||
| import {DefaultGuiState} from "./DefaultGuiState"; | ||||
|  | @ -37,6 +36,7 @@ import FeaturePipelineState from "../Logic/State/FeaturePipelineState"; | |||
| import {ConflateButton, ImportPointButton, ImportWayButton} from "./Popup/ImportButton"; | ||||
| import TagApplyButton from "./Popup/TagApplyButton"; | ||||
| 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"; | ||||
| 
 | ||||
| export interface SpecialVisualization { | ||||
|  | @ -52,7 +52,6 @@ export default class SpecialVisualizations { | |||
| 
 | ||||
|     public static specialVisualizations = SpecialVisualizations.init() | ||||
| 
 | ||||
| 
 | ||||
|     private static init(){ | ||||
|       const  specialVisualizations: SpecialVisualization[] = | ||||
|             [ | ||||
|  | @ -105,7 +104,7 @@ export default class SpecialVisualizations { | |||
|                         if (args.length > 0) { | ||||
|                             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" | ||||
|                     }], | ||||
|                     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}`", | ||||
|                     constr: (state, tagSource, args, defaultGuiState) => { | ||||
|                     constr: (state, tagSource, args, _) => { | ||||
| 
 | ||||
|                         const keys = [...args] | ||||
|                         keys.splice(0, 1) | ||||
|  | @ -268,7 +267,7 @@ export default class SpecialVisualizations { | |||
|                                 leafletMap: minimap["leafletMap"], | ||||
|                                 enablePopups: false, | ||||
|                                 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), | ||||
|                                 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)}`", | ||||
|                     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 {Translation} from "./i18n/Translation"; | ||||
| import Locale from "./i18n/Locale"; | ||||
| import State from "../State"; | ||||
| import {FixedUiElement} from "./Base/FixedUiElement"; | ||||
| import SpecialVisualizations, {SpecialVisualization} from "./SpecialVisualizations"; | ||||
| import {Utils} from "../Utils"; | ||||
|  | @ -15,6 +14,7 @@ export class SubstitutedTranslation extends VariableUiElement { | |||
|     public constructor( | ||||
|         translation: Translation, | ||||
|         tagsSource: UIEventSource<any>, | ||||
|         state, | ||||
|         mapping: Map<string, BaseUIElement> = undefined) { | ||||
| 
 | ||||
|         const extraMappings: SpecialVisualization[] = []; | ||||
|  | @ -50,7 +50,7 @@ export class SubstitutedTranslation extends VariableUiElement { | |||
|                         } | ||||
|                         const viz = proto.special; | ||||
|                         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) { | ||||
|                             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") | ||||
|  |  | |||
|  | @ -13,6 +13,9 @@ export class Translation extends BaseUIElement { | |||
|         if (translations === undefined) { | ||||
|             throw `Translation without content (${context})` | ||||
|         } | ||||
|         if(typeof translations === "string"){ | ||||
|             translations = {"*": translations}; | ||||
|         } | ||||
|         let count = 0; | ||||
|         for (const translationsKey in translations) { | ||||
|             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; | ||||
|     } | ||||
| 
 | ||||
|     public static Dupicates(arr: string[]): string[] { | ||||
|     public static Dupiclates(arr: string[]): string[] { | ||||
|         if (arr === 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), | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 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": { | ||||
|     "builtin": [ | ||||
|       "phonelink", | ||||
|       "emaillink", | ||||
|       "wikipedialink", | ||||
|       "osmlink", | ||||
|       "sharelink" | ||||
|     ], | ||||
|    | ||||
|     "override": {} | ||||
|   }, | ||||
|   "wikipedialink": { | ||||
|     "render": "<a href='https://wikipedia.org/wiki/{wikipedia}' target='_blank'><img src='./assets/svg/wikipedia.svg' alt='WP'/></a>", | ||||
|     "condition": { | ||||
|  |  | |||
|  | @ -36,7 +36,7 @@ | |||
|   ], | ||||
|   "maintainer": "MapComplete", | ||||
|   "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", | ||||
|   "startLat": 0, | ||||
|   "defaultBackgroundId": "CartoDB.Voyager", | ||||
|  |  | |||
|  | @ -80,6 +80,7 @@ | |||
|       "builtin": "nature_reserve", | ||||
|       "wayHandling": 1, | ||||
|       "override": { | ||||
|         "id": "nature_reserve_centerpoints", | ||||
|         "source": { | ||||
|           "osmTags": { | ||||
|             "+and": [ | ||||
|  |  | |||
|  | @ -74,11 +74,11 @@ | |||
|           }, | ||||
|           "renderings": [ | ||||
|             { | ||||
|               "id": "sidewalk_minimap", | ||||
|               "id": "sidewalk_minimap_left|right", | ||||
|               "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?", | ||||
|               "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?", | ||||
|               "render": "This sidewalk is {sidewalk:left|right:width}m wide", | ||||
|               "condition": "sidewalk:left|right=yes", | ||||
|  |  | |||
|  | @ -50,7 +50,6 @@ | |||
|           "geoJsonZoomLevel": 14, | ||||
|           "isOsmCache": true | ||||
|         }, | ||||
|         "icon": "./assets/themes/speelplekken/speelbos.svg", | ||||
|         "minzoom": 12, | ||||
|         "calculatedTags": [ | ||||
|           "_is_shadowed=feat.overlapWith('shadow').length > 0 ? 'yes': ''", | ||||
|  | @ -61,7 +60,6 @@ | |||
|     { | ||||
|       "builtin": "playground", | ||||
|       "override": { | ||||
|         "icon": "./assets/themes/speelplekken/speeltuin.svg", | ||||
|         "minzoom": 14, | ||||
|         "source": { | ||||
|           "geoJsonLocal": "http://127.0.0.1:8080/speelplekken_{layer}_{z}_{x}_{y}.geojson", | ||||
|  | @ -78,7 +76,6 @@ | |||
|     { | ||||
|       "builtin": "village_green", | ||||
|       "override": { | ||||
|         "icon": "./assets/themes/speelplekken/speelweide.svg", | ||||
|         "minzoom": 14, | ||||
|         "source": { | ||||
|           "geoJsonLocal": "http://127.0.0.1:8080/speelplekken_{layer}_{z}_{x}_{y}.geojson", | ||||
|  | @ -95,7 +92,6 @@ | |||
|     { | ||||
|       "builtin": "grass_in_parks", | ||||
|       "override": { | ||||
|         "icon": "./assets/themes/speelplekken/speelweide.svg", | ||||
|         "minzoom": 14, | ||||
|         "source": { | ||||
|           "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> | ||||
| <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> | ||||
| 
 | ||||
| </body> | ||||
|  |  | |||
							
								
								
									
										43
									
								
								index.ts
									
										
									
									
									
								
							
							
						
						
									
										43
									
								
								index.ts
									
										
									
									
									
								
							|  | @ -1,5 +1,4 @@ | |||
| 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"; | ||||
|  | @ -18,19 +17,10 @@ MinimapImplementation.initialize() | |||
| AvailableBaseLayers.implement(new AvailableBaseLayersImplementation()) | ||||
| ShowOverlayLayerImplementation.Implement(); | ||||
| // Miscelleanous
 | ||||
| 
 | ||||
| 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 { | ||||
|     public static Init(layoutToUse: LayoutConfig, encoded: string) { | ||||
|     public static Init(layoutToUse: LayoutConfig) { | ||||
| 
 | ||||
|         if (layoutToUse === null) { | ||||
|             // Something went wrong, error message is already on screen
 | ||||
|  | @ -43,40 +33,15 @@ class Init { | |||
|             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() | ||||
|         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) | ||||
| 
 | ||||
|         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,10 +57,10 @@ new Combine(["Initializing... <br/>", | |||
|         })]) | ||||
|     .AttachTo("centermessage"); // Add an initialization and reset button if something goes wrong
 | ||||
| 
 | ||||
| 
 | ||||
| // @ts-ignore
 | ||||
| DetermineLayout.GetLayout().then(value => { | ||||
|     console.log("Got ", value) | ||||
|     Init.Init(value[0], value[1]) | ||||
|     Init.Init(value) | ||||
|     }).catch(err => { | ||||
|         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:*", | ||||
|     "strt": "npm run start:prepare && npm run start:parallel:parcel", | ||||
|     "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", | ||||
|     "generate:css": "tailwindcss -i index.css -o css/index-tailwind-output.css", | ||||
|     "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": "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: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", | ||||
|     "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: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 ", | ||||
|     "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", | ||||
|     "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", | ||||
|     "build": "rm -rf dist/ && npm run generate && parcel build --public-url ./ *.html assets/** assets/**/** assets/**/**/** vendor/* vendor/*/*", | ||||
|     "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", | ||||
|     "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", | ||||
|     "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", | ||||
|     "prepare-deploy": "./scripts/build.sh", | ||||
|     "gittag": "ts-node scripts/printVersion.ts | bash", | ||||
|     "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", | ||||
|     "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", | ||||
|     "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" | ||||
|     "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 Logic/State/MapState.ts > dependencies.dot && dot dependencies.dot -T svg -o dependencies.svg && rm dependencies.dot" | ||||
|   }, | ||||
|   "keywords": [ | ||||
|     "OpenStreetMap", | ||||
|  | @ -84,7 +77,7 @@ | |||
|     "leaflet-providers": "^1.13.0", | ||||
|     "leaflet-simple-map-screenshoter": "^0.4.4", | ||||
|     "leaflet.markercluster": "^1.4.1", | ||||
|     "libphonenumber": "0.0.10", | ||||
|     "libphonenumber": "^0.0.9", | ||||
|     "libphonenumber-js": "^1.7.55", | ||||
|     "lz-string": "^1.4.4", | ||||
|     "mangrove-reviews": "^0.1.3", | ||||
|  | @ -92,7 +85,7 @@ | |||
|     "npm-run-all": "^4.1.5", | ||||
|     "opening_hours": "^3.6.0", | ||||
|     "osm-auth": "^1.0.2", | ||||
|     "osmtogeojson": "^3.0.0-beta.4", | ||||
|     "osmtogeojson": "^1.0.0", | ||||
|     "parcel": "^1.2.4", | ||||
|     "prompt-sync": "^4.2.0", | ||||
|     "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 {writeFileSync} from "fs"; | ||||
| import {existsSync, mkdirSync, writeFileSync} from "fs"; | ||||
| import * as licenses from "../assets/generated/license_info.json" | ||||
| import {LayoutConfigJson} from "../Models/ThemeConfig/Json/LayoutConfigJson"; | ||||
| import LayoutConfig from "../Models/ThemeConfig/LayoutConfig"; | ||||
| 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 {Utils} from "../Utils"; | ||||
| import AllKnownLayers from "../Customizations/AllKnownLayers"; | ||||
| import {TagRenderingConfigJson} from "../Models/ThemeConfig/Json/TagRenderingConfigJson"; | ||||
| 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.
 | ||||
| // It spits out an overview of those to be used to load them
 | ||||
|  | @ -20,218 +26,157 @@ interface LayersAndThemes { | |||
| 
 | ||||
| class LayerOverviewUtils { | ||||
| 
 | ||||
|     loadThemesAndLayers(): LayersAndThemes { | ||||
| 
 | ||||
|         const layerFiles = ScriptUtils.getLayerFiles(); | ||||
| 
 | ||||
|         const themeFiles: LayoutConfigJson[] = ScriptUtils.getThemeFiles().map(x => x.parsed); | ||||
| 
 | ||||
|         console.log("Discovered", layerFiles.length, "layers and", themeFiles.length, "themes\n") | ||||
|         if (layerFiles.length + themeFiles.length === 0) { | ||||
|             throw "Panic: no themes and layers loaded!" | ||||
|     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 | ||||
|             } | ||||
|         return { | ||||
|             layers: layerFiles, | ||||
|             themes: themeFiles | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     writeFiles(lt: LayersAndThemes) { | ||||
|         writeFileSync("./assets/generated/known_layers_and_themes.json", JSON.stringify({ | ||||
|             "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'") | ||||
|             perId.set(theme.id, data); | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
|             for (const image of images) { | ||||
|                 if (image.indexOf("{") >= 0) { | ||||
|                     console.warn("Ignoring image with { in the path: ", image) | ||||
|         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 | ||||
|             } | ||||
| 
 | ||||
|                 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}`) | ||||
|                 } | ||||
|             icons[key].id = key; | ||||
|             dict.set(key, <TagRenderingConfigJson>icons[key]) | ||||
|         } | ||||
| 
 | ||||
|         } catch (e) { | ||||
|             console.error(e) | ||||
|             return [`Layer ${layerJson.id}` ?? JSON.stringify(layerJson).substring(0, 50) + " is invalid: " + e] | ||||
|         } | ||||
|         return errorCount | ||||
|         dict.forEach((value, key) => { | ||||
|             value.id = value.id ?? key; | ||||
|         }) | ||||
| 
 | ||||
|         return dict; | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     main(args: string[]) { | ||||
|     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 ---------") | ||||
| 
 | ||||
|         AllKnownLayers.runningGenerateScript = true; | ||||
|         const sharedTagRenderings = this.getSharedTagRenderings(); | ||||
|         const layerFiles = ScriptUtils.getLayerFiles(); | ||||
|         const sharedLayers = new Map<string, LayerConfigJson>() | ||||
|         const prepLayer = new PrepareLayer(); | ||||
|         const state: DesugaringContext = { | ||||
|             tagRenderings: sharedTagRenderings, | ||||
|             sharedLayers | ||||
|         } | ||||
|         for (const sharedLayerJson of layerFiles) { | ||||
|             const context = "While building builtin layer " + sharedLayerJson.path | ||||
|             const fixed = prepLayer.convertStrict(state, sharedLayerJson.parsed, context) | ||||
|             const validator = new ValidateLayer(knownImagePaths, sharedLayerJson.path, true); | ||||
|             validator.convertStrict(state, fixed, context) | ||||
| 
 | ||||
|             if (sharedLayers.has(fixed.id)) { | ||||
|                 throw "There are multiple layers with the id " + fixed.id | ||||
|             } | ||||
| 
 | ||||
|             sharedLayers.set(fixed.id, fixed) | ||||
| 
 | ||||
|             this.writeLayer(fixed) | ||||
| 
 | ||||
|         } | ||||
|         return sharedLayers; | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     private buildThemeIndex(knownImagePaths: Set<string>, sharedLayers: Map<string, LayerConfigJson>): Map<string, LayoutConfigJson> { | ||||
|         console.log("   ---------- VALIDATING BUILTIN THEMES ---------") | ||||
|         const themeFiles = ScriptUtils.getThemeFiles(); | ||||
|         const fixed = new Map<string, LayoutConfigJson>(); | ||||
| 
 | ||||
| 
 | ||||
|         console.log("   ---------- VALIDATING ---------") | ||||
|         const licensePaths = [] | ||||
|         for (const i in licenses) { | ||||
|             licensePaths.push(licenses[i].path) | ||||
|         const convertState: DesugaringContext = { | ||||
|             sharedLayers, | ||||
|             tagRenderings: this.getSharedTagRenderings() | ||||
|         } | ||||
|         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) { | ||||
|             const themeFile = themeInfo.parsed | ||||
|             let themeFile = themeInfo.parsed | ||||
|             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` | ||||
|                     } | ||||
|                 } | ||||
|              | ||||
|             themeFile = new PrepareTheme().convertStrict(convertState, themeFile, themePath) | ||||
| 
 | ||||
|             new ValidateThemeAndLayers(knownImagePaths, themePath, true) | ||||
|                 .convertStrict(convertState, themeFile, themePath) | ||||
| 
 | ||||
|             this.writeTheme(themeFile) | ||||
|             fixed.set(themeFile.id, themeFile) | ||||
|         } | ||||
| 
 | ||||
|             const referencedLayers = Utils.NoNull([].concat(...themeFile.layers.map(layer => { | ||||
|                 if (typeof layer === "string") { | ||||
|                     return layer | ||||
|         this.writeSmallOverview(themeFiles.map(tf => { | ||||
|             const t = tf.parsed; | ||||
|             return { | ||||
|                 ...t, | ||||
|                 hideFromOverview: t.hideFromOverview ?? false, | ||||
|                 shortDescription: t.shortDescription ?? new Translation(t.description).FirstSentence().translations | ||||
|             } | ||||
|                 if (layer["builtin"] !== undefined) { | ||||
|                     return layer["builtin"] | ||||
|                 } | ||||
|                 return undefined | ||||
|             }).map(layerName => { | ||||
|                 if (typeof layerName === "string") { | ||||
|                     return [layerName] | ||||
|                 } | ||||
|                 return layerName | ||||
|             }))) | ||||
| 
 | ||||
|             themeFile.layers = themeFile.layers | ||||
|                 .filter(l => typeof l != "string") // We remove all the builtin layer references as they don't work with ts-node for some weird reason
 | ||||
|                 .filter(l => l["builtin"] === undefined) | ||||
| 
 | ||||
| 
 | ||||
|             try { | ||||
|                 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')) | ||||
|                             }) | ||||
|                     } | ||||
| 
 | ||||
|         })); | ||||
|         return fixed; | ||||
| 
 | ||||
|     } | ||||
|                 themeConfigs.push(theme) | ||||
|             } catch (e) { | ||||
|                 themeErrorCount.push("Could not parse theme " + themeFile["id"] + " due to", e) | ||||
|             } | ||||
| 
 | ||||
|     main(_: string[]) { | ||||
| 
 | ||||
|         const licensePaths = new Set<string>() | ||||
|         for (const i in licenses) { | ||||
|             licensePaths.add(licenses[i].path) | ||||
|         } | ||||
| 
 | ||||
|         if (layerErrorCount.length + themeErrorCount.length == 0) { | ||||
|             console.log("All good!") | ||||
|         const sharedLayers = this.buildLayerIndex(licensePaths); | ||||
|         const sharedThemes = this.buildThemeIndex(licensePaths, sharedLayers) | ||||
| 
 | ||||
|             // We load again from disc, as modifications were made above
 | ||||
|             const lt = this.loadThemesAndLayers(); | ||||
|         writeFileSync("./assets/generated/known_layers_and_themes.json", JSON.stringify({ | ||||
|             "layers": Array.from(sharedLayers.values()), | ||||
|             "themes": Array.from(sharedThemes.values()) | ||||
|         })) | ||||
| 
 | ||||
|              | ||||
|             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; | ||||
|             } | ||||
|         } | ||||
|         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 Translations from "../UI/i18n/Translations"; | ||||
| import {Translation} from "../UI/i18n/Translation"; | ||||
|  | @ -8,6 +8,8 @@ import {LayoutConfigJson} from "../Models/ThemeConfig/Json/LayoutConfigJson"; | |||
| import LayoutConfig from "../Models/ThemeConfig/LayoutConfig"; | ||||
| 
 | ||||
| const sharp = require('sharp'); | ||||
| const template = readFileSync("theme.html", "utf8"); | ||||
| const codeTemplate = readFileSync("index_theme.ts.template", "utf8"); | ||||
| 
 | ||||
| 
 | ||||
| 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) { | ||||
| 
 | ||||
|  | @ -160,7 +161,8 @@ async function createLandingPage(layout: LayoutConfig, manifest) { | |||
| 
 | ||||
|     let output = template | ||||
|         .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 { | ||||
|         output = output | ||||
|  | @ -173,12 +175,18 @@ async function createLandingPage(layout: LayoutConfig, manifest) { | |||
|     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"; | ||||
| if (!existsSync(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
 | ||||
| const all: LayoutConfigJson[] = all_known_layouts.themes; | ||||
| for (const i in all) { | ||||
|  | @ -203,6 +211,7 @@ for (const i in all) { | |||
|         createLandingPage(layout, manifObj).then(landing => { | ||||
|             writeFile(enc(layout.id) + ".html", landing, err) | ||||
|         }); | ||||
|         createIndexFor(layout) | ||||
|     }).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!"); | ||||
|  | @ -1,22 +1,24 @@ | |||
| import {writeFile} from "fs"; | ||||
| import Translations from "../UI/i18n/Translations"; | ||||
| import {AllKnownLayouts} from "../Customizations/AllKnownLayouts"; | ||||
| import LayoutConfig from "../Models/ThemeConfig/LayoutConfig"; | ||||
| import * as themeOverview from "../assets/generated/theme_overview.json" | ||||
| 
 | ||||
| function generateWikiEntry(layout: LayoutConfig) { | ||||
| function generateWikiEntry(layout: {hideFromOverview: boolean, id: string, shortDescription: any}) { | ||||
|     if (layout.hideFromOverview) { | ||||
|         return ""; | ||||
|     } | ||||
|     const languages = layout.language.map(ln => `{{#language:${ln}|en}}`).join(", ") | ||||
|     let auth = "Yes"; | ||||
|     if (layout.maintainer !== "" && layout.maintainer !== "MapComplete") { | ||||
|         auth = `Yes, by ${layout.maintainer};` | ||||
|      | ||||
|     const languagesInDescr = [] | ||||
|     for (const shortDescriptionKey in layout.shortDescription) { | ||||
|         languagesInDescr.push(shortDescriptionKey) | ||||
|     } | ||||
|      | ||||
|     const languages = languagesInDescr .map(ln => `{{#language:${ln}|en}}`).join(", ") | ||||
|     let auth = "Yes"; | ||||
|     return `{{service_item
 | ||||
| |name= [https://mapcomplete.osm.be/${layout.id} ${layout.id}]
 | ||||
| |region= Worldwide | ||||
| |lang= ${languages} | ||||
| |descr= A MapComplete theme: ${Translations.WT(layout.description) | ||||
| |descr= A MapComplete theme: ${Translations.WT(layout.shortDescription) | ||||
|         .textFor("en") | ||||
|         .replace("<a href='", "[[") | ||||
|         .replace(/'>.*<\/a>/, "]]") | ||||
|  | @ -31,7 +33,7 @@ let wikiPage = "{|class=\"wikitable sortable\"\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) { | ||||
|         continue; | ||||
|     } | ||||
|  |  | |||
|  | @ -1,6 +1,6 @@ | |||
| import ScriptUtils from "./ScriptUtils"; | ||||
| 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 | ||||
|  | @ -10,8 +10,9 @@ import LegacyJsonConvert from "../Models/ThemeConfig/LegacyJsonConvert"; | |||
| const layerFiles = ScriptUtils.getLayerFiles(); | ||||
| for (const layerFile of layerFiles) { | ||||
|     try { | ||||
|         LegacyJsonConvert.fixLayerConfig(layerFile.parsed) | ||||
|         writeFileSync(layerFile.path, JSON.stringify(layerFile.parsed, null, "  ")) | ||||
|         const state : any = undefined; // FIXME
 | ||||
|         const fixed = new UpdateLegacyLayer().convertStrict(state,layerFile.parsed, "While linting "+layerFile.path); | ||||
|         writeFileSync(layerFile.path, JSON.stringify(fixed, null, "  ")) | ||||
|     } catch (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() | ||||
| for (const themeFile of themeFiles) { | ||||
|     try { | ||||
|         LegacyJsonConvert.fixThemeConfig(themeFile.parsed) | ||||
|         writeFileSync(themeFile.path, JSON.stringify(themeFile.parsed, null, "  ")) | ||||
|         const state : any = undefined; // FIXME
 | ||||
|         const fixed = new FixLegacyTheme().convertStrict(state,themeFile.parsed, "While linting "+themeFile.path); | ||||
|         writeFileSync(themeFile.path, JSON.stringify(fixed, null, "  ")) | ||||
|     } catch (e) { | ||||
|         console.error("COULD NOT LINT THEME" + themeFile.path + ":\n\t" + e) | ||||
|     } | ||||
|  |  | |||
|  | @ -1,5 +1,4 @@ | |||
| import T from "./TestHelper"; | ||||
| import {AllKnownLayouts} from "../Customizations/AllKnownLayouts"; | ||||
| import SelectedElementTagsUpdater from "../Logic/Actors/SelectedElementTagsUpdater"; | ||||
| import UserRelatedState from "../Logic/State/UserRelatedState"; | ||||
| import {Utils} from "../Utils"; | ||||
|  | @ -7,6 +6,8 @@ import SelectedFeatureHandler from "../Logic/Actors/SelectedFeatureHandler"; | |||
| import {UIEventSource} from "../Logic/UIEventSource"; | ||||
| import {ElementStorage} from "../Logic/ElementStorage"; | ||||
| 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 { | ||||
| 
 | ||||
|  | @ -52,7 +53,7 @@ export default class ActorsSpec extends T { | |||
|             [ | ||||
|                 "download latest version", | ||||
|                 () => { | ||||
|                     const state = new UserRelatedState(AllKnownLayouts.allKnownLayouts.get("bookcases")) | ||||
|                     const state = new UserRelatedState(new LayoutConfig(bookcaseJson, true, "tests" )) | ||||
|                     const feature = { | ||||
|                         "type": "Feature", | ||||
|                         "id": "node/5568693115", | ||||
|  |  | |||
|  | @ -1,6 +1,8 @@ | |||
| import T from "./TestHelper"; | ||||
| import LegacyJsonConvert from "../Models/ThemeConfig/LegacyJsonConvert"; | ||||
| import {FixLegacyTheme} from "../Models/ThemeConfig/LegacyJsonConvert"; | ||||
| 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 { | ||||
| 
 | ||||
|  | @ -145,9 +147,12 @@ export default class LegacyThemeLoaderSpec extends T { | |||
|                 ["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
 | ||||
|                     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 {Utils} from "../Utils"; | ||||
| import * as assert from "assert"; | ||||
| import {LayoutConfigJson} from "../Models/ThemeConfig/Json/LayoutConfigJson"; | ||||
| 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 { | ||||
|     constructor() { | ||||
|  | @ -10,7 +15,7 @@ export default class ThemeSpec extends T { | |||
|             [ | ||||
|                 ["Nested overrides work", () => { | ||||
| 
 | ||||
|                     const themeConfigJson: LayoutConfigJson = { | ||||
|                     let themeConfigJson: LayoutConfigJson = { | ||||
|                         description: "Descr", | ||||
|                         icon: "", | ||||
|                         language: ["en"], | ||||
|  | @ -34,7 +39,14 @@ export default class ThemeSpec extends T { | |||
|                         version: "", | ||||
|                         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); | ||||
|                     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