Seperate logic to detect special renderings and to actually render them, add a minimap to all popups (if no minimap is defined seperately)

This commit is contained in:
Pieter Vander Vennet 2021-06-27 19:21:31 +02:00
parent ab0d510cdd
commit 284102c2f1
6 changed files with 118 additions and 57 deletions

View file

@ -7,6 +7,7 @@ import {Utils} from "../../Utils";
import {TagUtils} from "../../Logic/Tags/TagUtils"; import {TagUtils} from "../../Logic/Tags/TagUtils";
import {And} from "../../Logic/Tags/And"; import {And} from "../../Logic/Tags/And";
import {TagsFilter} from "../../Logic/Tags/TagsFilter"; import {TagsFilter} from "../../Logic/Tags/TagsFilter";
import {SubstitutedTranslation} from "../../UI/SubstitutedTranslation";
/*** /***
@ -67,7 +68,6 @@ export default class TagRenderingConfig {
if (json.freeform) { if (json.freeform) {
this.freeform = { this.freeform = {
key: json.freeform.key, key: json.freeform.key,
type: json.freeform.type ?? "string", type: json.freeform.type ?? "string",
@ -84,7 +84,6 @@ export default class TagRenderingConfig {
} }
if (ValidatedTextField.AllTypes[this.freeform.type] === undefined) { if (ValidatedTextField.AllTypes[this.freeform.type] === undefined) {
const knownKeys = ValidatedTextField.tpList.map(tp => tp.name).join(", "); const knownKeys = ValidatedTextField.tpList.map(tp => tp.name).join(", ");
throw `Freeform.key ${this.freeform.key} is an invalid type. Known keys are ${knownKeys}` throw `Freeform.key ${this.freeform.key} is an invalid type. Known keys are ${knownKeys}`
@ -328,4 +327,25 @@ export default class TagRenderingConfig {
return usedIcons; 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;
}
} }

View file

@ -12,6 +12,7 @@ import {Tag} from "../../Logic/Tags/Tag";
import Constants from "../../Models/Constants"; import Constants from "../../Models/Constants";
import SharedTagRenderings from "../../Customizations/SharedTagRenderings"; import SharedTagRenderings from "../../Customizations/SharedTagRenderings";
import BaseUIElement from "../BaseUIElement"; import BaseUIElement from "../BaseUIElement";
import AllKnownLayers from "../../Customizations/AllKnownLayers";
export default class FeatureInfoBox extends ScrollableFullScreen { export default class FeatureInfoBox extends ScrollableFullScreen {
@ -65,12 +66,18 @@ export default class FeatureInfoBox extends ScrollableFullScreen {
renderings.push(questionBox); 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 || if (State.state.osmConnection.userDetails.data.csCount >= Constants.userJourney.historyLinkVisible ||
State.state.featureSwitchIsDebugging.data == true || State.state.featureSwitchIsDebugging.data == true ||
State.state.featureSwitchIsTesting.data == true) { State.state.featureSwitchIsTesting.data == true) {
renderings.push(new TagRenderingAnswer( tags, SharedTagRenderings.SharedTagRendering.get("last_edit"))) renderings.push(new TagRenderingAnswer( tags, SharedTagRenderings.SharedTagRendering.get("last_edit")))
} }
if (State.state.featureSwitchIsDebugging.data) { if (State.state.featureSwitchIsDebugging.data) {
const config: TagRenderingConfig = new TagRenderingConfig({render: "{all_tags()}"}, new Tag("id", ""), ""); const config: TagRenderingConfig = new TagRenderingConfig({render: "{all_tags()}"}, new Tag("id", ""), "");
renderings.push(new TagRenderingAnswer(tags, config)) renderings.push(new TagRenderingAnswer(tags, config))

View file

@ -25,16 +25,18 @@ import Loc from "../Models/Loc";
import {Utils} from "../Utils"; import {Utils} from "../Utils";
import BaseLayer from "../Models/BaseLayer"; import BaseLayer from "../Models/BaseLayer";
export default class SpecialVisualizations { export interface SpecialVisualization{
public static specialVisualizations: {
funcName: string, funcName: string,
constr: ((state: State, tagSource: UIEventSource<any>, argument: string[]) => BaseUIElement), constr: ((state: State, tagSource: UIEventSource<any>, argument: string[]) => BaseUIElement),
docs: string, docs: string,
example?: string, example?: string,
args: { name: string, defaultValue?: string, doc: string }[] args: { name: string, defaultValue?: string, doc: string }[]
}[] = }
export default class SpecialVisualizations {
public static specialVisualizations: SpecialVisualization[] =
[ [
{ {
funcName: "all_tags", 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", docs: "A small map showing the selected feature. Note that no styling is applied, wrap this in a div",
args: [ 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", name: "zoomlevel",
defaultValue: "18" defaultValue: "18"
}, },
@ -135,18 +137,27 @@ export default class SpecialVisualizations {
zoom = parsed; zoom = parsed;
} }
} }
const minimap = SpecialVisualizations.constructMiniMap( const locationSource =new UIEventSource<Loc>({
{
background: state.backgroundLayer,
location: new UIEventSource<Loc>({
lat: Number(properties._lat), lat: Number(properties._lat),
lon: Number(properties._lon), lon: Number(properties._lon),
zoom: zoom zoom: zoom
}), })
const minimap = SpecialVisualizations.constructMiniMap(
{
background: state.backgroundLayer,
location: locationSource,
allowMoving: false allowMoving: false
} }
) )
locationSource.addCallback(loc => {
if(loc.zoom > zoom){
// We zoom back
locationSource.data.zoom = zoom;
locationSource.ping();
}
})
SpecialVisualizations.constructShowDataLayer( SpecialVisualizations.constructShowDataLayer(
featuresToShow, featuresToShow,
minimap["leafletMap"], minimap["leafletMap"],
@ -362,6 +373,19 @@ export default class SpecialVisualizations {
} }
] ]
private static byName() : Map<string, SpecialVisualization>{
const result = new Map<string, SpecialVisualization>();
for (const specialVisualization of SpecialVisualizations.specialVisualizations) {
result.set(specialVisualization.funcName, specialVisualization)
}
return result;
}
public static specialVisualisationsByName: Map<string, SpecialVisualization> = SpecialVisualizations.byName();
static HelpMessage: BaseUIElement = SpecialVisualizations.GenHelpMessage(); static HelpMessage: BaseUIElement = SpecialVisualizations.GenHelpMessage();
static constructMiniMap: (options?: { static constructMiniMap: (options?: {
background?: UIEventSource<BaseLayer>, background?: UIEventSource<BaseLayer>,

View file

@ -3,8 +3,7 @@ import {Translation} from "./i18n/Translation";
import Locale from "./i18n/Locale"; import Locale from "./i18n/Locale";
import State from "../State"; import State from "../State";
import {FixedUiElement} from "./Base/FixedUiElement"; import {FixedUiElement} from "./Base/FixedUiElement";
import SpecialVisualizations from "./SpecialVisualizations"; import SpecialVisualizations, {SpecialVisualization} from "./SpecialVisualizations";
import BaseUIElement from "./BaseUIElement";
import {Utils} from "../Utils"; import {Utils} from "../Utils";
import {VariableUiElement} from "./Base/VariableUIElement"; import {VariableUiElement} from "./Base/VariableUIElement";
import Combine from "./Base/Combine"; import Combine from "./Base/Combine";
@ -15,13 +14,26 @@ export class SubstitutedTranslation extends VariableUiElement {
translation: Translation, translation: Translation,
tagsSource: UIEventSource<any>) { tagsSource: UIEventSource<any>) {
super( super(
tagsSource.map(tags => { Locale.language.map(language => {
const txt = Utils.SubstituteKeys(translation.txt, tags) const txt = translation.textFor(language)
if (txt === undefined) { if (txt === undefined) {
return undefined return undefined
} }
return new Combine(SubstitutedTranslation.EvaluateSpecialComponents(txt, tagsSource)) return new Combine(SubstitutedTranslation.ExtractSpecialComponents(txt).map(
}, [Locale.language]) 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<any>): BaseUIElement[] { public static ExtractSpecialComponents(template: string): {
fixed?: string, special?: {
func: SpecialVisualization,
args: string[],
style: string
}
}[] {
for (const knownSpecial of SpecialVisualizations.specialVisualizations) { for (const knownSpecial of SpecialVisualizations.specialVisualizations) {
@ -38,11 +56,10 @@ export class SubstitutedTranslation extends VariableUiElement {
if (matched != null) { if (matched != null) {
// We found a special component that should be brought to live // 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 argument = matched[2].trim();
const style = matched[3]?.substring(1) ?? "" const style = matched[3]?.substring(1) ?? ""
const partAfter = SubstitutedTranslation.EvaluateSpecialComponents(matched[4], tags); const partAfter = SubstitutedTranslation.ExtractSpecialComponents(matched[4]);
try {
const args = knownSpecial.args.map(arg => arg.defaultValue ?? ""); const args = knownSpecial.args.map(arg => arg.defaultValue ?? "");
if (argument.length > 0) { if (argument.length > 0) {
const realArgs = argument.split(",").map(str => str.trim()); const realArgs = argument.split(",").map(str => str.trim());
@ -55,21 +72,13 @@ export class SubstitutedTranslation extends VariableUiElement {
} }
} }
let element;
let element: BaseUIElement = new FixedUiElement(`Constructing ${knownSpecial}(${args.join(", ")})`) element = {special:{
try { args: args,
element = knownSpecial.constr(State.state, tags, args); style: style,
element.SetStyle(style) func: knownSpecial
} 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] return [...partBefore, element, ...partAfter]
} catch (e) {
console.error(e);
return [...partBefore, new FixedUiElement(`Failed loading ${knownSpecial.funcName}(${matched[2]}): ${e}`), ...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 {} // 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}];
} }
} }

View file

@ -195,4 +195,5 @@ export class Translation extends BaseUIElement {
} }
return allIcons.filter(icon => icon != undefined) return allIcons.filter(icon => icon != undefined)
} }
} }

View file

@ -6,7 +6,7 @@
"render": "{reviews()}" "render": "{reviews()}"
}, },
"minimap": { "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": { "phone": {
"question": { "question": {