forked from MapComplete/MapComplete
		
	Better tag rewriting, add icons, add bicycle rental theme
This commit is contained in:
		
							parent
							
								
									1dcb3897e4
								
							
						
					
					
						commit
						9594868e83
					
				
					 23 changed files with 389 additions and 117 deletions
				
			
		|  | @ -49,8 +49,26 @@ export default class SharedTagRenderings { | |||
|                 return | ||||
|             } | ||||
|             value.id = value.id ?? key; | ||||
|             if(value["builtin"] !== undefined){ | ||||
|                 if(value["override"] == undefined){ | ||||
|                     throw "HUH? Why whould you want to reuse a builtin if one doesn't override? In questions.json/"+key | ||||
|                 } | ||||
|                 if(typeof value["builtin"] !== "string"){ | ||||
|                     return; | ||||
|                 } | ||||
|                 // This is a really funny situation: we extend another tagRendering!
 | ||||
|                 const parent = Utils.Clone(dict.get(value["builtin"])) | ||||
|                 delete parent.id | ||||
|                 Utils.Merge(value["override"], parent) | ||||
|                 delete value["builtin"] | ||||
|                 delete value["override"] | ||||
|                 for (const pkey in parent) { | ||||
|                     value[pkey] = parent[pkey] | ||||
|                 } | ||||
|             } | ||||
|         }) | ||||
| 
 | ||||
|          | ||||
|         return dict; | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
|  | @ -16,6 +16,7 @@ import SharedTagRenderings from "../Customizations/SharedTagRenderings"; | |||
| import * as known_layers from "../assets/generated/known_layers.json" | ||||
| import {LayoutConfigJson} from "../Models/ThemeConfig/Json/LayoutConfigJson"; | ||||
| import {PrepareTheme} from "../Models/ThemeConfig/Conversion/PrepareTheme"; | ||||
| import {Layer} from "leaflet"; | ||||
| 
 | ||||
