forked from MapComplete/MapComplete
		
	Finish importer, add applicable import layers to every theme by default
This commit is contained in:
		
							parent
							
								
									3402ac0954
								
							
						
					
					
						commit
						ca1490902c
					
				
					 41 changed files with 1559 additions and 898 deletions
				
			
		|  | @ -1,439 +1,12 @@ | |||
| import {LayoutConfigJson} from "../Json/LayoutConfigJson"; | ||||
| import DependencyCalculator from "../DependencyCalculator"; | ||||
| import LayerConfig from "../LayerConfig"; | ||||
| import {Translation} from "../../../UI/i18n/Translation"; | ||||
| import LayoutConfig from "../LayoutConfig"; | ||||
| import {Utils} from "../../../Utils"; | ||||
| import {TagRenderingConfigJson} from "../Json/TagRenderingConfigJson"; | ||||
| import LineRenderingConfigJson from "../Json/LineRenderingConfigJson"; | ||||
| import {LayerConfigJson} from "../Json/LayerConfigJson"; | ||||
| import Constants from "../../Constants"; | ||||
| import {AllKnownLayouts} from "../../../Customizations/AllKnownLayouts"; | ||||
| import {SubstitutedTranslation} from "../../../UI/SubstitutedTranslation"; | ||||
| 
 | ||||
| export interface DesugaringContext { | ||||
|     tagRenderings: Map<string, TagRenderingConfigJson> | ||||
|     sharedLayers: Map<string, LayerConfigJson> | ||||
| } | ||||
| 
 | ||||
| export abstract class Conversion<TIn, TOut> { | ||||
|     public readonly modifiedAttributes: string[]; | ||||
|     protected readonly doc: string; | ||||
| 
 | ||||
|     constructor(doc: string, modifiedAttributes: string[] = []) { | ||||
|         this.modifiedAttributes = modifiedAttributes; | ||||
|         this.doc = doc + "\n\nModified attributes are\n" + modifiedAttributes.join(", "); | ||||
|     } | ||||
| 
 | ||||
|     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; | ||||
|     } | ||||
| 
 | ||||
|     public convertStrict(state: DesugaringContext, json: TIn, context: string): TOut { | ||||
|         const fixed = this.convert(state, json, context) | ||||
|         return DesugaringStep.strict(fixed) | ||||
|     } | ||||
| 
 | ||||
|     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 | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| export 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 AddMiniMap extends DesugaringStep<LayerConfigJson> { | ||||
|     constructor() { | ||||
|         super("Adds a default 'minimap'-element to the tagrenderings if none of the elements define such a minimap", ["tagRenderings"]); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Returns true if this tag rendering has a minimap in some language. | ||||
|      * Note: this minimap can be hidden by conditions | ||||
|      */ | ||||
|     private static hasMinimap(renderingConfig: TagRenderingConfigJson): boolean { | ||||
|         const translations: Translation[] = Utils.NoNull([renderingConfig.render, ...(renderingConfig.mappings ?? []).map(m => m.then)]); | ||||
|         for (const translation of translations) { | ||||
|             for (const key in translation.translations) { | ||||
|                 if (!translation.translations.hasOwnProperty(key)) { | ||||
|                     continue | ||||
|                 } | ||||
|                 const template = translation.translations[key] | ||||
|                 const parts = SubstitutedTranslation.ExtractSpecialComponents(template) | ||||
|                 const hasMiniMap = parts.filter(part => part.special !== undefined).some(special => special.special.func.funcName === "minimap") | ||||
|                 if (hasMiniMap) { | ||||
|                     return true; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         return false; | ||||
|     } | ||||
|      | ||||
|     convert(state: DesugaringContext, layerConfig: LayerConfigJson, context: string): { result: LayerConfigJson; errors: string[]; warnings: string[] } { | ||||
|          | ||||
| 
 | ||||
| 
 | ||||
|         const hasMinimap = layerConfig.tagRenderings?.some(tr => AddMiniMap.hasMinimap(<TagRenderingConfigJson> tr)) ?? true | ||||
|         if (!hasMinimap) { | ||||
|             layerConfig = {...layerConfig} | ||||
|             layerConfig.tagRenderings = [...layerConfig.tagRenderings] | ||||
|             layerConfig.tagRenderings.push(state.tagRenderings.get("minimap")) | ||||
|         } | ||||
|          | ||||
|         return { | ||||
|             errors:[], | ||||
|             warnings: [], | ||||
|             result: layerConfig | ||||
|         }; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| class ExpandTagRendering extends Conversion<string | TagRenderingConfigJson | { builtin: string | string[], override: any }, TagRenderingConfigJson[]> { | ||||
|     constructor() { | ||||
|         super("Converts a tagRenderingSpec into the full tagRendering", []); | ||||
|     } | ||||
| 
 | ||||
|     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 | ||||
|         }; | ||||
|     } | ||||
| 
 | ||||
|     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] | ||||
|             } | ||||
| 
 | ||||
|             for (const key of Object.keys(tr)) { | ||||
|                 if (key === "builtin" || key === "override" || key === "id" || key.startsWith("#")) { | ||||
|                     continue | ||||
|                 } | ||||
|                 errors.push("At " + ctx + ": an object calling a builtin can only have keys `builtin` or `override`, but a key with name `" + key + "` was found. This won't be picked up! The full object is: " + JSON.stringify(tr)) | ||||
|             } | ||||
| 
 | ||||
|             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 foundTr of lookup) { | ||||
|                     foundTr = Utils.Clone<any>(foundTr) | ||||
|                     Utils.Merge(tr["override"] ?? {}, foundTr) | ||||
|                     trs.push(foundTr) | ||||
|                 } | ||||
|             } | ||||
|             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; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| 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" | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     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, _) => { | ||||
|             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 | ||||
|         }; | ||||
|     } | ||||
| 
 | ||||
|     /* 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 | ||||
|     } | ||||
| } | ||||
| import {DesugaringContext, DesugaringStep, Fuse, OnEvery} from "./Conversion"; | ||||
| 
 | ||||
| 
 | ||||
| export class UpdateLegacyLayer extends DesugaringStep<LayerConfigJson | string | { builtin, override }> { | ||||
|  | @ -822,235 +395,3 @@ export class ValidateThemeAndLayers extends Fuse<LayoutConfigJson> { | |||
|     } | ||||
| } | ||||
| 
 | ||||
| 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 | ||||
|         }; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| class SetDefault<T> extends DesugaringStep<T> { | ||||
|     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<LayerConfigJson> { | ||||
|     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<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 = [] | ||||
|         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<LayoutConfigJson> { | ||||
|     constructor() { | ||||
|         super( | ||||
|             "Fully prepares and expands a theme", | ||||
|             new OnEveryConcat("layers", new SubstituteLayer()), | ||||
|             new SetDefault("socialImage", "assets/SocialImage.png", true), | ||||
|             new AddDefaultLayers(), | ||||
|             new AddDependencyLayersToTheme(), | ||||
|             new OnEvery("layers", new PrepareLayer()), | ||||
|             new OnEvery("layers", new AddMiniMap()) | ||||
|         ); | ||||
|     } | ||||
| } | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue