From 9594868e83d25ad0d2a93d8dd6b3edd0bfa02664 Mon Sep 17 00:00:00 2001 From: pietervdvn Date: Sat, 29 Jan 2022 02:45:59 +0100 Subject: [PATCH] Better tag rewriting, add icons, add bicycle rental theme --- Customizations/SharedTagRenderings.ts | 18 ++++ Logic/DetermineLayout.ts | 7 +- Models/ThemeConfig/Conversion/PrepareLayer.ts | 87 ++++++++++++++----- Models/ThemeConfig/Json/LayerConfigJson.ts | 6 +- .../Json/TagRenderingConfigJson.ts | 4 + Models/ThemeConfig/TagRenderingConfig.ts | 47 +++++----- UI/Base/TableOfContents.ts | 1 - UI/Popup/TagRenderingAnswer.ts | 12 ++- UI/Popup/TagRenderingQuestion.ts | 21 ++++- UI/i18n/Translation.ts | 27 ++++++ UI/i18n/Translations.ts | 11 ++- .../charging_station/charging_station.json | 22 +---- .../charging_station.protojson | 22 +---- assets/layers/picnic_table/picnic_table.json | 1 + assets/svg/cash.svg | 7 ++ assets/svg/license_info.json | 20 +++++ assets/svg/smartphone.svg | 4 + assets/tagRenderings/questions.json | 27 ++++++ .../themes/bicycle_rental/bicycle_rental.json | 81 ++++++++++++++--- .../mapcomplete-changes.json | 4 + assets/themes/sidewalks/sidewalks.json | 12 ++- scripts/generateDocs.ts | 53 ++++++++++- scripts/generateTranslations.ts | 12 ++- 23 files changed, 389 insertions(+), 117 deletions(-) create mode 100644 assets/svg/cash.svg create mode 100644 assets/svg/smartphone.svg diff --git a/Customizations/SharedTagRenderings.ts b/Customizations/SharedTagRenderings.ts index c45737471..3ad71b8fd 100644 --- a/Customizations/SharedTagRenderings.ts +++ b/Customizations/SharedTagRenderings.ts @@ -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; } diff --git a/Logic/DetermineLayout.ts b/Logic/DetermineLayout.ts index bc1e2794d..8d95d7e76 100644 --- a/Logic/DetermineLayout.ts +++ b/Logic/DetermineLayout.ts @@ -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() - 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, layer) } const converState = { tagRenderings: SharedTagRenderings.SharedTagRenderingJson, diff --git a/Models/ThemeConfig/Conversion/PrepareLayer.ts b/Models/ThemeConfig/Conversion/PrepareLayer.ts index deea492e7..a637fffbe 100644 --- a/Models/ThemeConfig/Conversion/PrepareLayer.ts +++ b/Models/ThemeConfig/Conversion/PrepareLayer.ts @@ -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 { 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() // 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) } } diff --git a/Models/ThemeConfig/Json/LayerConfigJson.ts b/Models/ThemeConfig/Json/LayerConfigJson.ts index e425d4cac..9aef92aa4 100644 --- a/Models/ThemeConfig/Json/LayerConfigJson.ts +++ b/Models/ThemeConfig/Json/LayerConfigJson.ts @@ -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)[] }) [], diff --git a/Models/ThemeConfig/Json/TagRenderingConfigJson.ts b/Models/ThemeConfig/Json/TagRenderingConfigJson.ts index d47a7b047..141c50751 100644 --- a/Models/ThemeConfig/Json/TagRenderingConfigJson.ts +++ b/Models/ThemeConfig/Json/TagRenderingConfigJson.ts @@ -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). * diff --git a/Models/ThemeConfig/TagRenderingConfig.ts b/Models/ThemeConfig/TagRenderingConfig.ts index c8da02f33..14768b35b 100644 --- a/Models/ThemeConfig/TagRenderingConfig.ts +++ b/Models/ThemeConfig/TagRenderingConfig.ts @@ -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}; } /** diff --git a/UI/Base/TableOfContents.ts b/UI/Base/TableOfContents.ts index 3a3555093..b9f284fc8 100644 --- a/UI/Base/TableOfContents.ts +++ b/UI/Base/TableOfContents.ts @@ -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) { diff --git a/UI/Popup/TagRenderingAnswer.ts b/UI/Popup/TagRenderingAnswer.ts index 71f28f5a1..0d155384d 100644 --- a/UI/Popup/TagRenderingAnswer.ts +++ b/UI/Popup/TagRenderingAnswer.ts @@ -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))) diff --git a/UI/Popup/TagRenderingQuestion.ts b/UI/Popup/TagRenderingQuestion.ts index ed76f5bf2..4ea4d5bbf 100644 --- a/UI/Popup/TagRenderingQuestion.ts +++ b/UI/Popup/TagRenderingQuestion.ts @@ -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 { 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, 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): InputElement { const freeform = configuration.freeform; diff --git a/UI/i18n/Translation.ts b/UI/i18n/Translation.ts index 6904ac6a9..283e4cd7f 100644 --- a/UI/i18n/Translation.ts +++ b/UI/i18n/Translation.ts @@ -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); diff --git a/UI/i18n/Translations.ts b/UI/i18n/Translations.ts index 6052d8b28..3341d83d8 100644 --- a/UI/i18n/Translations.ts +++ b/UI/i18n/Translations.ts @@ -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)) + } } diff --git a/assets/layers/charging_station/charging_station.json b/assets/layers/charging_station/charging_station.json index 6a40ffd8f..9ae5c9594 100644 --- a/assets/layers/charging_station/charging_station.json +++ b/assets/layers/charging_station/charging_station.json @@ -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" - } - } - ] + } } }, { diff --git a/assets/layers/charging_station/charging_station.protojson b/assets/layers/charging_station/charging_station.protojson index 693c2d1b8..c91305e3e 100644 --- a/assets/layers/charging_station/charging_station.protojson +++ b/assets/layers/charging_station/charging_station.protojson @@ -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" - } - } - ] + } } }, { diff --git a/assets/layers/picnic_table/picnic_table.json b/assets/layers/picnic_table/picnic_table.json index fd06f63f4..bc29b1eae 100644 --- a/assets/layers/picnic_table/picnic_table.json +++ b/assets/layers/picnic_table/picnic_table.json @@ -31,6 +31,7 @@ "de": "Die Ebene zeigt Picknicktische an" }, "tagRenderings": [ + "images", { "question": { "en": "What material is this picnic table made of?", diff --git a/assets/svg/cash.svg b/assets/svg/cash.svg new file mode 100644 index 000000000..5f47efb1a --- /dev/null +++ b/assets/svg/cash.svg @@ -0,0 +1,7 @@ + + + + + Svg Vector Icons : http://www.onlinewebfonts.com/icon + + \ No newline at end of file diff --git a/assets/svg/license_info.json b/assets/svg/license_info.json index e1f30e21e..1628a6b94 100644 --- a/assets/svg/license_info.json +++ b/assets/svg/license_info.json @@ -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", diff --git a/assets/svg/smartphone.svg b/assets/svg/smartphone.svg new file mode 100644 index 000000000..d2faa4a45 --- /dev/null +++ b/assets/svg/smartphone.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/assets/tagRenderings/questions.json b/assets/tagRenderings/questions.json index b74250ddd..7e3d45426 100644 --- a/assets/tagRenderings/questions.json +++ b/assets/tagRenderings/questions.json @@ -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~*", diff --git a/assets/themes/bicycle_rental/bicycle_rental.json b/assets/themes/bicycle_rental/bicycle_rental.json index 33a3d44c0..6edb33ca5 100644 --- a/assets/themes/bicycle_rental/bicycle_rental.json +++ b/assets/themes/bicycle_rental/bicycle_rental.json @@ -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.*" } ] } diff --git a/assets/themes/mapcomplete-changes/mapcomplete-changes.json b/assets/themes/mapcomplete-changes/mapcomplete-changes.json index 5b57cbb7f..a9ef48a38 100644 --- a/assets/themes/mapcomplete-changes/mapcomplete-changes.json +++ b/assets/themes/mapcomplete-changes/mapcomplete-changes.json @@ -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" diff --git a/assets/themes/sidewalks/sidewalks.json b/assets/themes/sidewalks/sidewalks.json index 4c8246446..72ef76042 100644 --- a/assets/themes/sidewalks/sidewalks.json +++ b/assets/themes/sidewalks/sidewalks.json @@ -62,10 +62,16 @@ }, { "rewrite": { - "sourceString": "left|right", + "sourceString": [ + "left|right" + ], "into": [ - "left", - "right" + [ + "left" + ], + [ + "right" + ] ] }, "renderings": [ diff --git a/scripts/generateDocs.ts b/scripts/generateDocs.ts index 7e74ff685..b0f07113e 100644 --- a/scripts/generateDocs.ts +++ b/scripts/generateDocs.ts @@ -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(); + var layersUsingBuiltin = new Map(); + 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: "", + title: {en:""}, description: "A theme to generate docs with", + socialImage: "./assets/SocialImage.png", startLat: 0, startLon: 0, startZoom: 0, diff --git a/scripts/generateTranslations.ts b/scripts/generateTranslations.ts index 50f0f1ef3..fbd87b96f 100644 --- a/scripts/generateTranslations.ts +++ b/scripts/generateTranslations.ts @@ -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