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

200 lines
4.6 KiB
Svelte
Raw Normal View History

2022-02-02 19:17:30 +01:00
<script lang="ts">
import { readable, type Readable } from "svelte/store";
2022-09-04 23:29:41 +02:00
import type { UpListing } from "upend";
import type { Address } from "upend/types";
2022-02-15 22:05:51 +01:00
import { query } from "../../lib/entity";
import UpObject from "../display/UpObject.svelte";
import UpObjectCard from "../display/UpObjectCard.svelte";
2023-06-24 16:26:14 +02:00
import { ATTR_LABEL } from "upend/constants";
2023-07-06 18:01:58 +02:00
import { i18n } from "../../i18n";
import Icon from "../utils/Icon.svelte";
2022-02-02 19:17:30 +01:00
2022-09-04 23:29:41 +02:00
export let entities: Address[];
export let thumbnails = true;
export let sort = true;
const deduplicatedEntities = Array.from(new Set(entities));
2022-09-04 23:29:41 +02:00
let style: "list" | "grid" | "flex" = "grid";
let clientWidth: number;
$: style = !thumbnails || clientWidth < 600 ? "list" : "grid";
2022-02-02 19:17:30 +01:00
// Sorting
let sortedEntities = [];
2022-02-02 19:17:30 +01:00
let sortKeys: { [key: string]: string[] } = {};
function addSortKeys(key: string, vals: string[], resort: boolean) {
2022-02-02 19:17:30 +01:00
if (!sortKeys[key]) {
sortKeys[key] = [];
}
let changed = false;
vals.forEach((val) => {
if (!sortKeys[key].includes(val)) {
changed = true;
sortKeys[key].push(val);
}
});
2022-12-22 13:15:20 +01:00
if (resort && changed) sortEntities();
2022-02-02 19:17:30 +01:00
}
2022-12-22 13:15:20 +01:00
function sortEntities() {
2022-09-04 23:29:41 +02:00
if (!sort) return;
sortedEntities = deduplicatedEntities.concat();
2022-09-04 23:29:41 +02:00
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,
});
}
});
2022-02-02 19:17:30 +01:00
}
// Labelling
let labelListing: Readable<UpListing> = readable(undefined);
$: {
const addressesString = deduplicatedEntities
.map((addr) => `@${addr}`)
.join(" ");
labelListing = query(
`(matches (in ${addressesString}) "${ATTR_LABEL}" ? )`,
).result;
}
$: {
if ($labelListing) {
deduplicatedEntities.forEach((address) => {
addSortKeys(
address,
$labelListing.getObject(address).identify(),
false,
);
});
2022-12-22 13:15:20 +01:00
sortEntities();
}
}
if (!sort) {
2022-12-22 13:15:20 +01:00
sortedEntities = entities;
}
// Visibility
let visible: Set<string> = new Set();
let observer = new IntersectionObserver((intersections) => {
intersections.forEach((intersection) => {
const address = (intersection.target as HTMLElement).dataset["address"];
if (!address) {
console.warn("Intersected wrong element?");
return;
}
if (intersection.isIntersecting) {
visible.add(address);
}
visible = visible;
});
});
function observe(node: HTMLElement) {
observer.observe(node);
return {
destroy() {
observer.unobserve(node);
},
};
}
2022-02-02 19:17:30 +01:00
</script>
2022-09-04 23:29:41 +02:00
<div
class="gallery style-{style}"
class:has-thumbnails={thumbnails}
bind:clientWidth
>
<div class="header" />
2023-07-06 18:01:58 +02:00
{#if sortedEntities.length}
<div class="items">
2023-07-06 18:01:58 +02:00
{#each sortedEntities as entity (entity)}
2023-08-31 12:53:58 +02:00
<div data-address={entity} use:observe class="item">
{#if visible.has(entity)}
{#if thumbnails}
<UpObjectCard
address={entity}
labels={sortKeys[entity]}
banner={false}
on:resolved={(event) => {
addSortKeys(entity, event.detail, true);
}}
/>
{:else}
<UpObject
link
address={entity}
labels={sortKeys[entity]}
on:resolved={(event) => {
addSortKeys(entity, event.detail, true);
}}
/>
{/if}
{:else}
<div class="skeleton" style="text-align: center">...</div>
{/if}
</div>
2023-07-06 18:01:58 +02:00
{/each}
</div>
{:else}
<div class="message">
<Icon name="x-circle" /><br />
{$i18n.t("No entries that could be previewed.")}
</div>
{/if}
2022-02-02 19:17:30 +01:00
</div>
<style lang="scss">
2022-09-04 23:29:41 +02:00
.items {
gap: 4px;
}
.gallery.has-thumbnails .items {
2022-02-02 19:17:30 +01:00
gap: 1rem;
2022-09-04 23:29:41 +02:00
}
:global(.gallery.style-grid .items) {
display: grid;
grid-template-columns: repeat(4, 1fr);
}
:global(.gallery.style-flex .items) {
display: flex;
2022-02-02 19:17:30 +01:00
flex-wrap: wrap;
align-items: flex-end;
2022-02-02 19:17:30 +01:00
}
2022-09-04 23:29:41 +02:00
:global(.gallery.style-list .items) {
display: flex;
flex-direction: column;
align-items: stretch;
}
2023-07-06 18:01:58 +02:00
2023-08-31 12:53:58 +02:00
.item {
min-width: 0;
}
2023-07-06 18:01:58 +02:00
.message {
text-align: center;
margin: 0.5em;
}
2022-02-02 19:17:30 +01:00
</style>