Files
wanderer/web/src/lib/components/list/list_panel.svelte
2024-12-13 18:24:24 +01:00

203 lines
6.5 KiB
Svelte

<script lang="ts">
import type { List } from "$lib/models/list";
import { getFileURL } from "$lib/util/file_util";
import {
formatDistance,
formatElevation,
formatTimeHHMM,
} from "$lib/util/format_util";
import { _ } from "svelte-i18n";
import TrailListItem from "../trail/trail_list_item.svelte";
import type { Trail } from "$lib/models/trail";
import { createEventDispatcher } from "svelte";
import { currentUser } from "$lib/stores/user_store";
import Dropdown from "../base/dropdown.svelte";
import ShareInfo from "../share_info.svelte";
export let list: List;
const dispatch = createEventDispatcher();
$: cumulativeDistance = list.expand?.trails?.reduce(
(s, b) => s + b.distance!,
0,
);
$: cumulativeElevationGain = list.expand?.trails?.reduce(
(s, b) => s + b.elevation_gain!,
0,
);
$: cumulativeElevationLoss = list.expand?.trails?.reduce(
(s, b) => s + b.elevation_loss!,
0,
);
$: cumulativeDuration = list.expand?.trails?.reduce(
(s, b) => s + b.duration!,
0,
);
$: allowEdit =
list.author == $currentUser?.id ||
list.expand?.list_share_via_list?.some((s) => s.permission == "edit");
$: allowShare =
list.author == $currentUser?.id &&
!list.expand?.trails?.some((t) => t.author !== $currentUser?.id);
$: dropdownItems = [
...(allowShare
? [{ text: $_("share"), value: "share", icon: "share" }]
: []),
...(allowEdit
? [{ text: $_("edit"), value: "edit", icon: "pen" }]
: []),
...(list.author == $currentUser?.id
? [{ text: $_("delete"), value: "delete", icon: "trash" }]
: []),
];
$: listIsShared = (list.expand?.list_share_via_list?.length ?? 0) > 0;
let fullDescription: boolean = false;
function handleTrailSelect(trail: Trail, index: number) {
dispatch("click", { trail, index });
}
function handleTrailMouseEnter(trail: Trail, index: number) {
dispatch("mouseenter", { trail, index });
}
function handleTrailMouseLeave(trail: Trail, index: number) {
dispatch("mouseleave", { trail, index });
}
</script>
<div class="relative">
{#if (list.public || listIsShared) && $currentUser}
<div
class="flex absolute top-4 right-6 {list.public && listIsShared
? 'w-16'
: 'w-8'} h-8 rounded-full items-center justify-center bg-white text-primary"
>
{#if list.public}
<span
class="tooltip"
class:mr-3={list.public && listIsShared}
data-title={$_("public")}
>
<i class="fa fa-globe"></i>
</span>
{/if}
{#if listIsShared}
<ShareInfo type="list" subject={list}></ShareInfo>
{/if}
</div>
{/if}
{#if dropdownItems.length}
<div class="absolute bottom-8 right-8">
<Dropdown
items={dropdownItems}
on:change
let:toggleMenu={openDropdown}
><button
class="rounded-full bg-white text-black hover:bg-gray-200 focus:ring-4 ring-gray-100/50 transition-colors h-12 w-12"
on:click={openDropdown}
>
<i class="fa fa-ellipsis-vertical"></i>
</button></Dropdown
>
</div>
{/if}
{#if list.avatar}
<img
class="w-full object-cover"
src={getFileURL(list, list.avatar)}
alt="avatar"
/>
{:else}
<div class="flex w-full shrink-0 items-center justify-center min-h-72">
<i class="fa fa-table-list text-5xl"></i>
</div>
{/if}
</div>
<div class="p-4 md:p-6">
<h4 class="text-2xl font-semibold mb-4">{list.name}</h4>
{#if list.expand?.author}
<p class="my-3 text-gray-500 text-sm">
{$_("by")}
<img
class="rounded-full w-8 aspect-square mx-1 inline"
src={getFileURL(
list.expand.author,
list.expand.author.avatar,
) ||
`https://api.dicebear.com/7.x/initials/svg?seed=${list.expand.author.username}&backgroundType=gradientLinear`}
alt="avatar"
/>
<a class="underline" href="/profile/{list.expand.author.id}"
>{list.expand.author.username}</a
>
</p>
{/if}
<hr />
<div
class="grid grid-cols-2 my-4 gap-4 font-semibold whitespace-nowrap flex-wrap justify-around"
>
<span
><i class="fa fa-left-right mr-2"></i>{formatDistance(
cumulativeDistance,
)}</span
>
<span
><i class="fa fa-clock mr-2"></i>{formatTimeHHMM(
cumulativeDuration,
)}</span
>
<span
><i class="fa fa-arrow-trend-up mr-2"></i>{formatElevation(
cumulativeElevationGain,
)}</span
>
<span
><i class="fa fa-arrow-trend-down mr-2"></i>{formatElevation(
cumulativeElevationLoss,
)}</span
>
</div>
<hr class="mb-4" />
<p
class="text-gray-500 whitespace-pre-wrap {fullDescription
? ''
: 'max-h-24 overflow-hidden text-ellipsis'}"
>
{!fullDescription
? list.description?.substring(0, 100)
: list.description}
{#if (list.description?.length ?? 0) > 100 && !fullDescription}
<button on:click={() => (fullDescription = true)}>
... <span class="underline">{$_("read-more")}</span></button
>
{/if}
</p>
<h5 class="text-xl font-semibold my-4">
{list.trails?.length ?? 0}
{$_("trail", { values: { n: list.trails?.length ?? 0 } })}
</h5>
<div class="space-y-2">
{#each list.expand?.trails ?? [] as trail, i}
<div
role="presentation"
on:click={() => handleTrailSelect(trail, i)}
on:mouseenter={() => handleTrailMouseEnter(trail, i)}
on:mouseleave={() => handleTrailMouseLeave(trail, i)}
>
<TrailListItem {trail} showDescription={false}></TrailListItem>
</div>
{/each}
</div>
</div>