Merge master

This commit is contained in:
Pieter Vander Vennet 2023-07-28 00:29:21 +02:00
commit 80168f5d0d
919 changed files with 95585 additions and 8504 deletions

View file

@ -0,0 +1,10 @@
<script lang="ts">
/**
* Simple wrapper around the HTML-color field.
*/
import { UIEventSource } from "../../../Logic/UIEventSource"
export let value: UIEventSource<undefined | string>
</script>
<input bind:value={$value} type="color" />

View file

@ -0,0 +1,10 @@
<script lang="ts">
/**
* Simple wrapper around the HTML-date field.
*/
import { UIEventSource } from "../../../Logic/UIEventSource"
export let value: UIEventSource<undefined | string>
</script>
<input bind:value={$value} type="date" />

View file

@ -0,0 +1,72 @@
<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"
/**
* 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)
mla.allowMoving.setData(false)
mla.allowZooming.setData(false)
let directionElem: HTMLElement | undefined
$: value.addCallbackAndRunD((degrees) => {
if (directionElem === undefined) {
return
}
directionElem.style.rotate = degrees + "deg"
})
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)
}
let isDown = false
</script>
<div
bind:this={mainElem}
class="relative h-48 w-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)
e.preventDefault()
}
}}
on:mouseup={() => {
isDown = false
}}
on:touchmove={(e) => {
onPosChange(e.touches[0].clientX, e.touches[0].clientY)
e.preventDefault()
}}
on:touchstart={(e) => onPosChange(e.touches[0].clientX, e.touches[0].clientY)}
>
<div class="absolute top-0 left-0 h-full w-full cursor-pointer">
<MaplibreMap {map} attribution={false} />
</div>
<div bind:this={directionElem} class="absolute top-0 left-0 h-full w-full">
<ToSvelte construct={Svg.direction_stroke_svg} />
</div>
</div>

View file

@ -0,0 +1,153 @@
<script lang="ts">
import { twJoin } from "tailwind-merge"
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="absolute right-0 h-full w-min">
{#each $floors as floor, i}
<button
style={`height: ${HEIGHT}px; width: ${HEIGHT}px`}
class={twJoin(
"content-box m-0 flex items-center justify-center border-2 border-gray-300",
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>

View file

@ -0,0 +1,10 @@
<script lang="ts">
import {UIEventSource} from "../../../Logic/UIEventSource";
/**
* Simply shows the image
*/
export let value: UIEventSource<undefined | string>
</script>
<img src={$value}/>

View file

@ -0,0 +1,96 @@
<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 { createEventDispatcher, 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
const dispatch = createEventDispatcher<{ click: { lon: number; lat: number } }>()
export let map: UIEventSource<MlMap> = new UIEventSource<MlMap>(undefined)
let mla = new MapLibreAdaptor(map, mapProperties)
mla.lastClickLocation.addCallbackAndRunD((lastClick) => {
dispatch("click", lastClick)
})
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)
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 (!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="min-h-32 relative h-full cursor-pointer overflow-hidden">
<div class="absolute top-0 left-0 h-full w-full cursor-pointer">
<MaplibreMap center={{ lng: initialCoordinate.lon, lat: initialCoordinate.lat }} {map} />
</div>
<div
class="pointer-events-none absolute top-0 left-0 flex h-full w-full items-center p-8 opacity-50"
>
<img class="h-full max-h-24" src="./assets/svg/move-arrows.svg" />
</div>
<DragInvitation hideSignal={mla.location} />
</div>

View file

@ -0,0 +1,53 @@
<script lang="ts">
import { UIEventSource } from "../../../Logic/UIEventSource";
import LanguageUtils from "../../../Utils/LanguageUtils";
import { onDestroy } from "svelte";
import ValidatedInput from "../ValidatedInput.svelte";
export let value: UIEventSource<string> = new UIEventSource<string>("");
let translations: UIEventSource<Record<string, string>> = value.sync((s) => {
try {
return JSON.parse(s);
} catch (e) {
return {};
}
}, [], v => JSON.stringify(v));
const allLanguages: string[] = LanguageUtils.usedLanguagesSorted;
let currentLang = new UIEventSource("en");
const currentVal = new UIEventSource<string>("");
function update() {
const v = currentVal.data;
const l = currentLang.data;
if (translations.data[l] === v) {
return;
}
translations.data[l] = v;
translations.ping();
}
onDestroy(currentLang.addCallbackAndRunD(currentLang => {
console.log("Applying current lang:", currentLang);
translations.data[currentLang] = translations.data[currentLang] ?? "";
currentVal.setData(translations.data[currentLang]);
}));
onDestroy(currentVal.addCallbackAndRunD(v => {
update();
}));
</script>
<div class="flex">
<select bind:value={$currentLang}>
{#each allLanguages as language}
<option value={language}>
{language}
</option>
{/each}
</select>
<ValidatedInput type="string" value={currentVal} />
</div>