First part of a huge refactoring

This commit is contained in:
pietervdvn 2021-12-21 18:35:31 +01:00
parent 0c22b15c8d
commit 11150a258d
56 changed files with 1425 additions and 1324 deletions

View file

@ -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,8 +6,30 @@ 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();
@ -35,14 +56,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 +110,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 +129,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`
} }
} }

View file

@ -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;
} }

View file

@ -28,8 +28,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)
@ -112,36 +110,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.
@ -233,7 +201,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
@ -305,7 +272,6 @@ The icon on the button is the default icon of the layer, but can be customized b
- [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
@ -441,29 +407,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)
@ -505,21 +448,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
@ -970,7 +898,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)
@ -1403,7 +1330,7 @@ A layer showing street lights
- This layer is needed as dependency for layer [Assen](#Assen)
@ -1513,358 +1440,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

View file

@ -4,9 +4,13 @@ import {Utils} from "../../Utils";
import LZString from "lz-string"; import LZString from "lz-string";
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"; import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig";
/**
* Gives an overview of themes that are installed in the user preferences
*/
export default class InstalledThemes { export default class InstalledThemes {
public installedThemes: UIEventSource<{ layout: LayoutConfig; definition: string }[]>; public installedThemes: UIEventSource<{ layout: LayoutConfig; definition: string }[]>;
constructor(osmConnection: OsmConnection) { constructor(osmConnection: OsmConnection) {
this.installedThemes = osmConnection.preferencesHandler.preferences.map<{ layout: LayoutConfig, definition: string }[]>(allPreferences => { this.installedThemes = osmConnection.preferencesHandler.preferences.map<{ layout: LayoutConfig, definition: string }[]>(allPreferences => {
const installedThemes: { layout: LayoutConfig, definition: string }[] = []; const installedThemes: { layout: LayoutConfig, definition: string }[] = [];

View file

@ -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) {

View file

@ -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;
} }
} }

View file

@ -10,7 +10,9 @@ 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} from "../Models/ThemeConfig/LegacyJsonConvert";
import {LayerConfigJson} from "../Models/ThemeConfig/Json/LayerConfigJson";
import SharedTagRenderings from "../Customizations/SharedTagRenderings";
export default class DetermineLayout { export default class DetermineLayout {
@ -43,7 +45,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);
@ -104,7 +106,11 @@ export default class DetermineLayout {
} }
} }
LegacyJsonConvert.fixThemeConfig(json) json = new FixLegacyTheme().convertStrict({
tagRenderings: SharedTagRenderings.SharedTagRenderingJson,
sharedLayers: new Map<string, LayerConfigJson>() // FIXME: actually add the layers
}, json, "While loading a dynamic theme")
const layoutToUse = new LayoutConfig(json, false); const layoutToUse = new LayoutConfig(json, false);
userLayoutParam.setData(layoutToUse.id); userLayoutParam.setData(layoutToUse.id);
return [layoutToUse, btoa(Utils.MinifyJSON(JSON.stringify(json)))]; return [layoutToUse, btoa(Utils.MinifyJSON(JSON.stringify(json)))];
@ -141,9 +147,12 @@ 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({
tagRenderings: SharedTagRenderings.SharedTagRenderingJson,
sharedLayers: new Map<string, LayerConfigJson>() // FIXME: actually add the layers
}, parsed, "While loading a dynamic theme")
try { try {
parsed.id = link; parsed.id = link;
return new LayoutConfig(parsed, false).patchImages(link, JSON.stringify(parsed)); return new LayoutConfig(parsed, false).patchImages(link, JSON.stringify(parsed));

View file

@ -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);
} }
} }

View file

@ -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>) {

View file

@ -2,7 +2,7 @@ import {Utils} from "../Utils";
export default class Constants { export default class Constants {
public static vNumber = "0.13.0-alpha-8"; public static vNumber = "0.13.0-alpha-9";
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"]
/**
* 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) {

View file

@ -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"

View file

@ -15,7 +15,7 @@ export default interface PointRenderingConfigJson {
* All the locations that this point should be rendered at. * All the locations that this point should be rendered at.
* Using `location: ["point", "centroid"] will always render centerpoint * Using `location: ["point", "centroid"] will always render centerpoint
*/ */
location: ("point" | "centroid" | "start" | "end")[] location: ("point" | "centroid" | "start" | "end" | string)[]
/** /**
* The icon for an element. * The icon for an element.

View file

@ -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 {
@ -230,26 +230,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)`
} }
@ -259,17 +247,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
}); });
@ -314,109 +293,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;
@ -506,5 +382,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)
} }
} }

View file

@ -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;
@ -70,6 +67,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)`
} }
@ -79,6 +77,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");
@ -88,20 +99,13 @@ 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.trackAllNodes = layerInfo.extractAllNodes this.trackAllNodes = this.layers.some(layer => layer.id === "type_node");
this.clustering = { this.clustering = {
@ -121,10 +125,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;
@ -153,120 +153,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 [];

View file

@ -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"]
@ -29,6 +419,7 @@ export default class LegacyJsonConvert {
} }
} }
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 +441,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 +457,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 +476,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()),
);
}
} }

View file

@ -16,7 +16,7 @@ import {VariableUiElement} from "../../UI/Base/VariableUIElement";
export default class PointRenderingConfig extends WithContextLoader { export default class PointRenderingConfig extends WithContextLoader {
private static readonly allowed_location_codes = new Set<string>(["point", "centroid", "start", "end"]) private static readonly allowed_location_codes = new Set<string>(["point", "centroid", "start", "end"])
public readonly location: Set<"point" | "centroid" | "start" | "end"> public readonly location: Set<"point" | "centroid" | "start" | "end" | string>
public readonly icon: TagRenderingConfig; public readonly icon: TagRenderingConfig;
public readonly iconBadges: { if: TagsFilter; then: TagRenderingConfig }[]; public readonly iconBadges: { if: TagsFilter; then: TagRenderingConfig }[];

View file

@ -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
} }

View file

@ -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`

View file

@ -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")
} }

View file

@ -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())

View file

@ -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;"))

View file

@ -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");
} }

View file

@ -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
}, customThemeDefinition: string = undefined
): ):
BaseUIElement { BaseUIElement {
if (layout === undefined) { if (layout === undefined) {
@ -75,11 +81,11 @@ export default class MoreScreen extends Combine {
let linkPrefix = `${path}/${layout.id.toLowerCase()}.html?` let linkPrefix = `${path}/${layout.id.toLowerCase()}.html?`
let linkSuffix = "" 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 (customThemeDefinition) {
linkPrefix = `${path}/index.html?userlayout=${layout.id}&` linkPrefix = `${path}/theme.html?userlayout=${layout.id}&`
linkSuffix = `#${customThemeDefinition}` linkSuffix = `#${customThemeDefinition}`
} }
@ -98,10 +104,10 @@ export default class MoreScreen extends Combine {
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});
} }
@ -122,27 +128,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 +166,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) {

View file

@ -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,
}) })

View file

@ -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")
} }

View file

@ -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

View file

@ -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
) )
} }

View file

@ -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)
) )
]); ]);
} }

View file

@ -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;

View file

@ -18,7 +18,6 @@ import {Utils} from "../../Utils";
import {SubstitutedTranslation} from "../SubstitutedTranslation"; import {SubstitutedTranslation} from "../SubstitutedTranslation";
import MoveWizard from "./MoveWizard"; import MoveWizard from "./MoveWizard";
import Toggle from "../Input/Toggle"; import Toggle from "../Input/Toggle";
import {FixedUiElement} from "../Base/FixedUiElement";
export default class FeatureInfoBox extends ScrollableFullScreen { export default class FeatureInfoBox extends ScrollableFullScreen {
@ -41,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,
@ -89,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(
@ -164,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(
@ -178,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])
) )

View file

@ -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";
abstract class AbstractImportButton implements SpecialVisualizations { abstract class AbstractImportButton implements SpecialVisualizations {
@ -255,7 +256,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)
}) })
}) })

View file

@ -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) {

View file

@ -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));
} }

View file

@ -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,

View file

