upend/webui/src/components/widgets/Gallery.svelte

162 lines
3.7 KiB
Svelte

<script lang="ts">
import { readable, type Readable } from "svelte/store";
import type { UpListing } from "upend";
import type { Address } from "upend/types";
import { query } from "../../lib/entity";
import BlobPreview from "../display/BlobPreview.svelte";
import UpLink from "../display/UpLink.svelte";
import UpObject from "../display/UpObject.svelte";
export let entities: Address[];
export let thumbnails = true;
export let sort = true;
const deduplicatedEntities = Array.from(new Set(entities));
let style: "list" | "grid" | "flex" = "grid";
let clientWidth: number;
$: style = !thumbnails || clientWidth < 600 ? "list" : "grid";
// Sorting
let sortedEntities = deduplicatedEntities || [];
let sortKeys: { [key: string]: string[] } = {};
function addSortKeys(key: string, vals: string[], resort = true) {
if (!sortKeys[key]) {
sortKeys[key] = [];
}
let changed = false;
vals.forEach((val) => {
if (!sortKeys[key].includes(val)) {
changed = true;
sortKeys[key].push(val);
}
});
if (resort && changed) sortAttributes();
}
// Labelling
let labelListing: Readable<UpListing> = readable(undefined);
$: {
const addresses = [];
deduplicatedEntities.forEach((addr) => {
if (!addresses.includes(addr)) {
addresses.push(addr);
}
});
const addressesString = addresses.map((addr) => `@${addr}`).join(" ");
labelListing = query(`(matches (in ${addressesString}) "LBL" ? )`).result;
}
function sortAttributes() {
if (!sort) return;
sortedEntities = deduplicatedEntities.concat();
sortedEntities.sort((a, b) => {
if (!sortKeys[a]?.length || !sortKeys[b]?.length) {
if (Boolean(sortKeys[a]?.length) && !sortKeys[b]?.length) {
return -1;
} else if (!sortKeys[a]?.length && Boolean(sortKeys[b]?.length)) {
return 1;
} else {
return a.localeCompare(b);
}
} else {
return sortKeys[a][0].localeCompare(sortKeys[b][0], undefined, {
numeric: true,
});
}
});
}
$: {
if ($labelListing) {
deduplicatedEntities.forEach((address) => {
addSortKeys(address, $labelListing.getObject(address).identify());
});
sortAttributes();
}
}
sortAttributes();
</script>
<div
class="gallery style-{style}"
class:has-thumbnails={thumbnails}
bind:clientWidth
>
<div class="header" />
<div class="items">
{#each sortedEntities as entity (entity)}
{#if thumbnails}
<div class="thumbnail">
<UpLink to={{ entity }} passthrough>
<BlobPreview address={entity} />
<div class="label">
<UpObject
address={entity}
on:resolved={(event) => {
addSortKeys(entity, event.detail);
}}
/>
</div>
</UpLink>
</div>
{:else}
<UpObject link address={entity} />
{/if}
{/each}
</div>
</div>
<style lang="scss">
.thumbnail {
border: 1px solid var(--foreground-lighter);
border-radius: 4px;
padding: 0.25em;
max-height: 420px;
overflow: hidden;
}
.items {
gap: 4px;
}
.gallery.has-thumbnails .items {
gap: 1rem;
}
:global(.gallery.style-grid .items) {
display: grid;
grid-template-columns: repeat(4, 1fr);
.thumbnail {
height: 256px;
}
}
:global(.gallery.style-flex .items) {
display: flex;
flex-wrap: wrap;
align-items: flex-end;
}
:global(.gallery.style-list .items) {
display: flex;
flex-direction: column;
align-items: stretch;
}
.thumbnail {
display: flex;
flex-direction: column;
justify-content: flex-end;
}
</style>