feat(webui): batch adding/removing groups
ci/woodpecker/push/woodpecker Pipeline failed Details

pull/79/head
Tomáš Mládek 2023-10-22 16:18:04 +02:00
parent c26f96bda0
commit e1d12565ad
2 changed files with 177 additions and 4 deletions

View File

@ -0,0 +1,163 @@
<script lang="ts">
import UpObjectDisplay from "./display/UpObject.svelte";
import Selector from "./utils/Selector.svelte";
import IconButton from "./utils/IconButton.svelte";
import type { IValue } from "@upnd/upend/types";
import api from "../lib/api";
import { ATTR_IN } from "@upnd/upend/constants";
import { i18n } from "../i18n";
import LabelBorder from "./utils/LabelBorder.svelte";
import { Query, UpListing } from "@upnd/upend";
export let entities: string[];
let adding = false;
let groupSelector: Selector;
$: if (adding && groupSelector) groupSelector.focus();
let groups = [];
let groupListing: UpListing | undefined = undefined;
async function updateGroups() {
const currentEntities = entities.concat();
const allGroups = await api.query(
new Query().matches(
currentEntities.map((e) => `@${e}`),
ATTR_IN,
undefined,
),
);
const commonGroups = new Set(
allGroups.values
.filter((v) => v.t == "Address")
.map((v) => v.c)
.filter((groupAddr) => {
return Object.values(allGroups.objects).every((obj) => {
return obj.attr[ATTR_IN].some((v) => v.value.c === groupAddr);
});
}),
);
if (entities.toString() == currentEntities.toString()) {
groups = Array.from(commonGroups);
groupListing = allGroups;
}
}
$: entities && updateGroups();
let groupToAdd: IValue | undefined;
async function addGroup() {
if (!groupToAdd) {
return;
}
await api.putEntry(
entities.map((entity) => ({
entity,
attribute: ATTR_IN,
value: {
t: "Address",
c: String(groupToAdd.c),
},
})),
);
groupToAdd = undefined;
await updateGroups();
}
async function removeGroup(groupAddress: string) {
if (confirm($i18n.t("Are you sure you want to remove this group?"))) {
await Promise.all(
entities.map((entity) =>
api.deleteEntry(
groupListing.objects[entity].attr[ATTR_IN].find(
(v) => v.value.c === groupAddress,
).address,
),
),
);
await updateGroups();
}
}
</script>
<LabelBorder>
<span slot="header">{$i18n.t("Common groups")}</span>
{#if adding}
<div class="selector">
<Selector
bind:this={groupSelector}
type="value"
valueTypes={["Address"]}
bind:value={groupToAdd}
on:input={addGroup}
on:focus={(ev) => {
if (!ev.detail) adding = false;
}}
placeholder={$i18n.t("Choose an entity...")}
/>
</div>
{/if}
<div class="body">
<div class="group-list">
{#each groups as groupAddress}
<div class="group">
<UpObjectDisplay address={groupAddress} link />
<IconButton
subdued
name="x-circle"
on:click={() => removeGroup(groupAddress)}
/>
</div>
{:else}
<div class="no-groups">
{$i18n.t("Entities have no groups in common.")}
</div>
{/each}
</div>
{#if !adding}
<div class="add-button">
<IconButton
outline
small
name="folder-plus"
on:click={() => (adding = true)}
/>
</div>
{/if}
</div>
</LabelBorder>
<style lang="scss">
.group-list {
display: flex;
flex-wrap: wrap;
gap: 0.25rem 0.2rem;
align-items: center;
}
.group {
display: inline-flex;
align-items: center;
}
.body {
display: flex;
align-items: start;
.group-list {
flex-grow: 1;
}
}
.selector {
width: 100%;
margin-bottom: 0.5rem;
}
.no-groups {
opacity: 0.66;
}
</style>

View File

@ -2,6 +2,8 @@
import { i18n } from "../i18n";
import { selected } from "./EntitySelect.svelte";
import EntryView from "./EntryView.svelte";
import MultiGroupEditor from "./MultiGroupEditor.svelte";
import Icon from "./utils/Icon.svelte";
import EntityList from "./widgets/EntityList.svelte";
const selectedWidgets = [
@ -14,7 +16,7 @@
props: {
entities,
thumbnails: false,
select: "remove"
select: "remove",
},
},
],
@ -28,7 +30,7 @@
props: {
entities,
thumbnails: true,
select: "remove"
select: "remove",
},
},
],
@ -37,9 +39,16 @@
</script>
<div class="view">
<h2>{$i18n.t("Selected")}</h2>
<h2><Icon plain name="select-multiple" /> {$i18n.t("Selected")}: {$selected.length}</h2>
<div class="actions">
<MultiGroupEditor entities={$selected} />
</div>
<div class="entities">
<EntryView entities={$selected} widgets={selectedWidgets} />
<EntryView
title={$i18n.t("Selected entities")}
entities={$selected}
widgets={selectedWidgets}
/>
</div>
</div>
@ -58,5 +67,6 @@
.entities {
flex-grow: 1;
overflow-y: auto;
height: 0;
}
</style>