upend/webui/src/lib/components/GroupColumn.svelte

175 lines
3.6 KiB
Svelte

<script lang="ts">
import { ATTR_IN, ATTR_LABEL } from '@upnd/upend/constants';
import api from '$lib/api';
import { i18n } from '../i18n';
import Spinner from './utils/Spinner.svelte';
import UpObject from './display/UpObject.svelte';
const groups = (async () => {
const data = await api.query(`(matches ? "${ATTR_IN}" ?)`);
const addresses = data.entries
.filter((e) => e.value.t === 'Address')
.map((e) => e.value.c) as string[];
const sortedAddresses = [...new Set(addresses)]
.map((address) => ({
address,
count: addresses.filter((a) => a === address).length
}))
.sort((a, b) => b.count - a.count);
const addressesString = sortedAddresses.map(({ address }) => `@${address}`).join(' ');
const labels = (
await api.query(`(matches (in ${addressesString}) "${ATTR_LABEL}" ? )`)
).entries.filter((e) => e.value.t === 'String');
const display = sortedAddresses.map(({ address, count }) => ({
address,
labels: labels
.filter((e) => e.entity === address)
.map((e) => e.value.c)
.sort() as string[],
count
}));
display
.sort((a, b) => (a.labels[0] || '').localeCompare(b.labels[0] || ''))
.sort((a, b) => b.count - a.count);
const labelsToGroups = new Map<string, string[]>();
labels.forEach((e) => {
const groups = labelsToGroups.get(e.value.c as string) || [];
if (!groups.includes(e.entity)) {
groups.push(e.entity);
}
labelsToGroups.set(e.value.c as string, groups);
});
const duplicates = [...labelsToGroups.entries()]
.filter(([_, groups]) => groups.length > 1)
.map(([label, groups]) => ({ label, groups }));
return {
groups: display,
total: sortedAddresses.length,
duplicateGroups: duplicates
};
})();
let clientWidth: number;
</script>
<div class="groups" bind:clientWidth class:small={clientWidth < 600}>
<h2>{$i18n.t('Groups')}</h2>
<div class="main">
{#await groups}
<Spinner centered />
{:then data}
<ul>
{#each data.groups as group}
<li class="group" data-address={group.address}>
<UpObject link address={group.address} labels={group.labels} />
<div class="count">{group.count}</div>
</li>
{:else}
<li>No groups?</li>
{/each}
{#if data.groups && data.total > data.groups.length}
<li>+ {data.total - data.groups.length}...</li>
{/if}
</ul>
{#if data.duplicateGroups.length > 0}
<h3>{$i18n.t('Duplicate groups')}</h3>
<ul class="duplicate">
{#each data.duplicateGroups as { label, groups }}
<li class="duplicate-group">
<div class="label">{label}</div>
<ul>
{#each groups as group}
<li>
<UpObject link address={group} backpath={2} />
</li>
{/each}
</ul>
</li>
{/each}
</ul>
{/if}
{/await}
</div>
</div>
<style lang="scss">
@use '../styles/colors';
.groups {
text-align: center;
flex-grow: 1;
height: 0;
display: flex;
flex-direction: column;
}
.main {
overflow: hidden auto;
}
h2 {
margin-top: -0.66em;
}
ul {
list-style: none;
padding: 0;
margin: 0;
display: flex;
flex-wrap: wrap;
gap: 0.5em;
justify-content: space-between;
}
.group {
display: flex;
}
.count {
display: inline-block;
font-size: 0.66em;
margin-left: 0.25em;
}
.label {
font-weight: bold;
margin-bottom: 1em;
}
.duplicate {
display: flex;
gap: 1rem;
flex-wrap: wrap;
justify-content: center;
}
.duplicate-group {
flex-basis: 49%;
border-radius: 4px;
border: 1px solid var(--foreground);
padding: 0.5rem;
overflow-x: auto;
max-width: 100%;
ul {
flex-direction: column;
}
}
.groups.small {
ul {
flex-direction: column;
}
}
</style>