Files
wanderer/web/src/lib/components/trail/trail_picker.svelte
2024-11-30 18:34:48 +01:00

150 lines
3.9 KiB
Svelte

<script lang="ts">
import * as M from "maplibre-gl";
import { fromFile, toGeoJson } from "$lib/util/gpx_util";
import { createEventDispatcher, onMount } from "svelte";
export let trailFile: File | null;
export let trailData: string | undefined;
export let label: string = "";
let map: M.Map;
let layer: M.LineLayerSpecification | undefined;
let source: M.GeoJSONSource | undefined;
const dispatch = createEventDispatcher();
$: if (trailData !== undefined) {
showTrailOnMap();
} else {
removeTrailFromMap();
}
onMount(async () => {
if (!map) {
await initMap();
}
});
async function initMap() {
map = new M.Map({
container: "trail-picker-map",
attributionControl: false,
dragPan: false,
scrollZoom: false,
preserveDrawingBuffer: true
});
}
function openTrailBrowser() {
if (trailData) {
trailFile = null;
trailData = undefined;
dispatch("change", null);
} else {
document.getElementById("trail-input")!.click();
}
}
async function handleTrailSelection(files?: FileList | null) {
if (!files) {
files = (document.getElementById("trail-input") as HTMLInputElement)
.files;
}
if (!files) {
return;
}
trailFile = files.item(0);
if (!trailFile) {
return;
}
const parseResult = await fromFile(trailFile);
trailData = parseResult.gpxData;
dispatch("change", trailData);
}
async function showTrailOnMap() {
if (!trailData || !map) {
return;
}
const sourceId = "trail-picker-geojson-source";
const layerId = "trail-picker-geojson-layer";
const geojson = toGeoJson(trailData);
if (layer) {
map.removeLayer(layerId);
map.removeSource(sourceId);
}
map.addSource(sourceId, {
type: "geojson",
data: geojson,
});
map.addLayer({
id: layerId,
type: "line",
source: sourceId,
paint: {
"line-color": "#3388ff",
"line-width": 3,
},
});
layer = map.getLayer(layerId) as M.LineLayerSpecification;
source = map.getSource(sourceId) as M.GeoJSONSource;
map.resize()
map.fitBounds(geojson.bbox as M.LngLatBoundsLike, {animate: false, padding: 8});
}
function removeTrailFromMap() {
if (layer) {
map?.removeLayer(layer.id);
layer = undefined;
}
if (source) {
map?.removeSource(source.id);
source = undefined;
}
trailData = undefined;
trailFile = null;
}
</script>
<div>
{#if label.length}
<p class="text-sm font-medium pb-1">
{label}
</p>
{/if}
<div class="flex gap-x-4" role="dialog">
<input
type="file"
id="trail-input"
accept=".gpx,.GPX,.tcx,.TCX,.kml,.KML,.fit,.FIT"
multiple={false}
style="display: none;"
on:change={() => handleTrailSelection()}
/>
<button
type="button"
on:click={openTrailBrowser}
class="h-28 aspect-square rounded-xl !bg-background border border-input-border-focus hover:!bg-secondary-hover group"
id="trail-picker-map"
>
{#if !trailFile && !trailData}
<i class="fa fa-plus text-lg"></i>
{:else}
<i
class="fa fa-trash text-red-500 text-lg hidden group-hover:block relative"
style="z-index: 1000"
></i>
{/if}
</button>
</div>
</div>