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