diff --git a/Customizations/SharedTagRenderings.ts b/Customizations/SharedTagRenderings.ts index 9901e608e7..b0b8681431 100644 --- a/Customizations/SharedTagRenderings.ts +++ b/Customizations/SharedTagRenderings.ts @@ -31,16 +31,22 @@ export default class SharedTagRenderings { if (!iconsOnly) { for (const key in questions) { + if(key === "id"){ + continue + } dict.set(key, questions[key]) } } for (const key in icons) { + if(key === "id"){ + continue + } dict.set(key, icons[key]) } dict.forEach((value, key) => { if(key === "id"){ - return; + return } value.id = value.id ?? key; }) diff --git a/Models/Constants.ts b/Models/Constants.ts index a44298fe37..e5777f8d78 100644 --- a/Models/Constants.ts +++ b/Models/Constants.ts @@ -2,7 +2,7 @@ import {Utils} from "../Utils"; export default class Constants { - public static vNumber = "0.15.0-alpha-1"; + public static vNumber = "0.15.0-alpha-2"; public static ImgurApiKey = '7070e7167f0a25a' public static readonly mapillary_client_token_v4 = "MLY|4441509239301885|b40ad2d3ea105435bd40c7e76993ae85" diff --git a/Models/ThemeConfig/Conversion/LegacyJsonConvert.ts b/Models/ThemeConfig/Conversion/LegacyJsonConvert.ts index 660c422187..04c09e6256 100644 --- a/Models/ThemeConfig/Conversion/LegacyJsonConvert.ts +++ b/Models/ThemeConfig/Conversion/LegacyJsonConvert.ts @@ -7,6 +7,7 @@ import LineRenderingConfigJson from "../Json/LineRenderingConfigJson"; import {LayerConfigJson} from "../Json/LayerConfigJson"; import Constants from "../../Constants"; import {DesugaringContext, DesugaringStep, Fuse, OnEvery} from "./Conversion"; +import {ApplyOverrideAll} from "./ApplyOverrideAll"; export class UpdateLegacyLayer extends DesugaringStep { @@ -395,3 +396,240 @@ export class ValidateThemeAndLayers extends Fuse { } } +<<<<<<< HEAD +======= +class AddDependencyLayersToTheme extends DesugaringStep { + 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, themeId: string): LayerConfigJson[] { + const dependenciesToAdd: LayerConfigJson[] = [] + const loadedLayerIds: Set = new Set(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 = state.sharedLayers; + const knownTagRenderings: Map = state.tagRenderings; + const errors = []; + const warnings = []; + const layers: 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 + }; + } +} + +class SetDefault extends DesugaringStep { + private readonly value: any; + private readonly key: string; + private readonly _overrideEmptyString: boolean; + + constructor(key: string, value: any, overrideEmptyString = false) { + super("Sets " + key + " to a default value if undefined"); + this.key = key; + this.value = value; + this._overrideEmptyString = overrideEmptyString; + } + + convert(state: DesugaringContext, json: T, context: string): { result: T; errors: string[]; warnings: string[] } { + if (json[this.key] === undefined || (json[this.key] === "" && this._overrideEmptyString)) { + json = {...json} + json[this.key] = this.value + } + + return { + errors: [], warnings: [], + result: json + }; + } +} + +export class PrepareLayer extends Fuse { + constructor() { + super( + "Fully prepares and expands a layer for the LayerConfig.", + new OnEveryConcat("tagRenderings", new ExpandGroupRewrite()), + new OnEveryConcat("tagRenderings", new ExpandTagRendering()), + new SetDefault("titleIcons", ["defaults"]), + 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 + } + if (json["override"]["tagRenderings"] !== undefined && (found["tagRenderings"] ?? []).length > 0) { + errors.push(`At ${context}: when overriding a layer, an override is not allowed to override into tagRenderings. Use "+tagRenderings" or "tagRenderings+" instead to prepend or append some questions.`) + } + try { + Utils.Merge(json["override"], found); + layers.push(found) + } catch (e) { + errors.push(`At ${context}: could not apply an override due to: ${e}.\nThe override is: ${JSON.stringify(json["override"],)}`) + } + } + return { + result: layers, + errors, warnings + } + + } + + return { + result: [json], + errors, warnings + }; + } + +} + +class AddDefaultLayers extends DesugaringStep { + + 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 = [] + const warnings = [] + json.layers = [...json.layers] + + if (json.id === "personal") { + json.layers = [] + for (const publicLayer of AllKnownLayouts.AllPublicLayers()) { + const id = publicLayer.id + const config = state.sharedLayers.get(id) + if(Constants.added_by_default.indexOf(id) >= 0){ + continue; + } + if(config === undefined){ + // This is a layer which is coded within a public theme, not as separate .json + continue + } + json.layers.push(config) + } + const publicIds = AllKnownLayouts.AllPublicLayers().map(l => l.id) + publicIds.map(id => state.sharedLayers.get(id)) + } + + 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 { + constructor() { + super( + "Fully prepares and expands a theme", + new OnEveryConcat("layers", new SubstituteLayer()), + new SetDefault("socialImage", "assets/SocialImage.png", true), + new OnEvery("layers", new PrepareLayer()), + new ApplyOverrideAll(), + new AddDefaultLayers(), + + new AddDependencyLayersToTheme(), + new OnEvery("layers", new AddMiniMap()) + ); + } +} +>>>>>>> master diff --git a/Models/ThemeConfig/Conversion/PrepareTheme.ts b/Models/ThemeConfig/Conversion/PrepareTheme.ts index 5ab12adba6..b51df94393 100644 --- a/Models/ThemeConfig/Conversion/PrepareTheme.ts +++ b/Models/ThemeConfig/Conversion/PrepareTheme.ts @@ -225,6 +225,37 @@ export class AddMiniMap extends DesugaringStep { } } + +class ApplyOverrideAll extends DesugaringStep { + + constructor() { + super("Applies 'overrideAll' onto every 'layer'. The 'overrideAll'-field is removed afterwards", ["overrideAll", "layers"]); + } + + convert(state: DesugaringContext, json: LayoutConfigJson, context: string): { result: LayoutConfigJson; errors: string[]; warnings: string[] } { + + const overrideAll = json.overrideAll; + if (overrideAll === undefined) { + return {result: json, warnings: [], errors: []} + } + + json = {...json} + + delete json.overrideAll + const newLayers = [] + for (let layer of json.layers) { + layer = {...layer} + Utils.Merge(overrideAll, layer) + newLayers.push(layer) + } + json.layers = newLayers + + + return {result: json, warnings: [], errors: []}; + } + +} + class AddDependencyLayersToTheme extends DesugaringStep { 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"]); @@ -306,11 +337,13 @@ export class PrepareTheme extends Fuse { constructor() { super( "Fully prepares and expands a theme", + new OnEveryConcat("layers", new SubstituteLayer()), new SetDefault("socialImage", "assets/SocialImage.png", true), + new OnEvery("layers", new PrepareLayer()), + new ApplyOverrideAll(), new AddDefaultLayers(), new AddDependencyLayersToTheme(), - new OnEvery("layers", new PrepareLayer()), new AddImportLayers(), new OnEvery("layers", new AddMiniMap()) ); diff --git a/assets/themes/speelplekken/speelplekken.json b/assets/themes/speelplekken/speelplekken.json index adf40461a3..226c789153 100644 --- a/assets/themes/speelplekken/speelplekken.json +++ b/assets/themes/speelplekken/speelplekken.json @@ -277,11 +277,10 @@ "render": "" } ], - "+iconOverlays": [ + "+iconBadges": [ { "if": "_video:id~*", - "then": "./assets/themes/speelplekken/youtube.svg", - "badge": true + "then": "./assets/themes/speelplekken/youtube.svg" } ], "isShown": { diff --git a/scripts/generateLayerOverview.ts b/scripts/generateLayerOverview.ts index 84c9c29359..0812f269f6 100644 --- a/scripts/generateLayerOverview.ts +++ b/scripts/generateLayerOverview.ts @@ -72,7 +72,7 @@ class LayerOverviewUtils { const dict = new Map(); for (const key in questions["default"]) { - if(key==="id"){ + if(key === "id"){ continue } questions[key].id = key; @@ -90,6 +90,9 @@ class LayerOverviewUtils { } dict.forEach((value, key) => { + if(key === "id"){ + return + } value.id = value.id ?? key; })