forked from MapComplete/MapComplete
		
	Add various improvements and fixes to studio, should fix #2055
This commit is contained in:
		
							parent
							
								
									3dc0014f35
								
							
						
					
					
						commit
						5d1c93396d
					
				
					 19 changed files with 531 additions and 418 deletions
				
			
		|  | @ -993,10 +993,6 @@ video { | ||||||
|   margin-right: 4rem; |   margin-right: 4rem; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .mb-4 { |  | ||||||
|   margin-bottom: 1rem; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .mt-4 { | .mt-4 { | ||||||
|   margin-top: 1rem; |   margin-top: 1rem; | ||||||
| } | } | ||||||
|  | @ -1029,6 +1025,10 @@ video { | ||||||
|   margin-right: 0.25rem; |   margin-right: 0.25rem; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | .mb-4 { | ||||||
|  |   margin-bottom: 1rem; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| .ml-1 { | .ml-1 { | ||||||
|   margin-left: 0.25rem; |   margin-left: 0.25rem; | ||||||
| } | } | ||||||
|  | @ -4686,6 +4686,16 @@ textarea { | ||||||
|   color: black; |   color: black; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | h2.group { | ||||||
|  |   /* For flowbite accordions */ | ||||||
|  |   margin: 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .group button { | ||||||
|  |   /* For flowbite accordions */ | ||||||
|  |   border-radius: 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| /************************* OTHER CATEGORIES ********************************/ | /************************* OTHER CATEGORIES ********************************/ | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  |  | ||||||
|  | @ -1,42 +1,14 @@ | ||||||
| import { Utils } from "../../Utils" | import { Utils } from "../../Utils" | ||||||
| /** This code is autogenerated - do not edit. Edit ./assets/layers/usersettings/usersettings.json instead */ | /** This code is autogenerated - do not edit. Edit ./assets/layers/usersettings/usersettings.json instead */ | ||||||
| export class ThemeMetaTagging { | export class ThemeMetaTagging { | ||||||
|     public static readonly themeName = "usersettings" |    public static readonly themeName = "usersettings" | ||||||
| 
 | 
 | ||||||
|     public metaTaggging_for_usersettings(feat: { properties: Record<string, string> }) { |    public metaTaggging_for_usersettings(feat: {properties: Record<string, string>}) { | ||||||
|         Utils.AddLazyProperty(feat.properties, "_mastodon_candidate_md", () => |       Utils.AddLazyProperty(feat.properties, '_mastodon_candidate_md', () => feat.properties._description.match(/\[[^\]]*\]\((.*(mastodon|en.osm.town).*)\).*/)?.at(1) )  | ||||||
|             feat.properties._description |       Utils.AddLazyProperty(feat.properties, '_d', () => feat.properties._description?.replace(/</g,'<')?.replace(/>/g,'>') ?? '' )  | ||||||
|                 .match(/\[[^\]]*\]\((.*(mastodon|en.osm.town).*)\).*/) |       Utils.AddLazyProperty(feat.properties, '_mastodon_candidate_a', () => (feat => {const e = document.createElement('div');e.innerHTML = feat.properties._d;return Array.from(e.getElementsByTagName("a")).filter(a => a.href.match(/mastodon|en.osm.town/) !== null)[0]?.href   }) (feat)  )  | ||||||
|                 ?.at(1) |       Utils.AddLazyProperty(feat.properties, '_mastodon_link', () => (feat => {const e = document.createElement('div');e.innerHTML = feat.properties._d;return Array.from(e.getElementsByTagName("a")).filter(a => a.getAttribute("rel")?.indexOf('me') >= 0)[0]?.href})(feat)  )  | ||||||
|         ) |       Utils.AddLazyProperty(feat.properties, '_mastodon_candidate', () => feat.properties._mastodon_candidate_md ?? feat.properties._mastodon_candidate_a )  | ||||||
|         Utils.AddLazyProperty( |       feat.properties['__current_backgroun'] = 'initial_value' | ||||||
|             feat.properties, |    } | ||||||
|             "_d", |  | ||||||
|             () => feat.properties._description?.replace(/</g, "<")?.replace(/>/g, ">") ?? "" |  | ||||||
|         ) |  | ||||||
|         Utils.AddLazyProperty(feat.properties, "_mastodon_candidate_a", () => |  | ||||||
|             ((feat) => { |  | ||||||
|                 const e = document.createElement("div") |  | ||||||
|                 e.innerHTML = feat.properties._d |  | ||||||
|                 return Array.from(e.getElementsByTagName("a")).filter( |  | ||||||
|                     (a) => a.href.match(/mastodon|en.osm.town/) !== null |  | ||||||
|                 )[0]?.href |  | ||||||
|             })(feat) |  | ||||||
|         ) |  | ||||||
|         Utils.AddLazyProperty(feat.properties, "_mastodon_link", () => |  | ||||||
|             ((feat) => { |  | ||||||
|                 const e = document.createElement("div") |  | ||||||
|                 e.innerHTML = feat.properties._d |  | ||||||
|                 return Array.from(e.getElementsByTagName("a")).filter( |  | ||||||
|                     (a) => a.getAttribute("rel")?.indexOf("me") >= 0 |  | ||||||
|                 )[0]?.href |  | ||||||
|             })(feat) |  | ||||||
|         ) |  | ||||||
|         Utils.AddLazyProperty( |  | ||||||
|             feat.properties, |  | ||||||
|             "_mastodon_candidate", |  | ||||||
|             () => feat.properties._mastodon_candidate_md ?? feat.properties._mastodon_candidate_a |  | ||||||
|         ) |  | ||||||
|         feat.properties["__current_backgroun"] = "initial_value" |  | ||||||
|     } |  | ||||||
| } | } | ||||||
|  | @ -73,15 +73,20 @@ export abstract class DesugaringStep<T> extends Conversion<T, T> {} | ||||||
| export class Pipe<TIn, TInter, TOut> extends Conversion<TIn, TOut> { | export class Pipe<TIn, TInter, TOut> extends Conversion<TIn, TOut> { | ||||||
|     private readonly _step0: Conversion<TIn, TInter> |     private readonly _step0: Conversion<TIn, TInter> | ||||||
|     private readonly _step1: Conversion<TInter, TOut> |     private readonly _step1: Conversion<TInter, TOut> | ||||||
|  |     private readonly _failfast: boolean | ||||||
| 
 | 
 | ||||||
|     constructor(step0: Conversion<TIn, TInter>, step1: Conversion<TInter, TOut>) { |     constructor(step0: Conversion<TIn, TInter>, step1: Conversion<TInter, TOut>, failfast = false) { | ||||||
|         super("Merges two steps with different types", [], `Pipe(${step0.name}, ${step1.name})`) |         super("Merges two steps with different types", [], `Pipe(${step0.name}, ${step1.name})`) | ||||||
|         this._step0 = step0 |         this._step0 = step0 | ||||||
|         this._step1 = step1 |         this._step1 = step1 | ||||||
|  |         this._failfast = failfast | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     convert(json: TIn, context: ConversionContext): TOut { |     convert(json: TIn, context: ConversionContext): TOut { | ||||||
|         const r0 = this._step0.convert(json, context.inOperation(this._step0.name)) |         const r0 = this._step0.convert(json, context.inOperation(this._step0.name)) | ||||||
|  |         if(context.hasErrors() && this._failfast){ | ||||||
|  |             return undefined | ||||||
|  |         } | ||||||
|         return this._step1.convert(r0, context.inOperation(this._step1.name)) |         return this._step1.convert(r0, context.inOperation(this._step1.name)) | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -21,6 +21,7 @@ import DependencyCalculator from "../DependencyCalculator" | ||||||
| import { AddContextToTranslations } from "./AddContextToTranslations" | import { AddContextToTranslations } from "./AddContextToTranslations" | ||||||
| import ValidationUtils from "./ValidationUtils" | import ValidationUtils from "./ValidationUtils" | ||||||
| import { ConversionContext } from "./ConversionContext" | import { ConversionContext } from "./ConversionContext" | ||||||
|  | import { PrevalidateTheme } from "./Validation" | ||||||
| 
 | 
 | ||||||
| class SubstituteLayer extends Conversion<string | LayerConfigJson, LayerConfigJson[]> { | class SubstituteLayer extends Conversion<string | LayerConfigJson, LayerConfigJson[]> { | ||||||
|     private readonly _state: DesugaringContext |     private readonly _state: DesugaringContext | ||||||
|  | @ -664,7 +665,6 @@ export class PrepareTheme extends Fuse<LayoutConfigJson> { | ||||||
|     ) { |     ) { | ||||||
|         super( |         super( | ||||||
|             "Fully prepares and expands a theme", |             "Fully prepares and expands a theme", | ||||||
| 
 |  | ||||||
|             new AddContextToTranslationsInLayout(), |             new AddContextToTranslationsInLayout(), | ||||||
|             new PreparePersonalTheme(state), |             new PreparePersonalTheme(state), | ||||||
|             new WarnForUnsubstitutedLayersInTheme(), |             new WarnForUnsubstitutedLayersInTheme(), | ||||||
|  |  | ||||||
|  | @ -36,7 +36,7 @@ class ValidateLanguageCompleteness extends DesugaringStep<LayoutConfig> { | ||||||
|         super( |         super( | ||||||
|             "Checks that the given object is fully translated in the specified languages", |             "Checks that the given object is fully translated in the specified languages", | ||||||
|             [], |             [], | ||||||
|             "ValidateLanguageCompleteness" |             "ValidateLanguageCompleteness", | ||||||
|         ) |         ) | ||||||
|         this._languages = languages ?? ["en"] |         this._languages = languages ?? ["en"] | ||||||
|     } |     } | ||||||
|  | @ -50,18 +50,18 @@ class ValidateLanguageCompleteness extends DesugaringStep<LayoutConfig> { | ||||||
|                 .filter( |                 .filter( | ||||||
|                     (t) => |                     (t) => | ||||||
|                         t.tr.translations[neededLanguage] === undefined && |                         t.tr.translations[neededLanguage] === undefined && | ||||||
|                         t.tr.translations["*"] === undefined |                         t.tr.translations["*"] === undefined, | ||||||
|                 ) |                 ) | ||||||
|                 .forEach((missing) => { |                 .forEach((missing) => { | ||||||
|                     context |                     context | ||||||
|                         .enter(missing.context.split(".")) |                         .enter(missing.context.split(".")) | ||||||
|                         .err( |                         .err( | ||||||
|                             `The theme ${obj.id} should be translation-complete for ` + |                             `The theme ${obj.id} should be translation-complete for ` + | ||||||
|                                 neededLanguage + |                             neededLanguage + | ||||||
|                                 ", but it lacks a translation for " + |                             ", but it lacks a translation for " + | ||||||
|                                 missing.context + |                             missing.context + | ||||||
|                                 ".\n\tThe known translation is " + |                             ".\n\tThe known translation is " + | ||||||
|                                 missing.tr.textFor("en") |                             missing.tr.textFor("en"), | ||||||
|                         ) |                         ) | ||||||
|                 }) |                 }) | ||||||
|         } |         } | ||||||
|  | @ -78,7 +78,7 @@ export class DoesImageExist extends DesugaringStep<string> { | ||||||
|     constructor( |     constructor( | ||||||
|         knownImagePaths: Set<string>, |         knownImagePaths: Set<string>, | ||||||
|         checkExistsSync: (path: string) => boolean = undefined, |         checkExistsSync: (path: string) => boolean = undefined, | ||||||
|         ignore?: Set<string> |         ignore?: Set<string>, | ||||||
|     ) { |     ) { | ||||||
|         super("Checks if an image exists", [], "DoesImageExist") |         super("Checks if an image exists", [], "DoesImageExist") | ||||||
|         this._ignore = ignore |         this._ignore = ignore | ||||||
|  | @ -114,15 +114,15 @@ export class DoesImageExist extends DesugaringStep<string> { | ||||||
|         if (!this._knownImagePaths.has(image)) { |         if (!this._knownImagePaths.has(image)) { | ||||||
|             if (this.doesPathExist === undefined) { |             if (this.doesPathExist === undefined) { | ||||||
|                 context.err( |                 context.err( | ||||||
|                     `Image with path ${image} not found or not attributed; it is used in ${context}` |                     `Image with path ${image} not found or not attributed; it is used in ${context}`, | ||||||
|                 ) |                 ) | ||||||
|             } else if (!this.doesPathExist(image)) { |             } else if (!this.doesPathExist(image)) { | ||||||
|                 context.err( |                 context.err( | ||||||
|                     `Image with path ${image} does not exist.\n     Check for typo's and missing directories in the path.` |                     `Image with path ${image} does not exist.\n     Check for typo's and missing directories in the path.`, | ||||||
|                 ) |                 ) | ||||||
|             } else { |             } else { | ||||||
|                 context.err( |                 context.err( | ||||||
|                     `Image with path ${image} is not attributed (but it exists); execute 'npm run query:licenses' to add the license information and/or run 'npm run generate:licenses' to compile all the license info` |                     `Image with path ${image} is not attributed (but it exists); execute 'npm run query:licenses' to add the license information and/or run 'npm run generate:licenses' to compile all the license info`, | ||||||
|                 ) |                 ) | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  | @ -146,7 +146,7 @@ export class ValidateTheme extends DesugaringStep<LayoutConfigJson> { | ||||||
|         doesImageExist: DoesImageExist, |         doesImageExist: DoesImageExist, | ||||||
|         path: string, |         path: string, | ||||||
|         isBuiltin: boolean, |         isBuiltin: boolean, | ||||||
|         sharedTagRenderings?: Set<string> |         sharedTagRenderings?: Set<string>, | ||||||
|     ) { |     ) { | ||||||
|         super("Doesn't change anything, but emits warnings and errors", [], "ValidateTheme") |         super("Doesn't change anything, but emits warnings and errors", [], "ValidateTheme") | ||||||
|         this._validateImage = doesImageExist |         this._validateImage = doesImageExist | ||||||
|  | @ -165,15 +165,15 @@ export class ValidateTheme extends DesugaringStep<LayoutConfigJson> { | ||||||
|                 if (json["units"] !== undefined) { |                 if (json["units"] !== undefined) { | ||||||
|                     context.err( |                     context.err( | ||||||
|                         "The theme " + |                         "The theme " + | ||||||
|                             json.id + |                         json.id + | ||||||
|                             " has units defined - these should be defined on the layer instead. (Hint: use overrideAll: { '+units': ... }) " |                         " has units defined - these should be defined on the layer instead. (Hint: use overrideAll: { '+units': ... }) ", | ||||||
|                     ) |                     ) | ||||||
|                 } |                 } | ||||||
|                 if (json["roamingRenderings"] !== undefined) { |                 if (json["roamingRenderings"] !== undefined) { | ||||||
|                     context.err( |                     context.err( | ||||||
|                         "Theme " + |                         "Theme " + | ||||||
|                             json.id + |                         json.id + | ||||||
|                             " contains an old 'roamingRenderings'. Use an 'overrideAll' instead" |                         " contains an old 'roamingRenderings'. Use an 'overrideAll' instead", | ||||||
|                     ) |                     ) | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|  | @ -191,10 +191,10 @@ export class ValidateTheme extends DesugaringStep<LayoutConfigJson> { | ||||||
|             for (const remoteImage of remoteImages) { |             for (const remoteImage of remoteImages) { | ||||||
|                 context.err( |                 context.err( | ||||||
|                     "Found a remote image: " + |                     "Found a remote image: " + | ||||||
|                         remoteImage.path + |                     remoteImage.path + | ||||||
|                         " in theme " + |                     " in theme " + | ||||||
|                         json.id + |                     json.id + | ||||||
|                         ", please download it." |                     ", please download it.", | ||||||
|                 ) |                 ) | ||||||
|             } |             } | ||||||
|             for (const image of images) { |             for (const image of images) { | ||||||
|  | @ -210,17 +210,17 @@ export class ValidateTheme extends DesugaringStep<LayoutConfigJson> { | ||||||
| 
 | 
 | ||||||
|                 const filename = this._path.substring( |                 const filename = this._path.substring( | ||||||
|                     this._path.lastIndexOf("/") + 1, |                     this._path.lastIndexOf("/") + 1, | ||||||
|                     this._path.length - 5 |                     this._path.length - 5, | ||||||
|                 ) |                 ) | ||||||
|                 if (theme.id !== filename) { |                 if (theme.id !== filename) { | ||||||
|                     context.err( |                     context.err( | ||||||
|                         "Theme ids should be the same as the name.json, but we got id: " + |                         "Theme ids should be the same as the name.json, but we got id: " + | ||||||
|                             theme.id + |                         theme.id + | ||||||
|                             " and filename " + |                         " and filename " + | ||||||
|                             filename + |                         filename + | ||||||
|                             " (" + |                         " (" + | ||||||
|                             this._path + |                         this._path + | ||||||
|                             ")" |                         ")", | ||||||
|                     ) |                     ) | ||||||
|                 } |                 } | ||||||
|                 this._validateImage.convert(theme.icon, context.enter("icon")) |                 this._validateImage.convert(theme.icon, context.enter("icon")) | ||||||
|  | @ -228,13 +228,13 @@ export class ValidateTheme extends DesugaringStep<LayoutConfigJson> { | ||||||
|             const dups = Utils.Duplicates(json.layers.map((layer) => layer["id"])) |             const dups = Utils.Duplicates(json.layers.map((layer) => layer["id"])) | ||||||
|             if (dups.length > 0) { |             if (dups.length > 0) { | ||||||
|                 context.err( |                 context.err( | ||||||
|                     `The theme ${json.id} defines multiple layers with id ${dups.join(", ")}` |                     `The theme ${json.id} defines multiple layers with id ${dups.join(", ")}`, | ||||||
|                 ) |                 ) | ||||||
|             } |             } | ||||||
|             if (json["mustHaveLanguage"] !== undefined) { |             if (json["mustHaveLanguage"] !== undefined) { | ||||||
|                 new ValidateLanguageCompleteness(...json["mustHaveLanguage"]).convert( |                 new ValidateLanguageCompleteness(...json["mustHaveLanguage"]).convert( | ||||||
|                     theme, |                     theme, | ||||||
|                     context |                     context, | ||||||
|                 ) |                 ) | ||||||
|             } |             } | ||||||
|             if (!json.hideFromOverview && theme.id !== "personal" && this._isBuiltin) { |             if (!json.hideFromOverview && theme.id !== "personal" && this._isBuiltin) { | ||||||
|  | @ -242,7 +242,7 @@ export class ValidateTheme extends DesugaringStep<LayoutConfigJson> { | ||||||
|                 const targetLanguage = theme.title.SupportedLanguages()[0] |                 const targetLanguage = theme.title.SupportedLanguages()[0] | ||||||
|                 if (targetLanguage !== "en") { |                 if (targetLanguage !== "en") { | ||||||
|                     context.err( |                     context.err( | ||||||
|                         `TargetLanguage is not 'en' for public theme ${theme.id}, it is ${targetLanguage}. Move 'en' up in the title of the theme and set it as the first key` |                         `TargetLanguage is not 'en' for public theme ${theme.id}, it is ${targetLanguage}. Move 'en' up in the title of the theme and set it as the first key`, | ||||||
|                     ) |                     ) | ||||||
|                 } |                 } | ||||||
| 
 | 
 | ||||||
|  | @ -286,7 +286,7 @@ export class ValidateTheme extends DesugaringStep<LayoutConfigJson> { | ||||||
|                     .err( |                     .err( | ||||||
|                         `This layer ID is not known: ${backgroundId}. Perhaps you meant one of ${nearby |                         `This layer ID is not known: ${backgroundId}. Perhaps you meant one of ${nearby | ||||||
|                             .slice(0, 5) |                             .slice(0, 5) | ||||||
|                             .join(", ")}` |                             .join(", ")}`,
 | ||||||
|                     ) |                     ) | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  | @ -309,7 +309,7 @@ export class ValidateThemeAndLayers extends Fuse<LayoutConfigJson> { | ||||||
|         doesImageExist: DoesImageExist, |         doesImageExist: DoesImageExist, | ||||||
|         path: string, |         path: string, | ||||||
|         isBuiltin: boolean, |         isBuiltin: boolean, | ||||||
|         sharedTagRenderings?: Set<string> |         sharedTagRenderings?: Set<string>, | ||||||
|     ) { |     ) { | ||||||
|         super( |         super( | ||||||
|             "Validates a theme and the contained layers", |             "Validates a theme and the contained layers", | ||||||
|  | @ -319,10 +319,10 @@ export class ValidateThemeAndLayers extends Fuse<LayoutConfigJson> { | ||||||
|                 new Each( |                 new Each( | ||||||
|                     new Bypass( |                     new Bypass( | ||||||
|                         (layer) => Constants.added_by_default.indexOf(<any>layer.id) < 0, |                         (layer) => Constants.added_by_default.indexOf(<any>layer.id) < 0, | ||||||
|                         new ValidateLayerConfig(undefined, isBuiltin, doesImageExist, false, true) |                         new ValidateLayerConfig(undefined, isBuiltin, doesImageExist, false, true), | ||||||
|                     ) |                     ), | ||||||
|                 ) |                 ), | ||||||
|             ) |             ), | ||||||
|         ) |         ) | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | @ -332,7 +332,7 @@ class OverrideShadowingCheck extends DesugaringStep<LayoutConfigJson> { | ||||||
|         super( |         super( | ||||||
|             "Checks that an 'overrideAll' does not override a single override", |             "Checks that an 'overrideAll' does not override a single override", | ||||||
|             [], |             [], | ||||||
|             "OverrideShadowingCheck" |             "OverrideShadowingCheck", | ||||||
|         ) |         ) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | @ -378,6 +378,9 @@ class MiscThemeChecks extends DesugaringStep<LayoutConfigJson> { | ||||||
|         if (json.id !== "personal" && (json.layers === undefined || json.layers.length === 0)) { |         if (json.id !== "personal" && (json.layers === undefined || json.layers.length === 0)) { | ||||||
|             context.err("The theme " + json.id + " has no 'layers' defined") |             context.err("The theme " + json.id + " has no 'layers' defined") | ||||||
|         } |         } | ||||||
|  |         if (!Array.isArray(json.layers)) { | ||||||
|  |             context.enter("layers").err("The 'layers'-field should be an array, but it is not. Did you pase a layer identifier and forget to add the '[' and ']'?") | ||||||
|  |         } | ||||||
|         if (json.socialImage === "") { |         if (json.socialImage === "") { | ||||||
|             context.warn("Social image for theme " + json.id + " is the emtpy string") |             context.warn("Social image for theme " + json.id + " is the emtpy string") | ||||||
|         } |         } | ||||||
|  | @ -406,7 +409,7 @@ class MiscThemeChecks extends DesugaringStep<LayoutConfigJson> { | ||||||
|             context |             context | ||||||
|                 .enter("overideAll") |                 .enter("overideAll") | ||||||
|                 .err( |                 .err( | ||||||
|                     "'overrideAll' is spelled with _two_ `r`s. You only wrote a single one of them." |                     "'overrideAll' is spelled with _two_ `r`s. You only wrote a single one of them.", | ||||||
|                 ) |                 ) | ||||||
|         } |         } | ||||||
|         return json |         return json | ||||||
|  | @ -418,7 +421,7 @@ export class PrevalidateTheme extends Fuse<LayoutConfigJson> { | ||||||
|         super( |         super( | ||||||
|             "Various consistency checks on the raw JSON", |             "Various consistency checks on the raw JSON", | ||||||
|             new MiscThemeChecks(), |             new MiscThemeChecks(), | ||||||
|             new OverrideShadowingCheck() |             new OverrideShadowingCheck(), | ||||||
|         ) |         ) | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | @ -428,7 +431,7 @@ export class DetectConflictingAddExtraTags extends DesugaringStep<TagRenderingCo | ||||||
|         super( |         super( | ||||||
|             "The `if`-part in a mapping might set some keys. Those keys are not allowed to be set in the `addExtraTags`, as this might result in conflicting values", |             "The `if`-part in a mapping might set some keys. Those keys are not allowed to be set in the `addExtraTags`, as this might result in conflicting values", | ||||||
|             [], |             [], | ||||||
|             "DetectConflictingAddExtraTags" |             "DetectConflictingAddExtraTags", | ||||||
|         ) |         ) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | @ -455,7 +458,7 @@ export class DetectConflictingAddExtraTags extends DesugaringStep<TagRenderingCo | ||||||
|                         .enters("mappings", i) |                         .enters("mappings", i) | ||||||
|                         .err( |                         .err( | ||||||
|                             "AddExtraTags overrides a key that is set in the `if`-clause of this mapping. Selecting this answer might thus first set one value (needed to match as answer) and then override it with a different value, resulting in an unsaveable question. The offending `addExtraTags` is " + |                             "AddExtraTags overrides a key that is set in the `if`-clause of this mapping. Selecting this answer might thus first set one value (needed to match as answer) and then override it with a different value, resulting in an unsaveable question. The offending `addExtraTags` is " + | ||||||
|                                 duplicateKeys.join(", ") |                             duplicateKeys.join(", "), | ||||||
|                         ) |                         ) | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|  | @ -473,13 +476,13 @@ export class DetectNonErasedKeysInMappings extends DesugaringStep<QuestionableTa | ||||||
|         super( |         super( | ||||||
|             "A tagRendering might set a freeform key (e.g. `name` and have an option that _should_ erase this name, e.g. `noname=yes`). Under normal circumstances, every mapping/freeform should affect all touched keys", |             "A tagRendering might set a freeform key (e.g. `name` and have an option that _should_ erase this name, e.g. `noname=yes`). Under normal circumstances, every mapping/freeform should affect all touched keys", | ||||||
|             [], |             [], | ||||||
|             "DetectNonErasedKeysInMappings" |             "DetectNonErasedKeysInMappings", | ||||||
|         ) |         ) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     convert( |     convert( | ||||||
|         json: QuestionableTagRenderingConfigJson, |         json: QuestionableTagRenderingConfigJson, | ||||||
|         context: ConversionContext |         context: ConversionContext, | ||||||
|     ): QuestionableTagRenderingConfigJson { |     ): QuestionableTagRenderingConfigJson { | ||||||
|         if (json.multiAnswer) { |         if (json.multiAnswer) { | ||||||
|             // No need to check this here, this has its own validation
 |             // No need to check this here, this has its own validation
 | ||||||
|  | @ -533,8 +536,8 @@ export class DetectNonErasedKeysInMappings extends DesugaringStep<QuestionableTa | ||||||
|                         .enters("freeform") |                         .enters("freeform") | ||||||
|                         .warn( |                         .warn( | ||||||
|                             "The freeform block does not modify the key `" + |                             "The freeform block does not modify the key `" + | ||||||
|                                 neededKey + |                             neededKey + | ||||||
|                                 "` which is set in a mapping. Use `addExtraTags` to overwrite it" |                             "` which is set in a mapping. Use `addExtraTags` to overwrite it", | ||||||
|                         ) |                         ) | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|  | @ -552,8 +555,8 @@ export class DetectNonErasedKeysInMappings extends DesugaringStep<QuestionableTa | ||||||
|                         .enters("mappings", i) |                         .enters("mappings", i) | ||||||
|                         .warn( |                         .warn( | ||||||
|                             "This mapping does not modify the key `" + |                             "This mapping does not modify the key `" + | ||||||
|                                 neededKey + |                             neededKey + | ||||||
|                                 "` which is set in a mapping or by the freeform block. Use `addExtraTags` to overwrite it" |                             "` which is set in a mapping or by the freeform block. Use `addExtraTags` to overwrite it", | ||||||
|                         ) |                         ) | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|  | @ -570,7 +573,7 @@ export class DetectMappingsShadowedByCondition extends DesugaringStep<TagRenderi | ||||||
|         super( |         super( | ||||||
|             "Checks that, if the tagrendering has a condition, that a mapping is not contradictory to it, i.e. that there are no dead mappings", |             "Checks that, if the tagrendering has a condition, that a mapping is not contradictory to it, i.e. that there are no dead mappings", | ||||||
|             [], |             [], | ||||||
|             "DetectMappingsShadowedByCondition" |             "DetectMappingsShadowedByCondition", | ||||||
|         ) |         ) | ||||||
|         this._forceError = forceError |         this._forceError = forceError | ||||||
|     } |     } | ||||||
|  | @ -642,7 +645,7 @@ export class DetectShadowedMappings extends DesugaringStep<TagRenderingConfigJso | ||||||
|      * DetectShadowedMappings.extractCalculatedTagNames({calculatedTags: ["_abc=js()"]}) // => ["_abc"]
 |      * DetectShadowedMappings.extractCalculatedTagNames({calculatedTags: ["_abc=js()"]}) // => ["_abc"]
 | ||||||
|      */ |      */ | ||||||
|     private static extractCalculatedTagNames( |     private static extractCalculatedTagNames( | ||||||
|         layerConfig?: LayerConfigJson | { calculatedTags: string[] } |         layerConfig?: LayerConfigJson | { calculatedTags: string[] }, | ||||||
|     ) { |     ) { | ||||||
|         return ( |         return ( | ||||||
|             layerConfig?.calculatedTags?.map((ct) => { |             layerConfig?.calculatedTags?.map((ct) => { | ||||||
|  | @ -728,16 +731,16 @@ export class DetectShadowedMappings extends DesugaringStep<TagRenderingConfigJso | ||||||
|                     json.mappings[i]["hideInAnswer"] !== true |                     json.mappings[i]["hideInAnswer"] !== true | ||||||
|                 ) { |                 ) { | ||||||
|                     context.warn( |                     context.warn( | ||||||
|                         `Mapping ${i} is shadowed by mapping ${j}. However, mapping ${j} has 'hideInAnswer' set, which will result in a different rendering in question-mode.` |                         `Mapping ${i} is shadowed by mapping ${j}. However, mapping ${j} has 'hideInAnswer' set, which will result in a different rendering in question-mode.`, | ||||||
|                     ) |                     ) | ||||||
|                 } else if (doesMatch) { |                 } else if (doesMatch) { | ||||||
|                     // The current mapping is shadowed!
 |                     // The current mapping is shadowed!
 | ||||||
|                     context.err(`Mapping ${i} is shadowed by mapping ${j} and will thus never be shown:
 |                     context.err(`Mapping ${i} is shadowed by mapping ${j} and will thus never be shown:
 | ||||||
|     The mapping ${parsedConditions[i].asHumanString( |     The mapping ${parsedConditions[i].asHumanString( | ||||||
|         false, |                         false, | ||||||
|         false, |                         false, | ||||||
|         {} |                         {}, | ||||||
|     )} is fully matched by a previous mapping (namely ${j}), which matches: |                     )} is fully matched by a previous mapping (namely ${j}), which matches: | ||||||
|     ${parsedConditions[j].asHumanString(false, false, {})}. |     ${parsedConditions[j].asHumanString(false, false, {})}. | ||||||
| 
 | 
 | ||||||
|     To fix this problem, you can try to: |     To fix this problem, you can try to: | ||||||
|  | @ -764,7 +767,7 @@ export class DetectMappingsWithImages extends DesugaringStep<TagRenderingConfigJ | ||||||
|         super( |         super( | ||||||
|             "Checks that 'then'clauses in mappings don't have images, but use 'icon' instead", |             "Checks that 'then'clauses in mappings don't have images, but use 'icon' instead", | ||||||
|             [], |             [], | ||||||
|             "DetectMappingsWithImages" |             "DetectMappingsWithImages", | ||||||
|         ) |         ) | ||||||
|         this._doesImageExist = doesImageExist |         this._doesImageExist = doesImageExist | ||||||
|     } |     } | ||||||
|  | @ -804,14 +807,14 @@ export class DetectMappingsWithImages extends DesugaringStep<TagRenderingConfigJ | ||||||
|                 if (!ignore) { |                 if (!ignore) { | ||||||
|                     ctx.err( |                     ctx.err( | ||||||
|                         `A mapping has an image in the 'then'-clause. Remove the image there and use \`"icon": <your-image>\` instead. The images found are ${images.join( |                         `A mapping has an image in the 'then'-clause. Remove the image there and use \`"icon": <your-image>\` instead. The images found are ${images.join( | ||||||
|                             ", " |                             ", ", | ||||||
|                         )}. (This check can be turned of by adding "#": "${ignoreToken}" in the mapping, but this is discouraged` |                         )}. (This check can be turned of by adding "#": "${ignoreToken}" in the mapping, but this is discouraged`,
 | ||||||
|                     ) |                     ) | ||||||
|                 } else { |                 } else { | ||||||
|                     ctx.info( |                     ctx.info( | ||||||
|                         `Ignored image ${images.join( |                         `Ignored image ${images.join( | ||||||
|                             ", " |                             ", ", | ||||||
|                         )} in 'then'-clause of a mapping as this check has been disabled` |                         )} in 'then'-clause of a mapping as this check has been disabled`,
 | ||||||
|                     ) |                     ) | ||||||
| 
 | 
 | ||||||
|                     for (const image of images) { |                     for (const image of images) { | ||||||
|  | @ -832,7 +835,7 @@ class ValidatePossibleLinks extends DesugaringStep<string | Record<string, strin | ||||||
|         super( |         super( | ||||||
|             "Given a possible set of translations, validates that <a href=... target='_blank'> does have `rel='noopener'` set", |             "Given a possible set of translations, validates that <a href=... target='_blank'> does have `rel='noopener'` set", | ||||||
|             [], |             [], | ||||||
|             "ValidatePossibleLinks" |             "ValidatePossibleLinks", | ||||||
|         ) |         ) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | @ -862,21 +865,21 @@ class ValidatePossibleLinks extends DesugaringStep<string | Record<string, strin | ||||||
| 
 | 
 | ||||||
|     convert( |     convert( | ||||||
|         json: string | Record<string, string>, |         json: string | Record<string, string>, | ||||||
|         context: ConversionContext |         context: ConversionContext, | ||||||
|     ): string | Record<string, string> { |     ): string | Record<string, string> { | ||||||
|         if (typeof json === "string") { |         if (typeof json === "string") { | ||||||
|             if (this.isTabnabbingProne(json)) { |             if (this.isTabnabbingProne(json)) { | ||||||
|                 context.err( |                 context.err( | ||||||
|                     "The string " + |                     "The string " + | ||||||
|                         json + |                     json + | ||||||
|                         " has a link targeting `_blank`, but it doesn't have `rel='noopener'` set. This gives rise to reverse tabnapping" |                     " has a link targeting `_blank`, but it doesn't have `rel='noopener'` set. This gives rise to reverse tabnapping", | ||||||
|                 ) |                 ) | ||||||
|             } |             } | ||||||
|         } else { |         } else { | ||||||
|             for (const k in json) { |             for (const k in json) { | ||||||
|                 if (this.isTabnabbingProne(json[k])) { |                 if (this.isTabnabbingProne(json[k])) { | ||||||
|                     context.err( |                     context.err( | ||||||
|                         `The translation for ${k} '${json[k]}' has a link targeting \`_blank\`, but it doesn't have \`rel='noopener'\` set. This gives rise to reverse tabnapping` |                         `The translation for ${k} '${json[k]}' has a link targeting \`_blank\`, but it doesn't have \`rel='noopener'\` set. This gives rise to reverse tabnapping`, | ||||||
|                     ) |                     ) | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|  | @ -894,7 +897,7 @@ class CheckTranslation extends DesugaringStep<Translatable> { | ||||||
|         super( |         super( | ||||||
|             "Checks that a translation is valid and internally consistent", |             "Checks that a translation is valid and internally consistent", | ||||||
|             ["*"], |             ["*"], | ||||||
|             "CheckTranslation" |             "CheckTranslation", | ||||||
|         ) |         ) | ||||||
|         this._allowUndefined = allowUndefined |         this._allowUndefined = allowUndefined | ||||||
|     } |     } | ||||||
|  | @ -935,6 +938,7 @@ class CheckTranslation extends DesugaringStep<Translatable> { | ||||||
| 
 | 
 | ||||||
| class MiscTagRenderingChecks extends DesugaringStep<TagRenderingConfigJson> { | class MiscTagRenderingChecks extends DesugaringStep<TagRenderingConfigJson> { | ||||||
|     private readonly _layerConfig: LayerConfigJson |     private readonly _layerConfig: LayerConfigJson | ||||||
|  | 
 | ||||||
|     constructor(layerConfig?: LayerConfigJson) { |     constructor(layerConfig?: LayerConfigJson) { | ||||||
|         super("Miscellaneous checks on the tagrendering", ["special"], "MiscTagRenderingChecks") |         super("Miscellaneous checks on the tagrendering", ["special"], "MiscTagRenderingChecks") | ||||||
|         this._layerConfig = layerConfig |         this._layerConfig = layerConfig | ||||||
|  | @ -942,17 +946,17 @@ class MiscTagRenderingChecks extends DesugaringStep<TagRenderingConfigJson> { | ||||||
| 
 | 
 | ||||||
|     convert( |     convert( | ||||||
|         json: TagRenderingConfigJson | QuestionableTagRenderingConfigJson, |         json: TagRenderingConfigJson | QuestionableTagRenderingConfigJson, | ||||||
|         context: ConversionContext |         context: ConversionContext, | ||||||
|     ): TagRenderingConfigJson { |     ): TagRenderingConfigJson { | ||||||
|         if (json["special"] !== undefined) { |         if (json["special"] !== undefined) { | ||||||
|             context.err( |             context.err( | ||||||
|                 'Detected `special` on the top level. Did you mean `{"render":{ "special": ... }}`' |                 "Detected `special` on the top level. Did you mean `{\"render\":{ \"special\": ... }}`", | ||||||
|             ) |             ) | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         if (Object.keys(json).length === 1 && typeof json["render"] === "string") { |         if (Object.keys(json).length === 1 && typeof json["render"] === "string") { | ||||||
|             context.warn( |             context.warn( | ||||||
|                 `use the content directly instead of {render: ${JSON.stringify(json["render"])}}` |                 `use the content directly instead of {render: ${JSON.stringify(json["render"])}}`, | ||||||
|             ) |             ) | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  | @ -964,7 +968,7 @@ class MiscTagRenderingChecks extends DesugaringStep<TagRenderingConfigJson> { | ||||||
|                 const mapping: MappingConfigJson = json.mappings[i] |                 const mapping: MappingConfigJson = json.mappings[i] | ||||||
|                 CheckTranslation.noUndefined.convert( |                 CheckTranslation.noUndefined.convert( | ||||||
|                     mapping.then, |                     mapping.then, | ||||||
|                     context.enters("mappings", i, "then") |                     context.enters("mappings", i, "then"), | ||||||
|                 ) |                 ) | ||||||
|                 if (!mapping.if) { |                 if (!mapping.if) { | ||||||
|                     console.log( |                     console.log( | ||||||
|  | @ -973,7 +977,7 @@ class MiscTagRenderingChecks extends DesugaringStep<TagRenderingConfigJson> { | ||||||
|                         "if", |                         "if", | ||||||
|                         mapping.if, |                         mapping.if, | ||||||
|                         context.path.join("."), |                         context.path.join("."), | ||||||
|                         mapping.then |                         mapping.then, | ||||||
|                     ) |                     ) | ||||||
|                     context.enters("mappings", i, "if").err("No `if` is defined") |                     context.enters("mappings", i, "if").err("No `if` is defined") | ||||||
|                 } |                 } | ||||||
|  | @ -983,7 +987,7 @@ class MiscTagRenderingChecks extends DesugaringStep<TagRenderingConfigJson> { | ||||||
|                             context |                             context | ||||||
|                                 .enters("mappings", i, "addExtraTags", j) |                                 .enters("mappings", i, "addExtraTags", j) | ||||||
|                                 .err( |                                 .err( | ||||||
|                                     "Detected a 'null' or 'undefined' value. Either specify a tag or delete this item" |                                     "Detected a 'null' or 'undefined' value. Either specify a tag or delete this item", | ||||||
|                                 ) |                                 ) | ||||||
|                         } |                         } | ||||||
|                     } |                     } | ||||||
|  | @ -994,18 +998,18 @@ class MiscTagRenderingChecks extends DesugaringStep<TagRenderingConfigJson> { | ||||||
|                     context |                     context | ||||||
|                         .enters("mappings", i, "then") |                         .enters("mappings", i, "then") | ||||||
|                         .warn( |                         .warn( | ||||||
|                             "A mapping should not start with 'yes' or 'no'. If the attribute is known, it will only show 'yes' or 'no' <i>without</i> the question, resulting in a weird phrasing in the information box" |                             "A mapping should not start with 'yes' or 'no'. If the attribute is known, it will only show 'yes' or 'no' <i>without</i> the question, resulting in a weird phrasing in the information box", | ||||||
|                         ) |                         ) | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|         if (json["group"]) { |         if (json["group"]) { | ||||||
|             context.err('Groups are deprecated, use `"label": ["' + json["group"] + '"]` instead') |             context.err("Groups are deprecated, use `\"label\": [\"" + json["group"] + "\"]` instead") | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         if (json["question"] && json.freeform?.key === undefined && json.mappings === undefined) { |         if (json["question"] && json.freeform?.key === undefined && json.mappings === undefined) { | ||||||
|             context.err( |             context.err( | ||||||
|                 "A question is defined, but no mappings nor freeform (key) are. Add at least one of them" |                 "A question is defined, but no mappings nor freeform (key) are. Add at least one of them", | ||||||
|             ) |             ) | ||||||
|         } |         } | ||||||
|         if (json["question"] && !json.freeform && (json.mappings?.length ?? 0) == 1) { |         if (json["question"] && !json.freeform && (json.mappings?.length ?? 0) == 1) { | ||||||
|  | @ -1015,7 +1019,7 @@ class MiscTagRenderingChecks extends DesugaringStep<TagRenderingConfigJson> { | ||||||
|             context |             context | ||||||
|                 .enter("questionHint") |                 .enter("questionHint") | ||||||
|                 .err( |                 .err( | ||||||
|                     "A questionHint is defined, but no question is given. As such, the questionHint will never be shown" |                     "A questionHint is defined, but no question is given. As such, the questionHint will never be shown", | ||||||
|                 ) |                 ) | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  | @ -1023,7 +1027,7 @@ class MiscTagRenderingChecks extends DesugaringStep<TagRenderingConfigJson> { | ||||||
|             context |             context | ||||||
|                 .enters("icon", "size") |                 .enters("icon", "size") | ||||||
|                 .err( |                 .err( | ||||||
|                     "size is not a valid attribute. Did you mean 'class'? Class can be one of `small`, `medium` or `large`" |                     "size is not a valid attribute. Did you mean 'class'? Class can be one of `small`, `medium` or `large`", | ||||||
|                 ) |                 ) | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  | @ -1033,10 +1037,10 @@ class MiscTagRenderingChecks extends DesugaringStep<TagRenderingConfigJson> { | ||||||
|                     .enter("render") |                     .enter("render") | ||||||
|                     .err( |                     .err( | ||||||
|                         "This tagRendering allows to set a value to key " + |                         "This tagRendering allows to set a value to key " + | ||||||
|                             json.freeform.key + |                         json.freeform.key + | ||||||
|                             ", but does not define a `render`. Please, add a value here which contains `{" + |                         ", but does not define a `render`. Please, add a value here which contains `{" + | ||||||
|                             json.freeform.key + |                         json.freeform.key + | ||||||
|                             "}`" |                         "}`", | ||||||
|                     ) |                     ) | ||||||
|             } else { |             } else { | ||||||
|                 const render = new Translation(<any>json.render) |                 const render = new Translation(<any>json.render) | ||||||
|  | @ -1067,7 +1071,7 @@ class MiscTagRenderingChecks extends DesugaringStep<TagRenderingConfigJson> { | ||||||
|                     const keyFirstArg = ["canonical", "fediverse_link", "translated"] |                     const keyFirstArg = ["canonical", "fediverse_link", "translated"] | ||||||
|                     if ( |                     if ( | ||||||
|                         keyFirstArg.some( |                         keyFirstArg.some( | ||||||
|                             (funcName) => txt.indexOf(`{${funcName}(${json.freeform.key}`) >= 0 |                             (funcName) => txt.indexOf(`{${funcName}(${json.freeform.key}`) >= 0, | ||||||
|                         ) |                         ) | ||||||
|                     ) { |                     ) { | ||||||
|                         continue |                         continue | ||||||
|  | @ -1091,7 +1095,7 @@ class MiscTagRenderingChecks extends DesugaringStep<TagRenderingConfigJson> { | ||||||
|                         context |                         context | ||||||
|                             .enter("render") |                             .enter("render") | ||||||
|                             .err( |                             .err( | ||||||
|                                 `The rendering for language ${ln} does not contain \`{${json.freeform.key}}\`. Did you perhaps forget to set "freeform.type: 'wikidata'"?` |                                 `The rendering for language ${ln} does not contain \`{${json.freeform.key}}\`. Did you perhaps forget to set "freeform.type: 'wikidata'"?`, | ||||||
|                             ) |                             ) | ||||||
|                         continue |                         continue | ||||||
|                     } |                     } | ||||||
|  | @ -1103,7 +1107,7 @@ class MiscTagRenderingChecks extends DesugaringStep<TagRenderingConfigJson> { | ||||||
|                         context |                         context | ||||||
|                             .enter("render") |                             .enter("render") | ||||||
|                             .err( |                             .err( | ||||||
|                                 `The rendering for language ${ln} does not contain \`{${json.freeform.key}}\`. However, it does contain ${json.freeform.key} without braces. Did you forget the braces?\n\tThe current text is ${txt}` |                                 `The rendering for language ${ln} does not contain \`{${json.freeform.key}}\`. However, it does contain ${json.freeform.key} without braces. Did you forget the braces?\n\tThe current text is ${txt}`, | ||||||
|                             ) |                             ) | ||||||
|                         continue |                         continue | ||||||
|                     } |                     } | ||||||
|  | @ -1111,7 +1115,7 @@ class MiscTagRenderingChecks extends DesugaringStep<TagRenderingConfigJson> { | ||||||
|                     context |                     context | ||||||
|                         .enter("render") |                         .enter("render") | ||||||
|                         .err( |                         .err( | ||||||
|                             `The rendering for language ${ln} does not contain \`{${json.freeform.key}}\`. This is a bug, as this rendering should show exactly this freeform key!\n\tThe current text is ${txt}` |                             `The rendering for language ${ln} does not contain \`{${json.freeform.key}}\`. This is a bug, as this rendering should show exactly this freeform key!\n\tThe current text is ${txt}`, | ||||||
|                         ) |                         ) | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|  | @ -1126,22 +1130,22 @@ class MiscTagRenderingChecks extends DesugaringStep<TagRenderingConfigJson> { | ||||||
|                         .enters("freeform", "type") |                         .enters("freeform", "type") | ||||||
|                         .err( |                         .err( | ||||||
|                             "No entry found in the 'Name Suggestion Index'. None of the 'osmSource'-tags match an entry in the NSI.\n\tOsmSource-tags are " + |                             "No entry found in the 'Name Suggestion Index'. None of the 'osmSource'-tags match an entry in the NSI.\n\tOsmSource-tags are " + | ||||||
|                                 tags.map((t) => new Tag(t.key, t.value).asHumanString()).join(" ; ") |                             tags.map((t) => new Tag(t.key, t.value).asHumanString()).join(" ; "), | ||||||
|                         ) |                         ) | ||||||
|                 } |                 } | ||||||
|             } else if (json.freeform.type === "nsi") { |             } else if (json.freeform.type === "nsi") { | ||||||
|                 context |                 context | ||||||
|                     .enters("freeform", "type") |                     .enters("freeform", "type") | ||||||
|                     .warn( |                     .warn( | ||||||
|                         "No need to explicitly set type to 'NSI', autodetected based on freeform type" |                         "No need to explicitly set type to 'NSI', autodetected based on freeform type", | ||||||
|                     ) |                     ) | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|         if (json.render && json["question"] && json.freeform === undefined) { |         if (json.render && json["question"] && json.freeform === undefined) { | ||||||
|             context.err( |             context.err( | ||||||
|                 `Detected a tagrendering which takes input without freeform key in ${context}; the question is ${new Translation( |                 `Detected a tagrendering which takes input without freeform key in ${context}; the question is ${new Translation( | ||||||
|                     json["question"] |                     json["question"], | ||||||
|                 ).textFor("en")}` |                 ).textFor("en")}`,
 | ||||||
|             ) |             ) | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  | @ -1152,9 +1156,9 @@ class MiscTagRenderingChecks extends DesugaringStep<TagRenderingConfigJson> { | ||||||
|                     .enters("freeform", "type") |                     .enters("freeform", "type") | ||||||
|                     .err( |                     .err( | ||||||
|                         "Unknown type: " + |                         "Unknown type: " + | ||||||
|                             freeformType + |                         freeformType + | ||||||
|                             "; try one of " + |                         "; try one of " + | ||||||
|                             Validators.availableTypes.join(", ") |                         Validators.availableTypes.join(", "), | ||||||
|                     ) |                     ) | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  | @ -1192,7 +1196,7 @@ export class ValidateTagRenderings extends Fuse<TagRenderingConfigJson> { | ||||||
|             new On("question", new ValidatePossibleLinks()), |             new On("question", new ValidatePossibleLinks()), | ||||||
|             new On("questionHint", new ValidatePossibleLinks()), |             new On("questionHint", new ValidatePossibleLinks()), | ||||||
|             new On("mappings", new Each(new On("then", new ValidatePossibleLinks()))), |             new On("mappings", new Each(new On("then", new ValidatePossibleLinks()))), | ||||||
|             new MiscTagRenderingChecks(layerConfig) |             new MiscTagRenderingChecks(layerConfig), | ||||||
|         ) |         ) | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | @ -1211,7 +1215,7 @@ export class PrevalidateLayer extends DesugaringStep<LayerConfigJson> { | ||||||
|         path: string, |         path: string, | ||||||
|         isBuiltin: boolean, |         isBuiltin: boolean, | ||||||
|         doesImageExist: DoesImageExist, |         doesImageExist: DoesImageExist, | ||||||
|         studioValidations: boolean |         studioValidations: boolean, | ||||||
|     ) { |     ) { | ||||||
|         super("Runs various checks against common mistakes for a layer", [], "PrevalidateLayer") |         super("Runs various checks against common mistakes for a layer", [], "PrevalidateLayer") | ||||||
|         this._path = path |         this._path = path | ||||||
|  | @ -1237,7 +1241,7 @@ export class PrevalidateLayer extends DesugaringStep<LayerConfigJson> { | ||||||
|             context |             context | ||||||
|                 .enter("source") |                 .enter("source") | ||||||
|                 .err( |                 .err( | ||||||
|                     "No source section is defined; please define one as data is not loaded otherwise" |                     "No source section is defined; please define one as data is not loaded otherwise", | ||||||
|                 ) |                 ) | ||||||
|         } else { |         } else { | ||||||
|             if (json.source === "special" || json.source === "special:library") { |             if (json.source === "special" || json.source === "special:library") { | ||||||
|  | @ -1245,7 +1249,7 @@ export class PrevalidateLayer extends DesugaringStep<LayerConfigJson> { | ||||||
|                 context |                 context | ||||||
|                     .enters("source", "osmTags") |                     .enters("source", "osmTags") | ||||||
|                     .err( |                     .err( | ||||||
|                         "No osmTags defined in the source section - these should always be present, even for geojson layer" |                         "No osmTags defined in the source section - these should always be present, even for geojson layer", | ||||||
|                     ) |                     ) | ||||||
|             } else { |             } else { | ||||||
|                 const osmTags = TagUtils.Tag(json.source["osmTags"], context + "source.osmTags") |                 const osmTags = TagUtils.Tag(json.source["osmTags"], context + "source.osmTags") | ||||||
|  | @ -1254,7 +1258,7 @@ export class PrevalidateLayer extends DesugaringStep<LayerConfigJson> { | ||||||
|                         .enters("source", "osmTags") |                         .enters("source", "osmTags") | ||||||
|                         .err( |                         .err( | ||||||
|                             "The source states tags which give a very wide selection: it only uses negative expressions, which will result in too much and unexpected data. Add at least one required tag. The tags are:\n\t" + |                             "The source states tags which give a very wide selection: it only uses negative expressions, which will result in too much and unexpected data. Add at least one required tag. The tags are:\n\t" + | ||||||
|                                 osmTags.asHumanString(false, false, {}) |                             osmTags.asHumanString(false, false, {}), | ||||||
|                         ) |                         ) | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|  | @ -1280,10 +1284,10 @@ export class PrevalidateLayer extends DesugaringStep<LayerConfigJson> { | ||||||
|                 .enter("syncSelection") |                 .enter("syncSelection") | ||||||
|                 .err( |                 .err( | ||||||
|                     "Invalid sync-selection: must be one of " + |                     "Invalid sync-selection: must be one of " + | ||||||
|                         LayerConfig.syncSelectionAllowed.map((v) => `'${v}'`).join(", ") + |                     LayerConfig.syncSelectionAllowed.map((v) => `'${v}'`).join(", ") + | ||||||
|                         " but got '" + |                     " but got '" + | ||||||
|                         json.syncSelection + |                     json.syncSelection + | ||||||
|                         "'" |                     "'", | ||||||
|                 ) |                 ) | ||||||
|         } |         } | ||||||
|         if (json["pointRenderings"]?.length > 0) { |         if (json["pointRenderings"]?.length > 0) { | ||||||
|  | @ -1302,7 +1306,7 @@ export class PrevalidateLayer extends DesugaringStep<LayerConfigJson> { | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         json.pointRendering?.forEach((pr, i) => |         json.pointRendering?.forEach((pr, i) => | ||||||
|             this._validatePointRendering.convert(pr, context.enters("pointeRendering", i)) |             this._validatePointRendering.convert(pr, context.enters("pointeRendering", i)), | ||||||
|         ) |         ) | ||||||
| 
 | 
 | ||||||
|         if (json["mapRendering"]) { |         if (json["mapRendering"]) { | ||||||
|  | @ -1319,8 +1323,8 @@ export class PrevalidateLayer extends DesugaringStep<LayerConfigJson> { | ||||||
|             if (!Constants.priviliged_layers.find((x) => x == json.id)) { |             if (!Constants.priviliged_layers.find((x) => x == json.id)) { | ||||||
|                 context.err( |                 context.err( | ||||||
|                     "Layer " + |                     "Layer " + | ||||||
|                         json.id + |                     json.id + | ||||||
|                         " uses 'special' as source.osmTags. However, this layer is not a priviliged layer" |                     " uses 'special' as source.osmTags. However, this layer is not a priviliged layer", | ||||||
|                 ) |                 ) | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  | @ -1335,19 +1339,19 @@ export class PrevalidateLayer extends DesugaringStep<LayerConfigJson> { | ||||||
|                 context |                 context | ||||||
|                     .enter("title") |                     .enter("title") | ||||||
|                     .err( |                     .err( | ||||||
|                         "This layer does not have a title defined but it does have tagRenderings. Not having a title will disable the popups, resulting in an unclickable element. Please add a title. If not having a popup is intended and the tagrenderings need to be kept (e.g. in a library layer), set `title: null` to disable this error." |                         "This layer does not have a title defined but it does have tagRenderings. Not having a title will disable the popups, resulting in an unclickable element. Please add a title. If not having a popup is intended and the tagrenderings need to be kept (e.g. in a library layer), set `title: null` to disable this error.", | ||||||
|                     ) |                     ) | ||||||
|             } |             } | ||||||
|             if (json.title === null) { |             if (json.title === null) { | ||||||
|                 context.info( |                 context.info( | ||||||
|                     "Title is `null`. This results in an element that cannot be clicked - even though tagRenderings is set." |                     "Title is `null`. This results in an element that cannot be clicked - even though tagRenderings is set.", | ||||||
|                 ) |                 ) | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             { |             { | ||||||
|                 // Check for multiple, identical builtin questions - usability for studio users
 |                 // Check for multiple, identical builtin questions - usability for studio users
 | ||||||
|                 const duplicates = Utils.Duplicates( |                 const duplicates = Utils.Duplicates( | ||||||
|                     <string[]>json.tagRenderings.filter((tr) => typeof tr === "string") |                     <string[]>json.tagRenderings.filter((tr) => typeof tr === "string"), | ||||||
|                 ) |                 ) | ||||||
|                 for (let i = 0; i < json.tagRenderings.length; i++) { |                 for (let i = 0; i < json.tagRenderings.length; i++) { | ||||||
|                     const tagRendering = json.tagRenderings[i] |                     const tagRendering = json.tagRenderings[i] | ||||||
|  | @ -1377,7 +1381,7 @@ export class PrevalidateLayer extends DesugaringStep<LayerConfigJson> { | ||||||
|         { |         { | ||||||
|             // duplicate ids in tagrenderings check
 |             // duplicate ids in tagrenderings check
 | ||||||
|             const duplicates = Utils.NoNull( |             const duplicates = Utils.NoNull( | ||||||
|                 Utils.Duplicates(Utils.NoNull((json.tagRenderings ?? []).map((tr) => tr["id"]))) |                 Utils.Duplicates(Utils.NoNull((json.tagRenderings ?? []).map((tr) => tr["id"]))), | ||||||
|             ) |             ) | ||||||
|             if (duplicates.length > 0) { |             if (duplicates.length > 0) { | ||||||
|                 // It is tempting to add an index to this warning; however, due to labels the indices here might be different from the index in the tagRendering list
 |                 // It is tempting to add an index to this warning; however, due to labels the indices here might be different from the index in the tagRendering list
 | ||||||
|  | @ -1385,11 +1389,11 @@ export class PrevalidateLayer extends DesugaringStep<LayerConfigJson> { | ||||||
|                     .enter("tagRenderings") |                     .enter("tagRenderings") | ||||||
|                     .err( |                     .err( | ||||||
|                         "Some tagrenderings have a duplicate id: " + |                         "Some tagrenderings have a duplicate id: " + | ||||||
|                             duplicates.join(", ") + |                         duplicates.join(", ") + | ||||||
|                             "\n" + |                         "\n" + | ||||||
|                             JSON.stringify( |                         JSON.stringify( | ||||||
|                                 json.tagRenderings.filter((tr) => duplicates.indexOf(tr["id"]) >= 0) |                             json.tagRenderings.filter((tr) => duplicates.indexOf(tr["id"]) >= 0), | ||||||
|                             ) |                         ), | ||||||
|                     ) |                     ) | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  | @ -1422,8 +1426,8 @@ export class PrevalidateLayer extends DesugaringStep<LayerConfigJson> { | ||||||
|             if (json["overpassTags"] !== undefined) { |             if (json["overpassTags"] !== undefined) { | ||||||
|                 context.err( |                 context.err( | ||||||
|                     "Layer " + |                     "Layer " + | ||||||
|                         json.id + |                     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)' |                     "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 = [ |             const forbiddenTopLevel = [ | ||||||
|  | @ -1443,7 +1447,7 @@ export class PrevalidateLayer extends DesugaringStep<LayerConfigJson> { | ||||||
|             } |             } | ||||||
|             if (json["hideUnderlayingFeaturesMinPercentage"] !== undefined) { |             if (json["hideUnderlayingFeaturesMinPercentage"] !== undefined) { | ||||||
|                 context.err( |                 context.err( | ||||||
|                     "Layer " + json.id + " contains an old 'hideUnderlayingFeaturesMinPercentage'" |                     "Layer " + json.id + " contains an old 'hideUnderlayingFeaturesMinPercentage'", | ||||||
|                 ) |                 ) | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|  | @ -1460,9 +1464,9 @@ export class PrevalidateLayer extends DesugaringStep<LayerConfigJson> { | ||||||
|             if (this._path != undefined && this._path.indexOf(expected) < 0) { |             if (this._path != undefined && this._path.indexOf(expected) < 0) { | ||||||
|                 context.err( |                 context.err( | ||||||
|                     "Layer is in an incorrect place. The path is " + |                     "Layer is in an incorrect place. The path is " + | ||||||
|                         this._path + |                     this._path + | ||||||
|                         ", but expected " + |                     ", but expected " + | ||||||
|                         expected |                     expected, | ||||||
|                 ) |                 ) | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  | @ -1480,13 +1484,13 @@ export class PrevalidateLayer extends DesugaringStep<LayerConfigJson> { | ||||||
|                     .enter(["tagRenderings", ...emptyIndexes]) |                     .enter(["tagRenderings", ...emptyIndexes]) | ||||||
|                     .err( |                     .err( | ||||||
|                         `Some tagrendering-ids are empty or have an emtpy string; this is not allowed (at ${emptyIndexes.join( |                         `Some tagrendering-ids are empty or have an emtpy string; this is not allowed (at ${emptyIndexes.join( | ||||||
|                             "," |                             ",", | ||||||
|                         )}])` |                         )}])`,
 | ||||||
|                     ) |                     ) | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             const duplicateIds = Utils.Duplicates( |             const duplicateIds = Utils.Duplicates( | ||||||
|                 (json.tagRenderings ?? [])?.map((f) => f["id"]).filter((id) => id !== "questions") |                 (json.tagRenderings ?? [])?.map((f) => f["id"]).filter((id) => id !== "questions"), | ||||||
|             ) |             ) | ||||||
|             if (duplicateIds.length > 0 && !Utils.runningFromConsole) { |             if (duplicateIds.length > 0 && !Utils.runningFromConsole) { | ||||||
|                 context |                 context | ||||||
|  | @ -1510,7 +1514,7 @@ export class PrevalidateLayer extends DesugaringStep<LayerConfigJson> { | ||||||
|         if (json.tagRenderings !== undefined) { |         if (json.tagRenderings !== undefined) { | ||||||
|             new On( |             new On( | ||||||
|                 "tagRenderings", |                 "tagRenderings", | ||||||
|                 new Each(new ValidateTagRenderings(json, this._doesImageExist)) |                 new Each(new ValidateTagRenderings(json, this._doesImageExist)), | ||||||
|             ).convert(json, context) |             ).convert(json, context) | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  | @ -1537,7 +1541,7 @@ export class PrevalidateLayer extends DesugaringStep<LayerConfigJson> { | ||||||
|                         context |                         context | ||||||
|                             .enters("pointRendering", i, "marker", indexM, "icon", "condition") |                             .enters("pointRendering", i, "marker", indexM, "icon", "condition") | ||||||
|                             .err( |                             .err( | ||||||
|                                 "Don't set a condition in a marker as this will result in an invisible but clickable element. Use extra filters in the source instead." |                                 "Don't set a condition in a marker as this will result in an invisible but clickable element. Use extra filters in the source instead.", | ||||||
|                             ) |                             ) | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|  | @ -1575,9 +1579,9 @@ export class PrevalidateLayer extends DesugaringStep<LayerConfigJson> { | ||||||
|                         .enters("presets", i, "tags") |                         .enters("presets", i, "tags") | ||||||
|                         .err( |                         .err( | ||||||
|                             "This preset does not match the required tags of this layer. This implies that a newly added point will not show up.\n    A newly created point will have properties: " + |                             "This preset does not match the required tags of this layer. This implies that a newly added point will not show up.\n    A newly created point will have properties: " + | ||||||
|                                 tags.asHumanString(false, false, {}) + |                             tags.asHumanString(false, false, {}) + | ||||||
|                                 "\n    The required tags are: " + |                             "\n    The required tags are: " + | ||||||
|                                 baseTags.asHumanString(false, false, {}) |                             baseTags.asHumanString(false, false, {}), | ||||||
|                         ) |                         ) | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|  | @ -1594,7 +1598,7 @@ export class ValidateLayerConfig extends DesugaringStep<LayerConfigJson> { | ||||||
|         isBuiltin: boolean, |         isBuiltin: boolean, | ||||||
|         doesImageExist: DoesImageExist, |         doesImageExist: DoesImageExist, | ||||||
|         studioValidations: boolean = false, |         studioValidations: boolean = false, | ||||||
|         skipDefaultLayers: boolean = false |         skipDefaultLayers: boolean = false, | ||||||
|     ) { |     ) { | ||||||
|         super("Thin wrapper around 'ValidateLayer", [], "ValidateLayerConfig") |         super("Thin wrapper around 'ValidateLayer", [], "ValidateLayerConfig") | ||||||
|         this.validator = new ValidateLayer( |         this.validator = new ValidateLayer( | ||||||
|  | @ -1602,7 +1606,7 @@ export class ValidateLayerConfig extends DesugaringStep<LayerConfigJson> { | ||||||
|             isBuiltin, |             isBuiltin, | ||||||
|             doesImageExist, |             doesImageExist, | ||||||
|             studioValidations, |             studioValidations, | ||||||
|             skipDefaultLayers |             skipDefaultLayers, | ||||||
|         ) |         ) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | @ -1630,7 +1634,7 @@ class ValidatePointRendering extends DesugaringStep<PointRenderingConfigJson> { | ||||||
|             context |             context | ||||||
|                 .enter("markers") |                 .enter("markers") | ||||||
|                 .err( |                 .err( | ||||||
|                     `Detected a field 'markerS' in pointRendering. It is written as a singular case` |                     `Detected a field 'markerS' in pointRendering. It is written as a singular case`, | ||||||
|                 ) |                 ) | ||||||
|         } |         } | ||||||
|         if (json.marker && !Array.isArray(json.marker)) { |         if (json.marker && !Array.isArray(json.marker)) { | ||||||
|  | @ -1640,7 +1644,7 @@ class ValidatePointRendering extends DesugaringStep<PointRenderingConfigJson> { | ||||||
|             context |             context | ||||||
|                 .enter("location") |                 .enter("location") | ||||||
|                 .err( |                 .err( | ||||||
|                     "A pointRendering should have at least one 'location' to defined where it should be rendered. " |                     "A pointRendering should have at least one 'location' to defined where it should be rendered. ", | ||||||
|                 ) |                 ) | ||||||
|         } |         } | ||||||
|         return json |         return json | ||||||
|  | @ -1659,26 +1663,26 @@ export class ValidateLayer extends Conversion< | ||||||
|         isBuiltin: boolean, |         isBuiltin: boolean, | ||||||
|         doesImageExist: DoesImageExist, |         doesImageExist: DoesImageExist, | ||||||
|         studioValidations: boolean = false, |         studioValidations: boolean = false, | ||||||
|         skipDefaultLayers: boolean = false |         skipDefaultLayers: boolean = false, | ||||||
|     ) { |     ) { | ||||||
|         super("Doesn't change anything, but emits warnings and errors", [], "ValidateLayer") |         super("Doesn't change anything, but emits warnings and errors", [], "ValidateLayer") | ||||||
|         this._prevalidation = new PrevalidateLayer( |         this._prevalidation = new PrevalidateLayer( | ||||||
|             path, |             path, | ||||||
|             isBuiltin, |             isBuiltin, | ||||||
|             doesImageExist, |             doesImageExist, | ||||||
|             studioValidations |             studioValidations, | ||||||
|         ) |         ) | ||||||
|         this._skipDefaultLayers = skipDefaultLayers |         this._skipDefaultLayers = skipDefaultLayers | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     convert( |     convert( | ||||||
|         json: LayerConfigJson, |         json: LayerConfigJson, | ||||||
|         context: ConversionContext |         context: ConversionContext, | ||||||
|     ): { parsed: LayerConfig; raw: LayerConfigJson } { |     ): { parsed: LayerConfig; raw: LayerConfigJson } { | ||||||
|         context = context.inOperation(this.name) |         context = context.inOperation(this.name) | ||||||
|         if (typeof json === "string") { |         if (typeof json === "string") { | ||||||
|             context.err( |             context.err( | ||||||
|                 `Not a valid layer: the layerConfig is a string. 'npm run generate:layeroverview' might be needed` |                 `Not a valid layer: the layerConfig is a string. 'npm run generate:layeroverview' might be needed`, | ||||||
|             ) |             ) | ||||||
|             return undefined |             return undefined | ||||||
|         } |         } | ||||||
|  | @ -1709,7 +1713,7 @@ export class ValidateLayer extends Conversion< | ||||||
|                 context |                 context | ||||||
|                     .enters("calculatedTags", i) |                     .enters("calculatedTags", i) | ||||||
|                     .err( |                     .err( | ||||||
|                         `Invalid function definition: the custom javascript is invalid:${e}. The offending javascript code is:\n    ${code}` |                         `Invalid function definition: the custom javascript is invalid:${e}. The offending javascript code is:\n    ${code}`, | ||||||
|                     ) |                     ) | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  | @ -1757,7 +1761,7 @@ export class ValidateLayer extends Conversion< | ||||||
|             context |             context | ||||||
|                 .enters("allowMove", "enableAccuracy") |                 .enters("allowMove", "enableAccuracy") | ||||||
|                 .err( |                 .err( | ||||||
|                     "`enableAccuracy` is written with two C in the first occurrence and only one in the last" |                     "`enableAccuracy` is written with two C in the first occurrence and only one in the last", | ||||||
|                 ) |                 ) | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  | @ -1788,8 +1792,8 @@ export class ValidateFilter extends DesugaringStep<FilterConfigJson> { | ||||||
|                         .enters("fields", i) |                         .enters("fields", i) | ||||||
|                         .err( |                         .err( | ||||||
|                             `Invalid filter: ${type} is not a valid textfield type.\n\tTry one of ${Array.from( |                             `Invalid filter: ${type} is not a valid textfield type.\n\tTry one of ${Array.from( | ||||||
|                                 Validators.availableTypes |                                 Validators.availableTypes, | ||||||
|                             ).join(",")}` |                             ).join(",")}`,
 | ||||||
|                         ) |                         ) | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|  | @ -1806,13 +1810,13 @@ export class DetectDuplicateFilters extends DesugaringStep<{ | ||||||
|         super( |         super( | ||||||
|             "Tries to detect layers where a shared filter can be used (or where similar filters occur)", |             "Tries to detect layers where a shared filter can be used (or where similar filters occur)", | ||||||
|             [], |             [], | ||||||
|             "DetectDuplicateFilters" |             "DetectDuplicateFilters", | ||||||
|         ) |         ) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     convert( |     convert( | ||||||
|         json: { layers: LayerConfigJson[]; themes: LayoutConfigJson[] }, |         json: { layers: LayerConfigJson[]; themes: LayoutConfigJson[] }, | ||||||
|         context: ConversionContext |         context: ConversionContext, | ||||||
|     ): { layers: LayerConfigJson[]; themes: LayoutConfigJson[] } { |     ): { layers: LayerConfigJson[]; themes: LayoutConfigJson[] } { | ||||||
|         const { layers, themes } = json |         const { layers, themes } = json | ||||||
|         const perOsmTag = new Map< |         const perOsmTag = new Map< | ||||||
|  | @ -1876,7 +1880,7 @@ export class DetectDuplicateFilters extends DesugaringStep<{ | ||||||
|                 filter: FilterConfigJson |                 filter: FilterConfigJson | ||||||
|             }[] |             }[] | ||||||
|         >, |         >, | ||||||
|         layout?: LayoutConfigJson | undefined |         layout?: LayoutConfigJson | undefined, | ||||||
|     ): void { |     ): void { | ||||||
|         if (layer.filter === undefined || layer.filter === null) { |         if (layer.filter === undefined || layer.filter === null) { | ||||||
|             return |             return | ||||||
|  | @ -1916,7 +1920,7 @@ export class DetectDuplicatePresets extends DesugaringStep<LayoutConfig> { | ||||||
|         super( |         super( | ||||||
|             "Detects mappings which have identical (english) names or identical mappings.", |             "Detects mappings which have identical (english) names or identical mappings.", | ||||||
|             ["presets"], |             ["presets"], | ||||||
|             "DetectDuplicatePresets" |             "DetectDuplicatePresets", | ||||||
|         ) |         ) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | @ -1927,13 +1931,13 @@ export class DetectDuplicatePresets extends DesugaringStep<LayoutConfig> { | ||||||
|         if (new Set(enNames).size != enNames.length) { |         if (new Set(enNames).size != enNames.length) { | ||||||
|             const dups = Utils.Duplicates(enNames) |             const dups = Utils.Duplicates(enNames) | ||||||
|             const layersWithDup = json.layers.filter((l) => |             const layersWithDup = json.layers.filter((l) => | ||||||
|                 l.presets.some((p) => dups.indexOf(p.title.textFor("en")) >= 0) |                 l.presets.some((p) => dups.indexOf(p.title.textFor("en")) >= 0), | ||||||
|             ) |             ) | ||||||
|             const layerIds = layersWithDup.map((l) => l.id) |             const layerIds = layersWithDup.map((l) => l.id) | ||||||
|             context.err( |             context.err( | ||||||
|                 `This theme has multiple presets which are named:${dups}, namely layers ${layerIds.join( |                 `This theme has multiple presets which are named:${dups}, namely layers ${layerIds.join( | ||||||
|                     ", " |                     ", ", | ||||||
|                 )} this is confusing for contributors and is probably the result of reusing the same layer multiple times. Use \`{"override": {"=presets": []}}\` to remove some presets` |                 )} this is confusing for contributors and is probably the result of reusing the same layer multiple times. Use \`{"override": {"=presets": []}}\` to remove some presets`, | ||||||
|             ) |             ) | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  | @ -1948,17 +1952,17 @@ export class DetectDuplicatePresets extends DesugaringStep<LayoutConfig> { | ||||||
|                     Utils.SameObject(presetATags, presetBTags) && |                     Utils.SameObject(presetATags, presetBTags) && | ||||||
|                     Utils.sameList( |                     Utils.sameList( | ||||||
|                         presetA.preciseInput.snapToLayers, |                         presetA.preciseInput.snapToLayers, | ||||||
|                         presetB.preciseInput.snapToLayers |                         presetB.preciseInput.snapToLayers, | ||||||
|                     ) |                     ) | ||||||
|                 ) { |                 ) { | ||||||
|                     context.err( |                     context.err( | ||||||
|                         `This theme has multiple presets with the same tags: ${presetATags.asHumanString( |                         `This theme has multiple presets with the same tags: ${presetATags.asHumanString( | ||||||
|                             false, |                             false, | ||||||
|                             false, |                             false, | ||||||
|                             {} |                             {}, | ||||||
|                         )}, namely the preset '${presets[i].title.textFor("en")}' and '${presets[ |                         )}, namely the preset '${presets[i].title.textFor("en")}' and '${presets[ | ||||||
|                             j |                             j | ||||||
|                         ].title.textFor("en")}'` |                             ].title.textFor("en")}'`,
 | ||||||
|                     ) |                     ) | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|  | @ -1983,13 +1987,13 @@ export class ValidateThemeEnsemble extends Conversion< | ||||||
|         super( |         super( | ||||||
|             "Validates that all themes together are logical, i.e. no duplicate ids exists within (overriden) themes", |             "Validates that all themes together are logical, i.e. no duplicate ids exists within (overriden) themes", | ||||||
|             [], |             [], | ||||||
|             "ValidateThemeEnsemble" |             "ValidateThemeEnsemble", | ||||||
|         ) |         ) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     convert( |     convert( | ||||||
|         json: LayoutConfig[], |         json: LayoutConfig[], | ||||||
|         context: ConversionContext |         context: ConversionContext, | ||||||
|     ): Map< |     ): Map< | ||||||
|         string, |         string, | ||||||
|         { |         { | ||||||
|  | @ -2040,11 +2044,11 @@ export class ValidateThemeEnsemble extends Conversion< | ||||||
|                 context.err( |                 context.err( | ||||||
|                     [ |                     [ | ||||||
|                         "The layer with id '" + |                         "The layer with id '" + | ||||||
|                             id + |                         id + | ||||||
|                             "' is found in multiple themes with different tag definitions:", |                         "' is found in multiple themes with different tag definitions:", | ||||||
|                         "\t In theme " + oldTheme + ":\t" + oldTags.asHumanString(false, false, {}), |                         "\t In theme " + oldTheme + ":\t" + oldTags.asHumanString(false, false, {}), | ||||||
|                         "\tIn theme " + theme.id + ":\t" + tags.asHumanString(false, false, {}), |                         "\tIn theme " + theme.id + ":\t" + tags.asHumanString(false, false, {}), | ||||||
|                     ].join("\n") |                     ].join("\n"), | ||||||
|                 ) |                 ) | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  | @ -80,6 +80,7 @@ export interface LayoutConfigJson { | ||||||
|      * Either a URL or a base64 encoded value (which should include 'data:image/svg+xml;base64) |      * Either a URL or a base64 encoded value (which should include 'data:image/svg+xml;base64) | ||||||
|      * |      * | ||||||
|      * Type: icon |      * Type: icon | ||||||
|  |      * suggestions: return Constants.defaultPinIcons.map(i => ({if: "value="+i, then: i, icon: i})) | ||||||
|      * group: basic |      * group: basic | ||||||
|      * |      * | ||||||
|      */ |      */ | ||||||
|  | @ -156,6 +157,7 @@ export interface LayoutConfigJson { | ||||||
|      * type: layer[] |      * type: layer[] | ||||||
|      * types: hidden | layer | hidden |      * types: hidden | layer | hidden | ||||||
|      * group: layers |      * group: layers | ||||||
|  |      * title: value["builtin"] ?? value["id"] ?? value | ||||||
|      * suggestions: return Array.from(layers.keys()).map(key => ({if: "value="+key, then: "<b>"+key+"</b> (builtin) - "+layers.get(key).description})) |      * suggestions: return Array.from(layers.keys()).map(key => ({if: "value="+key, then: "<b>"+key+"</b> (builtin) - "+layers.get(key).description})) | ||||||
|      * |      * | ||||||
|      * A theme must contain at least one layer. |      * A theme must contain at least one layer. | ||||||
|  |  | ||||||
|  | @ -6,7 +6,7 @@ | ||||||
| 
 | 
 | ||||||
| <Accordion> | <Accordion> | ||||||
|   <AccordionItem open={expanded} paddingDefault="p-0" inactiveClass="text-black"> |   <AccordionItem open={expanded} paddingDefault="p-0" inactiveClass="text-black"> | ||||||
|     <span slot="header" class="p-2 text-base"> |     <span slot="header" class="p-2 text-base w-full"> | ||||||
|       <slot name="header" /> |       <slot name="header" /> | ||||||
|     </span> |     </span> | ||||||
|     <div class="low-interaction rounded-b p-2"> |     <div class="low-interaction rounded-b p-2"> | ||||||
|  |  | ||||||
|  | @ -373,7 +373,6 @@ | ||||||
|             {feedback} |             {feedback} | ||||||
|             {unit} |             {unit} | ||||||
|             {state} |             {state} | ||||||
|             {extraTags} |  | ||||||
|             feature={selectedElement} |             feature={selectedElement} | ||||||
|             value={freeformInput} |             value={freeformInput} | ||||||
|             unvalidatedText={freeformInputUnvalidated} |             unvalidatedText={freeformInputUnvalidated} | ||||||
|  | @ -418,7 +417,6 @@ | ||||||
|                   {feedback} |                   {feedback} | ||||||
|                   {unit} |                   {unit} | ||||||
|                   {state} |                   {state} | ||||||
|                   {extraTags} |  | ||||||
|                   feature={selectedElement} |                   feature={selectedElement} | ||||||
|                   value={freeformInput} |                   value={freeformInput} | ||||||
|                   unvalidatedText={freeformInputUnvalidated} |                   unvalidatedText={freeformInputUnvalidated} | ||||||
|  | @ -464,7 +462,6 @@ | ||||||
|                   {feedback} |                   {feedback} | ||||||
|                   {unit} |                   {unit} | ||||||
|                   {state} |                   {state} | ||||||
|                   {extraTags} |  | ||||||
|                   feature={selectedElement} |                   feature={selectedElement} | ||||||
|                   value={freeformInput} |                   value={freeformInput} | ||||||
|                   unvalidatedText={freeformInputUnvalidated} |                   unvalidatedText={freeformInputUnvalidated} | ||||||
|  |  | ||||||
							
								
								
									
										207
									
								
								src/UI/Studio/CollapsedTagRenderingPreview.svelte
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										207
									
								
								src/UI/Studio/CollapsedTagRenderingPreview.svelte
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,207 @@ | ||||||
|  | <script lang="ts"> | ||||||
|  | 
 | ||||||
|  |   import Translations from "../i18n/Translations" | ||||||
|  |   import type { ConfigMeta } from "./configMeta" | ||||||
|  |   import Icon from "../Map/Icon.svelte" | ||||||
|  |   import Tr from "../Base/Tr.svelte" | ||||||
|  |   import { Translation } from "../i18n/Translation" | ||||||
|  |   import { UIEventSource } from "../../Logic/UIEventSource" | ||||||
|  |   import SchemaBasedInput from "./SchemaBasedInput.svelte" | ||||||
|  |   import { TrashIcon } from "@babeard/svelte-heroicons/mini" | ||||||
|  |   import QuestionPreview from "./QuestionPreview.svelte" | ||||||
|  |   import SchemaBasedMultiType from "./SchemaBasedMultiType.svelte" | ||||||
|  |   import { EditJsonState } from "./EditLayerState" | ||||||
|  |   import type { | ||||||
|  |     QuestionableTagRenderingConfigJson, | ||||||
|  |   } from "../../Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson" | ||||||
|  |   import { AccordionItem } from "flowbite-svelte" | ||||||
|  | 
 | ||||||
|  |   export let state: EditJsonState<any> | ||||||
|  | 
 | ||||||
|  |   export let isTagRenderingBlock: boolean | ||||||
|  |   export let schema: ConfigMeta | ||||||
|  |   export let currentValue: UIEventSource<(string | QuestionableTagRenderingConfigJson)[]> | ||||||
|  | 
 | ||||||
|  |   export let value: any | ||||||
|  |   export let singular: string | ||||||
|  |   export let i: number | ||||||
|  |   export let path: (string | number)[] = [] | ||||||
|  | 
 | ||||||
|  |   export let expanded = new UIEventSource(false) | ||||||
|  | 
 | ||||||
|  |   const subparts: ConfigMeta[] = state | ||||||
|  |     .getSchemaStartingWith(schema.path) | ||||||
|  |     .filter((part) => part.path.length - 1 === schema.path.length) | ||||||
|  | 
 | ||||||
|  |   function schemaForMultitype() { | ||||||
|  |     const sch = { ...schema } | ||||||
|  |     sch.hints.typehint = undefined | ||||||
|  |     return sch | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   function fusePath(i: number, subpartPath: string[]): (string | number)[] { | ||||||
|  |     const newPath = [...path, i] | ||||||
|  |     const toAdd = [...subpartPath] | ||||||
|  |     for (const part of path) { | ||||||
|  |       if (toAdd[0] === part) { | ||||||
|  |         toAdd.splice(0, 1) | ||||||
|  |       } else { | ||||||
|  |         break | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     newPath.push(...toAdd) | ||||||
|  |     return newPath | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   function del(i: number) { | ||||||
|  |     expanded.setData(false) | ||||||
|  |     currentValue.data.splice(i, 1) | ||||||
|  |     currentValue.ping() | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   function swap(i: number, j: number) { | ||||||
|  |     expanded.setData(false) | ||||||
|  |     const x = currentValue.data[i] | ||||||
|  |     currentValue.data[i] = currentValue.data[j] | ||||||
|  |     currentValue.data[j] = x | ||||||
|  |     currentValue.ping() | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   function moveTo(source: number, target: number) { | ||||||
|  |     expanded.setData(false) | ||||||
|  |     const x = currentValue.data[source] | ||||||
|  |     currentValue.data.splice(source, 1) | ||||||
|  |     currentValue.data.splice(target, 0, x) | ||||||
|  |     currentValue.ping() | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   function genTitle(value: any, singular: string, i: number): Translation { | ||||||
|  |     try { | ||||||
|  |       if (schema.hints.title) { | ||||||
|  |         const v = Function("value", "return " + schema.hints.title)(value) | ||||||
|  |         return Translations.T(v) | ||||||
|  |       } | ||||||
|  |     } catch (e) { | ||||||
|  |       console.log( | ||||||
|  |         "Warning: could not translate a title for " + | ||||||
|  |         `${singular} ${i} with function ` + | ||||||
|  |         schema.hints.title + | ||||||
|  |         " and value " + | ||||||
|  |         JSON.stringify(value), | ||||||
|  |       ) | ||||||
|  |     } | ||||||
|  |     return Translations.T(`${singular} ${i}`) | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   let genIconF: (x: any) => { icon: string; color: string } = <any>( | ||||||
|  |     Function("value", "return " + schema.hints.icon) | ||||||
|  |   ) | ||||||
|  | 
 | ||||||
|  |   function genIcon(value: any): string { | ||||||
|  |     return genIconF(value)?.icon | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   function genColor(value: any): string { | ||||||
|  |     if (!schema.hints.icon) { | ||||||
|  |       return undefined | ||||||
|  |     } | ||||||
|  |     return genIconF(value)?.color | ||||||
|  |   } | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | <AccordionItem open={$expanded} paddingDefault="p-0" inactiveClass="text-black m-0" > | ||||||
|  |   <div slot="header" class="p-1 text-base w-full m-0 text-black"> | ||||||
|  |     {#if !isTagRenderingBlock} | ||||||
|  |       <div class="flex items-center justify-between w-full"> | ||||||
|  |         <div class="m-0 flex"> | ||||||
|  |           {#if schema.hints.icon} | ||||||
|  |             <Icon clss="w-6 h-6" icon={genIcon(value)} color={genColor(value)} /> | ||||||
|  |           {/if} | ||||||
|  |           {#if schema.hints.title} | ||||||
|  |             <Tr t={genTitle(value, singular, i)} /> | ||||||
|  |             <div class="subtle ml-2"> | ||||||
|  |               {singular} | ||||||
|  |               {i} | ||||||
|  |             </div> | ||||||
|  |           {:else} | ||||||
|  |             {singular} | ||||||
|  |             {i} | ||||||
|  |           {/if} | ||||||
|  |         </div> | ||||||
|  |         <button | ||||||
|  |           class="h-fit w-fit rounded-full border border-black p-1" | ||||||
|  |           on:click={() => { | ||||||
|  |                   del(i) | ||||||
|  |                 }} | ||||||
|  |         > | ||||||
|  |           <TrashIcon class="h-4 w-4" /> | ||||||
|  |         </button> | ||||||
|  |       </div> | ||||||
|  |     {:else if typeof value === "string"} | ||||||
|  |       Builtin: <b>{value}</b> | ||||||
|  |       {:else if value["builtin"]} | ||||||
|  |       reused tagrendering <span class="font-bold">{JSON.stringify(value["builtin"])}</span> | ||||||
|  |     {:else} | ||||||
|  |       <Tr cls="font-bold" t={Translations.T(value?.question ?? value?.render)} /> | ||||||
|  |     {/if} | ||||||
|  |   </div> | ||||||
|  |   <div class="normal-background p-2"> | ||||||
|  |     {#if isTagRenderingBlock} | ||||||
|  |       <QuestionPreview {state} {path} {schema}> | ||||||
|  |         <button | ||||||
|  |           on:click={() => { | ||||||
|  |                   del(i) | ||||||
|  |                 }} | ||||||
|  |         > | ||||||
|  |           <TrashIcon class="h-4 w-4" /> | ||||||
|  |           Delete this question | ||||||
|  |         </button> | ||||||
|  | 
 | ||||||
|  |         {#if i > 0} | ||||||
|  |           <button | ||||||
|  |             on:click={() => { | ||||||
|  |                     moveTo(i, 0) | ||||||
|  |                   }} | ||||||
|  |           > | ||||||
|  |             Move to front | ||||||
|  |           </button> | ||||||
|  | 
 | ||||||
|  |           <button | ||||||
|  |             on:click={() => { | ||||||
|  |                     swap(i, i - 1) | ||||||
|  |                   }} | ||||||
|  |           > | ||||||
|  |             Move up | ||||||
|  |           </button> | ||||||
|  |         {/if} | ||||||
|  |         {#if i + 1 < $currentValue.length} | ||||||
|  |           <button | ||||||
|  |             on:click={() => { | ||||||
|  |                     swap(i, i + 1) | ||||||
|  |                   }} | ||||||
|  |           > | ||||||
|  |             Move down | ||||||
|  |           </button> | ||||||
|  |           <button | ||||||
|  |             on:click={() => { | ||||||
|  |                     moveTo(i, $currentValue.length - 1) | ||||||
|  |                   }} | ||||||
|  |           > | ||||||
|  |             Move to back | ||||||
|  |           </button> | ||||||
|  |         {/if} | ||||||
|  |       </QuestionPreview> | ||||||
|  |     {:else if schema.hints.types} | ||||||
|  |       <SchemaBasedMultiType {state} path={fusePath(i, [])} schema={schemaForMultitype()} /> | ||||||
|  |     {:else} | ||||||
|  |       {#each subparts as subpart} | ||||||
|  |         <SchemaBasedInput | ||||||
|  |           {state} | ||||||
|  |           path={fusePath(i, [subpart.path.at(-1)])} | ||||||
|  |           schema={subpart} | ||||||
|  |         /> | ||||||
|  |       {/each} | ||||||
|  |     {/if} | ||||||
|  |   </div> | ||||||
|  | </AccordionItem> | ||||||
|  | @ -8,7 +8,7 @@ import { | ||||||
|     Pipe, |     Pipe, | ||||||
| } from "../../Models/ThemeConfig/Conversion/Conversion" | } from "../../Models/ThemeConfig/Conversion/Conversion" | ||||||
| import { PrepareLayer } from "../../Models/ThemeConfig/Conversion/PrepareLayer" | import { PrepareLayer } from "../../Models/ThemeConfig/Conversion/PrepareLayer" | ||||||
| import { ValidateLayer, ValidateTheme } from "../../Models/ThemeConfig/Conversion/Validation" | import { PrevalidateTheme, ValidateLayer, ValidateTheme } from "../../Models/ThemeConfig/Conversion/Validation" | ||||||
| import { AllSharedLayers } from "../../Customizations/AllSharedLayers" | import { AllSharedLayers } from "../../Customizations/AllSharedLayers" | ||||||
| import { QuestionableTagRenderingConfigJson } from "../../Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson" | import { QuestionableTagRenderingConfigJson } from "../../Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson" | ||||||
| import { TagUtils } from "../../Logic/Tags/TagUtils" | import { TagUtils } from "../../Logic/Tags/TagUtils" | ||||||
|  | @ -33,6 +33,8 @@ export abstract class EditJsonState<T> { | ||||||
|     public readonly schema: ConfigMeta[] |     public readonly schema: ConfigMeta[] | ||||||
|     public readonly category: "layers" | "themes" |     public readonly category: "layers" | "themes" | ||||||
|     public readonly server: StudioServer |     public readonly server: StudioServer | ||||||
|  |     public readonly osmConnection: OsmConnection | ||||||
|  | 
 | ||||||
|     public readonly showIntro: UIEventSource<"no" | "intro" | "tagrenderings"> = <any>( |     public readonly showIntro: UIEventSource<"no" | "intro" | "tagrenderings"> = <any>( | ||||||
|         LocalStorageSource.Get("studio-show-intro", "intro") |         LocalStorageSource.Get("studio-show-intro", "intro") | ||||||
|     ) |     ) | ||||||
|  | @ -51,7 +53,7 @@ export abstract class EditJsonState<T> { | ||||||
|      * The EditLayerUI shows a 'schemaBasedInput' for this path to pop advanced questions out |      * The EditLayerUI shows a 'schemaBasedInput' for this path to pop advanced questions out | ||||||
|      */ |      */ | ||||||
|     public readonly highlightedItem: UIEventSource<HighlightedTagRendering> = new UIEventSource( |     public readonly highlightedItem: UIEventSource<HighlightedTagRendering> = new UIEventSource( | ||||||
|         undefined |         undefined, | ||||||
|     ) |     ) | ||||||
|     private sendingUpdates = false |     private sendingUpdates = false | ||||||
|     private readonly _stores = new Map<string, UIEventSource<any>>() |     private readonly _stores = new Map<string, UIEventSource<any>>() | ||||||
|  | @ -60,10 +62,12 @@ export abstract class EditJsonState<T> { | ||||||
|         schema: ConfigMeta[], |         schema: ConfigMeta[], | ||||||
|         server: StudioServer, |         server: StudioServer, | ||||||
|         category: "layers" | "themes", |         category: "layers" | "themes", | ||||||
|  |         osmConnection: OsmConnection, | ||||||
|         options?: { |         options?: { | ||||||
|             expertMode?: UIEventSource<boolean> |             expertMode?: UIEventSource<boolean> | ||||||
|         } |         }, | ||||||
|     ) { |     ) { | ||||||
|  |         this.osmConnection = osmConnection | ||||||
|         this.schema = schema |         this.schema = schema | ||||||
|         this.server = server |         this.server = server | ||||||
|         this.category = category |         this.category = category | ||||||
|  | @ -88,6 +92,10 @@ export abstract class EditJsonState<T> { | ||||||
|                 await this.server.update(id, config, this.category) |                 await this.server.update(id, config, this.category) | ||||||
|             }) |             }) | ||||||
|         this.messages = this.createMessagesStore() |         this.messages = this.createMessagesStore() | ||||||
|  |         this.register(["credits"], this.osmConnection.userDetails.mapD(u => u.name), false) | ||||||
|  |         this.register(["credits:uid"], this.osmConnection.userDetails.mapD(u => u.uid), false) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public startSavingUpdates(enabled = true) { |     public startSavingUpdates(enabled = true) { | ||||||
|  | @ -132,7 +140,7 @@ export abstract class EditJsonState<T> { | ||||||
|     public register( |     public register( | ||||||
|         path: ReadonlyArray<string | number>, |         path: ReadonlyArray<string | number>, | ||||||
|         value: Store<any>, |         value: Store<any>, | ||||||
|         noInitialSync: boolean = true |         noInitialSync: boolean = true, | ||||||
|     ): () => void { |     ): () => void { | ||||||
|         const unsync = value.addCallback((v) => { |         const unsync = value.addCallback((v) => { | ||||||
|             this.setValueAt(path, v) |             this.setValueAt(path, v) | ||||||
|  | @ -146,7 +154,7 @@ export abstract class EditJsonState<T> { | ||||||
|     public getSchemaStartingWith(path: string[]) { |     public getSchemaStartingWith(path: string[]) { | ||||||
|         return this.schema.filter( |         return this.schema.filter( | ||||||
|             (sch) => |             (sch) => | ||||||
|                 !path.some((part, i) => !(sch.path.length > path.length && sch.path[i] === part)) |                 !path.some((part, i) => !(sch.path.length > path.length && sch.path[i] === part)), | ||||||
|         ) |         ) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | @ -167,7 +175,7 @@ export abstract class EditJsonState<T> { | ||||||
|         const schemas = this.schema.filter( |         const schemas = this.schema.filter( | ||||||
|             (sch) => |             (sch) => | ||||||
|                 sch !== undefined && |                 sch !== undefined && | ||||||
|                 !path.some((part, i) => !(sch.path.length == path.length && sch.path[i] === part)) |                 !path.some((part, i) => !(sch.path.length == path.length && sch.path[i] === part)), | ||||||
|         ) |         ) | ||||||
|         if (schemas.length == 0) { |         if (schemas.length == 0) { | ||||||
|             console.warn("No schemas found for path", path.join(".")) |             console.warn("No schemas found for path", path.join(".")) | ||||||
|  | @ -257,12 +265,12 @@ class ContextRewritingStep<T> extends Conversion<LayerConfigJson, T> { | ||||||
|     constructor( |     constructor( | ||||||
|         state: DesugaringContext, |         state: DesugaringContext, | ||||||
|         step: Conversion<LayerConfigJson, T>, |         step: Conversion<LayerConfigJson, T>, | ||||||
|         getTagRenderings: (t: T) => TagRenderingConfigJson[] |         getTagRenderings: (t: T) => TagRenderingConfigJson[], | ||||||
|     ) { |     ) { | ||||||
|         super( |         super( | ||||||
|             "When validating a layer, the tagRenderings are first expanded. Some builtin tagRendering-calls (e.g. `contact`) will introduce _multiple_ tagRenderings, causing the count to be off. This class rewrites the error messages to fix this", |             "When validating a layer, the tagRenderings are first expanded. Some builtin tagRendering-calls (e.g. `contact`) will introduce _multiple_ tagRenderings, causing the count to be off. This class rewrites the error messages to fix this", | ||||||
|             [], |             [], | ||||||
|             "ContextRewritingStep" |             "ContextRewritingStep", | ||||||
|         ) |         ) | ||||||
|         this._state = state |         this._state = state | ||||||
|         this._step = step |         this._step = step | ||||||
|  | @ -272,7 +280,7 @@ class ContextRewritingStep<T> extends Conversion<LayerConfigJson, T> { | ||||||
|     convert(json: LayerConfigJson, context: ConversionContext): T { |     convert(json: LayerConfigJson, context: ConversionContext): T { | ||||||
|         const converted = this._step.convert(json, context) |         const converted = this._step.convert(json, context) | ||||||
|         const originalIds = json.tagRenderings?.map( |         const originalIds = json.tagRenderings?.map( | ||||||
|             (tr) => (<QuestionableTagRenderingConfigJson>tr)["id"] |             (tr) => (<QuestionableTagRenderingConfigJson>tr)["id"], | ||||||
|         ) |         ) | ||||||
|         if (!originalIds) { |         if (!originalIds) { | ||||||
|             return converted |             return converted | ||||||
|  | @ -307,7 +315,6 @@ class ContextRewritingStep<T> extends Conversion<LayerConfigJson, T> { | ||||||
| 
 | 
 | ||||||
| export default class EditLayerState extends EditJsonState<LayerConfigJson> { | export default class EditLayerState extends EditJsonState<LayerConfigJson> { | ||||||
|     // Needed for the special visualisations
 |     // Needed for the special visualisations
 | ||||||
|     public readonly osmConnection: OsmConnection |  | ||||||
|     public readonly imageUploadManager = { |     public readonly imageUploadManager = { | ||||||
|         getCountsFor() { |         getCountsFor() { | ||||||
|             return 0 |             return 0 | ||||||
|  | @ -335,10 +342,9 @@ export default class EditLayerState extends EditJsonState<LayerConfigJson> { | ||||||
|         schema: ConfigMeta[], |         schema: ConfigMeta[], | ||||||
|         server: StudioServer, |         server: StudioServer, | ||||||
|         osmConnection: OsmConnection, |         osmConnection: OsmConnection, | ||||||
|         options: { expertMode: UIEventSource<boolean> } |         options: { expertMode: UIEventSource<boolean> }, | ||||||
|     ) { |     ) { | ||||||
|         super(schema, server, "layers", options) |         super(schema, server, "layers", osmConnection, options) | ||||||
|         this.osmConnection = osmConnection |  | ||||||
|         this.layout = { |         this.layout = { | ||||||
|             getMatchingLayer: () => { |             getMatchingLayer: () => { | ||||||
|                 try { |                 try { | ||||||
|  | @ -393,7 +399,7 @@ export default class EditLayerState extends EditJsonState<LayerConfigJson> { | ||||||
|         return new ContextRewritingStep( |         return new ContextRewritingStep( | ||||||
|             state, |             state, | ||||||
|             new Pipe(new PrepareLayer(state), new ValidateLayer("dynamic", false, undefined, true)), |             new Pipe(new PrepareLayer(state), new ValidateLayer("dynamic", false, undefined, true)), | ||||||
|             (t) => <TagRenderingConfigJson[]>t.raw.tagRenderings |             (t) => <TagRenderingConfigJson[]>t.raw.tagRenderings, | ||||||
|         ) |         ) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | @ -427,7 +433,7 @@ export default class EditLayerState extends EditJsonState<LayerConfigJson> { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     protected async validate( |     protected async validate( | ||||||
|         configuration: Partial<LayerConfigJson> |         configuration: Partial<LayerConfigJson>, | ||||||
|     ): Promise<ConversionMessage[]> { |     ): Promise<ConversionMessage[]> { | ||||||
|         const layers = AllSharedLayers.getSharedLayersConfigs() |         const layers = AllSharedLayers.getSharedLayersConfigs() | ||||||
| 
 | 
 | ||||||
|  | @ -456,16 +462,19 @@ export class EditThemeState extends EditJsonState<LayoutConfigJson> { | ||||||
|     constructor( |     constructor( | ||||||
|         schema: ConfigMeta[], |         schema: ConfigMeta[], | ||||||
|         server: StudioServer, |         server: StudioServer, | ||||||
|         options: { expertMode: UIEventSource<boolean> } |         osmConnection: OsmConnection, | ||||||
|  |         options: { expertMode: UIEventSource<boolean> }, | ||||||
|     ) { |     ) { | ||||||
|         super(schema, server, "themes", options) |         super(schema, server, "themes", osmConnection, options) | ||||||
|         this.setupFixers() |         this.setupFixers() | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     protected buildValidation(state: DesugaringContext): Conversion<LayoutConfigJson, any> { |     protected buildValidation(state: DesugaringContext): Conversion<LayoutConfigJson, any> { | ||||||
|         return new Pipe( |         return new Pipe(new PrevalidateTheme(), | ||||||
|             new PrepareTheme(state), |             new Pipe( | ||||||
|             new ValidateTheme(undefined, "", false, new Set(state.tagRenderings.keys())) |                 new PrepareTheme(state), | ||||||
|  |                 new ValidateTheme(undefined, "", false, new Set(state.tagRenderings.keys())), | ||||||
|  |             ), true, | ||||||
|         ) |         ) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,11 +1,13 @@ | ||||||
| <script lang="ts"> | <script lang="ts"> | ||||||
|   import type { ConfigMeta } from "./configMeta" |   import type { ConfigMeta } from "./configMeta" | ||||||
|   import EditLayerState from "./EditLayerState" |   import EditLayerState, { EditJsonState } from "./EditLayerState" | ||||||
|   import * as questions from "../../assets/generated/layers/questions.json" |   import * as questions from "../../assets/generated/layers/questions.json" | ||||||
|   import { ImmutableStore, Store } from "../../Logic/UIEventSource" |   import { ImmutableStore, Store } from "../../Logic/UIEventSource" | ||||||
|   import TagRenderingEditable from "../Popup/TagRendering/TagRenderingEditable.svelte" |   import TagRenderingEditable from "../Popup/TagRendering/TagRenderingEditable.svelte" | ||||||
|   import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig" |   import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig" | ||||||
|   import type { QuestionableTagRenderingConfigJson } from "../../Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson.js" |   import type { | ||||||
|  |     QuestionableTagRenderingConfigJson, | ||||||
|  |   } from "../../Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson.js" | ||||||
|   import type { TagRenderingConfigJson } from "../../Models/ThemeConfig/Json/TagRenderingConfigJson" |   import type { TagRenderingConfigJson } from "../../Models/ThemeConfig/Json/TagRenderingConfigJson" | ||||||
|   import FromHtml from "../Base/FromHtml.svelte" |   import FromHtml from "../Base/FromHtml.svelte" | ||||||
|   import ShowConversionMessage from "./ShowConversionMessage.svelte" |   import ShowConversionMessage from "./ShowConversionMessage.svelte" | ||||||
|  | @ -31,15 +33,25 @@ | ||||||
|     if (typeof x === "string") { |     if (typeof x === "string") { | ||||||
|       return perId[x] |       return perId[x] | ||||||
|     } else { |     } else { | ||||||
|       return [x] |       return <any>[x] | ||||||
|     } |     } | ||||||
|   }) |   }) | ||||||
|   let configs: Store<TagRenderingConfig[]> = configJson.map((configs) => { |   let configs: Store<TagRenderingConfig[]> = configJson.map((configs) => { | ||||||
|     if (!configs) { |     if (!configs) { | ||||||
|       return [{ error: "No configuartions found" }] |       return [{ error: "No configuartions found" }] | ||||||
|     } |     } | ||||||
|     console.log("Regenerating configs") |  | ||||||
|     return configs.map((config) => { |     return configs.map((config) => { | ||||||
|  |       if (config["builtin"]) { | ||||||
|  |         let override = "" | ||||||
|  |         if (config["override"]) { | ||||||
|  |           override = ". Some items are changed with an override. Editing this is not yet supported with Studio." | ||||||
|  |         } | ||||||
|  |         return new TagRenderingConfig({ | ||||||
|  |           render: { | ||||||
|  |             "en": "This question reuses <b>" + JSON.stringify(config["builtin"]) + "</b>" + override, | ||||||
|  |           }, | ||||||
|  |         }) | ||||||
|  |       } | ||||||
|       try { |       try { | ||||||
|         return new TagRenderingConfig(config) |         return new TagRenderingConfig(config) | ||||||
|       } catch (e) { |       } catch (e) { | ||||||
|  | @ -47,15 +59,6 @@ | ||||||
|       } |       } | ||||||
|     }) |     }) | ||||||
|   }) |   }) | ||||||
|   let id: Store<string> = value.mapD((c) => { |  | ||||||
|     if (c?.id) { |  | ||||||
|       return c.id |  | ||||||
|     } |  | ||||||
|     if (typeof c === "string") { |  | ||||||
|       return c |  | ||||||
|     } |  | ||||||
|     return undefined |  | ||||||
|   }) |  | ||||||
| 
 | 
 | ||||||
|   let tags = state.testTags |   let tags = state.testTags | ||||||
| 
 | 
 | ||||||
|  | @ -66,11 +69,18 @@ | ||||||
| 
 | 
 | ||||||
| <div class="flex"> | <div class="flex"> | ||||||
|   <div class="m-4 flex w-full flex-col"> |   <div class="m-4 flex w-full flex-col"> | ||||||
|     <NextButton clss="primary" on:click={() => state.highlightedItem.setData({ path, schema })}> |     {#if $configJson.some(config => config["builtin"] !== undefined)} | ||||||
|       {#if schema.hints.question} |       <div class="interactive p-2 rounded-2xl"> | ||||||
|         {schema.hints.question} |         This question uses an advanced 'builtin'+'override' construction in the source code. | ||||||
|       {/if} |         Editing this with Studio is not supported. | ||||||
|     </NextButton> |       </div> | ||||||
|  |     {:else} | ||||||
|  |       <NextButton clss="primary" on:click={() => state.highlightedItem.setData({ path, schema })}> | ||||||
|  |         {#if schema.hints.question} | ||||||
|  |           {schema.hints.question} | ||||||
|  |         {/if} | ||||||
|  |       </NextButton> | ||||||
|  |     {/if} | ||||||
|     {#if description} |     {#if description} | ||||||
|       <Markdown src={description} /> |       <Markdown src={description} /> | ||||||
|     {/if} |     {/if} | ||||||
|  | @ -86,6 +96,7 @@ | ||||||
|     {#each $configs as config} |     {#each $configs as config} | ||||||
|       {#if config.error !== undefined} |       {#if config.error !== undefined} | ||||||
|         <div class="alert">Could not create a preview of this tagRendering: {config.error}</div> |         <div class="alert">Could not create a preview of this tagRendering: {config.error}</div> | ||||||
|  |         {JSON.stringify($value)} | ||||||
|       {:else if config.condition && !config.condition.matchesProperties($tags)} |       {:else if config.condition && !config.condition.matchesProperties($tags)} | ||||||
|         This tagRendering is currently not shown. It will appear if the feature matches the |         This tagRendering is currently not shown. It will appear if the feature matches the | ||||||
|         condition |         condition | ||||||
|  |  | ||||||
|  | @ -8,6 +8,15 @@ | ||||||
| 
 | 
 | ||||||
|   export let state: EditLayerState | EditThemeState |   export let state: EditLayerState | EditThemeState | ||||||
| 
 | 
 | ||||||
|  |   let rawConfig = state.configuration.sync(f => JSON.stringify(f, null, "  "), [], json => { | ||||||
|  |     try { | ||||||
|  |       return JSON.parse(json) | ||||||
|  |     } catch (e) { | ||||||
|  |       console.error("Could not parse", json) | ||||||
|  |       return undefined | ||||||
|  |     } | ||||||
|  |   }) | ||||||
|  | 
 | ||||||
|   let container: HTMLDivElement |   let container: HTMLDivElement | ||||||
|   let monaco: typeof Monaco |   let monaco: typeof Monaco | ||||||
|   let editor: Monaco.editor.IStandaloneCodeEditor |   let editor: Monaco.editor.IStandaloneCodeEditor | ||||||
|  | @ -34,13 +43,19 @@ | ||||||
|     return () => window.removeEventListener("keydown", handler) |     return () => window.removeEventListener("keydown", handler) | ||||||
|   }) |   }) | ||||||
| 
 | 
 | ||||||
|  |   let useFallback = false | ||||||
|   onMount(async () => { |   onMount(async () => { | ||||||
|     const monacoEditor = await import("monaco-editor") |     const monacoEditor = await import("monaco-editor") | ||||||
|     loader.config({ |     loader.config({ | ||||||
|       monaco: monacoEditor.default, |       monaco: monacoEditor.default, | ||||||
|     }) |     }) | ||||||
| 
 | 
 | ||||||
|     monaco = await loader.init() |     try { | ||||||
|  |       monaco = await loader.init() | ||||||
|  |     } catch (e) { | ||||||
|  |       console.error("Could not load Monaco Editor, falling back", e) | ||||||
|  |       useFallback = true | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|     // Determine schema based on the state |     // Determine schema based on the state | ||||||
|     let schemaUri: string |     let schemaUri: string | ||||||
|  | @ -50,7 +65,7 @@ | ||||||
|       schemaUri = "https://mapcomplete.org/schemas/layoutconfig.json" |       schemaUri = "https://mapcomplete.org/schemas/layoutconfig.json" | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     monaco.languages.json.jsonDefaults.setDiagnosticsOptions({ |     monaco?.languages?.json?.jsonDefaults?.setDiagnosticsOptions({ | ||||||
|       validate: true, |       validate: true, | ||||||
|       schemas: [ |       schemas: [ | ||||||
|         { |         { | ||||||
|  | @ -64,23 +79,28 @@ | ||||||
|       ], |       ], | ||||||
|     }) |     }) | ||||||
| 
 | 
 | ||||||
|     let modelUri = monaco.Uri.parse("inmemory://inmemory/file.json") |     let modelUri = monaco?.Uri?.parse("inmemory://inmemory/file.json") | ||||||
| 
 | 
 | ||||||
|     // Create a new model |     // Create a new model | ||||||
|     model = monaco.editor.createModel( |     try { | ||||||
|       JSON.stringify(state.configuration.data, null, "  "), |       model = monaco?.editor?.createModel( | ||||||
|       "json", |         JSON.stringify(state.configuration.data, null, "  "), | ||||||
|       modelUri |         "json", | ||||||
|     ) |         modelUri, | ||||||
|  |       ) | ||||||
|  |     } catch (e) { | ||||||
|  |       console.error("Could not create model in MOnaco Editor", e) | ||||||
|  |       useFallback = true | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|     editor = monaco.editor.create(container, { |     editor = monaco?.editor?.create(container, { | ||||||
|       model, |       model, | ||||||
|       automaticLayout: true, |       automaticLayout: true, | ||||||
|     }) |     }) | ||||||
| 
 | 
 | ||||||
|     // When the editor is changed, update the configuration, but only if the user hasn't typed for 500ms and the JSON is valid |     // When the editor is changed, update the configuration, but only if the user hasn't typed for 500ms and the JSON is valid | ||||||
|     let timeout: number |     let timeout: number | ||||||
|     editor.onDidChangeModelContent(() => { |     editor?.onDidChangeModelContent(() => { | ||||||
|       clearTimeout(timeout) |       clearTimeout(timeout) | ||||||
|       timeout = setTimeout(() => { |       timeout = setTimeout(() => { | ||||||
|         save() |         save() | ||||||
|  | @ -98,4 +118,8 @@ | ||||||
|   }) |   }) | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
| <div bind:this={container} class="h-full w-full" /> | {#if useFallback} | ||||||
|  |   <textarea class="w-full" rows="25" bind:value={$rawConfig} /> | ||||||
|  | {:else} | ||||||
|  |   <div bind:this={container} class="h-full w-full" /> | ||||||
|  | {/if} | ||||||
|  |  | ||||||
|  | @ -4,9 +4,10 @@ | ||||||
|    * They will typically be a subset of some properties |    * They will typically be a subset of some properties | ||||||
|    */ |    */ | ||||||
|   import type { ConfigMeta } from "./configMeta" |   import type { ConfigMeta } from "./configMeta" | ||||||
|   import EditLayerState from "./EditLayerState" |   import EditLayerState, { EditJsonState } from "./EditLayerState" | ||||||
|   import SchemaBasedInput from "./SchemaBasedInput.svelte" |   import SchemaBasedInput from "./SchemaBasedInput.svelte" | ||||||
|   import AccordionSingle from "../Flowbite/AccordionSingle.svelte" |   import AccordionSingle from "../Flowbite/AccordionSingle.svelte" | ||||||
|  |   import { Utils } from "../../Utils" | ||||||
| 
 | 
 | ||||||
|   export let state: EditLayerState |   export let state: EditLayerState | ||||||
|   export let configs: ConfigMeta[] |   export let configs: ConfigMeta[] | ||||||
|  | @ -30,8 +31,8 @@ | ||||||
|     <div slot="header">{title}</div> |     <div slot="header">{title}</div> | ||||||
|     <div class="flex w-full flex-col gap-y-1 pl-2"> |     <div class="flex w-full flex-col gap-y-1 pl-2"> | ||||||
|       <slot name="description" /> |       <slot name="description" /> | ||||||
|       {#each configsFiltered as config} |       {#each configsFiltered as config (config.path)} | ||||||
|         <SchemaBasedInput {state} path={config.path} schema={config} /> |         <SchemaBasedInput {state} path={config.path} } /> | ||||||
|       {/each} |       {/each} | ||||||
|     </div> |     </div> | ||||||
|   </AccordionSingle> |   </AccordionSingle> | ||||||
|  |  | ||||||
|  | @ -1,24 +1,21 @@ | ||||||
| <script lang="ts"> | <script lang="ts"> | ||||||
|   import EditLayerState from "./EditLayerState" |   import { EditJsonState } from "./EditLayerState" | ||||||
|   import type { ConfigMeta } from "./configMeta" |   import type { ConfigMeta } from "./configMeta" | ||||||
|   import { UIEventSource } from "../../Logic/UIEventSource" |  | ||||||
|   import SchemaBasedInput from "./SchemaBasedInput.svelte" |  | ||||||
|   import SchemaBasedField from "./SchemaBasedField.svelte" |   import SchemaBasedField from "./SchemaBasedField.svelte" | ||||||
|   import { TrashIcon } from "@babeard/svelte-heroicons/mini" |   import { TrashIcon } from "@babeard/svelte-heroicons/mini" | ||||||
|   import QuestionPreview from "./QuestionPreview.svelte" |  | ||||||
|   import SchemaBasedMultiType from "./SchemaBasedMultiType.svelte" |  | ||||||
|   import ShowConversionMessage from "./ShowConversionMessage.svelte" |   import ShowConversionMessage from "./ShowConversionMessage.svelte" | ||||||
|   import Markdown from "../Base/Markdown.svelte" |   import Markdown from "../Base/Markdown.svelte" | ||||||
|   import AccordionSingle from "../Flowbite/AccordionSingle.svelte" |   import { Utils } from "../../Utils" | ||||||
|   import Icon from "../Map/Icon.svelte" |   import type { QuestionableTagRenderingConfigJson } from "../../Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson" | ||||||
|   import Tr from "../Base/Tr.svelte" |   import CollapsedTagRenderingPreview from "./CollapsedTagRenderingPreview.svelte" | ||||||
|   import Translations from "../i18n/Translations" |   import { Accordion } from "flowbite-svelte" | ||||||
| 
 | 
 | ||||||
|   export let state: EditLayerState |   export let state: EditJsonState<any> | ||||||
|  |   export let path: (string | number)[] = [] | ||||||
|   export let schema: ConfigMeta |   export let schema: ConfigMeta | ||||||
| 
 | 
 | ||||||
|  |   schema = Utils.Clone(schema) | ||||||
|   let title = schema.path.at(-1) |   let title = schema.path.at(-1) | ||||||
|   console.log(">>>", schema) |  | ||||||
|   let singular = title |   let singular = title | ||||||
|   if (title?.endsWith("s")) { |   if (title?.endsWith("s")) { | ||||||
|     singular = title.slice(0, title.length - 1) |     singular = title.slice(0, title.length - 1) | ||||||
|  | @ -27,7 +24,6 @@ | ||||||
|   if (singular?.match(/^[aeoui]/)) { |   if (singular?.match(/^[aeoui]/)) { | ||||||
|     article = "an" |     article = "an" | ||||||
|   } |   } | ||||||
|   export let path: (string | number)[] = [] |  | ||||||
| 
 | 
 | ||||||
|   const isTagRenderingBlock = path.length === 1 && path[0] === "tagRenderings" |   const isTagRenderingBlock = path.length === 1 && path[0] === "tagRenderings" | ||||||
| 
 | 
 | ||||||
|  | @ -41,12 +37,12 @@ | ||||||
|     .filter((part) => part.path.length - 1 === schema.path.length) |     .filter((part) => part.path.length - 1 === schema.path.length) | ||||||
|   let messages = state.messagesFor(path) |   let messages = state.messagesFor(path) | ||||||
| 
 | 
 | ||||||
|   const currentValue: UIEventSource<any[]> = state.getStoreFor(path) |   const currentValue = state.getStoreFor<(string | QuestionableTagRenderingConfigJson)[]>(path) | ||||||
|   if (currentValue.data === undefined) { |   if (currentValue.data === undefined) { | ||||||
|     currentValue.setData([]) |     currentValue.setData([]) | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   function createItem(valueToSet?: any) { |   function createItem(valueToSet?: string | QuestionableTagRenderingConfigJson) { | ||||||
|     if (currentValue.data === undefined) { |     if (currentValue.data === undefined) { | ||||||
|       currentValue.setData([]) |       currentValue.setData([]) | ||||||
|     } |     } | ||||||
|  | @ -72,64 +68,13 @@ | ||||||
|     return newPath |     return newPath | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   function schemaForMultitype() { |  | ||||||
|     const sch = { ...schema } |  | ||||||
|     sch.hints.typehint = undefined |  | ||||||
|     return sch |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   function del(i: number) { |   function del(i: number) { | ||||||
|     currentValue.data.splice(i, 1) |     currentValue.data.splice(i, 1) | ||||||
|     currentValue.ping() |     currentValue.ping() | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   function swap(i: number, j: number) { |  | ||||||
|     const x = currentValue.data[i] |  | ||||||
|     currentValue.data[i] = currentValue.data[j] |  | ||||||
|     currentValue.data[j] = x |  | ||||||
|     currentValue.ping() |  | ||||||
|   } |  | ||||||
| 
 | 
 | ||||||
|   function moveTo(source: number, target: number) { |  | ||||||
|     const x = currentValue.data[source] |  | ||||||
|     currentValue.data.splice(source, 1) |  | ||||||
|     currentValue.data.splice(target, 0, x) |  | ||||||
|     currentValue.ping() |  | ||||||
|   } |  | ||||||
| 
 | 
 | ||||||
|   function genTitle(value: any, singular: string, i: number): Translation { |  | ||||||
|     try { |  | ||||||
|       if (schema.hints.title) { |  | ||||||
|         const v = Function("value", "return " + schema.hints.title)(value) |  | ||||||
|         return Translations.T(v) |  | ||||||
|       } |  | ||||||
|     } catch (e) { |  | ||||||
|       console.log( |  | ||||||
|         "Warning: could not translate a title for " + |  | ||||||
|           `${singular} ${i} with function ` + |  | ||||||
|           schema.hints.title + |  | ||||||
|           " and value " + |  | ||||||
|           JSON.stringify(value) |  | ||||||
|       ) |  | ||||||
|     } |  | ||||||
|     return Translations.T(`${singular} ${i}`) |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   let genIconF: (x: any) => { icon: string; color: string } = <any>( |  | ||||||
|     Function("value", "return " + schema.hints.icon) |  | ||||||
|   ) |  | ||||||
|   console.log("Icon lambda is", schema.hints.icon, path, genIconF("test")) |  | ||||||
| 
 |  | ||||||
|   function genIcon(value: any): string { |  | ||||||
|     return genIconF(value)?.icon |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   function genColor(value: any): string { |  | ||||||
|     if (!schema.hints.icon) { |  | ||||||
|       return undefined |  | ||||||
|     } |  | ||||||
|     return genIconF(value)?.color |  | ||||||
|   } |  | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
| <div class="pl-2"> | <div class="pl-2"> | ||||||
|  | @ -163,99 +108,11 @@ | ||||||
|       </div> |       </div> | ||||||
|     {/each} |     {/each} | ||||||
|   {:else} |   {:else} | ||||||
|     {#each $currentValue as value, i} |     <Accordion> | ||||||
|       <AccordionSingle expanded={false}> |     {#each $currentValue as value, i  (value)} | ||||||
|         <span slot="header"> |       <CollapsedTagRenderingPreview {state} {isTagRenderingBlock} {schema} {currentValue} {value} {i} {singular} path={fusePath(i, [])}/> | ||||||
|           {#if !isTagRenderingBlock} |  | ||||||
|             <div class="flex items-center justify-between"> |  | ||||||
|               <h3 class="m-0 flex"> |  | ||||||
|                 {#if schema.hints.icon} |  | ||||||
|                   <Icon clss="w-6 h-6" icon={genIcon(value)} color={genColor(value)} /> |  | ||||||
|                 {/if} |  | ||||||
|                 {singular} |  | ||||||
|                 {i} |  | ||||||
| 
 |  | ||||||
|                 {#if schema.hints.title} |  | ||||||
|                   <div class="subtle ml-2"> |  | ||||||
|                     <Tr t={genTitle(value, singular, i)} /> |  | ||||||
|                   </div> |  | ||||||
|                 {/if} |  | ||||||
|               </h3> |  | ||||||
|               <button |  | ||||||
|                 class="h-fit w-fit rounded-full border border-black p-1" |  | ||||||
|                 on:click={() => { |  | ||||||
|                   del(i) |  | ||||||
|                 }} |  | ||||||
|               > |  | ||||||
|                 <TrashIcon class="h-4 w-4" /> |  | ||||||
|               </button> |  | ||||||
|             </div> |  | ||||||
|           {:else if typeof value === "string"} |  | ||||||
|             Builtin: <b>{value}</b> |  | ||||||
|           {:else} |  | ||||||
|             <Tr cls="font-bold" t={Translations.T(value?.question ?? value?.render)} /> |  | ||||||
|           {/if} |  | ||||||
|         </span> |  | ||||||
|         <div class="normal-background p-2"> |  | ||||||
|           {#if isTagRenderingBlock} |  | ||||||
|             <QuestionPreview {state} path={fusePath(i, [])} {schema}> |  | ||||||
|               <button |  | ||||||
|                 on:click={() => { |  | ||||||
|                   del(i) |  | ||||||
|                 }} |  | ||||||
|               > |  | ||||||
|                 <TrashIcon class="h-4 w-4" /> |  | ||||||
|                 Delete this question |  | ||||||
|               </button> |  | ||||||
| 
 |  | ||||||
|               {#if i > 0} |  | ||||||
|                 <button |  | ||||||
|                   on:click={() => { |  | ||||||
|                     moveTo(i, 0) |  | ||||||
|                   }} |  | ||||||
|                 > |  | ||||||
|                   Move to front |  | ||||||
|                 </button> |  | ||||||
| 
 |  | ||||||
|                 <button |  | ||||||
|                   on:click={() => { |  | ||||||
|                     swap(i, i - 1) |  | ||||||
|                   }} |  | ||||||
|                 > |  | ||||||
|                   Move up |  | ||||||
|                 </button> |  | ||||||
|               {/if} |  | ||||||
|               {#if i + 1 < $currentValue.length} |  | ||||||
|                 <button |  | ||||||
|                   on:click={() => { |  | ||||||
|                     swap(i, i + 1) |  | ||||||
|                   }} |  | ||||||
|                 > |  | ||||||
|                   Move down |  | ||||||
|                 </button> |  | ||||||
|                 <button |  | ||||||
|                   on:click={() => { |  | ||||||
|                     moveTo(i, $currentValue.length - 1) |  | ||||||
|                   }} |  | ||||||
|                 > |  | ||||||
|                   Move to back |  | ||||||
|                 </button> |  | ||||||
|               {/if} |  | ||||||
|             </QuestionPreview> |  | ||||||
|           {:else if schema.hints.types} |  | ||||||
|             <SchemaBasedMultiType {state} path={fusePath(i, [])} schema={schemaForMultitype()} /> |  | ||||||
|           {:else} |  | ||||||
|             {#each subparts as subpart} |  | ||||||
|               <SchemaBasedInput |  | ||||||
|                 {state} |  | ||||||
|                 path={fusePath(i, [subpart.path.at(-1)])} |  | ||||||
|                 schema={subpart} |  | ||||||
|               /> |  | ||||||
|             {/each} |  | ||||||
|           {/if} |  | ||||||
|         </div> |  | ||||||
|       </AccordionSingle> |  | ||||||
|     {/each} |     {/each} | ||||||
|  |     </Accordion> | ||||||
|   {/if} |   {/if} | ||||||
|   <div class="flex"> |   <div class="flex"> | ||||||
|     <button on:click={() => createItem()}>Add {article} {singular}</button> |     <button on:click={() => createItem()}>Add {article} {singular}</button> | ||||||
|  |  | ||||||
|  | @ -4,13 +4,13 @@ | ||||||
|   import TagRenderingEditable from "../Popup/TagRendering/TagRenderingEditable.svelte" |   import TagRenderingEditable from "../Popup/TagRendering/TagRenderingEditable.svelte" | ||||||
|   import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig" |   import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig" | ||||||
|   import type { QuestionableTagRenderingConfigJson } from "../../Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson" |   import type { QuestionableTagRenderingConfigJson } from "../../Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson" | ||||||
|   import EditLayerState from "./EditLayerState" |   import { EditJsonState } from "./EditLayerState" | ||||||
|   import { onDestroy } from "svelte" |   import { onDestroy } from "svelte" | ||||||
|   import type { JsonSchemaType } from "./jsonSchema" |   import type { JsonSchemaType } from "./jsonSchema" | ||||||
|   import { ConfigMetaUtils } from "./configMeta" |   import { ConfigMetaUtils } from "./configMeta" | ||||||
|   import ShowConversionMessage from "./ShowConversionMessage.svelte" |   import ShowConversionMessage from "./ShowConversionMessage.svelte" | ||||||
| 
 | 
 | ||||||
|   export let state: EditLayerState |   export let state: EditJsonState<any> | ||||||
|   export let path: (string | number)[] = [] |   export let path: (string | number)[] = [] | ||||||
|   export let schema: ConfigMeta |   export let schema: ConfigMeta | ||||||
|   export let startInEditModeIfUnset: boolean = schema.hints && !schema.hints.ifunset |   export let startInEditModeIfUnset: boolean = schema.hints && !schema.hints.ifunset | ||||||
|  |  | ||||||
|  | @ -1,14 +1,15 @@ | ||||||
| <script lang="ts"> | <script lang="ts"> | ||||||
|   import type { ConfigMeta } from "./configMeta" |   import type { ConfigMeta } from "./configMeta" | ||||||
|   import SchemaBasedField from "./SchemaBasedField.svelte" |   import SchemaBasedField from "./SchemaBasedField.svelte" | ||||||
|   import EditLayerState from "./EditLayerState" |   import { EditJsonState } from "./EditLayerState" | ||||||
|   import SchemaBasedArray from "./SchemaBasedArray.svelte" |   import SchemaBasedArray from "./SchemaBasedArray.svelte" | ||||||
|   import SchemaBasedMultiType from "./SchemaBasedMultiType.svelte" |   import SchemaBasedMultiType from "./SchemaBasedMultiType.svelte" | ||||||
|   import ArrayMultiAnswer from "./ArrayMultiAnswer.svelte" |   import ArrayMultiAnswer from "./ArrayMultiAnswer.svelte" | ||||||
| 
 | 
 | ||||||
|   export let schema: ConfigMeta |   export let state: EditJsonState<any> | ||||||
|   export let state: EditLayerState |  | ||||||
|   export let path: (string | number)[] = [] |   export let path: (string | number)[] = [] | ||||||
|  |   console.log("Fetched schema:", path, state.getSchema(<any> path)) | ||||||
|  |   let schema: ConfigMeta = state.getSchema(<any> path)[0] | ||||||
|   let expertMode = state.expertMode |   let expertMode = state.expertMode | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -204,9 +204,10 @@ | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
| <div class="m-1 flex flex-col gap-y-2 border-2 border-dashed border-gray-300 p-2"> | <div class="m-1 flex flex-col gap-y-2 border-2 border-dashed border-gray-300 p-2"> | ||||||
|   {#if schema.hints.title !== undefined} |   {#if schema.hints.title !== undefined && typeof path.at(-1) !== "number"} | ||||||
|     <h3>{schema.hints.title}</h3> |     <h3>{schema.hints.title}</h3> | ||||||
|     <div>{schema.description}</div> |     <div>{schema.description}</div> | ||||||
|  |     {path.join(".")} | ||||||
|   {/if} |   {/if} | ||||||
|   {#if hasOverride} |   {#if hasOverride} | ||||||
|     This object refers to {existingValue.builtin} and overrides some properties. This cannot be edited |     This object refers to {existingValue.builtin} and overrides some properties. This cannot be edited | ||||||
|  | @ -221,7 +222,6 @@ | ||||||
|         {#if $expertMode || subschema.hints?.group !== "expert"} |         {#if $expertMode || subschema.hints?.group !== "expert"} | ||||||
|           <SchemaBasedInput |           <SchemaBasedInput | ||||||
|             {state} |             {state} | ||||||
|             schema={subschema} |  | ||||||
|             path={[...subpath, subschema?.path?.at(-1) ?? "???"]} |             path={[...subpath, subschema?.path?.at(-1) ?? "???"]} | ||||||
|           /> |           /> | ||||||
|         {:else if window.location.hostname === "127.0.0.1"} |         {:else if window.location.hostname === "127.0.0.1"} | ||||||
|  |  | ||||||
|  | @ -42,9 +42,11 @@ | ||||||
|     undefined, |     undefined, | ||||||
|     "Used to complete the login" |     "Used to complete the login" | ||||||
|   ) |   ) | ||||||
|  |   const fakeUser = UIEventSource.asBoolean( QueryParameters.GetQueryParameter("fake-user", "Test switch for fake login")) | ||||||
|   let osmConnection = new OsmConnection({ |   let osmConnection = new OsmConnection({ | ||||||
|     oauth_token, |     oauth_token, | ||||||
|     checkOnlineRegularly: true, |     checkOnlineRegularly: true, | ||||||
|  |     fakeUser: fakeUser.data | ||||||
|   }) |   }) | ||||||
|   const expertMode = UIEventSource.asBoolean( |   const expertMode = UIEventSource.asBoolean( | ||||||
|     osmConnection.GetPreference("studio-expert-mode", "false", { |     osmConnection.GetPreference("studio-expert-mode", "false", { | ||||||
|  | @ -129,7 +131,7 @@ | ||||||
|   let showIntro = editLayerState.showIntro |   let showIntro = editLayerState.showIntro | ||||||
| 
 | 
 | ||||||
|   const layoutSchema: ConfigMeta[] = <any>layoutSchemaRaw |   const layoutSchema: ConfigMeta[] = <any>layoutSchemaRaw | ||||||
|   let editThemeState = new EditThemeState(layoutSchema, studio, { expertMode }) |   let editThemeState = new EditThemeState(layoutSchema, studio, osmConnection, { expertMode }) | ||||||
| 
 | 
 | ||||||
|   const version = meta.version |   const version = meta.version | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -335,6 +335,17 @@ textarea { | ||||||
|   color: black; |   color: black; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | h2.group { | ||||||
|  |     /* For flowbite accordions */ | ||||||
|  |     margin: 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .group button { | ||||||
|  |     /* For flowbite accordions */ | ||||||
|  | 
 | ||||||
|  |     border-radius: 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| /************************* OTHER CATEGORIES ********************************/ | /************************* OTHER CATEGORIES ********************************/ | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue