forked from MapComplete/MapComplete
		
	Add level selector and global filters
This commit is contained in:
		
							parent
							
								
									5504d49d59
								
							
						
					
					
						commit
						7fd7a3722e
					
				
					 19 changed files with 401 additions and 253 deletions
				
			
		
							
								
								
									
										140
									
								
								UI/InputElement/Helpers/FloorSelector.svelte
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										140
									
								
								UI/InputElement/Helpers/FloorSelector.svelte
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,140 @@ | |||
| <script lang="ts"> | ||||
|   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>; | ||||
| 
 | ||||
|   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 mouseDown = false; | ||||
| 
 | ||||
|   let container: HTMLElement; | ||||
| 
 | ||||
|   $:{ | ||||
|     if (top > 0 || forceIndex !== undefined) { | ||||
|       index.setData(closestFloorIndex()); | ||||
|       value.setData(floors.data[forceIndex ?? closestFloorIndex()]); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   function unclick() { | ||||
|     mouseDown = false; | ||||
|   } | ||||
| 
 | ||||
|   function click() { | ||||
|     mouseDown = true; | ||||
|   } | ||||
| 
 | ||||
|   function closestFloorIndex() { | ||||
|     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); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   let momentum = 0; | ||||
| 
 | ||||
|   function stabilize() { | ||||
|     // Automatically move the elevator to the closes floor | ||||
|     if (mouseDown) { | ||||
|       return; | ||||
|     } | ||||
|     const target = (forceIndex ?? index.data) * HEIGHT; | ||||
|     let diff = target - top; | ||||
|     if (diff > 1) { | ||||
|       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; | ||||
|     if (index.data === forceIndex) { | ||||
|       forceIndex = undefined; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   Stores.Chronic(50).addCallback(_ => stabilize()); | ||||
| 
 | ||||
|   let image: HTMLImageElement; | ||||
|   $:{ | ||||
|     if (image) { | ||||
|       let lastY = 0; | ||||
|       image.ontouchstart = (e: TouchEvent) => { | ||||
|         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; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
| </script> | ||||
| 
 | ||||
| <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={"border-2 border-gray-300 flex content-box justify-center items-center "+(i === (forceIndex ?? $index) ? "selected": "normal-background" ) | ||||
|            } | ||||
|               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;"} /> | ||||
|   </div> | ||||
| </div> | ||||
| 
 | ||||
| <svelte:window on:mousemove={onMove} on:mouseup={unclick} /> | ||||
| 
 | ||||
| <style> | ||||
|     .selected { | ||||
|         background: var(--subtle-detail-color); | ||||
|         font-weight: bold; | ||||
|         border-color: black; | ||||
|     } | ||||
| 
 | ||||
|     .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; | ||||
|     } | ||||
| </style> | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue