mapcomplete/UI/InputElement/Helpers/FloorSelector.svelte

139 lines
3.8 KiB
Svelte

<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;
}
top = Math.max(top, 0)
}
Stores.Chronic(50).addCallback(_ => stabilize());
floors.addCallback(floors => {
forceIndex = floors.findIndex(s => s === value.data)
})
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={"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;"} />
</div>
</div>
<svelte:window on:mousemove={onMove} on:mouseup={unclick} />
<style>
.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>