@ -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";
export interface SpecialVisualization { export interface SpecialVisualization {
funcName: string, funcName: string,
@ -51,7 +51,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[] =
[ [
@ -104,7 +103,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);
} }
}, },
{ {
@ -120,7 +119,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])
} }
}, },
{ {
@ -161,7 +160,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)
@ -267,7 +266,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
} }
@ -324,7 +323,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])
} }
}, },
{ {

View file

@ -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}(${viz.args.join(", ")}) ${e}`).SetStyle("alert") return new FixedUiElement(`Could not generate special rendering for ${viz.func}(${viz.args.join(", ")}) ${e}`).SetStyle("alert")

View file

@ -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)) {

View file

@ -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));
}
} }

View file

@ -13,92 +13,7 @@ import AvailableBaseLayersImplementation from "./Logic/Actors/AvailableBaseLayer
import ShowOverlayLayerImplementation from "./UI/ShowDataLayer/ShowOverlayLayerImplementation"; import ShowOverlayLayerImplementation from "./UI/ShowDataLayer/ShowOverlayLayerImplementation";
import {DefaultGuiState} from "./UI/DefaultGuiState"; import {DefaultGuiState} from "./UI/DefaultGuiState";
// 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 // 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 {
public static Init(layoutToUse: LayoutConfig, encoded: string) {
if (layoutToUse === null) {
// Something went wrong, error message is already on screen
return;
}
if (layoutToUse === undefined) {
// No layout found
new AllThemesGui()
return;
}
// Workaround/legacy to keep the old paramters working as I renamed some of them
if (layoutToUse?.id === "cyclofix") {
const legacy = QueryParameters.GetQueryParameter("layer-bike_shops", "true", "Legacy - keep De Fietsambassade working");
const correct = QueryParameters.GetQueryParameter("layer-bike_shop", "true", "Legacy - keep De Fietsambassade working")
if (legacy.data !== "true") {
correct.setData(legacy.data)
}
console.log("layer-bike_shop toggles: legacy:", legacy.data, "new:", correct.data)
const legacyCafe = QueryParameters.GetQueryParameter("layer-bike_cafes", "true", "Legacy - keep De Fietsambassade working")
const correctCafe = QueryParameters.GetQueryParameter("layer-bike_cafe", "true", "Legacy - keep De Fietsambassade working")
if (legacyCafe.data !== "true") {
correctCafe.setData(legacy.data)
}
}
const guiState = new DefaultGuiState()
State.state = new State(layoutToUse);
DefaultGuiState.state = guiState;
// This 'leaks' the global state via the window object, useful for debugging
// @ts-ignore
window.mapcomplete_state = State.state;
new DefaultGUI(State.state, guiState)
if (encoded !== undefined && encoded.length > 10) {
// We save the layout to the user settings and local storage
State.state.osmConnection.OnLoggedIn(() => {
State.state.osmConnection
.GetLongPreference("installed-theme-" + layoutToUse.id)
.setData(encoded);
});
}
}
}
document.getElementById("decoration-desktop").remove(); document.getElementById("decoration-desktop").remove();
new Combine(["Initializing... <br/>", new AllThemesGui();
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
// @ts-ignore
if(typeof theme === "undefined"){
DetermineLayout.GetLayout().then(value => {
console.log("Got ", value)
Init.Init(value[0], value[1])
}).catch(err => {
console.error("Error while initializing: ", err, err.stack)
})
}

View file

@ -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": {

View file

@ -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",

View file

@ -38,7 +38,7 @@
"point" "point"
], ],
"icon": { "icon": {
"render": "./assets/themes/grb_import/robot.svg" "render": "./assets/svg/robot.svg"
}, },
"iconSize": "15,15,center" "iconSize": "15,15,center"
} }
@ -99,8 +99,7 @@
"osmTags": "building~*", "osmTags": "building~*",
"maxCacheAge": 0 "maxCacheAge": 0
}, },
"calculatedTags": [ "calculatedTags": [],
],
"mapRendering": [ "mapRendering": [
{ {
"width": { "width": {
@ -490,7 +489,6 @@
"if": "_overlaps_with_other_features~*", "if": "_overlaps_with_other_features~*",
"then": "This GRB building overlaps with the following features: {_overlaps_with_other_features}.<br/>Fix the overlap and try again" "then": "This GRB building overlaps with the following features: {_overlaps_with_other_features}.<br/>Fix the overlap and try again"
}, },
{ {
"if": { "if": {
"and": [ "and": [

View file

@ -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": [

View file

@ -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",

View file

@ -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",

View file

@ -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">new AllThemesGui();</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>

View file

@ -18,7 +18,6 @@ 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 ----------------- // --------------------- Special actions based on the parameters -----------------
@ -66,7 +65,6 @@ class Init {
// 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) { if (encoded !== undefined && encoded.length > 10) {
@ -92,7 +90,7 @@ 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[0], value[1])

View file

@ -4,8 +4,6 @@ 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";
import {Utils} from "./Utils"; import {Utils} from "./Utils";
import AllThemesGui from "./UI/AllThemesGui";
import DetermineLayout from "./Logic/DetermineLayout";
import LayoutConfig from "./Models/ThemeConfig/LayoutConfig"; import LayoutConfig from "./Models/ThemeConfig/LayoutConfig";
import DefaultGUI from "./UI/DefaultGUI"; import DefaultGUI from "./UI/DefaultGUI";
import State from "./State"; import State from "./State";
@ -13,6 +11,23 @@ import AvailableBaseLayersImplementation from "./Logic/Actors/AvailableBaseLayer
import ShowOverlayLayerImplementation from "./UI/ShowDataLayer/ShowOverlayLayerImplementation"; import ShowOverlayLayerImplementation from "./UI/ShowDataLayer/ShowOverlayLayerImplementation";
import {DefaultGuiState} from "./UI/DefaultGuiState"; 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 // 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() MinimapImplementation.initialize()
AvailableBaseLayers.implement(new AvailableBaseLayersImplementation()) AvailableBaseLayers.implement(new AvailableBaseLayersImplementation())
@ -20,28 +35,9 @@ ShowOverlayLayerImplementation.Implement();
// Miscelleanous // Miscelleanous
Utils.DisableLongPresses() Utils.DisableLongPresses()
// --------------------- Special actions based on the parameters ----------------- const layoutToUse = new LayoutConfig(themeConfig["default"])
// @ts-ignore
if (location.href.startsWith("http://buurtnatuur.be")) {
// Reload the https version. This is important for the 'locate me' button
window.location.replace("https://buurtnatuur.be");
}
class Init {
public static Init(layoutToUse: LayoutConfig, encoded: string) {
if (layoutToUse === null) {
// Something went wrong, error message is already on screen
return;
}
if (layoutToUse === undefined) {
// No layout found
new AllThemesGui()
return;
}
// Workaround/legacy to keep the old paramters working as I renamed some of them // Workaround/legacy to keep the old paramters working as I renamed some of them
if (layoutToUse?.id === "cyclofix") { if (layoutToUse?.id === "cyclofix") {
const legacy = QueryParameters.GetQueryParameter("layer-bike_shops", "true", "Legacy - keep De Fietsambassade working"); const legacy = QueryParameters.GetQueryParameter("layer-bike_shops", "true", "Legacy - keep De Fietsambassade working");
@ -59,6 +55,7 @@ class Init {
} }
const guiState = new DefaultGuiState() const guiState = new DefaultGuiState()
State.state = new State(layoutToUse); State.state = new State(layoutToUse);
DefaultGuiState.state = guiState; DefaultGuiState.state = guiState;
@ -66,36 +63,3 @@ class Init {
// @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);
});
}
}
}
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
// @ts-ignore
DetermineLayout.GetLayout().then(value => {
console.log("Got ", value)
Init.Init(value[0], value[1])
}).catch(err => {
console.error("Error while initializing: ", err, err.stack)
})

View file

@ -28,7 +28,7 @@
"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",
@ -39,7 +39,7 @@
"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 validate:layeroverview",
"build": "rm -rf dist/ && npm run generate && parcel build --public-url ./ *.html assets/** assets/**/** assets/**/**/** vendor/* vendor/*/*", "build": "rm -rf dist/ && npm run generate && node --max_old_space_size=12000 $(which 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": "npm run generate && npm run test && npm run generate:editor-layer-index && npm run generate:layouts && npm run build && rm -rf .cache",
"deploy:staging": "npm run prepare-deploy && rm -rf ~/git/pietervdvn.github.io/Staging/* && cp -r dist/* ~/git/pietervdvn.github.io/Staging/ && cd ~/git/pietervdvn.github.io/ && git add * && git commit -m 'New MapComplete Version' && git push && cd - && npm run clean", "deploy: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",
@ -47,8 +47,8 @@
"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", "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 \"\\(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" "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": [

View file

@ -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
@ -36,202 +42,155 @@ class LayerOverviewUtils {
} }
} }
writeFiles(lt: LayersAndThemes) { writeSmallOverview(themes: { id: string, title: any, shortDescription: any, icon: string, hideFromOverview: boolean }[]) {
writeFileSync("./assets/generated/known_layers_and_themes.json", JSON.stringify({ const perId = new Map<string, any>();
"layers": lt.layers.map(l => l.parsed), for (const theme of themes) {
"themes": lt.themes const data = {
})) id: theme.id,
title: theme.title,
shortDescription: theme.shortDescription,
icon: theme.icon,
hideFromOverview: theme.hideFromOverview
} }
perId.set(theme.id, data);
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())
}))
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;
}
}
} }
} }

View file

@ -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 {
@ -106,7 +108,6 @@ async function createManifest(layout: LayoutConfig) {
}; };
} }
const template = readFileSync("index.html", "utf8");
async function createLandingPage(layout: LayoutConfig, manifest) { async function createLandingPage(layout: LayoutConfig, manifest) {
@ -159,7 +160,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
@ -172,12 +174,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) {
@ -202,6 +210,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))
} }
@ -224,6 +233,4 @@ createManifest(new LayoutConfig({
}) })
console.log("Counting all translations")
Translations.CountTranslations();
console.log("All done!"); console.log("All done!");

View file

@ -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;
} }

View file

@ -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)
} }

View file

@ -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/themes/bookcases/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",

View file

@ -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)
}] }]
] ]

84
theme.html Normal file
View 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>