feat(webui): add group view, duplicate group view
ci/woodpecker/push/woodpecker Pipeline failed Details

refactor/errors
Tomáš Mládek 2023-10-31 19:22:24 +01:00
parent f597f0a69a
commit 0b211c237d
3 changed files with 142 additions and 1 deletions

View File

@ -10,6 +10,7 @@
import AddModal from "./components/AddModal.svelte";
import Store from "./views/Store.svelte";
import Surface from "./views/Surface.svelte";
import Groups from "./views/Groups.svelte";
import "./styles/main.scss";
@ -39,6 +40,10 @@
<Store />
</Route>
<Route path="/groups">
<Groups />
</Route>
<Footer />
<AddModal />

View File

@ -0,0 +1,136 @@
<script lang="ts">
import { ATTR_IN, ATTR_LABEL } from "@upnd/upend/constants";
import api from "../lib/api";
import { i18n } from "../i18n";
import Spinner from "../components/utils/Spinner.svelte";
import UpObject from "../components/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,
};
})();
</script>
<div class="groups">
<h1>{$i18n.t("Groups")}</h1>
{#await groups}
<Spinner centered />
{:then data}
<ul>
{#each data.groups as group}
<li class="group">
<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}
<h2>{$i18n.t("Duplicate groups")}</h2>
<ul>
{#each data.duplicateGroups as { label, groups }}
<li class="duplicate">
<div class="label">{label}</div>
<ul>
{#each groups as group}
<li>
<UpObject link address={group} />
</li>
{/each}
</ul>
</li>
{/each}
</ul>
{/if}
{/await}
</div>
<style lang="scss">
.groups {
text-align: center;
}
ul {
list-style: none;
padding: 0;
margin: 0 2rem;
display: flex;
flex-wrap: wrap;
gap: 0.5em;
}
.group {
display: flex;
}
.count {
display: inline-block;
font-size: 0.66em;
margin-left: 0.25em;
}
.label {
font-weight: bold;
margin-bottom: 1em;
}
.duplicate {
margin-bottom: 1em;
border-radius: 4px;
border: 1px solid var(--foreground);
padding: 1em;
}
</style>

View File

@ -178,7 +178,7 @@
</section>
<section class="groups">
<h2>{$i18n.t("Groups")}</h2>
<h2><Link to="/groups">{$i18n.t("Groups")}</Link></h2>
{#await groups}
<Spinner centered />
{:then data}