forked from MapComplete/MapComplete
		
	Merge develop
This commit is contained in:
		
						commit
						1010b159e5
					
				
					 87 changed files with 2292 additions and 718 deletions
				
			
		|  | @ -44,7 +44,6 @@ export default class Minimap extends BaseUIElement { | |||
|         const self = this; | ||||
|         // @ts-ignore
 | ||||
|         const resizeObserver = new ResizeObserver(_ => { | ||||
|             console.log("Change in size detected!") | ||||
|             self.InitMap(); | ||||
|             self.leafletMap?.data?.invalidateSize() | ||||
|         }); | ||||
|  | @ -53,7 +52,7 @@ export default class Minimap extends BaseUIElement { | |||
|         return wrapper; | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|      | ||||
|     private InitMap() { | ||||
|         if (this._constructedHtmlElement === undefined) { | ||||
|             // This element isn't initialized yet
 | ||||
|  | @ -82,7 +81,9 @@ export default class Minimap extends BaseUIElement { | |||
|             scrollWheelZoom: this._allowMoving, | ||||
|             doubleClickZoom: this._allowMoving, | ||||
|             keyboard: this._allowMoving, | ||||
|             touchZoom: this._allowMoving | ||||
|             touchZoom: this._allowMoving, | ||||
|             zoomAnimation: this._allowMoving, | ||||
|             fadeAnimation: this._allowMoving | ||||
|         }); | ||||
| 
 | ||||
|         map.setMaxBounds( | ||||
|  |  | |||
|  | @ -3,6 +3,7 @@ import {UIEventSource} from "../../Logic/UIEventSource"; | |||
| import Loc from "../../Models/Loc"; | ||||
| import BaseLayer from "../../Models/BaseLayer"; | ||||
| import BaseUIElement from "../BaseUIElement"; | ||||
| import {FixedUiElement} from "../Base/FixedUiElement"; | ||||
| 
 | ||||
| export class Basemap { | ||||
| 
 | ||||
|  | @ -35,9 +36,8 @@ export class Basemap { | |||
|         ); | ||||
| 
 | ||||
|         this.map.attributionControl.setPrefix( | ||||
|             "<span id='leaflet-attribution'></span> | <a href='https://osm.org'>OpenStreetMap</a>"); | ||||
|             "<span id='leaflet-attribution'>A</span>"); | ||||
| 
 | ||||
|         extraAttribution.AttachTo('leaflet-attribution') | ||||
|         const self = this; | ||||
| 
 | ||||
|         currentLayer.addCallbackAndRun(layer => { | ||||
|  | @ -77,6 +77,7 @@ export class Basemap { | |||
|             lastClickLocation?.setData({lat: e.latlng.lat, lon: e.latlng.lng}); | ||||
|         }); | ||||
| 
 | ||||
|         extraAttribution.AttachTo('leaflet-attribution') | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
|  | @ -62,6 +62,10 @@ export default class MoreScreen extends Combine { | |||
|         let officialThemes = AllKnownLayouts.layoutsList | ||||
| 
 | ||||
|         let buttons = officialThemes.map((layout) => { | ||||
|             if(layout === undefined){ | ||||
|                 console.trace("Layout is undefined") | ||||
|                 return undefined | ||||
|             } | ||||
|             const button = MoreScreen.createLinkButton(layout)?.SetClass(buttonClass); | ||||
|             if(layout.id === personal.id){ | ||||
|                 return new VariableUiElement( | ||||
|  |  | |||
|  | @ -16,6 +16,10 @@ import {VariableUiElement} from "../Base/VariableUIElement"; | |||
| import Toggle from "../Input/Toggle"; | ||||
| import UserDetails from "../../Logic/Osm/OsmConnection"; | ||||
| import {Translation} from "../i18n/Translation"; | ||||
| import LocationInput from "../Input/LocationInput"; | ||||
| import {InputElement} from "../Input/InputElement"; | ||||
| import Loc from "../../Models/Loc"; | ||||
| import AvailableBaseLayers from "../../Logic/Actors/AvailableBaseLayers"; | ||||
| 
 | ||||
| /* | ||||
| * The SimpleAddUI is a single panel, which can have multiple states: | ||||
|  | @ -25,14 +29,18 @@ import {Translation} from "../i18n/Translation"; | |||
| * - A 'read your unread messages before adding a point' | ||||
|  */ | ||||
| 
 | ||||
| /*private*/ | ||||
| interface PresetInfo { | ||||
|     description: string | Translation, | ||||
|     name: string | BaseUIElement, | ||||
|     icon: BaseUIElement, | ||||
|     icon: () => BaseUIElement, | ||||
|     tags: Tag[], | ||||
|     layerToAddTo: { | ||||
|         layerDef: LayerConfig, | ||||
|         isDisplayed: UIEventSource<boolean> | ||||
|     }, | ||||
|     preciseInput?: { | ||||
|         preferredBackground?: string | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  | @ -48,18 +56,16 @@ export default class SimpleAddUI extends Toggle { | |||
|             new SubtleButton(Svg.envelope_ui(), | ||||
|                 Translations.t.general.goToInbox, {url: "https://www.openstreetmap.org/messages/inbox", newTab: false}) | ||||
|         ]); | ||||
|          | ||||
|          | ||||
|          | ||||
| 
 | ||||
| 
 | ||||
|         const selectedPreset = new UIEventSource<PresetInfo>(undefined); | ||||
|         isShown.addCallback(_ => selectedPreset.setData(undefined)) // Clear preset selection when the UI is closed/opened
 | ||||
|          | ||||
|         function createNewPoint(tags: any[]){ | ||||
|            const loc = State.state.LastClickLocation.data; | ||||
|             let feature = State.state.changes.createElement(tags, loc.lat, loc.lon); | ||||
| 
 | ||||
|         function createNewPoint(tags: any[], location: { lat: number, lon: number }) { | ||||
|             let feature = State.state.changes.createElement(tags, location.lat, location.lon); | ||||
|             State.state.selectedElement.setData(feature); | ||||
|         } | ||||
|          | ||||
| 
 | ||||
|         const presetsOverview = SimpleAddUI.CreateAllPresetsPanel(selectedPreset) | ||||
| 
 | ||||
|         const addUi = new VariableUiElement( | ||||
|  | @ -68,8 +74,8 @@ export default class SimpleAddUI extends Toggle { | |||
|                         return presetsOverview | ||||
|                     } | ||||
|                     return SimpleAddUI.CreateConfirmButton(preset, | ||||
|                         tags => { | ||||
|                             createNewPoint(tags) | ||||
|                         (tags, location) => { | ||||
|                             createNewPoint(tags, location) | ||||
|                             selectedPreset.setData(undefined) | ||||
|                         }, () => { | ||||
|                             selectedPreset.setData(undefined) | ||||
|  | @ -86,7 +92,7 @@ export default class SimpleAddUI extends Toggle { | |||
|                         addUi, | ||||
|                         State.state.layerUpdater.runningQuery | ||||
|                     ), | ||||
|                     Translations.t.general.add.zoomInFurther.Clone().SetClass("alert")                    , | ||||
|                     Translations.t.general.add.zoomInFurther.Clone().SetClass("alert"), | ||||
|                     State.state.locationControl.map(loc => loc.zoom >= Constants.userJourney.minZoomLevelToAddNewPoints) | ||||
|                 ), | ||||
|                 readYourMessages, | ||||
|  | @ -103,22 +109,48 @@ export default class SimpleAddUI extends Toggle { | |||
|     } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|     private static CreateConfirmButton(preset: PresetInfo, | ||||
|                                        confirm: (tags: any[]) => void,  | ||||
|                                        confirm: (tags: any[], location: { lat: number, lon: number }) => void, | ||||
|                                        cancel: () => void): BaseUIElement { | ||||
| 
 | ||||
|         let location = State.state.LastClickLocation; | ||||
|         let preciseInput: InputElement<Loc> = undefined | ||||
|         if (preset.preciseInput !== undefined) { | ||||
|             const locationSrc = new UIEventSource({ | ||||
|                 lat: location.data.lat, | ||||
|                 lon: location.data.lon, | ||||
|                 zoom: 19 | ||||
|             }); | ||||
|              | ||||
|             let backgroundLayer = undefined; | ||||
|             if(preset.preciseInput.preferredBackground){ | ||||
|                backgroundLayer= AvailableBaseLayers.SelectBestLayerAccordingTo(locationSrc, new UIEventSource<string | string[]>(preset.preciseInput.preferredBackground)) | ||||
|             } | ||||
|              | ||||
|             preciseInput = new LocationInput({ | ||||
|                 mapBackground: backgroundLayer, | ||||
|                 centerLocation:locationSrc | ||||
|                      | ||||
|             }) | ||||
|             preciseInput.SetClass("h-32 rounded-xl overflow-hidden border border-gray").SetStyle("height: 12rem;") | ||||
|         } | ||||
| 
 | ||||
|         const confirmButton = new SubtleButton(preset.icon, | ||||
| 
 | ||||
|         let confirmButton: BaseUIElement = new SubtleButton(preset.icon(), | ||||
|             new Combine([ | ||||
|                 Translations.t.general.add.addNew.Subs({category: preset.name}), | ||||
|                 Translations.t.general.add.warnVisibleForEveryone.Clone().SetClass("alert") | ||||
|             ]).SetClass("flex flex-col") | ||||
|         ).SetClass("font-bold break-words") | ||||
|             .onClick(() => confirm(preset.tags)); | ||||
|             .onClick(() => { | ||||
|                 confirm(preset.tags, (preciseInput?.GetValue() ?? location).data); | ||||
|             }); | ||||
|          | ||||
|         if (preciseInput !== undefined) { | ||||
|             confirmButton = new Combine([preciseInput, confirmButton]) | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
|         const openLayerControl =   | ||||
|         const openLayerControl = | ||||
|             new SubtleButton( | ||||
|                 Svg.layers_ui(), | ||||
|                 new Combine([ | ||||
|  | @ -128,9 +160,9 @@ export default class SimpleAddUI extends Toggle { | |||
|                     Translations.t.general.add.openLayerControl | ||||
|                 ]) | ||||
|             ) | ||||
|             | ||||
|             .onClick(() => State.state.layerControlIsOpened.setData(true)) | ||||
|          | ||||
| 
 | ||||
|                 .onClick(() => State.state.layerControlIsOpened.setData(true)) | ||||
| 
 | ||||
|         const openLayerOrConfirm = new Toggle( | ||||
|             confirmButton, | ||||
|             openLayerControl, | ||||
|  | @ -140,12 +172,12 @@ export default class SimpleAddUI extends Toggle { | |||
| 
 | ||||
|         const cancelButton = new SubtleButton(Svg.close_ui(), | ||||
|             Translations.t.general.cancel | ||||
|         ).onClick(cancel        ) | ||||
|         ).onClick(cancel) | ||||
| 
 | ||||
|         return new Combine([ | ||||
|             Translations.t.general.add.confirmIntro.Subs({title: preset.name}), | ||||
|             State.state.osmConnection.userDetails.data.dryRun ?  | ||||
|                 Translations.t.general.testing.Clone().SetClass("alert") : undefined           ,  | ||||
|             State.state.osmConnection.userDetails.data.dryRun ? | ||||
|                 Translations.t.general.testing.Clone().SetClass("alert") : undefined, | ||||
|             openLayerOrConfirm, | ||||
|             cancelButton, | ||||
|             preset.description, | ||||
|  | @ -180,11 +212,11 @@ export default class SimpleAddUI extends Toggle { | |||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     private static CreatePresetSelectButton(preset: PresetInfo){ | ||||
|     private static CreatePresetSelectButton(preset: PresetInfo) { | ||||
| 
 | ||||
|         const tagInfo =SimpleAddUI.CreateTagInfoFor(preset, false); | ||||
|         const tagInfo = SimpleAddUI.CreateTagInfoFor(preset, false); | ||||
|         return new SubtleButton( | ||||
|             preset.icon, | ||||
|             preset.icon(), | ||||
|             new Combine([ | ||||
|                 Translations.t.general.add.addNew.Subs({ | ||||
|                     category: preset.name | ||||
|  | @ -194,29 +226,30 @@ export default class SimpleAddUI extends Toggle { | |||
|             ]).SetClass("flex flex-col") | ||||
|         ) | ||||
|     } | ||||
|   | ||||
| /* | ||||
| * Generates the list with all the buttons.*/ | ||||
| 
 | ||||
|     /* | ||||
|     * Generates the list with all the buttons.*/ | ||||
|     private static CreatePresetButtons(selectedPreset: UIEventSource<PresetInfo>): BaseUIElement { | ||||
|         const allButtons = []; | ||||
|         for (const layer of State.state.filteredLayers.data) { | ||||
|              | ||||
|             if(layer.isDisplayed.data === false && State.state.featureSwitchLayers){ | ||||
| 
 | ||||
|             if (layer.isDisplayed.data === false && State.state.featureSwitchLayers) { | ||||
|                 continue; | ||||
|             } | ||||
|              | ||||
| 
 | ||||
|             const presets = layer.layerDef.presets; | ||||
|             for (const preset of presets) { | ||||
| 
 | ||||
|                 const tags = TagUtils.KVtoProperties(preset.tags ?? []); | ||||
|                 let icon: BaseUIElement = layer.layerDef.GenerateLeafletStyle(new UIEventSource<any>(tags), false).icon.html | ||||
|                 let icon:() => BaseUIElement = () => layer.layerDef.GenerateLeafletStyle(new UIEventSource<any>(tags), false).icon.html | ||||
|                     .SetClass("w-12 h-12 block relative"); | ||||
|                 const presetInfo: PresetInfo = { | ||||
|                     tags: preset.tags, | ||||
|                     layerToAddTo: layer, | ||||
|                     name: preset.title, | ||||
|                     description: preset.description, | ||||
|                     icon: icon | ||||
|                     icon: icon, | ||||
|                     preciseInput: preset.preciseInput | ||||
|                 } | ||||
| 
 | ||||
|                 const button = SimpleAddUI.CreatePresetSelectButton(presetInfo); | ||||
|  |  | |||
|  | @ -68,7 +68,7 @@ export default class UserBadge extends Toggle { | |||
|                 if (user.unreadMessages > 0) { | ||||
|                     messageSpan = new Link( | ||||
|                         new Combine([Svg.envelope, "" + user.unreadMessages]), | ||||
|                         '${user.backend}/messages/inbox', | ||||
|                         `${user.backend}/messages/inbox`, | ||||
|                         true | ||||
|                     ).SetClass("alert") | ||||
|                 } | ||||
|  |  | |||
|  | @ -66,6 +66,7 @@ export default class DirectionInput extends InputElement<string> { | |||
|         }) | ||||
| 
 | ||||
|         this.RegisterTriggers(element) | ||||
|         element.style.overflow = "hidden" | ||||
| 
 | ||||
|         return element; | ||||
|     } | ||||
|  |  | |||
							
								
								
									
										35
									
								
								UI/Input/InputElementWrapper.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								UI/Input/InputElementWrapper.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,35 @@ | |||
| import {InputElement} from "./InputElement"; | ||||
| import {UIEventSource} from "../../Logic/UIEventSource"; | ||||
| import BaseUIElement from "../BaseUIElement"; | ||||
| import {Translation} from "../i18n/Translation"; | ||||
| import {SubstitutedTranslation} from "../SubstitutedTranslation"; | ||||
| 
 | ||||
| export default class InputElementWrapper<T> extends InputElement<T> { | ||||
|     public readonly IsSelected: UIEventSource<boolean>; | ||||
|     private readonly _inputElement: InputElement<T>; | ||||
|     private readonly _renderElement: BaseUIElement | ||||
| 
 | ||||
|     constructor(inputElement: InputElement<T>, translation: Translation, key: string, tags: UIEventSource<any>) { | ||||
|         super() | ||||
|         this._inputElement = inputElement; | ||||
|         this.IsSelected = inputElement.IsSelected | ||||
|         const mapping = new Map<string, BaseUIElement>() | ||||
| 
 | ||||
|         mapping.set(key, inputElement) | ||||
| 
 | ||||
|         this._renderElement = new SubstitutedTranslation(translation, tags, mapping) | ||||
|     } | ||||
| 
 | ||||
|     GetValue(): UIEventSource<T> { | ||||
|         return this._inputElement.GetValue(); | ||||
|     } | ||||
| 
 | ||||
|     IsValid(t: T): boolean { | ||||
|         return this._inputElement.IsValid(t); | ||||
|     } | ||||
| 
 | ||||
|     protected InnerConstructElement(): HTMLElement { | ||||
|         return this._renderElement.ConstructElement(); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
							
								
								
									
										76
									
								
								UI/Input/LocationInput.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								UI/Input/LocationInput.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,76 @@ | |||
| import {InputElement} from "./InputElement"; | ||||
| import Loc from "../../Models/Loc"; | ||||
| import {UIEventSource} from "../../Logic/UIEventSource"; | ||||
| import Minimap from "../Base/Minimap"; | ||||
| import BaseLayer from "../../Models/BaseLayer"; | ||||
| import Combine from "../Base/Combine"; | ||||
| import Svg from "../../Svg"; | ||||
| import State from "../../State"; | ||||
| 
 | ||||
| export default class LocationInput extends InputElement<Loc> { | ||||
| 
 | ||||
|     IsSelected: UIEventSource<boolean> = new UIEventSource<boolean>(false); | ||||
|     private _centerLocation: UIEventSource<Loc>; | ||||
|     private readonly mapBackground : UIEventSource<BaseLayer>; | ||||
| 
 | ||||
|     constructor(options?: { | ||||
|         mapBackground?: UIEventSource<BaseLayer>, | ||||
|         centerLocation?: UIEventSource<Loc>, | ||||
|     }) { | ||||
|         super(); | ||||
|         options = options ?? {} | ||||
|         options.centerLocation = options.centerLocation ?? new UIEventSource<Loc>({lat: 0, lon: 0, zoom: 1}) | ||||
|         this._centerLocation = options.centerLocation; | ||||
| 
 | ||||
|         this.mapBackground = options.mapBackground ?? State.state.backgroundLayer | ||||
|         this.SetClass("block h-full") | ||||
|     } | ||||
| 
 | ||||
|     GetValue(): UIEventSource<Loc> { | ||||
|         return this._centerLocation; | ||||
|     } | ||||
| 
 | ||||
|     IsValid(t: Loc): boolean { | ||||
|         return t !== undefined; | ||||
|     } | ||||
| 
 | ||||
|     protected InnerConstructElement(): HTMLElement { | ||||
|         const map = new Minimap( | ||||
|             { | ||||
|                 location: this._centerLocation, | ||||
|                 background: this.mapBackground | ||||
|             } | ||||
|         ) | ||||
|         map.leafletMap.addCallbackAndRunD(leaflet => { | ||||
|             console.log(leaflet.getBounds(), leaflet.getBounds().pad(0.15)) | ||||
|             leaflet.setMaxBounds( | ||||
|                 leaflet.getBounds().pad(0.15) | ||||
|             ) | ||||
|         }) | ||||
| 
 | ||||
|         this.mapBackground.map(layer => { | ||||
| 
 | ||||
|             const leaflet = map.leafletMap.data | ||||
|             if (leaflet === undefined || layer === undefined) { | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             leaflet.setMaxZoom(layer.max_zoom) | ||||
|             leaflet.setMinZoom(layer.max_zoom - 3) | ||||
|             leaflet.setZoom(layer.max_zoom - 1) | ||||
| 
 | ||||
|         }, [map.leafletMap]) | ||||
|         return new Combine([ | ||||
|             new Combine([ | ||||
|                 Svg.crosshair_empty_ui() | ||||
|                     .SetClass("block relative") | ||||
|                     .SetStyle("left: -1.25rem; top: -1.25rem; width: 2.5rem; height: 2.5rem") | ||||
|             ]).SetClass("block w-0 h-0 z-10 relative") | ||||
|                 .SetStyle("background: rgba(255, 128, 128, 0.21); left: 50%; top: 50%"), | ||||
|             map | ||||
|                 .SetClass("z-0 relative block w-full h-full bg-gray-100") | ||||
| 
 | ||||
|         ]).ConstructElement(); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | @ -103,7 +103,7 @@ export class RadioButton<T> extends InputElement<T> { | |||
|             const block = document.createElement("div") | ||||
|             block.appendChild(input) | ||||
|             block.appendChild(label) | ||||
|             block.classList.add("flex","w-full","border", "rounded-full", "border-gray-400","m-1") | ||||
|             block.classList.add("flex","w-full","border", "rounded-3xl", "border-gray-400","m-1") | ||||
|             wrappers.push(block) | ||||
| 
 | ||||
|             form.appendChild(block) | ||||
|  |  | |||
|  | @ -36,11 +36,11 @@ export class TextField extends InputElement<string> { | |||
|         this.SetClass("form-text-field") | ||||
|         let inputEl: HTMLElement | ||||
|         if (options.htmlType === "area") { | ||||
|             this.SetClass("w-full box-border max-w-full") | ||||
|             const el = document.createElement("textarea") | ||||
|             el.placeholder = placeholder | ||||
|             el.rows = options.textAreaRows | ||||
|             el.cols = 50 | ||||
|             el.style.cssText = "max-width: 100%; width: 100%; box-sizing: border-box" | ||||
|             inputEl = el; | ||||
|         } else { | ||||
|             const el = document.createElement("input") | ||||
|  |  | |||
|  | @ -282,7 +282,7 @@ export default class ValidatedTextField { | |||
|                 }) | ||||
|             ) | ||||
|             unitDropDown.GetValue().setData(unit.defaultDenom) | ||||
|             unitDropDown.SetStyle("width: min-content") | ||||
|             unitDropDown.SetClass("w-min") | ||||
| 
 | ||||
|             input = new CombinedInputElement( | ||||
|                 input, | ||||
|  |  | |||
|  | @ -24,6 +24,7 @@ import {TagUtils} from "../../Logic/Tags/TagUtils"; | |||
| import BaseUIElement from "../BaseUIElement"; | ||||
| import {DropDown} from "../Input/DropDown"; | ||||
| import {Unit} from "../../Customizations/JSON/Denomination"; | ||||
| import InputElementWrapper from "../Input/InputElementWrapper"; | ||||
| 
 | ||||
| /** | ||||
|  * Shows the question element. | ||||
|  | @ -128,7 +129,7 @@ export default class TagRenderingQuestion extends Combine { | |||
|             } | ||||
|             return Utils.NoNull(configuration.mappings?.map((m,i) => excludeIndex === i ? undefined:  m.ifnot)) | ||||
|         } | ||||
|         const ff = TagRenderingQuestion.GenerateFreeform(configuration, applicableUnit, tagsSource.data); | ||||
|         const ff = TagRenderingQuestion.GenerateFreeform(configuration, applicableUnit, tagsSource); | ||||
|         const hasImages = mappings.filter(mapping => mapping.then.ExtractImages().length > 0).length > 0 | ||||
| 
 | ||||
|         if (mappings.length < 8 || configuration.multiAnswer || hasImages) { | ||||
|  | @ -289,7 +290,7 @@ export default class TagRenderingQuestion extends Combine { | |||
|             (t0, t1) => t1.isEquivalent(t0)); | ||||
|     } | ||||
| 
 | ||||
|     private static GenerateFreeform(configuration: TagRenderingConfig, applicableUnit: Unit, tagsData: any): InputElement<TagsFilter> { | ||||
|     private static GenerateFreeform(configuration: TagRenderingConfig, applicableUnit: Unit, tags: UIEventSource<any>): InputElement<TagsFilter> { | ||||
|         const freeform = configuration.freeform; | ||||
|         if (freeform === undefined) { | ||||
|             return undefined; | ||||
|  | @ -328,7 +329,8 @@ export default class TagRenderingQuestion extends Combine { | |||
|             return undefined; | ||||
|         } | ||||
| 
 | ||||
|         let input: InputElement<string> = ValidatedTextField.InputForType(configuration.freeform.type, { | ||||
|         const tagsData = tags.data; | ||||
|         const input: InputElement<string> = ValidatedTextField.InputForType(configuration.freeform.type, { | ||||
|             isValid: (str) => (str.length <= 255), | ||||
|             country: () => tagsData._country, | ||||
|             location: [tagsData._lat, tagsData._lon], | ||||
|  | @ -336,12 +338,22 @@ export default class TagRenderingQuestion extends Combine { | |||
|             unit: applicableUnit | ||||
|         }); | ||||
| 
 | ||||
|         input.GetValue().setData(tagsData[configuration.freeform.key]); | ||||
|         input.GetValue().setData(tagsData[freeform.key] ?? freeform.default); | ||||
| 
 | ||||
|         return new InputElementMap( | ||||
|         let inputTagsFilter : InputElement<TagsFilter> = new InputElementMap( | ||||
|             input, (a, b) => a === b || (a?.isEquivalent(b) ?? false), | ||||
|             pickString, toString | ||||
|         ); | ||||
|          | ||||
|         if(freeform.inline){ | ||||
|              | ||||
|             inputTagsFilter.SetClass("w-16-imp") | ||||
|             inputTagsFilter = new InputElementWrapper(inputTagsFilter, configuration.render, freeform.key, tags) | ||||
|             inputTagsFilter.SetClass("block") | ||||
|              | ||||
|         } | ||||
|          | ||||
|         return inputTagsFilter; | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
|  | @ -80,9 +80,7 @@ export default class ShowDataLayer { | |||
| 
 | ||||
|             if (zoomToFeatures) { | ||||
|                 try { | ||||
| 
 | ||||
|                     mp.fitBounds(geoLayer.getBounds()) | ||||
| 
 | ||||
|                     mp.fitBounds(geoLayer.getBounds(), {animate: false}) | ||||
|                 } catch (e) { | ||||
|                     console.error(e) | ||||
|                 } | ||||
|  |  | |||
|  | @ -369,7 +369,6 @@ export default class SpecialVisualizations { | |||
|                                 if (unit === undefined) { | ||||
|                                     return value; | ||||
|                                 } | ||||
| 
 | ||||
|                                 return unit.asHumanLongValue(value); | ||||
| 
 | ||||
|                             }, | ||||
|  | @ -379,6 +378,7 @@ export default class SpecialVisualizations { | |||
|             } | ||||
| 
 | ||||
|         ] | ||||
|      | ||||
|     static HelpMessage: BaseUIElement = SpecialVisualizations.GenHelpMessage(); | ||||
|     private static GenHelpMessage() { | ||||
| 
 | ||||
|  |  | |||
|  | @ -7,19 +7,43 @@ import SpecialVisualizations, {SpecialVisualization} from "./SpecialVisualizatio | |||
| import {Utils} from "../Utils"; | ||||
| import {VariableUiElement} from "./Base/VariableUIElement"; | ||||
| import Combine from "./Base/Combine"; | ||||
| import BaseUIElement from "./BaseUIElement"; | ||||
| 
 | ||||
| export class SubstitutedTranslation extends VariableUiElement { | ||||
| 
 | ||||
|     public constructor( | ||||
|         translation: Translation, | ||||
|         tagsSource: UIEventSource<any>) { | ||||
|         tagsSource: UIEventSource<any>, | ||||
|         mapping: Map<string, BaseUIElement> = undefined) { | ||||
| 
 | ||||
|         const extraMappings: SpecialVisualization[] = []; | ||||
| 
 | ||||
|         mapping?.forEach((value, key) => { | ||||
|             console.log("KV:", key, value) | ||||
|             extraMappings.push( | ||||
|                 { | ||||
|                     funcName: key, | ||||
|                     constr: (() => { | ||||
|                         return value | ||||
|                     }), | ||||
|                     docs: "Dynamically injected input element", | ||||
|                     args: [], | ||||
|                     example: "" | ||||
|                 } | ||||
|             ) | ||||
|         }) | ||||
| 
 | ||||
|         super( | ||||
|             Locale.language.map(language => { | ||||
|                 const txt = translation.textFor(language) | ||||
|                 let txt = translation.textFor(language); | ||||
|                 if (txt === undefined) { | ||||
|                     return undefined | ||||
|                 } | ||||
|                 return new Combine(SubstitutedTranslation.ExtractSpecialComponents(txt).map( | ||||
|                 mapping?.forEach((_, key) => { | ||||
|                     txt = txt.replace(new RegExp(`{${key}}`, "g"), `{${key}()}`) | ||||
|                 }) | ||||
| 
 | ||||
|                 return new Combine(SubstitutedTranslation.ExtractSpecialComponents(txt, extraMappings).map( | ||||
|                     proto => { | ||||
|                         if (proto.fixed !== undefined) { | ||||
|                             return new VariableUiElement(tagsSource.map(tags => Utils.SubstituteKeys(proto.fixed, tags))); | ||||
|  | @ -36,30 +60,35 @@ export class SubstitutedTranslation extends VariableUiElement { | |||
|             }) | ||||
|         ) | ||||
| 
 | ||||
| 
 | ||||
|         this.SetClass("w-full") | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     public static ExtractSpecialComponents(template: string): { | ||||
|         fixed?: string, special?: { | ||||
|     public static ExtractSpecialComponents(template: string, extraMappings: SpecialVisualization[] = []): { | ||||
|         fixed?: string, | ||||
|         special?: { | ||||
|             func: SpecialVisualization, | ||||
|             args: string[], | ||||
|             style: string | ||||
|         } | ||||
|     }[] { | ||||
| 
 | ||||
|         for (const knownSpecial of SpecialVisualizations.specialVisualizations) { | ||||
|         if (extraMappings.length > 0) { | ||||
| 
 | ||||
|             console.log("Extra mappings are", extraMappings) | ||||
|         } | ||||
| 
 | ||||
|         for (const knownSpecial of SpecialVisualizations.specialVisualizations.concat(extraMappings)) { | ||||
| 
 | ||||
|             // Note: the '.*?' in the regex reads as 'any character, but in a non-greedy way'
 | ||||
|             const matched = template.match(`(.*){${knownSpecial.funcName}\\((.*?)\\)(:.*)?}(.*)`); | ||||
|             if (matched != null) { | ||||
| 
 | ||||
|                 // We found a special component that should be brought to live
 | ||||
|                 const partBefore = SubstitutedTranslation.ExtractSpecialComponents(matched[1]); | ||||
|                 const partBefore = SubstitutedTranslation.ExtractSpecialComponents(matched[1], extraMappings); | ||||
|                 const argument = matched[2].trim(); | ||||
|                 const style = matched[3]?.substring(1) ?? "" | ||||
|                 const partAfter = SubstitutedTranslation.ExtractSpecialComponents(matched[4]); | ||||
|                 const partAfter = SubstitutedTranslation.ExtractSpecialComponents(matched[4], extraMappings); | ||||
|                 const args = knownSpecial.args.map(arg => arg.defaultValue ?? ""); | ||||
|                 if (argument.length > 0) { | ||||
|                     const realArgs = argument.split(",").map(str => str.trim()); | ||||
|  | @ -73,11 +102,13 @@ export class SubstitutedTranslation extends VariableUiElement { | |||
|                 } | ||||
| 
 | ||||
|                 let element; | ||||
|                 element =  {special:{ | ||||
|                     args: args, | ||||
|                     style: style, | ||||
|                     func: knownSpecial | ||||
|                 }} | ||||
|                 element = { | ||||
|                     special: { | ||||
|                         args: args, | ||||
|                         style: style, | ||||
|                         func: knownSpecial | ||||
|                     } | ||||
|                 } | ||||
|                 return [...partBefore, element, ...partAfter] | ||||
|             } | ||||
|         } | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue