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