| export default class DetermineLayout { | ||||
| 
 | ||||
|  | @ -134,9 +135,9 @@ export default class DetermineLayout { | |||
| 
 | ||||
|     private static prepCustomTheme(json: any): LayoutConfigJson { | ||||
|         const knownLayersDict = new Map<string, LayerConfigJson>() | ||||
|         for (const key in known_layers["default"]) { | ||||
|             const layer = known_layers["default"][key] | ||||
|             knownLayersDict.set(layer.id, layer) | ||||
|         for (const key in known_layers.layers) { | ||||
|             const layer = known_layers.layers[key] | ||||
|             knownLayersDict.set(layer.id,<LayerConfigJson> layer) | ||||
|         } | ||||
|         const converState = { | ||||
|             tagRenderings: SharedTagRenderings.SharedTagRenderingJson, | ||||
|  |  | |||
|  | @ -2,6 +2,8 @@ import {Conversion, DesugaringContext, Fuse, OnEveryConcat, SetDefault} from "./ | |||
| import {LayerConfigJson} from "../Json/LayerConfigJson"; | ||||
| import {TagRenderingConfigJson} from "../Json/TagRenderingConfigJson"; | ||||
| import {Utils} from "../../../Utils"; | ||||
| import Translations from "../../../UI/i18n/Translations"; | ||||
| import {Translation} from "../../../UI/i18n/Translation"; | ||||
| 
 | ||||
| class ExpandTagRendering extends Conversion<string | TagRenderingConfigJson | { builtin: string | string[], override: any }, TagRenderingConfigJson[]> { | ||||
|     constructor() { | ||||
|  | @ -156,13 +158,36 @@ class ExpandGroupRewrite extends Conversion<{ | |||
|         } | ||||
|         let config = <{ | ||||
|             rewrite: | ||||
|                 { sourceString: string; into: string[] }[]; | ||||
|                 { sourceString: string[]; into: (string | any)[][] }; | ||||
|             renderings: (string | { builtin: string; override: any } | TagRenderingConfigJson)[] | ||||
|         }>json; | ||||
| 
 | ||||
|         { | ||||
|             const errors = [] | ||||
|    | ||||
|             const expectedLength = config.rewrite.sourceString.length | ||||
|             for (let i = 0; i < config.rewrite.into.length; i++){ | ||||
|                 const targets = config.rewrite.into[i]; | ||||
|                 if(targets.length !== expectedLength){ | ||||
|                     errors.push(context+".rewrite.into["+i+"]: expected "+expectedLength+" values, but got "+targets.length) | ||||
|                 } | ||||
|                 if(typeof targets[0] !== "string"){ | ||||
|                     errors.push(context+".rewrite.into["+i+"]: expected a string as first rewrite value values, but got "+targets[0]) | ||||
| 
 | ||||
|         const subRenderingsRes = ExpandGroupRewrite.expandSubTagRenderings.convertAll(state, config.renderings, context); | ||||
|         const subRenderings: TagRenderingConfigJson[] = [].concat(subRenderingsRes.result); | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             if (errors.length > 0) { | ||||
|                 return { | ||||
|                     errors, | ||||
|                     warnings: [], | ||||
|                     result: undefined | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         const subRenderingsRes = <{ result: TagRenderingConfigJson[][], errors, warnings }> ExpandGroupRewrite.expandSubTagRenderings.convertAll(state, config.renderings, context); | ||||
|         const subRenderings: TagRenderingConfigJson[] = [].concat(...subRenderingsRes.result); | ||||
|         const errors = subRenderingsRes.errors; | ||||
|         const warnings = subRenderingsRes.warnings; | ||||
| 
 | ||||
|  | @ -170,22 +195,31 @@ class ExpandGroupRewrite extends Conversion<{ | |||
|         const rewrittenPerGroup = new Map<string, TagRenderingConfigJson[]>() | ||||
| 
 | ||||
|         // The actual rewriting
 | ||||
|         for (const rewrite of config.rewrite) { | ||||
|             const source = rewrite.sourceString; | ||||
|             for (const target of rewrite.into) { | ||||
|                 const groupName = target; | ||||
|                 const trs: TagRenderingConfigJson[] = [] | ||||
|         const sourceStrings = config.rewrite.sourceString; | ||||
|         for (const targets of config.rewrite.into) { | ||||
|             const groupName = targets[0]; | ||||
|             if(typeof groupName !== "string"){ | ||||
|                 throw "The first string of 'targets' should always be a string" | ||||
|             } | ||||
|             const trs: TagRenderingConfigJson[] = [] | ||||
| 
 | ||||
|                 for (const tr of subRenderings) { | ||||
|                     trs.push(this.prepConfig(source, target, tr)) | ||||
|             for (const tr of subRenderings) { | ||||
|                 let rewritten = tr; | ||||
|                 for (let i = 0; i < sourceStrings.length; i++) { | ||||
|                     const source = sourceStrings[i] | ||||
|                     const target = targets[i] // This is a string OR a translation
 | ||||
|                     console.log("Replacing every "+source+" with "+JSON.stringify(target)) | ||||
|                     rewritten = this.prepConfig(source, target, rewritten) | ||||
|                 } | ||||
|                 if (rewrittenPerGroup.has(groupName)) { | ||||
|                     rewrittenPerGroup.get(groupName).push(...trs) | ||||
|                 rewritten.group = rewritten.group ?? groupName | ||||
|                 trs.push(rewritten) | ||||
|             } | ||||
| 
 | ||||
|                 } else { | ||||
|                     rewrittenPerGroup.set(groupName, trs) | ||||
|             if (rewrittenPerGroup.has(groupName)) { | ||||
|                 rewrittenPerGroup.get(groupName).push(...trs) | ||||
|             } else { | ||||
|                 rewrittenPerGroup.set(groupName, trs) | ||||
| 
 | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|  | @ -201,7 +235,7 @@ class ExpandGroupRewrite extends Conversion<{ | |||
|         rewrittenPerGroup.forEach((group, _) => { | ||||
|             group.forEach(tr => { | ||||
|                 if (tr.id === undefined || tr.id === "") { | ||||
|                     errors.push("A tagrendering has an empty ID after expanding the tag") | ||||
|                     errors.push("A tagrendering has an empty ID after expanding the tag; the tagrendering is: "+JSON.stringify(tr)) | ||||
|                 } | ||||
|             }) | ||||
|         }) | ||||
|  | @ -212,16 +246,26 @@ class ExpandGroupRewrite extends Conversion<{ | |||
|         }; | ||||
|     } | ||||
| 
 | ||||
|     /* Used for left|right group creation and replacement */ | ||||
|     private prepConfig(keyToRewrite: string, target: string, tr: TagRenderingConfigJson) { | ||||
|     /* Used for left|right group creation and replacement. | ||||
|     * Every 'keyToRewrite' will be replaced with 'target' recursively. This substitution will happen in place in the object 'tr' */ | ||||
|     private prepConfig(keyToRewrite: string, target: string | any, tr: TagRenderingConfigJson): TagRenderingConfigJson { | ||||
| 
 | ||||
|         const isTranslation = typeof target !== "string" | ||||
| 
 | ||||
|         function replaceRecursive(transl: string | any) { | ||||
|             if (typeof transl === "string") { | ||||
|                 // This is a simple string - we do a simple replace
 | ||||
|                 return transl.replace(keyToRewrite, target) | ||||
|             } | ||||
|             if (transl.map !== undefined) { | ||||
|                 // This is a list of items
 | ||||
|                 return transl.map(o => replaceRecursive(o)) | ||||
|             } | ||||
| 
 | ||||
|             if (Translations.isProbablyATranslation(transl) && isTranslation) { | ||||
|                 return Translations.T(transl).Fuse(new Translation(target), keyToRewrite).translations | ||||
|             } | ||||
| 
 | ||||
|             transl = {...transl} | ||||
|             for (const key in transl) { | ||||
|                 transl[key] = replaceRecursive(transl[key]) | ||||
|  | @ -229,12 +273,7 @@ class ExpandGroupRewrite extends Conversion<{ | |||
|             return transl | ||||
|         } | ||||
| 
 | ||||
|         const orig = tr; | ||||
|         tr = replaceRecursive(tr) | ||||
| 
 | ||||
|         tr.id = target + "-" + orig.id | ||||
|         tr.group = target | ||||
|         return tr | ||||
|         return replaceRecursive(tr) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -225,9 +225,9 @@ export interface LayerConfigJson { | |||
|      */ | ||||
|     tagRenderings?: (string | { builtin: string, override: any } | TagRenderingConfigJson | { | ||||
|         rewrite: { | ||||
|             sourceString: string, | ||||
|             into: string[] | ||||
|         }[], | ||||
|             sourceString: string[], | ||||
|             into: (string | any)[][] | ||||
|         }, | ||||
|         renderings: (string | { builtin: string, override: any } | TagRenderingConfigJson)[] | ||||
|     }) [], | ||||
| 
 | ||||
|  |  | |||
|  | @ -106,6 +106,10 @@ export interface TagRenderingConfigJson { | |||
|          * If not known yet, the user will be presented with `then` as an option | ||||
|          */ | ||||
|         then: string | any, | ||||
|         /** | ||||
|          * An icon supporting this mapping; typically shown pretty small | ||||
|          */ | ||||
|         icon?: string | ||||
|         /** | ||||
|          * In some cases, multiple taggings exist (e.g. a default assumption, or a commonly mapped abbreviation and a fully written variation). | ||||
|          * | ||||
|  |  | |||
|  | @ -41,7 +41,8 @@ export default class TagRenderingConfig { | |||
|     readonly mappings?: { | ||||
|         readonly if: TagsFilter, | ||||
|         readonly ifnot?: TagsFilter, | ||||
|         readonly then: Translation | ||||
|         readonly then: Translation, | ||||
|         readonly icon: string, | ||||
|         readonly hideInAnswer: boolean | TagsFilter | ||||
|         readonly addExtraTags: Tag[] | ||||
|     }[] | ||||
|  | @ -163,11 +164,16 @@ export default class TagRenderingConfig { | |||
|                 } else if (mapping.hideInAnswer !== undefined) { | ||||
|                     hideInAnswer = TagUtils.Tag(mapping.hideInAnswer, `${context}.mapping[${i}].hideInAnswer`); | ||||
|                 } | ||||
|                 let icon = undefined; | ||||
|                 if(mapping.icon !== ""){ | ||||
|                     icon = mapping.icon | ||||
|                 } | ||||
|                 const mp = { | ||||
|                     if: TagUtils.Tag(mapping.if, `${ctx}.if`), | ||||
|                     ifnot: (mapping.ifnot !== undefined ? TagUtils.Tag(mapping.ifnot, `${ctx}.ifnot`) : undefined), | ||||
|                     then: Translations.T(mapping.then, `${ctx}.then`), | ||||
|                     hideInAnswer: hideInAnswer, | ||||
|                     hideInAnswer, | ||||
|                     icon, | ||||
|                     addExtraTags: (mapping.addExtraTags ?? []).map((str, j) => TagUtils.SimpleTag(str, `${ctx}.addExtraTags[${j}]`)) | ||||
|                 }; | ||||
|                 if (this.question) { | ||||
|  | @ -329,18 +335,18 @@ export default class TagRenderingConfig { | |||
|      * @param tags | ||||
|      * @constructor | ||||
|      */ | ||||
|     public GetRenderValues(tags: any): Translation[] { | ||||
|     public GetRenderValues(tags: any): {then: Translation, icon?: string}[] { | ||||
|         if (!this.multiAnswer) { | ||||
|             return [this.GetRenderValue(tags)] | ||||
|             return [this.GetRenderValueWithImage(tags)] | ||||
|         } | ||||
| 
 | ||||
|         // A flag to check that the freeform key isn't matched multiple times 
 | ||||
|         // If it is undefined, it is "used" already, or at least we don't have to check for it anymore
 | ||||
|         let freeformKeyUsed = this.freeform?.key === undefined; | ||||
|         // We run over all the mappings first, to check if the mapping matches
 | ||||
|         const applicableMappings: Translation[] = Utils.NoNull((this.mappings ?? [])?.map(mapping => { | ||||
|         const applicableMappings: {then: Translation, img?: string}[] = Utils.NoNull((this.mappings ?? [])?.map(mapping => { | ||||
|             if (mapping.if === undefined) { | ||||
|                 return mapping.then; | ||||
|                 return mapping; | ||||
|             } | ||||
|             if (TagUtils.MatchesMultiAnswer(mapping.if, tags)) { | ||||
|                 if (!freeformKeyUsed) { | ||||
|  | @ -349,7 +355,7 @@ export default class TagRenderingConfig { | |||
|                         freeformKeyUsed = true; | ||||
|                     } | ||||
|                 } | ||||
|                 return mapping.then; | ||||
|                 return mapping; | ||||
|             } | ||||
|             return undefined; | ||||
|         })) | ||||
|  | @ -357,43 +363,42 @@ export default class TagRenderingConfig { | |||
| 
 | ||||
|         if (!freeformKeyUsed | ||||
|             && tags[this.freeform.key] !== undefined) { | ||||
|             applicableMappings.push(this.render) | ||||
|             applicableMappings.push({then: this.render}) | ||||
|         } | ||||
|         return applicableMappings | ||||
|     } | ||||
| 
 | ||||
|     public GetRenderValue(tags: any, defltValue: any = undefined): Translation { | ||||
|         return this.GetRenderValueWithImage(tags, defltValue).then | ||||
|     } | ||||
|     /** | ||||
|      * Gets the correct rendering value (or undefined if not known) | ||||
|      * Not compatible with multiAnswer - use GetRenderValueS instead in that case | ||||
|      * @constructor | ||||
|      */ | ||||
|     public GetRenderValue(tags: any, defltValue: any = undefined): Translation { | ||||
|     public GetRenderValueWithImage(tags: any, defltValue: any = undefined): { then: Translation, icon?: string } { | ||||
|         if (this.mappings !== undefined && !this.multiAnswer) { | ||||
|             for (const mapping of this.mappings) { | ||||
|                 if (mapping.if === undefined) { | ||||
|                     return mapping.then; | ||||
|                     return mapping; | ||||
|                 } | ||||
|                 if (mapping.if.matchesProperties(tags)) { | ||||
|                     if (this.id === "uk_addresses_placename") { | ||||
|                         console.log("Matched", mapping.if, "with ", tags["addr:place"]) | ||||
|                     } | ||||
|                     return mapping.then; | ||||
|                     return mapping; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         if (this.id === "questions") { | ||||
|             return this.render | ||||
|         if (this.id === "questions" || | ||||
|             this.freeform?.key === undefined || | ||||
|             tags[this.freeform.key] !== undefined | ||||
|         ) { | ||||
|             return {then: this.render} | ||||
|         } | ||||
| 
 | ||||
|         if (this.freeform?.key === undefined) { | ||||
|             return this.render; | ||||
|         } | ||||
| 
 | ||||
|         if (tags[this.freeform.key] !== undefined) { | ||||
|             return this.render; | ||||
|         } | ||||
|         return defltValue; | ||||
|         return {then: defltValue}; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|  |  | |||
|  | @ -26,7 +26,6 @@ export default class TableOfContents extends Combine { | |||
|         let els: { level: number, content: BaseUIElement }[] = [] | ||||
|         for (const title of titles) { | ||||
|             let content: BaseUIElement | ||||
|             console.log("Constructing content for ", title) | ||||
|             if (title.title instanceof Translation) { | ||||
|                 content = title.title.Clone() | ||||
|             } else if (title.title instanceof FixedUiElement) { | ||||
|  |  | |||
|  | @ -5,6 +5,8 @@ import {VariableUiElement} from "../Base/VariableUIElement"; | |||
| import List from "../Base/List"; | ||||
| import {SubstitutedTranslation} from "../SubstitutedTranslation"; | ||||
| import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig"; | ||||
| import Combine from "../Base/Combine"; | ||||
| import Img from "../Base/Img"; | ||||
| 
 | ||||
| /*** | ||||
|  * Displays the correct value for a known tagrendering | ||||
|  | @ -38,11 +40,17 @@ export default class TagRenderingAnswer extends VariableUiElement { | |||
|                 return undefined; | ||||
|             } | ||||
| 
 | ||||
|             const valuesToRender: BaseUIElement[] = trs.map(tr => new SubstitutedTranslation(tr, tagsSource, state, options?.specialViz)) | ||||
|             const valuesToRender: BaseUIElement[] = trs.map(tr => { | ||||
|                 const text = new SubstitutedTranslation(tr.then, tagsSource, state, options?.specialViz); | ||||
|                 if(tr.icon === undefined){ | ||||
|                     return text | ||||
|                 } | ||||
|                 return new Combine([new Img(tr.icon).SetClass("w-6 max-h-6 pr-2"), text]).SetClass("flex") | ||||
|             }) | ||||
|             if (valuesToRender.length === 1) { | ||||
|                 return valuesToRender[0]; | ||||
|             } else if (valuesToRender.length > 1) { | ||||
|                 return new List(valuesToRender) | ||||
|                 return new Combine(valuesToRender).SetClass("flex flex-col") | ||||
|             } | ||||
|             return undefined; | ||||
|         }).map((element: BaseUIElement) => element?.SetClass(contentClasses)?.SetStyle(contentStyle))) | ||||
|  |  | |||
|  | @ -26,6 +26,10 @@ import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig"; | |||
| import {Unit} from "../../Models/Unit"; | ||||
| import VariableInputElement from "../Input/VariableInputElement"; | ||||
| import Toggle from "../Input/Toggle"; | ||||
| import Img from "../Base/Img"; | ||||
| import {flattenEach, tag} from "@turf/turf"; | ||||
| import FeaturePipeline from "../../Logic/FeatureSource/FeaturePipeline"; | ||||
| import FeaturePipelineState from "../../Logic/State/FeaturePipelineState"; | ||||
| 
 | ||||
| /** | ||||
|  * Shows the question element. | ||||
|  | @ -343,7 +347,8 @@ export default class TagRenderingQuestion extends Combine { | |||
|         mapping: { | ||||
|             if: TagsFilter, | ||||
|             then: Translation, | ||||
|             addExtraTags: Tag[] | ||||
|             addExtraTags: Tag[], | ||||
|             img?: string | ||||
|         }, ifNot?: TagsFilter[]): InputElement<TagsFilter> { | ||||
| 
 | ||||
|         let tagging: TagsFilter = mapping.if; | ||||
|  | @ -354,11 +359,23 @@ export default class TagRenderingQuestion extends Combine { | |||
|             tagging = new And([tagging, ...mapping.addExtraTags]) | ||||
|         } | ||||
| 
 | ||||
|       | ||||
|         return new FixedInputElement( | ||||
|             new SubstitutedTranslation(mapping.then, tagsSource, state), | ||||
|             TagRenderingQuestion.GenerateMappingContent(mapping, tagsSource, state)  , | ||||
|             tagging, | ||||
|             (t0, t1) => t1.isEquivalent(t0)); | ||||
|     } | ||||
|      | ||||
|     private static GenerateMappingContent(  mapping: { | ||||
|         then: Translation, | ||||
|         icon?: string | ||||
|     }, tagsSource: UIEventSource<any>, state: FeaturePipelineState): BaseUIElement{ | ||||
|         const text =  new SubstitutedTranslation(mapping.then, tagsSource, state) | ||||
|         if (mapping.icon === undefined) { | ||||
|             return text; | ||||
|         } | ||||
|         return new Combine([new Img(mapping.icon).SetClass("w-6 max-h-6 pr-2"), text]).SetClass("flex") | ||||
|     } | ||||
| 
 | ||||
|     private static GenerateFreeform(state, configuration: TagRenderingConfig, applicableUnit: Unit, tags: UIEventSource<any>): InputElement<TagsFilter> { | ||||
|         const freeform = configuration.freeform; | ||||
|  |  | |||
|  | @ -142,6 +142,33 @@ export class Translation extends BaseUIElement { | |||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      *  | ||||
|      * Given a translation such as `{en: "How much of bicycle_types are rented here}` (which is this translation) | ||||
|      * and a translation object `{ en: "electrical bikes" }`, plus the translation specification `bicycle_types`, will return  | ||||
|      * a new translation: | ||||
|      * `{en: "How much electrical bikes are rented here?"}` | ||||
|      *  | ||||
|      * @param translationObject | ||||
|      * @param stringToReplace | ||||
|      * @constructor | ||||
|      */ | ||||
|     public Fuse(translationObject: Translation, stringToReplace: string): Translation{ | ||||
|         const translations = this.translations | ||||
|         const newTranslations = {} | ||||
|         for (const lang in translations) { | ||||
|             const target = translationObject.textFor(lang) | ||||
|             if(target === undefined){ | ||||
|                 continue | ||||
|             } | ||||
|             if(typeof target !== "string"){ | ||||
|                 throw "Invalid object in Translation.fuse: translationObject['"+lang+"'] is not a string, it is: "+JSON.stringify(target) | ||||
|             } | ||||
|             newTranslations[lang] = this.translations[lang].replaceAll(stringToReplace, target) | ||||
|         } | ||||
|         return new Translation(newTranslations) | ||||
|     } | ||||
| 
 | ||||
|     public replace(a: string, b: string) { | ||||
|         if (a.startsWith("{") && a.endsWith("}")) { | ||||
|             a = a.substr(1, a.length - 2); | ||||
|  |  | |||
|  | @ -2,11 +2,11 @@ import {FixedUiElement} from "../Base/FixedUiElement"; | |||
| import AllTranslationAssets from "../../AllTranslationAssets"; | ||||
| import {Translation} from "./Translation"; | ||||
| import BaseUIElement from "../BaseUIElement"; | ||||
| 
 | ||||
| import * as known_languages from "../../assets/generated/used_languages.json" | ||||
| export default class Translations { | ||||
| 
 | ||||
|     static t = AllTranslationAssets.t; | ||||
| 
 | ||||
|     private static knownLanguages = new Set(known_languages.languages) | ||||
|     constructor() { | ||||
|         throw "Translations is static. If you want to intitialize a new translation, use the singular form" | ||||
|     } | ||||
|  | @ -89,4 +89,11 @@ export default class Translations { | |||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     static isProbablyATranslation(transl: any) { | ||||
|         if(typeof transl !== "object"){ | ||||
|             return false; | ||||
|         } | ||||
|         // is a weird key found?
 | ||||
|         return !Object.keys(transl).some(key => !this.knownLanguages.has(key)) | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -2975,32 +2975,14 @@ | |||
|     }, | ||||
|     { | ||||
|       "id": "payment-options", | ||||
|       "builtin": "payment-options", | ||||
|       "builtin": "payment-options-advanced", | ||||
|       "override": { | ||||
|         "condition": { | ||||
|           "or": [ | ||||
|             "fee=yes", | ||||
|             "charge~*" | ||||
|           ] | ||||
|         }, | ||||
|         "mappings+": [ | ||||
|           { | ||||
|             "if": "payment:app=yes", | ||||
|             "ifnot": "payment:app=no", | ||||
|             "then": { | ||||
|               "en": "Payment is done using a dedicated app", | ||||
|               "nl": "Betalen via een app van het netwerk" | ||||
|             } | ||||
|           }, | ||||
|           { | ||||
|             "if": "payment:membership_card=yes", | ||||
|             "ifnot": "payment:membership_card=no", | ||||
|             "then": { | ||||
|               "en": "Payment is done using a membership card", | ||||
|               "nl": "Betalen via een lidkaart van het netwerk" | ||||
|             } | ||||
|           } | ||||
|         ] | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     { | ||||
|  |  | |||
|  | @ -260,32 +260,14 @@ | |||
|     }, | ||||
|     { | ||||
|       "id": "payment-options", | ||||
|       "builtin": "payment-options", | ||||
|       "builtin": "payment-options-advanced", | ||||
|       "override": { | ||||
|         "condition": { | ||||
|           "or": [ | ||||
|             "fee=yes", | ||||
|             "charge~*" | ||||
|           ] | ||||
|         }, | ||||
|         "mappings+": [ | ||||
|           { | ||||
|             "if": "payment:app=yes", | ||||
|             "ifnot": "payment:app=no", | ||||
|             "then": { | ||||
|               "en": "Payment is done using a dedicated app", | ||||
|               "nl": "Betalen via een app van het netwerk" | ||||
|             } | ||||
|           }, | ||||
|           { | ||||
|             "if": "payment:membership_card=yes", | ||||
|             "ifnot": "payment:membership_card=no", | ||||
|             "then": { | ||||
|               "en": "Payment is done using a membership card", | ||||
|               "nl": "Betalen via een lidkaart van het netwerk" | ||||
|             } | ||||
|           } | ||||
|         ] | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     { | ||||
|  |  | |||
|  | @ -31,6 +31,7 @@ | |||
|     "de": "Die Ebene zeigt Picknicktische an" | ||||
|   }, | ||||
|   "tagRenderings": [ | ||||
|     "images", | ||||
|     { | ||||
|       "question": { | ||||
|         "en": "What material is this picnic table made of?", | ||||
|  |  | |||
							
								
								
									
										7
									
								
								assets/svg/cash.svg
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								assets/svg/cash.svg
									
										
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| After Width: | Height: | Size: 6.3 KiB | 
|  | @ -141,6 +141,16 @@ | |||
|       "https://commons.wikimedia.org/wiki/File:Camera_font_awesome.svg" | ||||
|     ] | ||||
|   }, | ||||
|   { | ||||
|     "path": "cash.svg", | ||||
|     "license": "CC-BY 3.0", | ||||
|     "authors": [ | ||||
|       "Online Web Fonts" | ||||
|     ], | ||||
|     "sources": [ | ||||
|       "https://www.onlinewebfonts.com/icon/464494" | ||||
|     ] | ||||
|   }, | ||||
|   { | ||||
|     "path": "checkbox-empty.svg", | ||||
|     "license": "CC0", | ||||
|  | @ -1109,6 +1119,16 @@ | |||
|     "authors": [], | ||||
|     "sources": [] | ||||
|   }, | ||||
|   { | ||||
|     "path": "smartphone.svg", | ||||
|     "license": "CC-BY 3.0", | ||||
|     "authors": [ | ||||
|       "To Uyen" | ||||
|     ], | ||||
|     "sources": [ | ||||
|       "https://commons.wikimedia.org/wiki/File:Smartphone_icon_-_Noun_Project_283536.svg" | ||||
|     ] | ||||
|   }, | ||||
|   { | ||||
|     "path": "speech_bubble.svg", | ||||
|     "license": "CC-BY 4.0", | ||||
|  |  | |||
							
								
								
									
										4
									
								
								assets/svg/smartphone.svg
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								assets/svg/smartphone.svg
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,4 @@ | |||
| <?xml version="1.0"?> | ||||
| <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"> | ||||
| <path d="m33.975 95h32.05c5.565 0 10.08-4.279 10.08-9.569v-70.855c0-5.285-4.513-9.576-10.07-9.576h-32.05c-5.564 0-10.08 4.291-10.08 9.576v70.854c0 5.288 4.513 9.57 10.08 9.57m16.03-3.257c-2.493 0-4.506-2.02-4.506-4.498 0-2.489 2.01-4.502 4.506-4.502 2.487 0 4.494 2.01 4.494 4.502 0 2.481-2.01 4.498-4.494 4.498m-6.868-80.911h13.727c.556 0 1.01.383 1.01.852 0 .471-.452.854-1.01.854h-13.727c-.55 0-1.01-.383-1.01-.854-.0001-.469.454-.852 1.01-.852m-15.289 10.798c0-.794.675-1.438 1.508-1.438h41.29c.835 0 1.507.641 1.507 1.438v56.49c0 .791-.672 1.433-1.507 1.433h-41.29c-.833 0-1.508-.642-1.508-1.433v-56.49"/> | ||||
| </svg> | ||||
| After Width: | Height: | Size: 702 B | 
|  | @ -558,6 +558,7 @@ | |||
|       { | ||||
|         "if": "payment:cash=yes", | ||||
|         "ifnot": "payment:cash=no", | ||||
|         "icon": "./assets/svg/cash.svg", | ||||
|         "then": { | ||||
|           "en": "Cash is accepted here", | ||||
|           "nl": "Cash geld wordt hier aanvaard", | ||||
|  | @ -579,6 +580,7 @@ | |||
|       { | ||||
|         "if": "payment:cards=yes", | ||||
|         "ifnot": "payment:cards=no", | ||||
|         "icon": "./assets/svg/payment_card.svg", | ||||
|         "then": { | ||||
|           "en": "Payment cards are accepted here", | ||||
|           "nl": "Betalen met bankkaarten kan hier", | ||||
|  | @ -599,6 +601,31 @@ | |||
|       } | ||||
|     ] | ||||
|   }, | ||||
|   "payment-options-advanced": { | ||||
|     "builtin": "payment-options", | ||||
|     "override": { | ||||
|       "mappings+": [ | ||||
|         { | ||||
|           "if": "payment:app=yes", | ||||
|           "ifnot": "payment:app=no", | ||||
|           "icon": "./assets/svg/smartphone.svg", | ||||
|           "then": { | ||||
|             "en": "Payment is done using a dedicated app", | ||||
|             "nl": "Betalen via een app van het netwerk" | ||||
|           } | ||||
|         }, | ||||
|         { | ||||
|           "if": "payment:membership_card=yes", | ||||
|           "ifnot": "payment:membership_card=no", | ||||
|           "icon": "./assets/svg/nfc_card.svg", | ||||
|           "then": { | ||||
|             "en": "Payment is done using a membership card", | ||||
|             "nl": "Betalen via een lidkaart van het netwerk" | ||||
|           } | ||||
|         } | ||||
|       ] | ||||
|     } | ||||
|   }, | ||||
|   "last_edit": { | ||||
|     "#": "Gives some metainfo about the last edit and who did edit it - rendering only", | ||||
|     "condition": "_last_edit:contributor~*", | ||||
|  |  | |||
|  | @ -171,19 +171,36 @@ | |||
|             { | ||||
|               "if": "rental=city_bike", | ||||
|               "then": { | ||||
|                 "en": "Normal city bikes can be rented here" | ||||
|                 "en": "Normal city bikes can be rented here", | ||||
|                 "nl": "Gewone stadsfietsen kunnen hier gehuurd worden" | ||||
|               } | ||||
|             }, | ||||
|             { | ||||
|               "if": "rental=ebike", | ||||
|               "then": { | ||||
|                 "en": "Electrical bikes can be rented here" | ||||
|                 "en": "Electrical bikes can be rented here", | ||||
|                 "nl": "Elektrische fietsen kunnen hier gehuurd worden" | ||||
|               } | ||||
|             }, | ||||
|             { | ||||
|               "if": "rental=bmx", | ||||
|               "then": { | ||||
|                 "en": "BMX bikes can be rented here" | ||||
|                 "en": "BMX bikes can be rented here", | ||||
|                 "nl": "BMX-fietsen kunnen hier gehuurd worden" | ||||
|               } | ||||
|             }, | ||||
|             { | ||||
|               "if": "rental=mtb", | ||||
|               "then": { | ||||
|                 "en": "Mountainbikes can be rented here", | ||||
|                 "nl": "Mountainbikes kunnen hier gehuurd worden" | ||||
|               } | ||||
|             }, | ||||
|             { | ||||
|               "if": "rental=kid_bike", | ||||
|               "then": { | ||||
|                 "en": "Bikes for childs can be rented here", | ||||
|                 "nl": "Kinderfietsen kunnen hier gehuurd worden" | ||||
|               } | ||||
|             } | ||||
|           ] | ||||
|  | @ -192,26 +209,70 @@ | |||
|           "rewrite": { | ||||
|             "sourceString": [ | ||||
|               "bicycle_type", | ||||
|               "bicycle_types" | ||||
|               "type_plural" | ||||
|             ], | ||||
|             "into": [ | ||||
|               [ | ||||
|                 "city_bike", | ||||
|                 "city bikes" | ||||
|                 { | ||||
|                   "en": "city bikes", | ||||
|                   "nl": "Stadsfietsen" | ||||
|                 } | ||||
|               ], | ||||
|               [ | ||||
|                 "ebike", | ||||
|                 "electrical bikes" | ||||
|                 { | ||||
|                   "en": "electrical bikes", | ||||
|                   "nl": "elektrische fietsen" | ||||
|                 } | ||||
|               ], | ||||
|               "bmx" | ||||
|               [ | ||||
|                 "kid_bike", | ||||
|                 { | ||||
|                   "en": "bikes for children", | ||||
|                   "nl": "Kinderfietsen" | ||||
|                 } | ||||
|               ], | ||||
|               [ | ||||
|                 "bmx", | ||||
|                 { | ||||
|                   "en": "BMX bikes", | ||||
|                   "nl": "BMX-fietsen" | ||||
|                 } | ||||
|               ], | ||||
|               [ | ||||
|                 "mtb", | ||||
|                 { | ||||
|                   "en": "mountainbike", | ||||
|                   "nl": "mountainbike" | ||||
|                 } | ||||
|               ], | ||||
|               [ | ||||
|                 "bicycle_pannier", | ||||
|                 { | ||||
|                   "en": "bicycle panniers", | ||||
|                   "nl": "Fietstassen" | ||||
|                 } | ||||
|               ] | ||||
|             ] | ||||
|           }, | ||||
|           "renderings": [ | ||||
|             { | ||||
|               "id": "rental-capacity-bicycle-type", | ||||
|               "group": "", | ||||
|               "question": { | ||||
|                 "en": "How much bicycle_type ", | ||||
|                 "nl": "Hoeveel " | ||||
|               } | ||||
|                 "en": "How much type_plural can be rented here? ", | ||||
|                 "nl": "Hoeveel type_plural kunnen hier uitgeleend worden?" | ||||
|               }, | ||||
|               "render": { | ||||
|                 "en": "{capacity:bicycle_type} type_plural can be rented here", | ||||
|                 "nl": "{capacity:bicycle_type} type_plural kunnen hier uitgeleend worden" | ||||
|               }, | ||||
|               "freeform": { | ||||
|                 "key": "capacity:bicycle_type", | ||||
|                 "type": "pnat" | ||||
|               }, | ||||
|               "condition": "rental~.*bicycle_type.*" | ||||
|             } | ||||
|           ] | ||||
|         } | ||||
|  |  | |||
|  | @ -95,6 +95,10 @@ | |||
|                 "if": "theme=benches", | ||||
|                 "then": "./assets/themes/benches/bench_poi.svg" | ||||
|               }, | ||||
|               { | ||||
|                 "if": "theme=bicycle_rental", | ||||
|                 "then": "./assets/themes/bicycle_rental/logo.svg" | ||||
|               }, | ||||
|               { | ||||
|                 "if": "theme=bicyclelib", | ||||
|                 "then": "./assets/themes/bicyclelib/logo.svg" | ||||
|  |  | |||
|  | @ -62,10 +62,16 @@ | |||
|         }, | ||||
|         { | ||||
|           "rewrite": { | ||||
|             "sourceString": "left|right", | ||||
|             "sourceString": [ | ||||
|               "left|right" | ||||
|             ], | ||||
|             "into": [ | ||||
|               "left", | ||||
|               "right" | ||||
|               [ | ||||
|                 "left" | ||||
|               ], | ||||
|               [ | ||||
|                 "right" | ||||
|               ] | ||||
|             ] | ||||
|           }, | ||||
|           "renderings": [ | ||||
|  |  | |||
|  | @ -14,6 +14,8 @@ import Title from "../UI/Base/Title"; | |||
| import Minimap from "../UI/Base/Minimap"; | ||||
| import {QueryParameters} from "../Logic/Web/QueryParameters"; | ||||
| import QueryParameterDocumentation from "../UI/QueryParameterDocumentation"; | ||||
| import ScriptUtils from "./ScriptUtils"; | ||||
| import List from "../UI/Base/List"; | ||||
| 
 | ||||
| function WriteFile(filename, html: BaseUIElement, autogenSource: string[]): void { | ||||
| 
 | ||||
|  | @ -50,6 +52,53 @@ WriteFile("./Docs/CalculatedTags.md", new Combine([new Title("Metatags", 1), | |||
| WriteFile("./Docs/SpecialInputElements.md", ValidatedTextField.HelpText(), ["ValidatedTextField.ts"]); | ||||
| WriteFile("./Docs/BuiltinLayers.md", AllKnownLayouts.GenLayerOverviewText(), ["AllKnownLayers.ts"]) | ||||
| 
 | ||||
| { | ||||
|    var layers = ScriptUtils.getLayerFiles().map(f => f.parsed) | ||||
|     var builtinsPerLayer= new Map<string, string[]>(); | ||||
|     var layersUsingBuiltin = new Map<string /* Builtin */, string[]>(); | ||||
|     for (const layer of layers) { | ||||
|         if(layer.tagRenderings === undefined){ | ||||
|             continue | ||||
|         } | ||||
|         const usedBuiltins : string[] = [] | ||||
|         for (const tagRendering of layer.tagRenderings) { | ||||
|             if(typeof tagRendering === "string"){ | ||||
|                 usedBuiltins.push(tagRendering) | ||||
|                 continue | ||||
|             } | ||||
|             if(tagRendering["builtin"] !== undefined){ | ||||
|                 const builtins = tagRendering["builtin"] | ||||
|                 if(typeof builtins === "string"){ | ||||
|                     usedBuiltins.push(builtins) | ||||
|                 }else{ | ||||
|                     usedBuiltins.push(...builtins) | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         for (const usedBuiltin of usedBuiltins) { | ||||
|             var using = layersUsingBuiltin.get(usedBuiltin) | ||||
|             if(using === undefined){ | ||||
|                 layersUsingBuiltin.set(usedBuiltin, [layer.id]) | ||||
|             }else{ | ||||
|                 using.push(layer.id) | ||||
|             } | ||||
|         } | ||||
|          | ||||
|         builtinsPerLayer.set(layer.id, usedBuiltins) | ||||
|     } | ||||
|      | ||||
|     const docs = new Combine([ | ||||
|         new Title("Index of builtin TagRendering" ,1), | ||||
|         new Title("Existing builtin tagrenderings", 2), | ||||
|         ... Array.from(layersUsingBuiltin.entries()).map(([builtin, usedByLayers]) =>  | ||||
|                 new Combine([ | ||||
|                     new Title(builtin), | ||||
|                     new List(usedByLayers) | ||||
|                 ]).SetClass("flex flex-col") | ||||
|             ) | ||||
|     ]).SetClass("flex flex-col") | ||||
|     WriteFile("./Docs/BuiltinIndex.md", docs, ["assets/layers/*.json"]) | ||||
| } | ||||
| 
 | ||||
| Minimap.createMiniMap = _ => { | ||||
|     console.log("Not creating a minimap, it is disabled"); | ||||
|  | @ -58,12 +107,12 @@ Minimap.createMiniMap = _ => { | |||
| 
 | ||||
| 
 | ||||
| const dummyLayout = new LayoutConfig({ | ||||
|     language: ["en"], | ||||
|     id: ">theme<", | ||||
|     maintainer: "pietervdvn", | ||||
|     version: "0", | ||||
|     title: "<theme>", | ||||
|     title: {en:"<theme>"}, | ||||
|     description: "A theme to generate docs with", | ||||
|     socialImage: "./assets/SocialImage.png", | ||||
|     startLat: 0, | ||||
|     startLon: 0, | ||||
|     startZoom: 0, | ||||
|  |  | |||
|  | @ -228,7 +228,7 @@ function compileTranslationsFromWeblate() { | |||
|  * @param objects | ||||
|  * @param target | ||||
|  */ | ||||
| function generateTranslationsObjectFrom(objects: { path: string, parsed: { id: string } }[], target: string) { | ||||
| function generateTranslationsObjectFrom(objects: { path: string, parsed: { id: string } }[], target: string): string[] { | ||||
|     const tr = new TranslationPart(); | ||||
| 
 | ||||
|     for (const layerFile of objects) { | ||||
|  | @ -257,6 +257,7 @@ function generateTranslationsObjectFrom(objects: { path: string, parsed: { id: s | |||
| 
 | ||||
|         writeFileSync(`langs/${target}/${lang}.json`, json) | ||||
|     } | ||||
|     return langs | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  | @ -398,11 +399,14 @@ if (!themeOverwritesWeblate) { | |||
| } else { | ||||
|     console.log("Ignore weblate") | ||||
| } | ||||
| generateTranslationsObjectFrom(ScriptUtils.getLayerFiles(), "layers") | ||||
| generateTranslationsObjectFrom(ScriptUtils.getThemeFiles().filter(th => th.parsed.mustHaveLanguage === undefined), "themes") | ||||
| 
 | ||||
| const l1 = generateTranslationsObjectFrom(ScriptUtils.getLayerFiles(), "layers") | ||||
| const l2 = generateTranslationsObjectFrom(ScriptUtils.getThemeFiles().filter(th => th.parsed.mustHaveLanguage === undefined), "themes") | ||||
| const l3 = generateTranslationsObjectFrom([{path: questionsPath, parsed: questionsParsed}], "shared-questions") | ||||
| 
 | ||||
| generateTranslationsObjectFrom([{path: questionsPath, parsed: questionsParsed}], "shared-questions") | ||||
| const usedLanguages = Utils.Dedup(l1.concat(l2).concat(l3)).filter(v => v !== "*") | ||||
| usedLanguages.sort() | ||||
| fs.writeFileSync("./assets/generated/used_languages.json", JSON.stringify({languages: usedLanguages})) | ||||
| 
 | ||||
| if (!themeOverwritesWeblate) { | ||||
| // Generates the core translations
 | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue