forked from MapComplete/MapComplete
		
	Chore: reformat all files with prettier
This commit is contained in:
		
							parent
							
								
									5757ae5dea
								
							
						
					
					
						commit
						d008dcb54d
					
				
					 214 changed files with 8926 additions and 8196 deletions
				
			
		|  | @ -2,11 +2,9 @@ | |||
|   /** | ||||
|    * Simple wrapper around the HTML-color field. | ||||
|    */ | ||||
|   import { UIEventSource } from "../../../Logic/UIEventSource"; | ||||
| 
 | ||||
|   export let value: UIEventSource<undefined | string>; | ||||
|   import { UIEventSource } from "../../../Logic/UIEventSource" | ||||
| 
 | ||||
|   export let value: UIEventSource<undefined | string> | ||||
| </script> | ||||
| 
 | ||||
| 
 | ||||
| <input bind:value={$value} type="color"> | ||||
| <input bind:value={$value} type="color" /> | ||||
|  |  | |||
|  | @ -2,11 +2,9 @@ | |||
|   /** | ||||
|    * Simple wrapper around the HTML-date field. | ||||
|    */ | ||||
|   import { UIEventSource } from "../../../Logic/UIEventSource"; | ||||
| 
 | ||||
|   export let value: UIEventSource<undefined | string>; | ||||
|   import { UIEventSource } from "../../../Logic/UIEventSource" | ||||
| 
 | ||||
|   export let value: UIEventSource<undefined | string> | ||||
| </script> | ||||
| 
 | ||||
| 
 | ||||
| <input bind:value={$value} type="date"> | ||||
| <input bind:value={$value} type="date" /> | ||||
|  |  | |||
|  | @ -1,69 +1,68 @@ | |||
| <script lang="ts"> | ||||
|   import { UIEventSource } from "../../../Logic/UIEventSource"; | ||||
|   import type { MapProperties } from "../../../Models/MapProperties"; | ||||
|   import { Map as MlMap } from "maplibre-gl"; | ||||
|   import { MapLibreAdaptor } from "../../Map/MapLibreAdaptor"; | ||||
|   import MaplibreMap from "../../Map/MaplibreMap.svelte"; | ||||
|   import ToSvelte from "../../Base/ToSvelte.svelte"; | ||||
|   import Svg from "../../../Svg.js"; | ||||
|   import { UIEventSource } from "../../../Logic/UIEventSource" | ||||
|   import type { MapProperties } from "../../../Models/MapProperties" | ||||
|   import { Map as MlMap } from "maplibre-gl" | ||||
|   import { MapLibreAdaptor } from "../../Map/MapLibreAdaptor" | ||||
|   import MaplibreMap from "../../Map/MaplibreMap.svelte" | ||||
|   import ToSvelte from "../../Base/ToSvelte.svelte" | ||||
|   import Svg from "../../../Svg.js" | ||||
| 
 | ||||
|   /** | ||||
|    * A visualisation to pick a direction on a map background. | ||||
|    */ | ||||
|   export let value: UIEventSource<undefined | string>; | ||||
|   export let mapProperties: Partial<MapProperties> & { readonly location: UIEventSource<{ lon: number; lat: number }> }; | ||||
|   let map: UIEventSource<MlMap> = new UIEventSource<MlMap>(undefined); | ||||
|   let mla = new MapLibreAdaptor(map, mapProperties); | ||||
|   export let value: UIEventSource<undefined | string> | ||||
|   export let mapProperties: Partial<MapProperties> & { | ||||
|     readonly location: UIEventSource<{ lon: number; lat: number }> | ||||
|   } | ||||
|   let map: UIEventSource<MlMap> = new UIEventSource<MlMap>(undefined) | ||||
|   let mla = new MapLibreAdaptor(map, mapProperties) | ||||
|   mla.allowMoving.setData(false) | ||||
|   mla.allowZooming.setData(false) | ||||
|   let directionElem: HTMLElement | undefined; | ||||
|   $: value.addCallbackAndRunD(degrees => { | ||||
|   let directionElem: HTMLElement | undefined | ||||
|   $: value.addCallbackAndRunD((degrees) => { | ||||
|     if (directionElem === undefined) { | ||||
|       return; | ||||
|       return | ||||
|     } | ||||
|     directionElem.style.rotate = degrees + "deg"; | ||||
|   }); | ||||
|     directionElem.style.rotate = degrees + "deg" | ||||
|   }) | ||||
| 
 | ||||
|   let mainElem : HTMLElement | ||||
|   let mainElem: HTMLElement | ||||
|   function onPosChange(x: number, y: number) { | ||||
|     const rect = mainElem.getBoundingClientRect(); | ||||
|     const dx = -(rect.left + rect.right) / 2 + x; | ||||
|     const dy = (rect.top + rect.bottom) / 2 - y; | ||||
|     const angle = (180 * Math.atan2(dy, dx)) / Math.PI; | ||||
|     const angleGeo = Math.floor((450 - angle) % 360); | ||||
|     value.setData(""+angleGeo); | ||||
|     const rect = mainElem.getBoundingClientRect() | ||||
|     const dx = -(rect.left + rect.right) / 2 + x | ||||
|     const dy = (rect.top + rect.bottom) / 2 - y | ||||
|     const angle = (180 * Math.atan2(dy, dx)) / Math.PI | ||||
|     const angleGeo = Math.floor((450 - angle) % 360) | ||||
|     value.setData("" + angleGeo) | ||||
|   } | ||||
| 
 | ||||
|   let isDown = false; | ||||
|   let isDown = false | ||||
| </script> | ||||
| 
 | ||||
| <div bind:this={mainElem} class="relative w-48 h-48 cursor-pointer overflow-hidden" | ||||
|      on:click={e => onPosChange(e.x, e.y)} | ||||
|      on:mousedown={e => { | ||||
|          isDown = true | ||||
|          onPosChange(e.clientX, e.clientY) | ||||
|        } } | ||||
|      on:mousemove={e => { | ||||
|       if(isDown){ | ||||
| <div | ||||
|   bind:this={mainElem} | ||||
|   class="relative w-48 h-48 cursor-pointer overflow-hidden" | ||||
|   on:click={(e) => onPosChange(e.x, e.y)} | ||||
|   on:mousedown={(e) => { | ||||
|     isDown = true | ||||
|     onPosChange(e.clientX, e.clientY) | ||||
|   }} | ||||
|   on:mousemove={(e) => { | ||||
|     if (isDown) { | ||||
|       onPosChange(e.clientX, e.clientY) | ||||
|          | ||||
|       }}} | ||||
| 
 | ||||
|      on:mouseup={() => { | ||||
|          isDown = false | ||||
|        } } | ||||
|      on:touchmove={e => onPosChange(e.touches[0].clientX, e.touches[0].clientY)} | ||||
| 
 | ||||
| 
 | ||||
|      on:touchstart={e => onPosChange(e.touches[0].clientX, e.touches[0].clientY)}> | ||||
|     } | ||||
|   }} | ||||
|   on:mouseup={() => { | ||||
|     isDown = false | ||||
|   }} | ||||
|   on:touchmove={(e) => onPosChange(e.touches[0].clientX, e.touches[0].clientY)} | ||||
|   on:touchstart={(e) => onPosChange(e.touches[0].clientX, e.touches[0].clientY)} | ||||
| > | ||||
|   <div class="w-full h-full absolute top-0 left-0 cursor-pointer"> | ||||
|     <MaplibreMap {map} attribution={false}></MaplibreMap> | ||||
|     <MaplibreMap {map} attribution={false} /> | ||||
|   </div> | ||||
| 
 | ||||
|   <div bind:this={directionElem} class="absolute w-full h-full top-0 left-0"> | ||||
| 
 | ||||
|     <ToSvelte construct={ Svg.direction_stroke_svg}> | ||||
| 
 | ||||
|     </ToSvelte> | ||||
|     <ToSvelte construct={Svg.direction_stroke_svg} /> | ||||
|   </div> | ||||
| </div> | ||||
|  |  | |||
|  | @ -1,139 +1,150 @@ | |||
| <script lang="ts"> | ||||
|   import { Store, Stores, UIEventSource } from "../../../Logic/UIEventSource"; | ||||
|   import { Store, Stores, UIEventSource } from "../../../Logic/UIEventSource" | ||||
| 
 | ||||
|   /** | ||||
|    * Given the available floors, shows an elevator to pick a single one | ||||
|    *  | ||||
|    * | ||||
|    * This is but the input element, the logic of handling the filter is in 'LevelSelector' | ||||
|    */ | ||||
|   export let floors: Store<string[]>; | ||||
|   export let value: UIEventSource<string>; | ||||
|   export let floors: Store<string[]> | ||||
|   export let value: UIEventSource<string> | ||||
| 
 | ||||
|   const HEIGHT = 40; | ||||
|   const HEIGHT = 40 | ||||
| 
 | ||||
|   let initialIndex = Math.max(0, floors?.data?.findIndex(f => f === value?.data) ?? 0); | ||||
|   let index: UIEventSource<number> = new UIEventSource<number>(initialIndex); | ||||
|   let forceIndex: number | undefined = undefined; | ||||
|   let top = Math.max(0, initialIndex) * HEIGHT; | ||||
|   let elevator: HTMLImageElement; | ||||
|   let initialIndex = Math.max(0, floors?.data?.findIndex((f) => f === value?.data) ?? 0) | ||||
|   let index: UIEventSource<number> = new UIEventSource<number>(initialIndex) | ||||
|   let forceIndex: number | undefined = undefined | ||||
|   let top = Math.max(0, initialIndex) * HEIGHT | ||||
|   let elevator: HTMLImageElement | ||||
| 
 | ||||
|   let mouseDown = false; | ||||
|   let mouseDown = false | ||||
| 
 | ||||
|   let container: HTMLElement; | ||||
|   let container: HTMLElement | ||||
| 
 | ||||
|   $:{ | ||||
|   $: { | ||||
|     if (top > 0 || forceIndex !== undefined) { | ||||
|       index.setData(closestFloorIndex()); | ||||
|       value.setData(floors.data[forceIndex ?? closestFloorIndex()]); | ||||
|       index.setData(closestFloorIndex()) | ||||
|       value.setData(floors.data[forceIndex ?? closestFloorIndex()]) | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   function unclick() { | ||||
|     mouseDown = false; | ||||
|     mouseDown = false | ||||
|   } | ||||
| 
 | ||||
|   function click() { | ||||
|     mouseDown = true; | ||||
|     mouseDown = true | ||||
|   } | ||||
| 
 | ||||
|   function closestFloorIndex() { | ||||
|     return Math.min(floors.data.length - 1, Math.max(0, Math.round(top / HEIGHT))); | ||||
|     return Math.min(floors.data.length - 1, Math.max(0, Math.round(top / HEIGHT))) | ||||
|   } | ||||
| 
 | ||||
|   function onMove(e: { movementY: number }) { | ||||
|     if (mouseDown) { | ||||
|       forceIndex = undefined; | ||||
|       const containerY = container.clientTop; | ||||
|       const containerMax = containerY + (floors.data.length - 1) * HEIGHT; | ||||
|       top = Math.min(Math.max(0, top + e.movementY), containerMax); | ||||
|       forceIndex = undefined | ||||
|       const containerY = container.clientTop | ||||
|       const containerMax = containerY + (floors.data.length - 1) * HEIGHT | ||||
|       top = Math.min(Math.max(0, top + e.movementY), containerMax) | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   let momentum = 0; | ||||
|   let momentum = 0 | ||||
| 
 | ||||
|   function stabilize() { | ||||
|     // Automatically move the elevator to the closes floor | ||||
|     if (mouseDown) { | ||||
|       return; | ||||
|       return | ||||
|     } | ||||
|     const target = (forceIndex ?? index.data) * HEIGHT; | ||||
|     let diff = target - top; | ||||
|     const target = (forceIndex ?? index.data) * HEIGHT | ||||
|     let diff = target - top | ||||
|     if (diff > 1) { | ||||
|       diff /= 3; | ||||
|       diff /= 3 | ||||
|     } | ||||
|     const sign = Math.sign(diff); | ||||
|     momentum = momentum + sign; | ||||
|     let diffR = Math.min(Math.abs(momentum), forceIndex !== undefined ? 9 : 3, Math.abs(diff)); | ||||
|     momentum = Math.sign(momentum) * Math.min(diffR, Math.abs(momentum)); | ||||
|     top += sign * diffR; | ||||
|     const sign = Math.sign(diff) | ||||
|     momentum = momentum + sign | ||||
|     let diffR = Math.min(Math.abs(momentum), forceIndex !== undefined ? 9 : 3, Math.abs(diff)) | ||||
|     momentum = Math.sign(momentum) * Math.min(diffR, Math.abs(momentum)) | ||||
|     top += sign * diffR | ||||
|     if (index.data === forceIndex) { | ||||
|       forceIndex = undefined; | ||||
|       forceIndex = undefined | ||||
|     } | ||||
|     top = Math.max(top, 0) | ||||
|   } | ||||
| 
 | ||||
|   Stores.Chronic(50).addCallback(_ => stabilize()); | ||||
|   floors.addCallback(floors => { | ||||
|     forceIndex = floors.findIndex(s => s === value.data) | ||||
|   Stores.Chronic(50).addCallback((_) => stabilize()) | ||||
|   floors.addCallback((floors) => { | ||||
|     forceIndex = floors.findIndex((s) => s === value.data) | ||||
|   }) | ||||
| 
 | ||||
|   let image: HTMLImageElement; | ||||
|   $:{ | ||||
|   let image: HTMLImageElement | ||||
|   $: { | ||||
|     if (image) { | ||||
|       let lastY = 0; | ||||
|       let lastY = 0 | ||||
|       image.ontouchstart = (e: TouchEvent) => { | ||||
|         mouseDown = true; | ||||
|         lastY = e.changedTouches[0].clientY; | ||||
|       }; | ||||
|       image.ontouchmove = e => { | ||||
|         const y = e.changedTouches[0].clientY; | ||||
|         mouseDown = true | ||||
|         lastY = e.changedTouches[0].clientY | ||||
|       } | ||||
|       image.ontouchmove = (e) => { | ||||
|         const y = e.changedTouches[0].clientY | ||||
|         console.log(y) | ||||
|         const movementY = y - lastY; | ||||
|         lastY = y; | ||||
|         onMove({ movementY }); | ||||
|       }; | ||||
|       image.ontouchend = unclick; | ||||
|         const movementY = y - lastY | ||||
|         lastY = y | ||||
|         onMove({ movementY }) | ||||
|       } | ||||
|       image.ontouchend = unclick | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
| </script> | ||||
| 
 | ||||
| <div bind:this={container} class="relative" | ||||
|      style={`height: calc(${HEIGHT}px * ${$floors.length}); width: 96px`}> | ||||
| <div | ||||
|   bind:this={container} | ||||
|   class="relative" | ||||
|   style={`height: calc(${HEIGHT}px * ${$floors.length}); width: 96px`} | ||||
| > | ||||
|   <div class="h-full absolute w-min right-0"> | ||||
|     {#each $floors as floor, i} | ||||
|       <button style={`height: ${HEIGHT}px; width: ${HEIGHT}px`} | ||||
|               class={"m-0 border-2 border-gray-300 flex content-box justify-center items-center "+(i === (forceIndex ?? $index) ? "selected": "" ) | ||||
|            } | ||||
|               on:click={() => {forceIndex = i}} | ||||
|       > {floor}</button> | ||||
|       <button | ||||
|         style={`height: ${HEIGHT}px; width: ${HEIGHT}px`} | ||||
|         class={"m-0 border-2 border-gray-300 flex content-box justify-center items-center " + | ||||
|           (i === (forceIndex ?? $index) ? "selected" : "")} | ||||
|         on:click={() => { | ||||
|           forceIndex = i | ||||
|         }} | ||||
|       > | ||||
|         {floor} | ||||
|       </button> | ||||
|     {/each} | ||||
|   </div> | ||||
| 
 | ||||
|   <div style={`width: ${HEIGHT}px`}> | ||||
|     <img bind:this={image} class="draggable" draggable="false" on:mousedown={click} src="./assets/svg/elevator.svg" | ||||
|          style={" top: "+top+"px;"} /> | ||||
|     <img | ||||
|       bind:this={image} | ||||
|       class="draggable" | ||||
|       draggable="false" | ||||
|       on:mousedown={click} | ||||
|       src="./assets/svg/elevator.svg" | ||||
|       style={" top: " + top + "px;"} | ||||
|     /> | ||||
|   </div> | ||||
| </div> | ||||
| 
 | ||||
| <svelte:window on:mousemove={onMove} on:mouseup={unclick} /> | ||||
| 
 | ||||
| <style> | ||||
|   .draggable { | ||||
|     user-select: none; | ||||
|     cursor: move; | ||||
|     position: absolute; | ||||
|     user-drag: none; | ||||
| 
 | ||||
|     .draggable { | ||||
|         user-select: none; | ||||
|         cursor: move; | ||||
|         position: absolute; | ||||
|         user-drag: none; | ||||
| 
 | ||||
|         height: 72px; | ||||
|         margin-top: -15px; | ||||
|         margin-bottom: -15px; | ||||
|         margin-left: -18px; | ||||
|         -webkit-user-drag: none; | ||||
|         -moz-user-select: none; | ||||
|         -webkit-user-select: none; | ||||
|         -ms-user-select: none; | ||||
|     } | ||||
|     height: 72px; | ||||
|     margin-top: -15px; | ||||
|     margin-bottom: -15px; | ||||
|     margin-left: -18px; | ||||
|     -webkit-user-drag: none; | ||||
|     -moz-user-select: none; | ||||
|     -webkit-user-select: none; | ||||
|     -ms-user-select: none; | ||||
|   } | ||||
| </style> | ||||
|  |  | |||
|  | @ -1,82 +1,91 @@ | |||
| <script lang="ts"> | ||||
|     import {Store, UIEventSource} from "../../../Logic/UIEventSource"; | ||||
|     import type {MapProperties} from "../../../Models/MapProperties"; | ||||
|     import {Map as MlMap} from "maplibre-gl"; | ||||
|     import {MapLibreAdaptor} from "../../Map/MapLibreAdaptor"; | ||||
|     import MaplibreMap from "../../Map/MaplibreMap.svelte"; | ||||
|     import DragInvitation from "../../Base/DragInvitation.svelte"; | ||||
|     import {GeoOperations} from "../../../Logic/GeoOperations"; | ||||
|     import ShowDataLayer from "../../Map/ShowDataLayer"; | ||||
|     import * as boundsdisplay from "../../../assets/layers/range/range.json" | ||||
|     import StaticFeatureSource from "../../../Logic/FeatureSource/Sources/StaticFeatureSource"; | ||||
|     import * as turf from "@turf/turf" | ||||
|     import LayerConfig from "../../../Models/ThemeConfig/LayerConfig"; | ||||
|     import {onDestroy} from "svelte"; | ||||
|   import { Store, UIEventSource } from "../../../Logic/UIEventSource" | ||||
|   import type { MapProperties } from "../../../Models/MapProperties" | ||||
|   import { Map as MlMap } from "maplibre-gl" | ||||
|   import { MapLibreAdaptor } from "../../Map/MapLibreAdaptor" | ||||
|   import MaplibreMap from "../../Map/MaplibreMap.svelte" | ||||
|   import DragInvitation from "../../Base/DragInvitation.svelte" | ||||
|   import { GeoOperations } from "../../../Logic/GeoOperations" | ||||
|   import ShowDataLayer from "../../Map/ShowDataLayer" | ||||
|   import * as boundsdisplay from "../../../assets/layers/range/range.json" | ||||
|   import StaticFeatureSource from "../../../Logic/FeatureSource/Sources/StaticFeatureSource" | ||||
|   import * as turf from "@turf/turf" | ||||
|   import LayerConfig from "../../../Models/ThemeConfig/LayerConfig" | ||||
|   import { onDestroy } from "svelte" | ||||
| 
 | ||||
|     /** | ||||
|      * A visualisation to pick a location on a map background | ||||
|      */ | ||||
|     export let value: UIEventSource<{ lon: number, lat: number }>; | ||||
|     export let initialCoordinate : {lon: number, lat :number} | ||||
|     initialCoordinate = initialCoordinate ?? value.data | ||||
|     export let maxDistanceInMeters: number = undefined | ||||
|     export let mapProperties: Partial<MapProperties> & { | ||||
|         readonly location: UIEventSource<{ lon: number; lat: number }> | ||||
|     } = undefined; | ||||
|     /** | ||||
|      * Called when setup is done, can be used to add more layers to the map | ||||
|      */ | ||||
|     export let onCreated: (value: Store<{ | ||||
|         lon: number, | ||||
|         lat: number | ||||
|     }>, map: Store<MlMap>, mapProperties: MapProperties) => void = undefined | ||||
|   /** | ||||
|    * A visualisation to pick a location on a map background | ||||
|    */ | ||||
|   export let value: UIEventSource<{ lon: number; lat: number }> | ||||
|   export let initialCoordinate: { lon: number; lat: number } | ||||
|   initialCoordinate = initialCoordinate ?? value.data | ||||
|   export let maxDistanceInMeters: number = undefined | ||||
|   export let mapProperties: Partial<MapProperties> & { | ||||
|     readonly location: UIEventSource<{ lon: number; lat: number }> | ||||
|   } = undefined | ||||
|   /** | ||||
|    * Called when setup is done, can be used to add more layers to the map | ||||
|    */ | ||||
|   export let onCreated: ( | ||||
|     value: Store<{ | ||||
|       lon: number | ||||
|       lat: number | ||||
|     }>, | ||||
|     map: Store<MlMap>, | ||||
|     mapProperties: MapProperties | ||||
|   ) => void = undefined | ||||
| 
 | ||||
|     export let map: UIEventSource<MlMap> = new UIEventSource<MlMap>(undefined); | ||||
|     let mla = new MapLibreAdaptor(map, mapProperties); | ||||
|     mapProperties.location.syncWith(value) | ||||
|     if (onCreated) { | ||||
|         onCreated(value, map, mla) | ||||
|     } | ||||
|   export let map: UIEventSource<MlMap> = new UIEventSource<MlMap>(undefined) | ||||
|   let mla = new MapLibreAdaptor(map, mapProperties) | ||||
|   mapProperties.location.syncWith(value) | ||||
|   if (onCreated) { | ||||
|     onCreated(value, map, mla) | ||||
|   } | ||||
| 
 | ||||
|     let rangeIsShown = false | ||||
|     if (maxDistanceInMeters) { | ||||
|         onDestroy(mla.location.addCallbackD(newLocation => { | ||||
|             const l = [newLocation.lon, newLocation.lat] | ||||
|             const c: [number, number] = [initialCoordinate.lon, initialCoordinate.lat] | ||||
|             const d = GeoOperations.distanceBetween(l, c) | ||||
|   let rangeIsShown = false | ||||
|   if (maxDistanceInMeters) { | ||||
|     onDestroy( | ||||
|       mla.location.addCallbackD((newLocation) => { | ||||
|         const l = [newLocation.lon, newLocation.lat] | ||||
|         const c: [number, number] = [initialCoordinate.lon, initialCoordinate.lat] | ||||
|         const d = GeoOperations.distanceBetween(l, c) | ||||
|         console.log("distance is", d, l, c) | ||||
|             if (d <= maxDistanceInMeters) { | ||||
|                 return | ||||
|             } | ||||
|             // This is too far away - let's move back | ||||
|             const correctLocation = GeoOperations.along(c, l, maxDistanceInMeters - 10) | ||||
|             window.setTimeout(() => { | ||||
|                 mla.location.setData({lon: correctLocation[0], lat: correctLocation[1]}) | ||||
|             }, 25) | ||||
|         if (d <= maxDistanceInMeters) { | ||||
|           return | ||||
|         } | ||||
|         // This is too far away - let's move back | ||||
|         const correctLocation = GeoOperations.along(c, l, maxDistanceInMeters - 10) | ||||
|         window.setTimeout(() => { | ||||
|           mla.location.setData({ lon: correctLocation[0], lat: correctLocation[1] }) | ||||
|         }, 25) | ||||
| 
 | ||||
|             if (!rangeIsShown) { | ||||
| 
 | ||||
|                 new ShowDataLayer(map, { | ||||
|                     layer: new LayerConfig(boundsdisplay), | ||||
|                     features: new StaticFeatureSource( | ||||
|                         [turf.circle(c, maxDistanceInMeters, {units: "meters", properties: {"range":"yes", id: "0"}}, )] | ||||
|                     ) | ||||
|                 }) | ||||
|                 rangeIsShown = true | ||||
|             } | ||||
|         })) | ||||
|     } | ||||
|         if (!rangeIsShown) { | ||||
|           new ShowDataLayer(map, { | ||||
|             layer: new LayerConfig(boundsdisplay), | ||||
|             features: new StaticFeatureSource([ | ||||
|               turf.circle(c, maxDistanceInMeters, { | ||||
|                 units: "meters", | ||||
|                 properties: { range: "yes", id: "0" }, | ||||
|               }), | ||||
|             ]), | ||||
|           }) | ||||
|           rangeIsShown = true | ||||
|         } | ||||
|       }) | ||||
|     ) | ||||
|   } | ||||
| </script> | ||||
| 
 | ||||
| <div class="relative h-full min-h-32 cursor-pointer overflow-hidden"> | ||||
|     <div class="w-full h-full absolute top-0 left-0 cursor-pointer"> | ||||
|         <MaplibreMap {map}/> | ||||
|     </div> | ||||
|   <div class="w-full h-full absolute top-0 left-0 cursor-pointer"> | ||||
|     <MaplibreMap {map} /> | ||||
|   </div> | ||||
| 
 | ||||
|     <div class="w-full h-full absolute top-0 left-0 p-8 pointer-events-none opacity-50 flex items-center"> | ||||
|         <img class="h-full max-h-24" src="./assets/svg/move-arrows.svg"/> | ||||
|     </div> | ||||
| 
 | ||||
|     <DragInvitation hideSignal={mla.location.stabilized(3000)}></DragInvitation> | ||||
|   <div | ||||
|     class="w-full h-full absolute top-0 left-0 p-8 pointer-events-none opacity-50 flex items-center" | ||||
|   > | ||||
|     <img class="h-full max-h-24" src="./assets/svg/move-arrows.svg" /> | ||||
|   </div> | ||||
| 
 | ||||
|   <DragInvitation hideSignal={mla.location.stabilized(3000)} /> | ||||
| </div> | ||||
|  |  | |||
|  | @ -1,32 +1,33 @@ | |||
| <script lang="ts"> | ||||
|     /** | ||||
|      * Constructs an input helper element for the given type. | ||||
|      * Note that all values are stringified | ||||
|      */ | ||||
|   /** | ||||
|    * Constructs an input helper element for the given type. | ||||
|    * Note that all values are stringified | ||||
|    */ | ||||
| 
 | ||||
|     import {UIEventSource} from "../../Logic/UIEventSource"; | ||||
|     import type {ValidatorType} from "./Validators"; | ||||
|     import InputHelpers from "./InputHelpers"; | ||||
|     import ToSvelte from "../Base/ToSvelte.svelte"; | ||||
|     import type {Feature} from "geojson"; | ||||
|     import BaseUIElement from "../BaseUIElement"; | ||||
|     import {VariableUiElement} from "../Base/VariableUIElement"; | ||||
|   import { UIEventSource } from "../../Logic/UIEventSource" | ||||
|   import type { ValidatorType } from "./Validators" | ||||
|   import InputHelpers from "./InputHelpers" | ||||
|   import ToSvelte from "../Base/ToSvelte.svelte" | ||||
|   import type { Feature } from "geojson" | ||||
|   import BaseUIElement from "../BaseUIElement" | ||||
|   import { VariableUiElement } from "../Base/VariableUIElement" | ||||
| 
 | ||||
|     export let type: ValidatorType; | ||||
|     export let value: UIEventSource<string>; | ||||
| 
 | ||||
|     export let feature: Feature; | ||||
|     export let args: (string | number | boolean)[] = undefined; | ||||
| 
 | ||||
|     let properties = {feature, args: args ?? []}; | ||||
|     let construct = new UIEventSource<(value, extraProperties) => BaseUIElement>(undefined) | ||||
|     $: { | ||||
|         construct.setData(InputHelpers.AvailableInputHelpers[type]) | ||||
|     } | ||||
|   export let type: ValidatorType | ||||
|   export let value: UIEventSource<string> | ||||
| 
 | ||||
|   export let feature: Feature | ||||
|   export let args: (string | number | boolean)[] = undefined | ||||
| 
 | ||||
|   let properties = { feature, args: args ?? [] } | ||||
|   let construct = new UIEventSource<(value, extraProperties) => BaseUIElement>(undefined) | ||||
|   $: { | ||||
|     construct.setData(InputHelpers.AvailableInputHelpers[type]) | ||||
|   } | ||||
| </script> | ||||
| 
 | ||||
| {#if construct !== undefined} | ||||
|     <ToSvelte construct={() => new VariableUiElement(construct.mapD(construct => construct(value, properties)))}/> | ||||
|   <ToSvelte | ||||
|     construct={() => | ||||
|       new VariableUiElement(construct.mapD((construct) => construct(value, properties)))} | ||||
|   /> | ||||
| {/if} | ||||
|  |  | |||
|  | @ -1,109 +1,116 @@ | |||
| <script lang="ts"> | ||||
|   import { UIEventSource } from "../../Logic/UIEventSource" | ||||
|   import type { ValidatorType } from "./Validators" | ||||
|   import Validators from "./Validators" | ||||
|   import { ExclamationIcon } from "@rgossiaux/svelte-heroicons/solid" | ||||
|   import { Translation } from "../i18n/Translation" | ||||
|   import { createEventDispatcher, onDestroy } from "svelte" | ||||
|   import { Validator } from "./Validator" | ||||
|   import { Unit } from "../../Models/Unit" | ||||
|   import UnitInput from "../Popup/UnitInput.svelte" | ||||
| 
 | ||||
|     import {UIEventSource} from "../../Logic/UIEventSource"; | ||||
|     import type {ValidatorType} from "./Validators"; | ||||
|     import Validators from "./Validators"; | ||||
|     import {ExclamationIcon} from "@rgossiaux/svelte-heroicons/solid"; | ||||
|     import {Translation} from "../i18n/Translation"; | ||||
|     import {createEventDispatcher, onDestroy} from "svelte"; | ||||
|     import {Validator} from "./Validator"; | ||||
|     import {Unit} from "../../Models/Unit"; | ||||
|     import UnitInput from "../Popup/UnitInput.svelte"; | ||||
|   export let type: ValidatorType | ||||
|   export let feedback: UIEventSource<Translation> | undefined = undefined | ||||
|   export let getCountry: () => string | undefined | ||||
|   export let placeholder: string | Translation | undefined | ||||
|   export let unit: Unit = undefined | ||||
| 
 | ||||
|   export let value: UIEventSource<string> | ||||
|   /** | ||||
|    * Internal state bound to the input element. | ||||
|    * | ||||
|    * This is only copied to 'value' when appropriate so that no invalid values leak outside; | ||||
|    * Additionally, the unit is added when copying | ||||
|    */ | ||||
|   let _value = new UIEventSource(value.data ?? "") | ||||
| 
 | ||||
|     export let type: ValidatorType; | ||||
|     export let feedback: UIEventSource<Translation> | undefined = undefined; | ||||
|     export let getCountry: () => string | undefined | ||||
|     export let placeholder: string | Translation | undefined | ||||
|     export let unit: Unit = undefined | ||||
|   let validator: Validator = Validators.get(type ?? "string") | ||||
|   let selectedUnit: UIEventSource<string> = new UIEventSource<string>(undefined) | ||||
|   let _placeholder = placeholder ?? validator?.getPlaceholder() ?? type | ||||
| 
 | ||||
|     export let value: UIEventSource<string>; | ||||
|     /** | ||||
|      * Internal state bound to the input element. | ||||
|      * | ||||
|      * This is only copied to 'value' when appropriate so that no invalid values leak outside; | ||||
|      * Additionally, the unit is added when copying | ||||
|      */ | ||||
|     let _value = new UIEventSource(value.data ?? ""); | ||||
| 
 | ||||
|     let validator: Validator = Validators.get(type ?? "string") | ||||
|     let selectedUnit: UIEventSource<string> = new UIEventSource<string>(undefined) | ||||
|     let _placeholder = placeholder ?? validator?.getPlaceholder() ?? type | ||||
| 
 | ||||
|     function initValueAndDenom() { | ||||
|         if (unit && value.data) { | ||||
|             const [v, denom] = unit?.findDenomination(value.data, getCountry) | ||||
|             if (denom) { | ||||
|                 _value.setData(v) | ||||
|                 selectedUnit.setData(denom.canonical) | ||||
|             } else { | ||||
|                 _value.setData(value.data ?? "") | ||||
|             } | ||||
|         } else { | ||||
|             _value.setData(value.data ?? "") | ||||
|         } | ||||
|   function initValueAndDenom() { | ||||
|     if (unit && value.data) { | ||||
|       const [v, denom] = unit?.findDenomination(value.data, getCountry) | ||||
|       if (denom) { | ||||
|         _value.setData(v) | ||||
|         selectedUnit.setData(denom.canonical) | ||||
|       } else { | ||||
|         _value.setData(value.data ?? "") | ||||
|       } | ||||
|     } else { | ||||
|       _value.setData(value.data ?? "") | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   initValueAndDenom() | ||||
| 
 | ||||
|   $: { | ||||
|     // The type changed -> reset some values | ||||
|     validator = Validators.get(type ?? "string") | ||||
|     _placeholder = placeholder ?? validator?.getPlaceholder() ?? type | ||||
|     feedback = feedback?.setData(validator?.getFeedback(_value.data, getCountry)) | ||||
| 
 | ||||
|     initValueAndDenom() | ||||
|   } | ||||
| 
 | ||||
|     $: { | ||||
|         // The type changed -> reset some values | ||||
|         validator = Validators.get(type ?? "string") | ||||
|         _placeholder = placeholder ?? validator?.getPlaceholder() ?? type | ||||
|         feedback = feedback?.setData(validator?.getFeedback(_value.data, getCountry)); | ||||
| 
 | ||||
|         initValueAndDenom() | ||||
|   function setValues() { | ||||
|     // Update the value stores | ||||
|     const v = _value.data | ||||
|     if (!validator.isValid(v, getCountry) || v === "") { | ||||
|       value.setData(undefined) | ||||
|       feedback?.setData(validator.getFeedback(v, getCountry)) | ||||
|       return | ||||
|     } | ||||
| 
 | ||||
|     function setValues() { | ||||
|         // Update the value stores | ||||
|         const v = _value.data | ||||
|         if (!validator.isValid(v, getCountry) || v === "") { | ||||
|             value.setData(undefined); | ||||
|             feedback?.setData(validator.getFeedback(v, getCountry)); | ||||
|             return | ||||
|         } | ||||
| 
 | ||||
|         if (unit && isNaN(Number(v))) { | ||||
|             value.setData(undefined); | ||||
|             return | ||||
|         } | ||||
| 
 | ||||
|         feedback?.setData(undefined); | ||||
|         value.setData(v + (selectedUnit.data ?? "")); | ||||
|     if (unit && isNaN(Number(v))) { | ||||
|       value.setData(undefined) | ||||
|       return | ||||
|     } | ||||
| 
 | ||||
|     onDestroy(_value.addCallbackAndRun(_ => setValues())) | ||||
|     onDestroy(selectedUnit.addCallback(_ => setValues())) | ||||
|     if (validator === undefined) { | ||||
|         throw "Not a valid type for a validator:" + type; | ||||
|     } | ||||
| 
 | ||||
|     const isValid = _value.map(v => validator.isValid(v, getCountry)); | ||||
| 
 | ||||
|     let htmlElem: HTMLInputElement; | ||||
| 
 | ||||
|     let dispatch = createEventDispatcher<{ selected }>(); | ||||
|     $: { | ||||
|         if (htmlElem !== undefined) { | ||||
|             htmlElem.onfocus = () => dispatch("selected"); | ||||
|         } | ||||
|     feedback?.setData(undefined) | ||||
|     value.setData(v + (selectedUnit.data ?? "")) | ||||
|   } | ||||
| 
 | ||||
|   onDestroy(_value.addCallbackAndRun((_) => setValues())) | ||||
|   onDestroy(selectedUnit.addCallback((_) => setValues())) | ||||
|   if (validator === undefined) { | ||||
|     throw "Not a valid type for a validator:" + type | ||||
|   } | ||||
| 
 | ||||
|   const isValid = _value.map((v) => validator.isValid(v, getCountry)) | ||||
| 
 | ||||
|   let htmlElem: HTMLInputElement | ||||
| 
 | ||||
|   let dispatch = createEventDispatcher<{ selected }>() | ||||
|   $: { | ||||
|     if (htmlElem !== undefined) { | ||||
|       htmlElem.onfocus = () => dispatch("selected") | ||||
|     } | ||||
|   } | ||||
| </script> | ||||
| 
 | ||||
| {#if validator.textArea} | ||||
|     <textarea class="w-full" bind:value={$_value} inputmode={validator.inputmode ?? "text"} | ||||
|               placeholder={_placeholder}></textarea> | ||||
| {:else } | ||||
|   <textarea | ||||
|     class="w-full" | ||||
|     bind:value={$_value} | ||||
|     inputmode={validator.inputmode ?? "text"} | ||||
|     placeholder={_placeholder} | ||||
|   /> | ||||
| {:else} | ||||
|   <span class="inline-flex"> | ||||
|     <input bind:this={htmlElem} bind:value={$_value} class="w-full" inputmode={validator.inputmode ?? "text"} | ||||
|            placeholder={_placeholder}> | ||||
|       {#if !$isValid} | ||||
|       <ExclamationIcon class="h-6 w-6 -ml-6"></ExclamationIcon> | ||||
|     <input | ||||
|       bind:this={htmlElem} | ||||
|       bind:value={$_value} | ||||
|       class="w-full" | ||||
|       inputmode={validator.inputmode ?? "text"} | ||||
|       placeholder={_placeholder} | ||||
|     /> | ||||
|     {#if !$isValid} | ||||
|       <ExclamationIcon class="h-6 w-6 -ml-6" /> | ||||
|     {/if} | ||||
|      | ||||
| 
 | ||||
|     {#if unit !== undefined} | ||||
|       <UnitInput {unit} {selectedUnit} textValue={_value} upstreamValue={value}/> | ||||
|       <UnitInput {unit} {selectedUnit} textValue={_value} upstreamValue={value} /> | ||||
|     {/if} | ||||
|   </span> | ||||
| {/if} | ||||
|  |  | |||
|  | @ -48,7 +48,7 @@ export abstract class Validator { | |||
|      * Returns 'undefined' if the element is valid | ||||
|      */ | ||||
|     public getFeedback(s: string, _?: () => string): Translation | undefined { | ||||
|         if(this.isValid(s)){ | ||||
|         if (this.isValid(s)) { | ||||
|             return undefined | ||||
|         } | ||||
|         const tr = Translations.t.validation[this.name] | ||||
|  | @ -57,7 +57,7 @@ export abstract class Validator { | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public getPlaceholder(){ | ||||
|     public getPlaceholder() { | ||||
|         return Translations.t.validation[this.name].description | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,7 +1,7 @@ | |||
| import {Translation} from "../../i18n/Translation.js" | ||||
| import { Translation } from "../../i18n/Translation.js" | ||||
| import Translations from "../../i18n/Translations.js" | ||||
| import * as emailValidatorLibrary from "email-validator" | ||||
| import {Validator} from "../Validator" | ||||
| import { Validator } from "../Validator" | ||||
| 
 | ||||
| export default class EmailValidator extends Validator { | ||||
|     constructor() { | ||||
|  |  | |||
|  | @ -1,24 +1,23 @@ | |||
| import {parsePhoneNumberFromString} from "libphonenumber-js" | ||||
| import {Validator} from "../Validator" | ||||
| import {Translation} from "../../i18n/Translation"; | ||||
| import Translations from "../../i18n/Translations"; | ||||
| import { parsePhoneNumberFromString } from "libphonenumber-js" | ||||
| import { Validator } from "../Validator" | ||||
| import { Translation } from "../../i18n/Translation" | ||||
| import Translations from "../../i18n/Translations" | ||||
| 
 | ||||
| export default class PhoneValidator extends Validator { | ||||
|     constructor() { | ||||
|         super("phone", "A phone number", "tel") | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     getFeedback(s: string, requestCountry?: () => string): Translation { | ||||
|         if(this.isValid(s, requestCountry)){ | ||||
|         if (this.isValid(s, requestCountry)) { | ||||
|             return undefined | ||||
|         } | ||||
|         const tr = Translations.t.validation.phone | ||||
|         const generic = tr.feedback | ||||
|         if(requestCountry){ | ||||
|         const country = requestCountry() | ||||
|             if(country){ | ||||
|                 return  tr.feedbackCountry.Subs({country}) | ||||
|         if (requestCountry) { | ||||
|             const country = requestCountry() | ||||
|             if (country) { | ||||
|                 return tr.feedbackCountry.Subs({ country }) | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|  | @ -44,7 +43,7 @@ export default class PhoneValidator extends Validator { | |||
|             str = str.substring("tel:".length) | ||||
|         } | ||||
|         let countryCode = undefined | ||||
|         if(country){ | ||||
|         if (country) { | ||||
|             countryCode = country() | ||||
|         } | ||||
|         return parsePhoneNumberFromString( | ||||
|  |  | |||
|  | @ -2,6 +2,11 @@ import { Validator } from "../Validator" | |||
| 
 | ||||
| export default class TextValidator extends Validator { | ||||
|     constructor() { | ||||
|         super("text", "A longer piece of text. Uses an textArea instead of a textField", "text", true) | ||||
|         super( | ||||
|             "text", | ||||
|             "A longer piece of text. Uses an textArea instead of a textField", | ||||
|             "text", | ||||
|             true | ||||
|         ) | ||||
|     } | ||||
| } | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue