175 lines
3.6 KiB
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>
|