From 284102c2f1454564652e51a647655692c4ac112a Mon Sep 17 00:00:00 2001 From: pietervdvn Date: Sun, 27 Jun 2021 19:21:31 +0200 Subject: [PATCH] Seperate logic to detect special renderings and to actually render them, add a minimap to all popups (if no minimap is defined seperately) --- Customizations/JSON/TagRenderingConfig.ts | 32 +++++++-- UI/Popup/FeatureInfoBox.ts | 11 +++- UI/SpecialVisualizations.ts | 50 ++++++++++---- UI/SubstitutedTranslation.ts | 79 +++++++++++++---------- UI/i18n/Translation.ts | 1 + assets/tagRenderings/questions.json | 2 +- 6 files changed, 118 insertions(+), 57 deletions(-) diff --git a/Customizations/JSON/TagRenderingConfig.ts b/Customizations/JSON/TagRenderingConfig.ts index 6bb63325a..d7e55ed8d 100644 --- a/Customizations/JSON/TagRenderingConfig.ts +++ b/Customizations/JSON/TagRenderingConfig.ts @@ -7,6 +7,7 @@ import {Utils} from "../../Utils"; import {TagUtils} from "../../Logic/Tags/TagUtils"; import {And} from "../../Logic/Tags/And"; import {TagsFilter} from "../../Logic/Tags/TagsFilter"; +import {SubstitutedTranslation} from "../../UI/SubstitutedTranslation"; /*** @@ -67,14 +68,13 @@ export default class TagRenderingConfig { if (json.freeform) { - this.freeform = { key: json.freeform.key, type: json.freeform.type ?? "string", addExtraTags: json.freeform.addExtraTags?.map((tg, i) => FromJSON.Tag(tg, `${context}.extratag[${i}]`)) ?? [], - - + + } if (json.freeform["extraTags"] !== undefined) { throw `Freeform.extraTags is defined. This should probably be 'freeform.addExtraTag' (at ${context})` @@ -82,9 +82,8 @@ export default class TagRenderingConfig { if (this.freeform.key === undefined || this.freeform.key === "") { throw `Freeform.key is undefined or the empty string - this is not allowed; either fill out something or remove the freeform block alltogether. Error in ${context}` } - - - + + if (ValidatedTextField.AllTypes[this.freeform.type] === undefined) { const knownKeys = ValidatedTextField.tpList.map(tp => tp.name).join(", "); throw `Freeform.key ${this.freeform.key} is an invalid type. Known keys are ${knownKeys}` @@ -328,4 +327,25 @@ export default class TagRenderingConfig { return usedIcons; } + /** + * Returns true if this tag rendering has a minimap in some language. + * Note: this might be hidden by conditions + */ + public hasMinimap(): boolean { + const translations : Translation[]= Utils.NoNull([this.render, ...(this.mappings ?? []).map(m => m.then)]); + for (const translation of translations) { + for (const key in translation.translations) { + if(!translation.translations.hasOwnProperty(key)){ + continue + } + const template = translation.translations[key] + const parts = SubstitutedTranslation.ExtractSpecialComponents(template) + const hasMiniMap = parts.filter(part =>part.special !== undefined ).some(special => special.special.func.funcName === "minimap") + if(hasMiniMap){ + return true; + } + } + } + return false; + } } \ No newline at end of file diff --git a/UI/Popup/FeatureInfoBox.ts b/UI/Popup/FeatureInfoBox.ts index 7055bfaad..c9462b759 100644 --- a/UI/Popup/FeatureInfoBox.ts +++ b/UI/Popup/FeatureInfoBox.ts @@ -12,6 +12,7 @@ import {Tag} from "../../Logic/Tags/Tag"; import Constants from "../../Models/Constants"; import SharedTagRenderings from "../../Customizations/SharedTagRenderings"; import BaseUIElement from "../BaseUIElement"; +import AllKnownLayers from "../../Customizations/AllKnownLayers"; export default class FeatureInfoBox extends ScrollableFullScreen { @@ -64,13 +65,19 @@ export default class FeatureInfoBox extends ScrollableFullScreen { if (!questionBoxIsUsed) { renderings.push(questionBox); } - + + const hasMinimap = layerConfig.tagRenderings.some(tr => tr.hasMinimap()) + if(!hasMinimap){ + renderings.push(new TagRenderingAnswer(tags, SharedTagRenderings.SharedTagRendering.get("minimap"))) + } + if (State.state.osmConnection.userDetails.data.csCount >= Constants.userJourney.historyLinkVisible || State.state.featureSwitchIsDebugging.data == true || State.state.featureSwitchIsTesting.data == true) { renderings.push(new TagRenderingAnswer( tags, SharedTagRenderings.SharedTagRendering.get("last_edit"))) } - + + if (State.state.featureSwitchIsDebugging.data) { const config: TagRenderingConfig = new TagRenderingConfig({render: "{all_tags()}"}, new Tag("id", ""), ""); renderings.push(new TagRenderingAnswer(tags, config)) diff --git a/UI/SpecialVisualizations.ts b/UI/SpecialVisualizations.ts index 3de0e66ca..447589c14 100644 --- a/UI/SpecialVisualizations.ts +++ b/UI/SpecialVisualizations.ts @@ -25,16 +25,18 @@ import Loc from "../Models/Loc"; import {Utils} from "../Utils"; import BaseLayer from "../Models/BaseLayer"; +export interface SpecialVisualization{ + funcName: string, + constr: ((state: State, tagSource: UIEventSource, argument: string[]) => BaseUIElement), + docs: string, + example?: string, + args: { name: string, defaultValue?: string, doc: string }[] +} + export default class SpecialVisualizations { - public static specialVisualizations: { - funcName: string, - constr: ((state: State, tagSource: UIEventSource, argument: string[]) => BaseUIElement), - docs: string, - example?: string, - args: { name: string, defaultValue?: string, doc: string }[] - }[] = + public static specialVisualizations: SpecialVisualization[] = [ { funcName: "all_tags", @@ -93,7 +95,7 @@ export default class SpecialVisualizations { docs: "A small map showing the selected feature. Note that no styling is applied, wrap this in a div", args: [ { - doc: "The zoomlevel: the higher, the more zoomed in with 1 being the entire world and 19 being really close", + doc: "The (maximum) zoomlevel: the target zoomlevel after fitting the entire feature. The minimap will fit the entire feature, then zoom out to this zoom level. The higher, the more zoomed in with 1 being the entire world and 19 being really close", name: "zoomlevel", defaultValue: "18" }, @@ -135,17 +137,26 @@ export default class SpecialVisualizations { zoom = parsed; } } + const locationSource =new UIEventSource({ + lat: Number(properties._lat), + lon: Number(properties._lon), + zoom: zoom + }) const minimap = SpecialVisualizations.constructMiniMap( { background: state.backgroundLayer, - location: new UIEventSource({ - lat: Number(properties._lat), - lon: Number(properties._lon), - zoom: zoom - }), + location: locationSource, allowMoving: false } ) + + locationSource.addCallback(loc => { + if(loc.zoom > zoom){ + // We zoom back + locationSource.data.zoom = zoom; + locationSource.ping(); + } + }) SpecialVisualizations.constructShowDataLayer( featuresToShow, @@ -362,6 +373,19 @@ export default class SpecialVisualizations { } ] + + private static byName() : Map{ + const result = new Map(); + + for (const specialVisualization of SpecialVisualizations.specialVisualizations) { + result.set(specialVisualization.funcName, specialVisualization) + } + + return result; + } + + public static specialVisualisationsByName: Map = SpecialVisualizations.byName(); + static HelpMessage: BaseUIElement = SpecialVisualizations.GenHelpMessage(); static constructMiniMap: (options?: { background?: UIEventSource, diff --git a/UI/SubstitutedTranslation.ts b/UI/SubstitutedTranslation.ts index 5bd7c6882..03c7eb074 100644 --- a/UI/SubstitutedTranslation.ts +++ b/UI/SubstitutedTranslation.ts @@ -3,8 +3,7 @@ import {Translation} from "./i18n/Translation"; import Locale from "./i18n/Locale"; import State from "../State"; import {FixedUiElement} from "./Base/FixedUiElement"; -import SpecialVisualizations from "./SpecialVisualizations"; -import BaseUIElement from "./BaseUIElement"; +import SpecialVisualizations, {SpecialVisualization} from "./SpecialVisualizations"; import {Utils} from "../Utils"; import {VariableUiElement} from "./Base/VariableUIElement"; import Combine from "./Base/Combine"; @@ -15,13 +14,26 @@ export class SubstitutedTranslation extends VariableUiElement { translation: Translation, tagsSource: UIEventSource) { super( - tagsSource.map(tags => { - const txt = Utils.SubstituteKeys(translation.txt, tags) + Locale.language.map(language => { + const txt = translation.textFor(language) if (txt === undefined) { return undefined } - return new Combine(SubstitutedTranslation.EvaluateSpecialComponents(txt, tagsSource)) - }, [Locale.language]) + return new Combine(SubstitutedTranslation.ExtractSpecialComponents(txt).map( + proto => { + if (proto.fixed !== undefined) { + return new VariableUiElement(tagsSource.map(tags => Utils.SubstituteKeys(proto.fixed, tags))); + } + const viz = proto.special; + try { + return viz.func.constr(State.state, tagsSource, proto.special.args).SetStyle(proto.special.style); + } catch (e) { + console.error("SPECIALRENDERING FAILED for", tagsSource.data?.id, e) + return new FixedUiElement(`Could not generate special rendering for ${viz.func}(${viz.args.join(", ")}) ${e}`).SetStyle("alert") + } + } + )) + }) ) @@ -29,7 +41,13 @@ export class SubstitutedTranslation extends VariableUiElement { } - private static EvaluateSpecialComponents(template: string, tags: UIEventSource): BaseUIElement[] { + public static ExtractSpecialComponents(template: string): { + fixed?: string, special?: { + func: SpecialVisualization, + args: string[], + style: string + } + }[] { for (const knownSpecial of SpecialVisualizations.specialVisualizations) { @@ -38,38 +56,29 @@ export class SubstitutedTranslation extends VariableUiElement { if (matched != null) { // We found a special component that should be brought to live - const partBefore = SubstitutedTranslation.EvaluateSpecialComponents(matched[1], tags); + const partBefore = SubstitutedTranslation.ExtractSpecialComponents(matched[1]); const argument = matched[2].trim(); const style = matched[3]?.substring(1) ?? "" - const partAfter = SubstitutedTranslation.EvaluateSpecialComponents(matched[4], tags); - try { - const args = knownSpecial.args.map(arg => arg.defaultValue ?? ""); - if (argument.length > 0) { - const realArgs = argument.split(",").map(str => str.trim()); - for (let i = 0; i < realArgs.length; i++) { - if (args.length <= i) { - args.push(realArgs[i]); - } else { - args[i] = realArgs[i]; - } + const partAfter = SubstitutedTranslation.ExtractSpecialComponents(matched[4]); + const args = knownSpecial.args.map(arg => arg.defaultValue ?? ""); + if (argument.length > 0) { + const realArgs = argument.split(",").map(str => str.trim()); + for (let i = 0; i < realArgs.length; i++) { + if (args.length <= i) { + args.push(realArgs[i]); + } else { + args[i] = realArgs[i]; } } - - - let element: BaseUIElement = new FixedUiElement(`Constructing ${knownSpecial}(${args.join(", ")})`) - try { - element = knownSpecial.constr(State.state, tags, args); - element.SetStyle(style) - } catch (e) { - console.error("SPECIALRENDERING FAILED for", tags.data.id, e) - element = new FixedUiElement(`Could not generate special rendering for ${knownSpecial}(${args.join(", ")}) ${e}`).SetClass("alert") - } - - return [...partBefore, element, ...partAfter] - } catch (e) { - console.error(e); - return [...partBefore, new FixedUiElement(`Failed loading ${knownSpecial.funcName}(${matched[2]}): ${e}`), ...partAfter] } + + let element; + element = {special:{ + args: args, + style: style, + func: knownSpecial + }} + return [...partBefore, element, ...partAfter] } } @@ -80,7 +89,7 @@ export class SubstitutedTranslation extends VariableUiElement { } // IF we end up here, no changes have to be made - except to remove any resting {} - return [new FixedUiElement(template.replace(/{.*}/g, ""))]; + return [{fixed: template}]; } } \ No newline at end of file diff --git a/UI/i18n/Translation.ts b/UI/i18n/Translation.ts index 508d9c026..bb1de40a7 100644 --- a/UI/i18n/Translation.ts +++ b/UI/i18n/Translation.ts @@ -195,4 +195,5 @@ export class Translation extends BaseUIElement { } return allIcons.filter(icon => icon != undefined) } + } \ No newline at end of file diff --git a/assets/tagRenderings/questions.json b/assets/tagRenderings/questions.json index 16eb121e3..dc1c731c4 100644 --- a/assets/tagRenderings/questions.json +++ b/assets/tagRenderings/questions.json @@ -6,7 +6,7 @@ "render": "{reviews()}" }, "minimap": { - "render": "{minimap(19, id): width:100%; height:6rem; border-radius:999rem; overflow: hidden; pointer-events: none;}" + "render": "{minimap(18, id): width:100%; height:8rem; border-radius:2rem; overflow: hidden; pointer-events: none;}" }, "phone": { "question": {