forked from MapComplete/MapComplete
		
	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:
		
							parent
							
								
									ab0d510cdd
								
							
						
					
					
						commit
						284102c2f1
					
				
					 6 changed files with 118 additions and 57 deletions
				
			
		| 
						 | 
				
			
			@ -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;
 | 
			
		||||
    } 
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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))
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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<any>, 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<any>, 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<Loc>({
 | 
			
		||||
                        lat: Number(properties._lat),
 | 
			
		||||
                        lon: Number(properties._lon),
 | 
			
		||||
                        zoom: zoom
 | 
			
		||||
                    })
 | 
			
		||||
                    const minimap = SpecialVisualizations.constructMiniMap(
 | 
			
		||||
                        {
 | 
			
		||||
                            background: state.backgroundLayer,
 | 
			
		||||
                            location: new UIEventSource<Loc>({
 | 
			
		||||
                                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<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 constructMiniMap: (options?: {
 | 
			
		||||
        background?: UIEventSource<BaseLayer>,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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<any>) {
 | 
			
		||||
        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<any>): 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}];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -195,4 +195,5 @@ export class Translation extends BaseUIElement {
 | 
			
		|||
        }
 | 
			
		||||
        return allIcons.filter(icon => icon != undefined)
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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": {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue