Files
wanderer/web/src/lib/components/summit_log/summit_log_card.svelte
2025-06-25 15:59:56 +02:00

140 lines
5.0 KiB
Svelte

<script lang="ts">
import emptyStateTrailDark from "$lib/assets/svgs/empty_states/empty_state_trail_dark.svg";
import emptyStateTrailLight from "$lib/assets/svgs/empty_states/empty_state_trail_light.svg";
import type { SummitLog } from "$lib/models/summit_log";
import { _ } from "svelte-i18n";
import Dropdown, { type DropdownItem } from "../base/dropdown.svelte";
import { theme } from "$lib/stores/theme_store";
import {
getFileURL,
isVideoURL,
readAsDataURLAsync,
} from "$lib/util/file_util";
import {
formatDistance,
formatElevation,
formatHTMLAsText,
formatTimeHHMM,
} from "$lib/util/format_util";
import { handleFromRecordWithIRI } from "$lib/util/activitypub_util";
let thumbnail: string = $state(
$theme === "light" ? emptyStateTrailLight : emptyStateTrailDark,
);
interface Props {
log: SummitLog;
mode?: "show" | "edit";
onchange?: (item: DropdownItem) => void;
}
let { log, mode = "show", onchange }: Props = $props();
const dropdownItems: DropdownItem[] = [
{ text: $_("edit"), value: "edit" },
{ text: $_("delete"), value: "delete" },
];
$effect(() => fetchPhotos(log._photos ?? []));
function fetchPhotos(photos: File[]) {
Promise.all(
photos.map(async (f) => {
return await readAsDataURLAsync(f);
}),
).then((v) => {
if (log.photos.length) {
thumbnail = getFileURL(log, log.photos[0]);
} else if (v.length) {
thumbnail = v[0];
} else {
thumbnail =
$theme === "light"
? emptyStateTrailLight
: emptyStateTrailDark;
}
});
}
</script>
<div class="p-4 my-2 border border-input-border rounded-xl">
<div class="flex items-center gap-x-4">
<div class="h-24 aspect-square shrink-0 rounded-xl overflow-hidden">
{#if isVideoURL(thumbnail)}
<!-- svelte-ignore a11y_media_has_caption -->
<video
controls={false}
loop
class="object-cover h-full w-full"
onmouseenter={(e) => (e.target as any).play()}
onmouseleave={(e) => (e.target as any).pause()}
src={thumbnail}
></video>
{:else}
<img
id="header-img"
class="object-cover h-full w-full"
src={thumbnail}
alt=""
/>
{/if}
</div>
<div class="basis-full">
<div
class="flex justify-between items-center"
class:mb-2={log.text}
>
<h5 class="font-medium mr-2">
{new Date(log.date).toLocaleDateString(undefined, {
month: "2-digit",
day: "2-digit",
year: "numeric",
timeZone: "UTC",
})}
</h5>
{#if mode == "edit"}
<Dropdown items={dropdownItems} {onchange}></Dropdown>
{/if}
</div>
{#if log.expand?.author}
<p class="text-xs text-gray-500 mb-3">
{$_("by")}
<img
class="rounded-full w-5 aspect-square mx-1 inline"
src={log.expand.author.icon ||
`https://api.dicebear.com/7.x/initials/svg?seed=${log.expand.author.preferred_username}&backgroundType=gradientLinear`}
alt="avatar"
/>
{handleFromRecordWithIRI(log)}
</p>
{/if}
{#if log.distance || log.elevation_gain || log.elevation_loss || log.duration}
<div
class="flex mt-1 gap-x-4 text-sm text-gray-500 flex-wrap mb-2"
>
<span
><i class="fa fa-left-right mr-2"></i>{formatDistance(
log.distance,
)}</span
>
<span
><i class="fa fa-clock mr-2"></i>{formatTimeHHMM(
log.duration ? log.duration : undefined,
)}</span
>
<span
><i class="fa fa-arrow-trend-up mr-2"
></i>{formatElevation(log.elevation_gain)}</span
>
<span
><i class="fa fa-arrow-trend-down mr-2"
></i>{formatElevation(log.elevation_loss)}</span
>
</div>
{/if}
<span class="whitespace-pre-wrap">{formatHTMLAsText(log.text)}</span>
</div>
</div>
</div>