forked from MapComplete/MapComplete
		
	Add various improvements and fixes to studio, should fix #2055
This commit is contained in:
		
							parent
							
								
									b19d9ef077
								
							
						
					
					
						commit
						d1ec9a43fc
					
				
					 19 changed files with 532 additions and 419 deletions
				
			
		|  | @ -993,10 +993,6 @@ video { | |||
|   margin-right: 4rem; | ||||
| } | ||||
| 
 | ||||
| .mb-4 { | ||||
|   margin-bottom: 1rem; | ||||
| } | ||||
| 
 | ||||
| .mt-4 { | ||||
|   margin-top: 1rem; | ||||
| } | ||||
|  | @ -1029,6 +1025,10 @@ video { | |||
|   margin-right: 0.25rem; | ||||
| } | ||||
| 
 | ||||
| .mb-4 { | ||||
|   margin-bottom: 1rem; | ||||
| } | ||||
| 
 | ||||
| .ml-1 { | ||||
|   margin-left: 0.25rem; | ||||
| } | ||||
|  | @ -4686,6 +4686,16 @@ textarea { | |||
|   color: black; | ||||
| } | ||||
| 
 | ||||
| h2.group { | ||||
|   /* For flowbite accordions */ | ||||
|   margin: 0; | ||||
| } | ||||
| 
 | ||||
| .group button { | ||||
|   /* For flowbite accordions */ | ||||
|   border-radius: 0; | ||||
| } | ||||
| 
 | ||||
| /************************* OTHER CATEGORIES ********************************/ | ||||
| 
 | ||||
| /** | ||||
|  |  | |||
|  | @ -1,42 +1,14 @@ | |||
| import { Utils } from "../../Utils" | ||||
| /** This code is autogenerated - do not edit. Edit ./assets/layers/usersettings/usersettings.json instead */ | ||||
| export class ThemeMetaTagging { | ||||
|     public static readonly themeName = "usersettings" | ||||
|    public static readonly themeName = "usersettings" | ||||
| 
 | ||||
|     public metaTaggging_for_usersettings(feat: { properties: Record<string, string> }) { | ||||
|         Utils.AddLazyProperty(feat.properties, "_mastodon_candidate_md", () => | ||||
|             feat.properties._description | ||||
|                 .match(/\[[^\]]*\]\((.*(mastodon|en.osm.town).*)\).*/) | ||||
|                 ?.at(1) | ||||
|         ) | ||||
|         Utils.AddLazyProperty( | ||||
|             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" | ||||
|     } | ||||
| } | ||||
|    public metaTaggging_for_usersettings(feat: {properties: Record<string, string>}) { | ||||
|       Utils.AddLazyProperty(feat.properties, '_mastodon_candidate_md', () => feat.properties._description.match(/\[[^\]]*\]\((.*(mastodon|en.osm.town).*)\).*/)?.at(1) )  | ||||
|       Utils.AddLazyProperty(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> { | ||||
|     private readonly _step0: Conversion<TIn, TInter> | ||||
|     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})`) | ||||
|         this._step0 = step0 | ||||
|         this._step1 = step1 | ||||
|         this._failfast = failfast | ||||
|     } | ||||
| 
 | ||||
|     convert(json: TIn, context: ConversionContext): TOut { | ||||
|         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)) | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -21,6 +21,7 @@ import DependencyCalculator from "../DependencyCalculator" | |||
| import { AddContextToTranslations } from "./AddContextToTranslations" | ||||
| import ValidationUtils from "./ValidationUtils" | ||||
| import { ConversionContext } from "./ConversionContext" | ||||
| import { PrevalidateTheme } from "./Validation" | ||||
| 
 | ||||
| class SubstituteLayer extends Conversion<string | LayerConfigJson, LayerConfigJson[]> { | ||||
|     private readonly _state: DesugaringContext | ||||
|  | @ -664,7 +665,6 @@ export class PrepareTheme extends Fuse<LayoutConfigJson> { | |||
|     ) { | ||||
|         super( | ||||
|             "Fully prepares and expands a theme", | ||||
| 
 | ||||
|             new AddContextToTranslationsInLayout(), | ||||
|             new PreparePersonalTheme(state), | ||||
|             new WarnForUnsubstitutedLayersInTheme(), | ||||
|  |  | |||
|  | @ -36,7 +36,7 @@ class ValidateLanguageCompleteness extends DesugaringStep<LayoutConfig> { | |||
|         super( | ||||
|             "Checks that the given object is fully translated in the specified languages", | ||||
|             [], | ||||
|             "ValidateLanguageCompleteness" | ||||
|             "ValidateLanguageCompleteness", | ||||
|         ) | ||||
|         this._languages = languages ?? ["en"] | ||||
|     } | ||||
|  | @ -50,18 +50,18 @@ class ValidateLanguageCompleteness extends DesugaringStep<LayoutConfig> { | |||
|                 .filter( | ||||
|                     (t) => | ||||
|                         t.tr.translations[neededLanguage] === undefined && | ||||
|                         t.tr.translations["*"] === undefined | ||||
|                         t.tr.translations["*"] === undefined, | ||||
|                 ) | ||||
|                 .forEach((missing) => { | ||||
|                     context | ||||
|                         .enter(missing.context.split(".")) | ||||
|                         .err( | ||||
|                             `The theme ${obj.id} should be translation-complete for ` + | ||||
|                                 neededLanguage + | ||||
|                                 ", but it lacks a translation for " + | ||||
|                                 missing.context + | ||||
|                                 ".\n\tThe known translation is " + | ||||
|                                 missing.tr.textFor("en") | ||||
|                             neededLanguage + | ||||
|                             ", but it lacks a translation for " + | ||||
|                             missing.context + | ||||
|                             ".\n\tThe known translation is " + | ||||
|                             missing.tr.textFor("en"), | ||||
|                         ) | ||||
|                 }) | ||||
|         } | ||||
|  | @ -78,7 +78,7 @@ export class DoesImageExist extends DesugaringStep<string> { | |||
|     constructor( | ||||
|         knownImagePaths: Set<string>, | ||||
|         checkExistsSync: (path: string) => boolean = undefined, | ||||
|         ignore?: Set<string> | ||||
|         ignore?: Set<string>, | ||||
|     ) { | ||||
|         super("Checks if an image exists", [], "DoesImageExist") | ||||
|         this._ignore = ignore | ||||
|  | @ -114,15 +114,15 @@ export class DoesImageExist extends DesugaringStep<string> { | |||
|         if (!this._knownImagePaths.has(image)) { | ||||
|             if (this.doesPathExist === undefined) { | ||||
|                 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)) { | ||||
|                 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 { | ||||
|                 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, | ||||
|         path: string, | ||||
|         isBuiltin: boolean, | ||||
|         sharedTagRenderings?: Set<string> | ||||
|         sharedTagRenderings?: Set<string>, | ||||
|     ) { | ||||
|         super("Doesn't change anything, but emits warnings and errors", [], "ValidateTheme") | ||||
|         this._validateImage = doesImageExist | ||||
|  | @ -165,15 +165,15 @@ export class ValidateTheme extends DesugaringStep<LayoutConfigJson> { | |||
|                 if (json["units"] !== undefined) { | ||||
|                     context.err( | ||||
|                         "The theme " + | ||||
|                             json.id + | ||||
|                             " has units defined - these should be defined on the layer instead. (Hint: use overrideAll: { '+units': ... }) " | ||||
|                         json.id + | ||||
|                         " has units defined - these should be defined on the layer instead. (Hint: use overrideAll: { '+units': ... }) ", | ||||
|                     ) | ||||
|                 } | ||||
|                 if (json["roamingRenderings"] !== undefined) { | ||||
|                     context.err( | ||||
|                         "Theme " + | ||||
|                             json.id + | ||||
|                             " contains an old 'roamingRenderings'. Use an 'overrideAll' instead" | ||||
|                         json.id + | ||||
|                         " contains an old 'roamingRenderings'. Use an 'overrideAll' instead", | ||||
|                     ) | ||||
|                 } | ||||
|             } | ||||
|  | @ -191,10 +191,10 @@ export class ValidateTheme extends DesugaringStep<LayoutConfigJson> { | |||
|             for (const remoteImage of remoteImages) { | ||||
|                 context.err( | ||||
|                     "Found a remote image: " + | ||||
|                         remoteImage.path + | ||||
|                         " in theme " + | ||||
|                         json.id + | ||||
|                         ", please download it." | ||||
|                     remoteImage.path + | ||||
|                     " in theme " + | ||||
|                     json.id + | ||||
|                     ", please download it.", | ||||
|                 ) | ||||
|             } | ||||
|             for (const image of images) { | ||||
|  | @ -210,17 +210,17 @@ export class ValidateTheme extends DesugaringStep<LayoutConfigJson> { | |||
| 
 | ||||
|                 const filename = this._path.substring( | ||||
|                     this._path.lastIndexOf("/") + 1, | ||||
|                     this._path.length - 5 | ||||
|                     this._path.length - 5, | ||||
|                 ) | ||||
|                 if (theme.id !== filename) { | ||||
|                     context.err( | ||||
|                         "Theme ids should be the same as the name.json, but we got id: " + | ||||
|                             theme.id + | ||||
|                             " and filename " + | ||||
|                             filename + | ||||
|                             " (" + | ||||
|                             this._path + | ||||
|                             ")" | ||||
|                         theme.id + | ||||
|                         " and filename " + | ||||
|                         filename + | ||||
|                         " (" + | ||||
|                         this._path + | ||||
|                         ")", | ||||
|                     ) | ||||
|                 } | ||||
|                 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"])) | ||||
|             if (dups.length > 0) { | ||||
|                 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) { | ||||
|                 new ValidateLanguageCompleteness(...json["mustHaveLanguage"]).convert( | ||||
|                     theme, | ||||
|                     context | ||||
|                     context, | ||||
|                 ) | ||||
|             } | ||||
|             if (!json.hideFromOverview && theme.id !== "personal" && this._isBuiltin) { | ||||
|  | @ -242,7 +242,7 @@ export class ValidateTheme extends DesugaringStep<LayoutConfigJson> { | |||
|                 const targetLanguage = theme.title.SupportedLanguages()[0] | ||||
|                 if (targetLanguage !== "en") { | ||||
|                     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( | ||||
|                         `This layer ID is not known: ${backgroundId}. Perhaps you meant one of ${nearby | ||||
|                             .slice(0, 5) | ||||
|                             .join(", ")}` | ||||
|                             .join(", ")}`,
 | ||||
|                     ) | ||||
|             } | ||||
|         } | ||||
|  | @ -309,7 +309,7 @@ export class ValidateThemeAndLayers extends Fuse<LayoutConfigJson> { | |||
|         doesImageExist: DoesImageExist, | ||||
|         path: string, | ||||
|         isBuiltin: boolean, | ||||
|         sharedTagRenderings?: Set<string> | ||||
|         sharedTagRenderings?: Set<string>, | ||||
|     ) { | ||||
|         super( | ||||
|             "Validates a theme and the contained layers", | ||||
|  | @ -319,10 +319,10 @@ export class ValidateThemeAndLayers extends Fuse<LayoutConfigJson> { | |||
|                 new Each( | ||||
|                     new Bypass( | ||||
|                         (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( | ||||
|             "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)) { | ||||
|             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 === "") { | ||||
|             context.warn("Social image for theme " + json.id + " is the emtpy string") | ||||
|         } | ||||
|  | @ -406,7 +409,7 @@ class MiscThemeChecks extends DesugaringStep<LayoutConfigJson> { | |||
|             context | ||||
|                 .enter("overideAll") | ||||
|                 .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 | ||||
|  | @ -418,7 +421,7 @@ export class PrevalidateTheme extends Fuse<LayoutConfigJson> { | |||
|         super( | ||||
|             "Various consistency checks on the raw JSON", | ||||
|             new MiscThemeChecks(), | ||||
|             new OverrideShadowingCheck() | ||||
|             new OverrideShadowingCheck(), | ||||
|         ) | ||||
|     } | ||||
| } | ||||
|  | @ -428,7 +431,7 @@ export class DetectConflictingAddExtraTags extends DesugaringStep<TagRenderingCo | |||
|         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", | ||||
|             [], | ||||
|             "DetectConflictingAddExtraTags" | ||||
|             "DetectConflictingAddExtraTags", | ||||
|         ) | ||||
|     } | ||||
| 
 | ||||
|  | @ -455,7 +458,7 @@ export class DetectConflictingAddExtraTags extends DesugaringStep<TagRenderingCo | |||
|                         .enters("mappings", i) | ||||
|                         .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 " + | ||||
|                                 duplicateKeys.join(", ") | ||||
|                             duplicateKeys.join(", "), | ||||
|                         ) | ||||
|                 } | ||||
|             } | ||||
|  | @ -473,13 +476,13 @@ export class DetectNonErasedKeysInMappings extends DesugaringStep<QuestionableTa | |||
|         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", | ||||
|             [], | ||||
|             "DetectNonErasedKeysInMappings" | ||||
|             "DetectNonErasedKeysInMappings", | ||||
|         ) | ||||
|     } | ||||
| 
 | ||||
|     convert( | ||||
|         json: QuestionableTagRenderingConfigJson, | ||||
|         context: ConversionContext | ||||
|         context: ConversionContext, | ||||
|     ): QuestionableTagRenderingConfigJson { | ||||
|         if (json.multiAnswer) { | ||||
|             // No need to check this here, this has its own validation
 | ||||
|  | @ -533,8 +536,8 @@ export class DetectNonErasedKeysInMappings extends DesugaringStep<QuestionableTa | |||
|                         .enters("freeform") | ||||
|                         .warn( | ||||
|                             "The freeform block does not modify the key `" + | ||||
|                                 neededKey + | ||||
|                                 "` which is set in a mapping. Use `addExtraTags` to overwrite it" | ||||
|                             neededKey + | ||||
|                             "` which is set in a mapping. Use `addExtraTags` to overwrite it", | ||||
|                         ) | ||||
|                 } | ||||
|             } | ||||
|  | @ -552,8 +555,8 @@ export class DetectNonErasedKeysInMappings extends DesugaringStep<QuestionableTa | |||
|                         .enters("mappings", i) | ||||
|                         .warn( | ||||
|                             "This mapping does not modify the key `" + | ||||
|                                 neededKey + | ||||
|                                 "` which is set in a mapping or by the freeform block. Use `addExtraTags` to overwrite it" | ||||
|                             neededKey + | ||||
|                             "` 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( | ||||
|             "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 | ||||
|     } | ||||
|  | @ -642,7 +645,7 @@ export class DetectShadowedMappings extends DesugaringStep<TagRenderingConfigJso | |||
|      * DetectShadowedMappings.extractCalculatedTagNames({calculatedTags: ["_abc=js()"]}) // => ["_abc"]
 | ||||
|      */ | ||||
|     private static extractCalculatedTagNames( | ||||
|         layerConfig?: LayerConfigJson | { calculatedTags: string[] } | ||||
|         layerConfig?: LayerConfigJson | { calculatedTags: string[] }, | ||||
|     ) { | ||||
|         return ( | ||||
|             layerConfig?.calculatedTags?.map((ct) => { | ||||
|  | @ -728,16 +731,16 @@ export class DetectShadowedMappings extends DesugaringStep<TagRenderingConfigJso | |||
|                     json.mappings[i]["hideInAnswer"] !== true | ||||
|                 ) { | ||||
|                     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) { | ||||
|                     // The current mapping is shadowed!
 | ||||
|                     context.err(`Mapping ${i} is shadowed by mapping ${j} and will thus never be shown:
 | ||||
|     The mapping ${parsedConditions[i].asHumanString( | ||||
|         false, | ||||
|         false, | ||||
|         {} | ||||
|     )} is fully matched by a previous mapping (namely ${j}), which matches: | ||||
|                         false, | ||||
|                         false, | ||||
|                         {}, | ||||
|                     )} is fully matched by a previous mapping (namely ${j}), which matches: | ||||
|     ${parsedConditions[j].asHumanString(false, false, {})}. | ||||
| 
 | ||||
|     To fix this problem, you can try to: | ||||
|  | @ -764,7 +767,7 @@ export class DetectMappingsWithImages extends DesugaringStep<TagRenderingConfigJ | |||
|         super( | ||||
|             "Checks that 'then'clauses in mappings don't have images, but use 'icon' instead", | ||||
|             [], | ||||
|             "DetectMappingsWithImages" | ||||
|             "DetectMappingsWithImages", | ||||
|         ) | ||||
|         this._doesImageExist = doesImageExist | ||||
|     } | ||||
|  | @ -804,14 +807,14 @@ export class DetectMappingsWithImages extends DesugaringStep<TagRenderingConfigJ | |||
|                 if (!ignore) { | ||||
|                     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( | ||||
|                             ", " | ||||
|                         )}. (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 { | ||||
|                     ctx.info( | ||||
|                         `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) { | ||||
|  | @ -832,7 +835,7 @@ class ValidatePossibleLinks extends DesugaringStep<string | Record<string, strin | |||
|         super( | ||||
|             "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( | ||||
|         json: string | Record<string, string>, | ||||
|         context: ConversionContext | ||||
|         context: ConversionContext, | ||||
|     ): string | Record<string, string> { | ||||
|         if (typeof json === "string") { | ||||
|             if (this.isTabnabbingProne(json)) { | ||||
|                 context.err( | ||||
|                     "The string " + | ||||
|                         json + | ||||
|                         " has a link targeting `_blank`, but it doesn't have `rel='noopener'` set. This gives rise to reverse tabnapping" | ||||
|                     json + | ||||
|                     " has a link targeting `_blank`, but it doesn't have `rel='noopener'` set. This gives rise to reverse tabnapping", | ||||
|                 ) | ||||
|             } | ||||
|         } else { | ||||
|             for (const k in json) { | ||||
|                 if (this.isTabnabbingProne(json[k])) { | ||||
|                     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( | ||||
|             "Checks that a translation is valid and internally consistent", | ||||
|             ["*"], | ||||
|             "CheckTranslation" | ||||
|             "CheckTranslation", | ||||
|         ) | ||||
|         this._allowUndefined = allowUndefined | ||||
|     } | ||||
|  | @ -935,6 +938,7 @@ class CheckTranslation extends DesugaringStep<Translatable> { | |||
| 
 | ||||
| class MiscTagRenderingChecks extends DesugaringStep<TagRenderingConfigJson> { | ||||
|     private readonly _layerConfig: LayerConfigJson | ||||
| 
 | ||||
|     constructor(layerConfig?: LayerConfigJson) { | ||||
|         super("Miscellaneous checks on the tagrendering", ["special"], "MiscTagRenderingChecks") | ||||
|         this._layerConfig = layerConfig | ||||
|  | @ -942,17 +946,17 @@ class MiscTagRenderingChecks extends DesugaringStep<TagRenderingConfigJson> { | |||
| 
 | ||||
|     convert( | ||||
|         json: TagRenderingConfigJson | QuestionableTagRenderingConfigJson, | ||||
|         context: ConversionContext | ||||
|         context: ConversionContext, | ||||
|     ): TagRenderingConfigJson { | ||||
|         if (json["special"] !== undefined) { | ||||
|             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") { | ||||
|             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] | ||||
|                 CheckTranslation.noUndefined.convert( | ||||
|                     mapping.then, | ||||
|                     context.enters("mappings", i, "then") | ||||
|                     context.enters("mappings", i, "then"), | ||||
|                 ) | ||||
|                 if (!mapping.if) { | ||||
|                     console.log( | ||||
|  | @ -973,7 +977,7 @@ class MiscTagRenderingChecks extends DesugaringStep<TagRenderingConfigJson> { | |||
|                         "if", | ||||
|                         mapping.if, | ||||
|                         context.path.join("."), | ||||
|                         mapping.then | ||||
|                         mapping.then, | ||||
|                     ) | ||||
|                     context.enters("mappings", i, "if").err("No `if` is defined") | ||||
|                 } | ||||
|  | @ -983,7 +987,7 @@ class MiscTagRenderingChecks extends DesugaringStep<TagRenderingConfigJson> { | |||
|                             context | ||||
|                                 .enters("mappings", i, "addExtraTags", j) | ||||
|                                 .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 | ||||
|                         .enters("mappings", i, "then") | ||||
|                         .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"]) { | ||||
|             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) { | ||||
|             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) { | ||||
|  | @ -1015,7 +1019,7 @@ class MiscTagRenderingChecks extends DesugaringStep<TagRenderingConfigJson> { | |||
|             context | ||||
|                 .enter("questionHint") | ||||
|                 .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 | ||||
|                 .enters("icon", "size") | ||||
|                 .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") | ||||
|                     .err( | ||||
|                         "This tagRendering allows to set a value to key " + | ||||
|                             json.freeform.key + | ||||
|                             ", but does not define a `render`. Please, add a value here which contains `{" + | ||||
|                             json.freeform.key + | ||||
|                             "}`" | ||||
|                         json.freeform.key + | ||||
|                         ", but does not define a `render`. Please, add a value here which contains `{" + | ||||
|                         json.freeform.key + | ||||
|                         "}`", | ||||
|                     ) | ||||
|             } else { | ||||
|                 const render = new Translation(<any>json.render) | ||||
|  | @ -1067,7 +1071,7 @@ class MiscTagRenderingChecks extends DesugaringStep<TagRenderingConfigJson> { | |||
|                     const keyFirstArg = ["canonical", "fediverse_link", "translated"] | ||||
|                     if ( | ||||
|                         keyFirstArg.some( | ||||
|                             (funcName) => txt.indexOf(`{${funcName}(${json.freeform.key}`) >= 0 | ||||
|                             (funcName) => txt.indexOf(`{${funcName}(${json.freeform.key}`) >= 0, | ||||
|                         ) | ||||
|                     ) { | ||||
|                         continue | ||||
|  | @ -1091,16 +1095,16 @@ class MiscTagRenderingChecks extends DesugaringStep<TagRenderingConfigJson> { | |||
|                         context | ||||
|                             .enter("render") | ||||
|                             .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 | ||||
|                     } | ||||
| 
 | ||||
|                     if(txt.indexOf(json.freeform.key) >= 0 && txt.indexOf("{"+json.freeform.key+"}") < 0){ | ||||
|                     if (txt.indexOf(json.freeform.key) >= 0 && txt.indexOf("{" + json.freeform.key + "}") < 0) { | ||||
|                         context | ||||
|                             .enter("render") | ||||
|                             .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 | ||||
|                     } | ||||
|  | @ -1109,7 +1113,7 @@ class MiscTagRenderingChecks extends DesugaringStep<TagRenderingConfigJson> { | |||
|                     context | ||||
|                         .enter("render") | ||||
|                         .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}`, | ||||
|                         ) | ||||
|                 } | ||||
|             } | ||||
|  | @ -1124,22 +1128,22 @@ class MiscTagRenderingChecks extends DesugaringStep<TagRenderingConfigJson> { | |||
|                         .enters("freeform", "type") | ||||
|                         .err( | ||||
|                             "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") { | ||||
|                 context | ||||
|                     .enters("freeform", "type") | ||||
|                     .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) { | ||||
|             context.err( | ||||
|                 `Detected a tagrendering which takes input without freeform key in ${context}; the question is ${new Translation( | ||||
|                     json["question"] | ||||
|                 ).textFor("en")}` | ||||
|                     json["question"], | ||||
|                 ).textFor("en")}`,
 | ||||
|             ) | ||||
|         } | ||||
| 
 | ||||
|  | @ -1150,9 +1154,9 @@ class MiscTagRenderingChecks extends DesugaringStep<TagRenderingConfigJson> { | |||
|                     .enters("freeform", "type") | ||||
|                     .err( | ||||
|                         "Unknown type: " + | ||||
|                             freeformType + | ||||
|                             "; try one of " + | ||||
|                             Validators.availableTypes.join(", ") | ||||
|                         freeformType + | ||||
|                         "; try one of " + | ||||
|                         Validators.availableTypes.join(", "), | ||||
|                     ) | ||||
|             } | ||||
|         } | ||||
|  | @ -1190,7 +1194,7 @@ export class ValidateTagRenderings extends Fuse<TagRenderingConfigJson> { | |||
|             new On("question", new ValidatePossibleLinks()), | ||||
|             new On("questionHint", new ValidatePossibleLinks()), | ||||
|             new On("mappings", new Each(new On("then", new ValidatePossibleLinks()))), | ||||
|             new MiscTagRenderingChecks(layerConfig) | ||||
|             new MiscTagRenderingChecks(layerConfig), | ||||
|         ) | ||||
|     } | ||||
| } | ||||
|  | @ -1209,7 +1213,7 @@ export class PrevalidateLayer extends DesugaringStep<LayerConfigJson> { | |||
|         path: string, | ||||
|         isBuiltin: boolean, | ||||
|         doesImageExist: DoesImageExist, | ||||
|         studioValidations: boolean | ||||
|         studioValidations: boolean, | ||||
|     ) { | ||||
|         super("Runs various checks against common mistakes for a layer", [], "PrevalidateLayer") | ||||
|         this._path = path | ||||
|  | @ -1235,7 +1239,7 @@ export class PrevalidateLayer extends DesugaringStep<LayerConfigJson> { | |||
|             context | ||||
|                 .enter("source") | ||||
|                 .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 { | ||||
|             if (json.source === "special" || json.source === "special:library") { | ||||
|  | @ -1243,7 +1247,7 @@ export class PrevalidateLayer extends DesugaringStep<LayerConfigJson> { | |||
|                 context | ||||
|                     .enters("source", "osmTags") | ||||
|                     .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 { | ||||
|                 const osmTags = TagUtils.Tag(json.source["osmTags"], context + "source.osmTags") | ||||
|  | @ -1252,7 +1256,7 @@ export class PrevalidateLayer extends DesugaringStep<LayerConfigJson> { | |||
|                         .enters("source", "osmTags") | ||||
|                         .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" + | ||||
|                                 osmTags.asHumanString(false, false, {}) | ||||
|                             osmTags.asHumanString(false, false, {}), | ||||
|                         ) | ||||
|                 } | ||||
|             } | ||||
|  | @ -1278,10 +1282,10 @@ export class PrevalidateLayer extends DesugaringStep<LayerConfigJson> { | |||
|                 .enter("syncSelection") | ||||
|                 .err( | ||||
|                     "Invalid sync-selection: must be one of " + | ||||
|                         LayerConfig.syncSelectionAllowed.map((v) => `'${v}'`).join(", ") + | ||||
|                         " but got '" + | ||||
|                         json.syncSelection + | ||||
|                         "'" | ||||
|                     LayerConfig.syncSelectionAllowed.map((v) => `'${v}'`).join(", ") + | ||||
|                     " but got '" + | ||||
|                     json.syncSelection + | ||||
|                     "'", | ||||
|                 ) | ||||
|         } | ||||
|         if (json["pointRenderings"]?.length > 0) { | ||||
|  | @ -1300,7 +1304,7 @@ export class PrevalidateLayer extends DesugaringStep<LayerConfigJson> { | |||
|         } | ||||
| 
 | ||||
|         json.pointRendering?.forEach((pr, i) => | ||||
|             this._validatePointRendering.convert(pr, context.enters("pointeRendering", i)) | ||||
|             this._validatePointRendering.convert(pr, context.enters("pointeRendering", i)), | ||||
|         ) | ||||
| 
 | ||||
|         if (json["mapRendering"]) { | ||||
|  | @ -1317,8 +1321,8 @@ export class PrevalidateLayer extends DesugaringStep<LayerConfigJson> { | |||
|             if (!Constants.priviliged_layers.find((x) => x == json.id)) { | ||||
|                 context.err( | ||||
|                     "Layer " + | ||||
|                         json.id + | ||||
|                         " uses 'special' as source.osmTags. However, this layer is not a priviliged layer" | ||||
|                     json.id + | ||||
|                     " uses 'special' as source.osmTags. However, this layer is not a priviliged layer", | ||||
|                 ) | ||||
|             } | ||||
|         } | ||||
|  | @ -1333,19 +1337,19 @@ export class PrevalidateLayer extends DesugaringStep<LayerConfigJson> { | |||
|                 context | ||||
|                     .enter("title") | ||||
|                     .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) { | ||||
|                 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
 | ||||
|                 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++) { | ||||
|                     const tagRendering = json.tagRenderings[i] | ||||
|  | @ -1375,7 +1379,7 @@ export class PrevalidateLayer extends DesugaringStep<LayerConfigJson> { | |||
|         { | ||||
|             // duplicate ids in tagrenderings check
 | ||||
|             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) { | ||||
|                 // 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
 | ||||
|  | @ -1383,11 +1387,11 @@ export class PrevalidateLayer extends DesugaringStep<LayerConfigJson> { | |||
|                     .enter("tagRenderings") | ||||
|                     .err( | ||||
|                         "Some tagrenderings have a duplicate id: " + | ||||
|                             duplicates.join(", ") + | ||||
|                             "\n" + | ||||
|                             JSON.stringify( | ||||
|                                 json.tagRenderings.filter((tr) => duplicates.indexOf(tr["id"]) >= 0) | ||||
|                             ) | ||||
|                         duplicates.join(", ") + | ||||
|                         "\n" + | ||||
|                         JSON.stringify( | ||||
|                             json.tagRenderings.filter((tr) => duplicates.indexOf(tr["id"]) >= 0), | ||||
|                         ), | ||||
|                     ) | ||||
|             } | ||||
|         } | ||||
|  | @ -1420,8 +1424,8 @@ export class PrevalidateLayer extends DesugaringStep<LayerConfigJson> { | |||
|             if (json["overpassTags"] !== undefined) { | ||||
|                 context.err( | ||||
|                     "Layer " + | ||||
|                         json.id + | ||||
|                         'still uses the old \'overpassTags\'-format. Please use "source": {"osmTags": <tags>}\' instead of "overpassTags": <tags> (note: this isn\'t your fault, the custom theme generator still spits out the old format)' | ||||
|                     json.id + | ||||
|                     "still uses the old 'overpassTags'-format. Please use \"source\": {\"osmTags\": <tags>}' instead of \"overpassTags\": <tags> (note: this isn't your fault, the custom theme generator still spits out the old format)", | ||||
|                 ) | ||||
|             } | ||||
|             const forbiddenTopLevel = [ | ||||
|  | @ -1441,7 +1445,7 @@ export class PrevalidateLayer extends DesugaringStep<LayerConfigJson> { | |||
|             } | ||||
|             if (json["hideUnderlayingFeaturesMinPercentage"] !== undefined) { | ||||
|                 context.err( | ||||
|                     "Layer " + json.id + " contains an old 'hideUnderlayingFeaturesMinPercentage'" | ||||
|                     "Layer " + json.id + " contains an old 'hideUnderlayingFeaturesMinPercentage'", | ||||
|                 ) | ||||
|             } | ||||
| 
 | ||||
|  | @ -1458,9 +1462,9 @@ export class PrevalidateLayer extends DesugaringStep<LayerConfigJson> { | |||
|             if (this._path != undefined && this._path.indexOf(expected) < 0) { | ||||
|                 context.err( | ||||
|                     "Layer is in an incorrect place. The path is " + | ||||
|                         this._path + | ||||
|                         ", but expected " + | ||||
|                         expected | ||||
|                     this._path + | ||||
|                     ", but expected " + | ||||
|                     expected, | ||||
|                 ) | ||||
|             } | ||||
|         } | ||||
|  | @ -1478,13 +1482,13 @@ export class PrevalidateLayer extends DesugaringStep<LayerConfigJson> { | |||
|                     .enter(["tagRenderings", ...emptyIndexes]) | ||||
|                     .err( | ||||
|                         `Some tagrendering-ids are empty or have an emtpy string; this is not allowed (at ${emptyIndexes.join( | ||||
|                             "," | ||||
|                         )}])` | ||||
|                             ",", | ||||
|                         )}])`,
 | ||||
|                     ) | ||||
|             } | ||||
| 
 | ||||
|             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) { | ||||
|                 context | ||||
|  | @ -1508,7 +1512,7 @@ export class PrevalidateLayer extends DesugaringStep<LayerConfigJson> { | |||
|         if (json.tagRenderings !== undefined) { | ||||
|             new On( | ||||
|                 "tagRenderings", | ||||
|                 new Each(new ValidateTagRenderings(json, this._doesImageExist)) | ||||
|                 new Each(new ValidateTagRenderings(json, this._doesImageExist)), | ||||
|             ).convert(json, context) | ||||
|         } | ||||
| 
 | ||||
|  | @ -1535,7 +1539,7 @@ export class PrevalidateLayer extends DesugaringStep<LayerConfigJson> { | |||
|                         context | ||||
|                             .enters("pointRendering", i, "marker", indexM, "icon", "condition") | ||||
|                             .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.", | ||||
|                             ) | ||||
|                     } | ||||
|                 } | ||||
|  | @ -1573,9 +1577,9 @@ export class PrevalidateLayer extends DesugaringStep<LayerConfigJson> { | |||
|                         .enters("presets", i, "tags") | ||||
|                         .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: " + | ||||
|                                 tags.asHumanString(false, false, {}) + | ||||
|                                 "\n    The required tags are: " + | ||||
|                                 baseTags.asHumanString(false, false, {}) | ||||
|                             tags.asHumanString(false, false, {}) + | ||||
|                             "\n    The required tags are: " + | ||||
|                             baseTags.asHumanString(false, false, {}), | ||||
|                         ) | ||||
|                 } | ||||
|             } | ||||
|  | @ -1592,7 +1596,7 @@ export class ValidateLayerConfig extends DesugaringStep<LayerConfigJson> { | |||
|         isBuiltin: boolean, | ||||
|         doesImageExist: DoesImageExist, | ||||
|         studioValidations: boolean = false, | ||||
|         skipDefaultLayers: boolean = false | ||||
|         skipDefaultLayers: boolean = false, | ||||
|     ) { | ||||
|         super("Thin wrapper around 'ValidateLayer", [], "ValidateLayerConfig") | ||||
|         this.validator = new ValidateLayer( | ||||
|  | @ -1600,7 +1604,7 @@ export class ValidateLayerConfig extends DesugaringStep<LayerConfigJson> { | |||
|             isBuiltin, | ||||
|             doesImageExist, | ||||
|             studioValidations, | ||||
|             skipDefaultLayers | ||||
|             skipDefaultLayers, | ||||
|         ) | ||||
|     } | ||||
| 
 | ||||
|  | @ -1628,7 +1632,7 @@ class ValidatePointRendering extends DesugaringStep<PointRenderingConfigJson> { | |||
|             context | ||||
|                 .enter("markers") | ||||
|                 .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)) { | ||||
|  | @ -1638,7 +1642,7 @@ class ValidatePointRendering extends DesugaringStep<PointRenderingConfigJson> { | |||
|             context | ||||
|                 .enter("location") | ||||
|                 .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 | ||||
|  | @ -1657,26 +1661,26 @@ export class ValidateLayer extends Conversion< | |||
|         isBuiltin: boolean, | ||||
|         doesImageExist: DoesImageExist, | ||||
|         studioValidations: boolean = false, | ||||
|         skipDefaultLayers: boolean = false | ||||
|         skipDefaultLayers: boolean = false, | ||||
|     ) { | ||||
|         super("Doesn't change anything, but emits warnings and errors", [], "ValidateLayer") | ||||
|         this._prevalidation = new PrevalidateLayer( | ||||
|             path, | ||||
|             isBuiltin, | ||||
|             doesImageExist, | ||||
|             studioValidations | ||||
|             studioValidations, | ||||
|         ) | ||||
|         this._skipDefaultLayers = skipDefaultLayers | ||||
|     } | ||||
| 
 | ||||
|     convert( | ||||
|         json: LayerConfigJson, | ||||
|         context: ConversionContext | ||||
|         context: ConversionContext, | ||||
|     ): { parsed: LayerConfig; raw: LayerConfigJson } { | ||||
|         context = context.inOperation(this.name) | ||||
|         if (typeof json === "string") { | ||||
|             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 | ||||
|         } | ||||
|  | @ -1707,7 +1711,7 @@ export class ValidateLayer extends Conversion< | |||
|                 context | ||||
|                     .enters("calculatedTags", i) | ||||
|                     .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}`, | ||||
|                     ) | ||||
|             } | ||||
|         } | ||||
|  | @ -1755,7 +1759,7 @@ export class ValidateLayer extends Conversion< | |||
|             context | ||||
|                 .enters("allowMove", "enableAccuracy") | ||||
|                 .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", | ||||
|                 ) | ||||
|         } | ||||
| 
 | ||||
|  | @ -1786,8 +1790,8 @@ export class ValidateFilter extends DesugaringStep<FilterConfigJson> { | |||
|                         .enters("fields", i) | ||||
|                         .err( | ||||
|                             `Invalid filter: ${type} is not a valid textfield type.\n\tTry one of ${Array.from( | ||||
|                                 Validators.availableTypes | ||||
|                             ).join(",")}` | ||||
|                                 Validators.availableTypes, | ||||
|                             ).join(",")}`,
 | ||||
|                         ) | ||||
|                 } | ||||
|             } | ||||
|  | @ -1804,13 +1808,13 @@ export class DetectDuplicateFilters extends DesugaringStep<{ | |||
|         super( | ||||
|             "Tries to detect layers where a shared filter can be used (or where similar filters occur)", | ||||
|             [], | ||||
|             "DetectDuplicateFilters" | ||||
|             "DetectDuplicateFilters", | ||||
|         ) | ||||
|     } | ||||
| 
 | ||||
|     convert( | ||||
|         json: { layers: LayerConfigJson[]; themes: LayoutConfigJson[] }, | ||||
|         context: ConversionContext | ||||
|         context: ConversionContext, | ||||
|     ): { layers: LayerConfigJson[]; themes: LayoutConfigJson[] } { | ||||
|         const { layers, themes } = json | ||||
|         const perOsmTag = new Map< | ||||
|  | @ -1874,7 +1878,7 @@ export class DetectDuplicateFilters extends DesugaringStep<{ | |||
|                 filter: FilterConfigJson | ||||
|             }[] | ||||
|         >, | ||||
|         layout?: LayoutConfigJson | undefined | ||||
|         layout?: LayoutConfigJson | undefined, | ||||
|     ): void { | ||||
|         if (layer.filter === undefined || layer.filter === null) { | ||||
|             return | ||||
|  | @ -1914,7 +1918,7 @@ export class DetectDuplicatePresets extends DesugaringStep<LayoutConfig> { | |||
|         super( | ||||
|             "Detects mappings which have identical (english) names or identical mappings.", | ||||
|             ["presets"], | ||||
|             "DetectDuplicatePresets" | ||||
|             "DetectDuplicatePresets", | ||||
|         ) | ||||
|     } | ||||
| 
 | ||||
|  | @ -1925,13 +1929,13 @@ export class DetectDuplicatePresets extends DesugaringStep<LayoutConfig> { | |||
|         if (new Set(enNames).size != enNames.length) { | ||||
|             const dups = Utils.Duplicates(enNames) | ||||
|             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) | ||||
|             context.err( | ||||
|                 `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`, | ||||
|             ) | ||||
|         } | ||||
| 
 | ||||
|  | @ -1946,17 +1950,17 @@ export class DetectDuplicatePresets extends DesugaringStep<LayoutConfig> { | |||
|                     Utils.SameObject(presetATags, presetBTags) && | ||||
|                     Utils.sameList( | ||||
|                         presetA.preciseInput.snapToLayers, | ||||
|                         presetB.preciseInput.snapToLayers | ||||
|                         presetB.preciseInput.snapToLayers, | ||||
|                     ) | ||||
|                 ) { | ||||
|                     context.err( | ||||
|                         `This theme has multiple presets with the same tags: ${presetATags.asHumanString( | ||||
|                             false, | ||||
|                             false, | ||||
|                             {} | ||||
|                             {}, | ||||
|                         )}, namely the preset '${presets[i].title.textFor("en")}' and '${presets[ | ||||
|                             j | ||||
|                         ].title.textFor("en")}'` | ||||
|                             ].title.textFor("en")}'`,
 | ||||
|                     ) | ||||
|                 } | ||||
|             } | ||||
|  | @ -1981,13 +1985,13 @@ export class ValidateThemeEnsemble extends Conversion< | |||
|         super( | ||||
|             "Validates that all themes together are logical, i.e. no duplicate ids exists within (overriden) themes", | ||||
|             [], | ||||
|             "ValidateThemeEnsemble" | ||||
|             "ValidateThemeEnsemble", | ||||
|         ) | ||||
|     } | ||||
| 
 | ||||
|     convert( | ||||
|         json: LayoutConfig[], | ||||
|         context: ConversionContext | ||||
|         context: ConversionContext, | ||||
|     ): Map< | ||||
|         string, | ||||
|         { | ||||
|  | @ -2038,11 +2042,11 @@ export class ValidateThemeEnsemble extends Conversion< | |||
|                 context.err( | ||||
|                     [ | ||||
|                         "The layer with id '" + | ||||
|                             id + | ||||
|                             "' is found in multiple themes with different tag definitions:", | ||||
|                         id + | ||||
|                         "' is found in multiple themes with different tag definitions:", | ||||
|                         "\t In theme " + oldTheme + ":\t" + oldTags.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) | ||||
|      * | ||||
|      * Type: icon | ||||
|      * suggestions: return Constants.defaultPinIcons.map(i => ({if: "value="+i, then: i, icon: i})) | ||||
|      * group: basic | ||||
|      * | ||||
|      */ | ||||
|  | @ -156,6 +157,7 @@ export interface LayoutConfigJson { | |||
|      * type: layer[] | ||||
|      * types: hidden | layer | hidden | ||||
|      * 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})) | ||||
|      * | ||||
|      * A theme must contain at least one layer. | ||||
|  |  | |||
|  | @ -6,7 +6,7 @@ | |||
| 
 | ||||
| <Accordion> | ||||
|   <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" /> | ||||
|     </span> | ||||
|     <div class="low-interaction rounded-b p-2"> | ||||
|  |  | |||
|  | @ -373,7 +373,6 @@ | |||
|             {feedback} | ||||
|             {unit} | ||||
|             {state} | ||||
|             {extraTags} | ||||
|             feature={selectedElement} | ||||
|             value={freeformInput} | ||||
|             unvalidatedText={freeformInputUnvalidated} | ||||
|  | @ -418,7 +417,6 @@ | |||
|                   {feedback} | ||||
|                   {unit} | ||||
|                   {state} | ||||
|                   {extraTags} | ||||
|                   feature={selectedElement} | ||||
|                   value={freeformInput} | ||||
|                   unvalidatedText={freeformInputUnvalidated} | ||||
|  | @ -464,7 +462,6 @@ | |||
|                   {feedback} | ||||
|                   {unit} | ||||
|                   {state} | ||||
|                   {extraTags} | ||||
|                   feature={selectedElement} | ||||
|                   value={freeformInput} | ||||
|                   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, | ||||
| } from "../../Models/ThemeConfig/Conversion/Conversion" | ||||
| 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 { QuestionableTagRenderingConfigJson } from "../../Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson" | ||||
| import { TagUtils } from "../../Logic/Tags/TagUtils" | ||||
|  | @ -33,6 +33,8 @@ export abstract class EditJsonState<T> { | |||
|     public readonly schema: ConfigMeta[] | ||||
|     public readonly category: "layers" | "themes" | ||||
|     public readonly server: StudioServer | ||||
|     public readonly osmConnection: OsmConnection | ||||
| 
 | ||||
|     public readonly showIntro: UIEventSource<"no" | "intro" | "tagrenderings"> = <any>( | ||||
|         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 | ||||
|      */ | ||||
|     public readonly highlightedItem: UIEventSource<HighlightedTagRendering> = new UIEventSource( | ||||
|         undefined | ||||
|         undefined, | ||||
|     ) | ||||
|     private sendingUpdates = false | ||||
|     private readonly _stores = new Map<string, UIEventSource<any>>() | ||||
|  | @ -60,10 +62,12 @@ export abstract class EditJsonState<T> { | |||
|         schema: ConfigMeta[], | ||||
|         server: StudioServer, | ||||
|         category: "layers" | "themes", | ||||
|         osmConnection: OsmConnection, | ||||
|         options?: { | ||||
|             expertMode?: UIEventSource<boolean> | ||||
|         } | ||||
|         }, | ||||
|     ) { | ||||
|         this.osmConnection = osmConnection | ||||
|         this.schema = schema | ||||
|         this.server = server | ||||
|         this.category = category | ||||
|  | @ -88,6 +92,10 @@ export abstract class EditJsonState<T> { | |||
|                 await this.server.update(id, config, this.category) | ||||
|             }) | ||||
|         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) { | ||||
|  | @ -132,7 +140,7 @@ export abstract class EditJsonState<T> { | |||
|     public register( | ||||
|         path: ReadonlyArray<string | number>, | ||||
|         value: Store<any>, | ||||
|         noInitialSync: boolean = true | ||||
|         noInitialSync: boolean = true, | ||||
|     ): () => void { | ||||
|         const unsync = value.addCallback((v) => { | ||||
|             this.setValueAt(path, v) | ||||
|  | @ -146,7 +154,7 @@ export abstract class EditJsonState<T> { | |||
|     public getSchemaStartingWith(path: string[]) { | ||||
|         return this.schema.filter( | ||||
|             (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( | ||||
|             (sch) => | ||||
|                 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) { | ||||
|             console.warn("No schemas found for path", path.join(".")) | ||||
|  | @ -257,12 +265,12 @@ class ContextRewritingStep<T> extends Conversion<LayerConfigJson, T> { | |||
|     constructor( | ||||
|         state: DesugaringContext, | ||||
|         step: Conversion<LayerConfigJson, T>, | ||||
|         getTagRenderings: (t: T) => TagRenderingConfigJson[] | ||||
|         getTagRenderings: (t: T) => TagRenderingConfigJson[], | ||||
|     ) { | ||||
|         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", | ||||
|             [], | ||||
|             "ContextRewritingStep" | ||||
|             "ContextRewritingStep", | ||||
|         ) | ||||
|         this._state = state | ||||
|         this._step = step | ||||
|  | @ -272,7 +280,7 @@ class ContextRewritingStep<T> extends Conversion<LayerConfigJson, T> { | |||
|     convert(json: LayerConfigJson, context: ConversionContext): T { | ||||
|         const converted = this._step.convert(json, context) | ||||
|         const originalIds = json.tagRenderings?.map( | ||||
|             (tr) => (<QuestionableTagRenderingConfigJson>tr)["id"] | ||||
|             (tr) => (<QuestionableTagRenderingConfigJson>tr)["id"], | ||||
|         ) | ||||
|         if (!originalIds) { | ||||
|             return converted | ||||
|  | @ -307,7 +315,6 @@ class ContextRewritingStep<T> extends Conversion<LayerConfigJson, T> { | |||
| 
 | ||||
| export default class EditLayerState extends EditJsonState<LayerConfigJson> { | ||||
|     // Needed for the special visualisations
 | ||||
|     public readonly osmConnection: OsmConnection | ||||
|     public readonly imageUploadManager = { | ||||
|         getCountsFor() { | ||||
|             return 0 | ||||
|  | @ -335,10 +342,9 @@ export default class EditLayerState extends EditJsonState<LayerConfigJson> { | |||
|         schema: ConfigMeta[], | ||||
|         server: StudioServer, | ||||
|         osmConnection: OsmConnection, | ||||
|         options: { expertMode: UIEventSource<boolean> } | ||||
|         options: { expertMode: UIEventSource<boolean> }, | ||||
|     ) { | ||||
|         super(schema, server, "layers", options) | ||||
|         this.osmConnection = osmConnection | ||||
|         super(schema, server, "layers", osmConnection, options) | ||||
|         this.layout = { | ||||
|             getMatchingLayer: () => { | ||||
|                 try { | ||||
|  | @ -393,7 +399,7 @@ export default class EditLayerState extends EditJsonState<LayerConfigJson> { | |||
|         return new ContextRewritingStep( | ||||
|             state, | ||||
|             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( | ||||
|         configuration: Partial<LayerConfigJson> | ||||
|         configuration: Partial<LayerConfigJson>, | ||||
|     ): Promise<ConversionMessage[]> { | ||||
|         const layers = AllSharedLayers.getSharedLayersConfigs() | ||||
| 
 | ||||
|  | @ -456,16 +462,19 @@ export class EditThemeState extends EditJsonState<LayoutConfigJson> { | |||
|     constructor( | ||||
|         schema: ConfigMeta[], | ||||
|         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() | ||||
|     } | ||||
| 
 | ||||
|     protected buildValidation(state: DesugaringContext): Conversion<LayoutConfigJson, any> { | ||||
|         return new Pipe( | ||||
|             new PrepareTheme(state), | ||||
|             new ValidateTheme(undefined, "", false, new Set(state.tagRenderings.keys())) | ||||
|         return new Pipe(new PrevalidateTheme(), | ||||
|             new Pipe( | ||||
|                 new PrepareTheme(state), | ||||
|                 new ValidateTheme(undefined, "", false, new Set(state.tagRenderings.keys())), | ||||
|             ), true, | ||||
|         ) | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,11 +1,13 @@ | |||
| <script lang="ts"> | ||||
|   import type { ConfigMeta } from "./configMeta" | ||||
|   import EditLayerState from "./EditLayerState" | ||||
|   import EditLayerState, { EditJsonState } from "./EditLayerState" | ||||
|   import * as questions from "../../assets/generated/layers/questions.json" | ||||
|   import { ImmutableStore, Store } from "../../Logic/UIEventSource" | ||||
|   import TagRenderingEditable from "../Popup/TagRendering/TagRenderingEditable.svelte" | ||||
|   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 FromHtml from "../Base/FromHtml.svelte" | ||||
|   import ShowConversionMessage from "./ShowConversionMessage.svelte" | ||||
|  | @ -31,15 +33,25 @@ | |||
|     if (typeof x === "string") { | ||||
|       return perId[x] | ||||
|     } else { | ||||
|       return [x] | ||||
|       return <any>[x] | ||||
|     } | ||||
|   }) | ||||
|   let configs: Store<TagRenderingConfig[]> = configJson.map((configs) => { | ||||
|     if (!configs) { | ||||
|       return [{ error: "No configuartions found" }] | ||||
|     } | ||||
|     console.log("Regenerating configs") | ||||
|     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 { | ||||
|         return new TagRenderingConfig(config) | ||||
|       } 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 | ||||
| 
 | ||||
|  | @ -66,11 +69,18 @@ | |||
| 
 | ||||
| <div class="flex"> | ||||
|   <div class="m-4 flex w-full flex-col"> | ||||
|     <NextButton clss="primary" on:click={() => state.highlightedItem.setData({ path, schema })}> | ||||
|       {#if schema.hints.question} | ||||
|         {schema.hints.question} | ||||
|       {/if} | ||||
|     </NextButton> | ||||
|     {#if $configJson.some(config => config["builtin"] !== undefined)} | ||||
|       <div class="interactive p-2 rounded-2xl"> | ||||
|         This question uses an advanced 'builtin'+'override' construction in the source code. | ||||
|         Editing this with Studio is not supported. | ||||
|       </div> | ||||
|     {:else} | ||||
|       <NextButton clss="primary" on:click={() => state.highlightedItem.setData({ path, schema })}> | ||||
|         {#if schema.hints.question} | ||||
|           {schema.hints.question} | ||||
|         {/if} | ||||
|       </NextButton> | ||||
|     {/if} | ||||
|     {#if description} | ||||
|       <Markdown src={description} /> | ||||
|     {/if} | ||||
|  | @ -86,6 +96,7 @@ | |||
|     {#each $configs as config} | ||||
|       {#if config.error !== undefined} | ||||
|         <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)} | ||||
|         This tagRendering is currently not shown. It will appear if the feature matches the | ||||
|         condition | ||||
|  |  | |||
|  | @ -8,6 +8,15 @@ | |||
| 
 | ||||
|   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 monaco: typeof Monaco | ||||
|   let editor: Monaco.editor.IStandaloneCodeEditor | ||||
|  | @ -34,13 +43,19 @@ | |||
|     return () => window.removeEventListener("keydown", handler) | ||||
|   }) | ||||
| 
 | ||||
|   let useFallback = false | ||||
|   onMount(async () => { | ||||
|     const monacoEditor = await import("monaco-editor") | ||||
|     loader.config({ | ||||
|       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 | ||||
|     let schemaUri: string | ||||
|  | @ -50,7 +65,7 @@ | |||
|       schemaUri = "https://mapcomplete.org/schemas/layoutconfig.json" | ||||
|     } | ||||
| 
 | ||||
|     monaco.languages.json.jsonDefaults.setDiagnosticsOptions({ | ||||
|     monaco?.languages?.json?.jsonDefaults?.setDiagnosticsOptions({ | ||||
|       validate: true, | ||||
|       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 | ||||
|     model = monaco.editor.createModel( | ||||
|       JSON.stringify(state.configuration.data, null, "  "), | ||||
|       "json", | ||||
|       modelUri | ||||
|     ) | ||||
|     try { | ||||
|       model = monaco?.editor?.createModel( | ||||
|         JSON.stringify(state.configuration.data, null, "  "), | ||||
|         "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, | ||||
|       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 | ||||
|     let timeout: number | ||||
|     editor.onDidChangeModelContent(() => { | ||||
|     editor?.onDidChangeModelContent(() => { | ||||
|       clearTimeout(timeout) | ||||
|       timeout = setTimeout(() => { | ||||
|         save() | ||||
|  | @ -98,4 +118,8 @@ | |||
|   }) | ||||
| </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 | ||||
|    */ | ||||
|   import type { ConfigMeta } from "./configMeta" | ||||
|   import EditLayerState from "./EditLayerState" | ||||
|   import EditLayerState, { EditJsonState } from "./EditLayerState" | ||||
|   import SchemaBasedInput from "./SchemaBasedInput.svelte" | ||||
|   import AccordionSingle from "../Flowbite/AccordionSingle.svelte" | ||||
|   import { Utils } from "../../Utils" | ||||
| 
 | ||||
|   export let state: EditLayerState | ||||
|   export let configs: ConfigMeta[] | ||||
|  | @ -30,8 +31,8 @@ | |||
|     <div slot="header">{title}</div> | ||||
|     <div class="flex w-full flex-col gap-y-1 pl-2"> | ||||
|       <slot name="description" /> | ||||
|       {#each configsFiltered as config} | ||||
|         <SchemaBasedInput {state} path={config.path} schema={config} /> | ||||
|       {#each configsFiltered as config (config.path)} | ||||
|         <SchemaBasedInput {state} path={config.path} } /> | ||||
|       {/each} | ||||
|     </div> | ||||
|   </AccordionSingle> | ||||
|  |  | |||
|  | @ -1,24 +1,21 @@ | |||
| <script lang="ts"> | ||||
|   import EditLayerState from "./EditLayerState" | ||||
|   import { EditJsonState } from "./EditLayerState" | ||||
|   import type { ConfigMeta } from "./configMeta" | ||||
|   import { UIEventSource } from "../../Logic/UIEventSource" | ||||
|   import SchemaBasedInput from "./SchemaBasedInput.svelte" | ||||
|   import SchemaBasedField from "./SchemaBasedField.svelte" | ||||
|   import { TrashIcon } from "@babeard/svelte-heroicons/mini" | ||||
|   import QuestionPreview from "./QuestionPreview.svelte" | ||||
|   import SchemaBasedMultiType from "./SchemaBasedMultiType.svelte" | ||||
|   import ShowConversionMessage from "./ShowConversionMessage.svelte" | ||||
|   import Markdown from "../Base/Markdown.svelte" | ||||
|   import AccordionSingle from "../Flowbite/AccordionSingle.svelte" | ||||
|   import Icon from "../Map/Icon.svelte" | ||||
|   import Tr from "../Base/Tr.svelte" | ||||
|   import Translations from "../i18n/Translations" | ||||
|   import { Utils } from "../../Utils" | ||||
|   import type { QuestionableTagRenderingConfigJson } from "../../Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson" | ||||
|   import CollapsedTagRenderingPreview from "./CollapsedTagRenderingPreview.svelte" | ||||
|   import { Accordion } from "flowbite-svelte" | ||||
| 
 | ||||
|   export let state: EditLayerState | ||||
|   export let state: EditJsonState<any> | ||||
|   export let path: (string | number)[] = [] | ||||
|   export let schema: ConfigMeta | ||||
| 
 | ||||
|   schema = Utils.Clone(schema) | ||||
|   let title = schema.path.at(-1) | ||||
|   console.log(">>>", schema) | ||||
|   let singular = title | ||||
|   if (title?.endsWith("s")) { | ||||
|     singular = title.slice(0, title.length - 1) | ||||
|  | @ -27,7 +24,6 @@ | |||
|   if (singular?.match(/^[aeoui]/)) { | ||||
|     article = "an" | ||||
|   } | ||||
|   export let path: (string | number)[] = [] | ||||
| 
 | ||||
|   const isTagRenderingBlock = path.length === 1 && path[0] === "tagRenderings" | ||||
| 
 | ||||
|  | @ -41,12 +37,12 @@ | |||
|     .filter((part) => part.path.length - 1 === schema.path.length) | ||||
|   let messages = state.messagesFor(path) | ||||
| 
 | ||||
|   const currentValue: UIEventSource<any[]> = state.getStoreFor(path) | ||||
|   const currentValue = state.getStoreFor<(string | QuestionableTagRenderingConfigJson)[]>(path) | ||||
|   if (currentValue.data === undefined) { | ||||
|     currentValue.setData([]) | ||||
|   } | ||||
| 
 | ||||
|   function createItem(valueToSet?: any) { | ||||
|   function createItem(valueToSet?: string | QuestionableTagRenderingConfigJson) { | ||||
|     if (currentValue.data === undefined) { | ||||
|       currentValue.setData([]) | ||||
|     } | ||||
|  | @ -72,64 +68,13 @@ | |||
|     return newPath | ||||
|   } | ||||
| 
 | ||||
|   function schemaForMultitype() { | ||||
|     const sch = { ...schema } | ||||
|     sch.hints.typehint = undefined | ||||
|     return sch | ||||
|   } | ||||
| 
 | ||||
|   function del(i: number) { | ||||
|     currentValue.data.splice(i, 1) | ||||
|     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> | ||||
| 
 | ||||
| <div class="pl-2"> | ||||
|  | @ -163,99 +108,11 @@ | |||
|       </div> | ||||
|     {/each} | ||||
|   {:else} | ||||
|     {#each $currentValue as value, i} | ||||
|       <AccordionSingle expanded={false}> | ||||
|         <span slot="header"> | ||||
|           {#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> | ||||
|     <Accordion> | ||||
|     {#each $currentValue as value, i  (value)} | ||||
|       <CollapsedTagRenderingPreview {state} {isTagRenderingBlock} {schema} {currentValue} {value} {i} {singular} path={fusePath(i, [])}/> | ||||
|     {/each} | ||||
|     </Accordion> | ||||
|   {/if} | ||||
|   <div class="flex"> | ||||
|     <button on:click={() => createItem()}>Add {article} {singular}</button> | ||||
|  |  | |||
|  | @ -4,13 +4,13 @@ | |||
|   import TagRenderingEditable from "../Popup/TagRendering/TagRenderingEditable.svelte" | ||||
|   import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig" | ||||
|   import type { QuestionableTagRenderingConfigJson } from "../../Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson" | ||||
|   import EditLayerState from "./EditLayerState" | ||||
|   import { EditJsonState } from "./EditLayerState" | ||||
|   import { onDestroy } from "svelte" | ||||
|   import type { JsonSchemaType } from "./jsonSchema" | ||||
|   import { ConfigMetaUtils } from "./configMeta" | ||||
|   import ShowConversionMessage from "./ShowConversionMessage.svelte" | ||||
| 
 | ||||
|   export let state: EditLayerState | ||||
|   export let state: EditJsonState<any> | ||||
|   export let path: (string | number)[] = [] | ||||
|   export let schema: ConfigMeta | ||||
|   export let startInEditModeIfUnset: boolean = schema.hints && !schema.hints.ifunset | ||||
|  |  | |||
|  | @ -1,14 +1,15 @@ | |||
| <script lang="ts"> | ||||
|   import type { ConfigMeta } from "./configMeta" | ||||
|   import SchemaBasedField from "./SchemaBasedField.svelte" | ||||
|   import EditLayerState from "./EditLayerState" | ||||
|   import { EditJsonState } from "./EditLayerState" | ||||
|   import SchemaBasedArray from "./SchemaBasedArray.svelte" | ||||
|   import SchemaBasedMultiType from "./SchemaBasedMultiType.svelte" | ||||
|   import ArrayMultiAnswer from "./ArrayMultiAnswer.svelte" | ||||
| 
 | ||||
|   export let schema: ConfigMeta | ||||
|   export let state: EditLayerState | ||||
|   export let state: EditJsonState<any> | ||||
|   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 | ||||
| </script> | ||||
| 
 | ||||
|  |  | |||
|  | @ -204,9 +204,10 @@ | |||
| </script> | ||||
| 
 | ||||
| <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> | ||||
|     <div>{schema.description}</div> | ||||
|     {path.join(".")} | ||||
|   {/if} | ||||
|   {#if hasOverride} | ||||
|     This object refers to {existingValue.builtin} and overrides some properties. This cannot be edited | ||||
|  | @ -221,7 +222,6 @@ | |||
|         {#if $expertMode || subschema.hints?.group !== "expert"} | ||||
|           <SchemaBasedInput | ||||
|             {state} | ||||
|             schema={subschema} | ||||
|             path={[...subpath, subschema?.path?.at(-1) ?? "???"]} | ||||
|           /> | ||||
|         {:else if window.location.hostname === "127.0.0.1"} | ||||
|  |  | |||
|  | @ -42,9 +42,11 @@ | |||
|     undefined, | ||||
|     "Used to complete the login" | ||||
|   ) | ||||
|   const fakeUser = UIEventSource.asBoolean( QueryParameters.GetQueryParameter("fake-user", "Test switch for fake login")) | ||||
|   let osmConnection = new OsmConnection({ | ||||
|     oauth_token, | ||||
|     checkOnlineRegularly: true, | ||||
|     fakeUser: fakeUser.data | ||||
|   }) | ||||
|   const expertMode = UIEventSource.asBoolean( | ||||
|     osmConnection.GetPreference("studio-expert-mode", "false", { | ||||
|  | @ -129,7 +131,7 @@ | |||
|   let showIntro = editLayerState.showIntro | ||||
| 
 | ||||
|   const layoutSchema: ConfigMeta[] = <any>layoutSchemaRaw | ||||
|   let editThemeState = new EditThemeState(layoutSchema, studio, { expertMode }) | ||||
|   let editThemeState = new EditThemeState(layoutSchema, studio, osmConnection, { expertMode }) | ||||
| 
 | ||||
|   const version = meta.version | ||||
| 
 | ||||
|  |  | |||
|  | @ -335,6 +335,17 @@ textarea { | |||
|   color: black; | ||||
| } | ||||
| 
 | ||||
| h2.group { | ||||
|     /* For flowbite accordions */ | ||||
|     margin: 0; | ||||
| } | ||||
| 
 | ||||
| .group button { | ||||
|     /* For flowbite accordions */ | ||||
| 
 | ||||
|     border-radius: 0; | ||||
| } | ||||
| 
 | ||||
| /************************* OTHER CATEGORIES ********************************/ | ||||
| 
 | ||||
| /** | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue