Migrates to svelte 5

This commit is contained in:
Christian Beutel
2025-01-24 17:46:13 +01:00
parent 7571f6e9b9
commit 3721b7f537
106 changed files with 5524 additions and 4232 deletions

View File

@@ -2,35 +2,42 @@
import type { Waypoint } from "$lib/models/waypoint";
import { getFileURL, readAsDataURLAsync } from "$lib/util/file_util";
import { _ } from "svelte-i18n";
import Dropdown from "../base/dropdown.svelte";
import Dropdown, { type DropdownItem } from "../base/dropdown.svelte";
import { browser } from "$app/environment";
import PhotoGallery from "../photo_gallery.svelte";
export let waypoint: Waypoint;
export let mode: "show" | "edit" = "show";
let openGallery: (idx?: number) => void;
let imgSrc: string[] = [];
$: if (waypoint.photos?.length) {
imgSrc = waypoint.photos
.filter((_, i) => i < 3)
.reverse()
.map((p) => getFileURL(waypoint, p));
} else if (waypoint._photos?.length && browser) {
Promise.all(
waypoint._photos
.filter((_, i) => i < 3)
.map(async (f) => {
return await readAsDataURLAsync(f);
}),
).then((v) => {
imgSrc = v;
});
} else {
imgSrc = [];
interface Props {
waypoint: Waypoint;
mode?: "show" | "edit";
onchange?: (item: DropdownItem) => void;
}
let { waypoint, mode = "show", onchange }: Props = $props();
let gallery: PhotoGallery;
let imgSrc: string[] = $state([]);
$effect(() => {
if (waypoint.photos?.length) {
imgSrc = waypoint.photos
.filter((_, i) => i < 3)
.reverse()
.map((p) => getFileURL(waypoint, p));
} else if (waypoint._photos?.length && browser) {
Promise.all(
waypoint._photos
.filter((_, i) => i < 3)
.map(async (f) => {
return await readAsDataURLAsync(f);
}),
).then((v) => {
imgSrc = v;
});
} else {
imgSrc = [];
}
});
const dropdownItems = [
{ text: $_("edit"), value: "edit" },
{ text: $_("delete"), value: "delete" },
@@ -44,13 +51,13 @@
{#if mode == "show"}
<PhotoGallery
photos={waypoint.photos.map((p) => getFileURL(waypoint, p))}
bind:open={openGallery}
bind:this={gallery}
></PhotoGallery>
{/if}
<button
class="relative basis-16 aspect-square ml-2 mb-3 shrink-0"
type="button"
on:click={mode == "show" ? () => openGallery() : undefined}
onclick={mode == "show" ? () => gallery.openGallery() : undefined}
>
{#each imgSrc as img, i}
<img
@@ -69,7 +76,7 @@
<i class="fa fa-{waypoint.icon} mr-2"></i>{waypoint.name}
</h5>
{#if mode == "edit"}
<Dropdown items={dropdownItems} on:change></Dropdown>
<Dropdown items={dropdownItems} {onchange}></Dropdown>
{/if}
</div>

View File

@@ -1,5 +1,5 @@
<script lang="ts">
import { createEventDispatcher } from "svelte";
import { type Snippet } from "svelte";
import { WaypointCreateSchema } from "$lib/models/api/waypoint_schema";
import { convertDMSToDD } from "$lib/models/gpx/utils";
@@ -17,11 +17,20 @@
import TextField from "../base/text_field.svelte";
import Textarea from "../base/textarea.svelte";
import PhotoPicker from "../trail/photo_picker.svelte";
import type { Waypoint } from "$lib/models/waypoint";
export let openModal: (() => void) | undefined = undefined;
export let closeModal: (() => void) | undefined = undefined;
interface Props {
children?: Snippet<[any]>;
onsave?: (waypoint: Waypoint) => void
}
const dispatch = createEventDispatcher();
let { children, onsave }: Props = $props();
let modal: Modal;
export function openModal() {
modal.openModal();
}
const ClientWaypointCreateSchema = WaypointCreateSchema.extend({
_photos: z.array(z.instanceof(File)).optional(),
@@ -33,9 +42,9 @@
initialValues: $waypoint,
extend: validator({ schema: ClientWaypointCreateSchema }),
onSubmit: async (form) => {
dispatch("save", form);
onsave?.(form);
closeModal!();
modal.closeModal!();
},
transform: (values: unknown) => {
const v = values as any;
@@ -47,14 +56,25 @@
},
});
$: setFields(cloneDeep($waypoint));
$effect(() => {
setFields(cloneDeep($waypoint));
});
$: filteredIcons =
let filteredIcons = $derived(
($data.icon?.length ?? 0) > 2
? icons
.filter((i) => i.replaceAll("-", " ").includes($data.icon?.toLowerCase() ?? ""))
.map((i) => ({ text: i.replaceAll("-", " "), value: i, icon: i }))
: [];
.filter((i) =>
i
.replaceAll("-", " ")
.includes($data.icon?.toLowerCase() ?? ""),
)
.map((i) => ({
text: i.replaceAll("-", " "),
value: i,
icon: i,
}))
: [],
);
function getCoordinatesFromPhoto(src: string) {
EXIF.getData({ src: src }, function (p) {
@@ -75,69 +95,79 @@
}
});
}
const children_render = $derived(children);
</script>
<Modal
id="waypoint-modal"
title={$data.id ? $_("edit-waypoint") : $_("add-waypoint")}
let:openModal
bind:openModal
bind:closeModal
bind:this={modal}
>
<slot {openModal} />
<form
id="waypoint-form"
slot="content"
class="modal-content space-y-4"
use:form
>
<div class="flex gap-4">
<div class="basis-2/3">
<TextField name="name" label={$_("name")} error={$errors.name}
></TextField>
{#snippet children({ openModal })}
{@render children_render?.({ openModal })}
{/snippet}
{#snippet content()}
<form id="waypoint-form" class="modal-content space-y-4" use:form>
<div class="flex gap-4">
<div class="basis-2/3">
<TextField
name="name"
label={$_("name")}
error={$errors.name}
></TextField>
</div>
<Combobox
name="icon"
icon={$data.icon}
bind:value={$data.icon}
items={filteredIcons}
label={$_("icon")}
></Combobox>
</div>
<Combobox
name="icon"
icon={$data.icon}
bind:value={$data.icon}
items={filteredIcons}
label={$_("icon")}
></Combobox>
<Textarea
name="description"
label={$_("description")}
error={$errors.description}
></Textarea>
<div class="flex gap-4">
<TextField name="lat" label={$_("latitude")} error={$errors.lat}
></TextField>
<TextField
name="lon"
label={$_("longitude")}
error={$errors.lat}
></TextField>
</div>
<div>
<label
for="waypoint-photo-input"
class="text-sm font-medium pb-1"
>
{$_("photos")}
</label>
<PhotoPicker
id="waypoint-photo-input"
parent={$data}
onexif={(src) => getCoordinatesFromPhoto(src)}
bind:photos={$data.photos}
bind:photoFiles={$data._photos}
showThumbnailControls={false}
showExifControls={true}
></PhotoPicker>
</div>
</form>
{/snippet}
{#snippet footer()}
<div class="flex items-center gap-4">
<button class="btn-secondary" onclick={() => modal.closeModal()}
>{$_("cancel")}</button
>
<button class="btn-primary" type="submit" form="waypoint-form"
>{$_("save")}</button
>
</div>
<Textarea
name="description"
label={$_("description")}
error={$errors.description}
></Textarea>
<div class="flex gap-4">
<TextField name="lat" label={$_("latitude")} error={$errors.lat}
></TextField>
<TextField name="lon" label={$_("longitude")} error={$errors.lat}
></TextField>
</div>
<div>
<label for="waypoint-photo-input" class="text-sm font-medium pb-1">
{$_("photos")}
</label>
<PhotoPicker
id="waypoint-photo-input"
parent={$data}
on:exif={(e) => getCoordinatesFromPhoto(e.detail)}
bind:photos={$data.photos}
bind:photoFiles={$data._photos}
showThumbnailControls={false}
showExifControls={true}
></PhotoPicker>
</div>
</form>
<div slot="footer" class="flex items-center gap-4">
<button class="btn-secondary" on:click={closeModal}
>{$_("cancel")}</button
>
<button class="btn-primary" type="submit" form="waypoint-form"
>{$_("save")}</button
>
</div>
{/snippet}
</Modal>