Migrates to svelte 5
This commit is contained in:
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user