wip: also pass `group` to all widgets, basic unused attr display

feat/axum
Tomáš Mládek 2023-08-28 18:14:06 +02:00
parent 1c858f8c44
commit 646f77b712
7 changed files with 97 additions and 60 deletions

View File

@ -7,7 +7,10 @@
export interface Widget {
name: string;
icon?: string;
components: (entries: UpEntry[]) => Array<WidgetComponent>;
components: (input: {
entries: UpEntry[];
group?: string;
}) => Array<WidgetComponent>;
}
</script>
@ -41,7 +44,7 @@
{
name: "Entry List",
icon: "table",
components: (entries) => [
components: ({ entries }) => [
{
component: EntryList,
props: { entries, columns: "entity, attribute, value" },
@ -65,7 +68,7 @@
$: {
components = availableWidgets
.find((w) => w.name === currentWidget)
.components(entries);
.components({ entries, group });
}
</script>

View File

@ -229,7 +229,7 @@
{
name: "List",
icon: "list-check",
components: (entries) => [
components: ({ entries }) => [
{
component: EntryList,
props: {
@ -245,12 +245,13 @@
{
name: "List",
icon: "list-check",
components: (entries) => [
components: ({ entries, group }) => [
{
component: EntryList,
props: {
entries,
columns: "attribute, value",
attributes: $allTypes[group]?.attributes || [],
},
},
],
@ -258,7 +259,7 @@
{
name: "Gallery",
icon: "image",
components: (entries) => [
components: ({ entries }) => [
{
component: Gallery,
props: {
@ -276,7 +277,7 @@
{
name: "List",
icon: "list-check",
components: (entries) => [
components: ({ entries }) => [
{
component: Gallery,
props: {
@ -289,7 +290,7 @@
{
name: "Gallery",
icon: "image",
components: (entries) => [
components: ({ entries }) => [
{
component: Gallery,
props: {
@ -427,15 +428,6 @@
</section>
{/if}
</div>
<div class="footer">
<IconButton
name="detail"
title={$i18n.t("Show as entries")}
active={showAsEntries}
on:click={() => (showAsEntries = !showAsEntries)}
/>
</div>
{:else}
<div class="error">
{$error}
@ -461,16 +453,23 @@
/>
</div>
{/if}
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div class="trash">
<div class="footer">
<IconButton
name="trash"
outline
color="#dc322f"
on:click={deleteObject}
title={$i18n.t("Delete object")}
name="detail"
title={$i18n.t("Show as entries")}
active={showAsEntries}
on:click={() => (showAsEntries = !showAsEntries)}
/>
</div>
<!-- svelte-ignore a11y-click-events-have-key-events -->
<IconButton
name="trash"
outline
subdued
color="#dc322f"
on:click={deleteObject}
title={$i18n.t("Delete object")}
/>
</div>
<style scoped lang="scss">
@ -545,6 +544,7 @@
}
.footer {
margin-top: 2rem;
display: flex;
justify-content: end;
}
@ -556,10 +556,4 @@
.error {
color: red;
}
.trash {
opacity: 0.66;
display: flex;
flex-direction: column;
}
</style>

View File

@ -6,7 +6,7 @@
import { isEqual } from "lodash";
const dispatch = createEventDispatcher();
export let editable: boolean;
let editable = false;
export let attribute: string | undefined = undefined;
export let value: IValue;
let newValue: IValue = value;
@ -17,7 +17,7 @@
FILE_SIZE: ["Number"],
FILE_MIME: ["String"],
ADDED: ["Number"],
LAST_VISITED: ["Number"]
LAST_VISITED: ["Number"],
};
</script>

View File

@ -6,6 +6,7 @@
export let disabled = false;
export let title: string | undefined = undefined;
export let outline = false;
export let subdued = false;
export let color: string | undefined = "var(--active-color, var(--primary))";
</script>
@ -13,6 +14,7 @@
on:click
class:active
class:outline
class:subdued
{disabled}
{title}
style="--color: {color}"
@ -44,6 +46,10 @@
border-color 0.2s;
}
button.subdued {
opacity: 0.4;
}
.outline {
border: 1px solid var(--foreground);
border-radius: 4px;

View File

@ -18,6 +18,9 @@
import { i18n } from "../../i18n";
import UpLink from "../display/UpLink.svelte";
import { ATTR_ADDED, ATTR_LABEL } from "upend/constants";
import api from "../../lib/api";
import { AddressComponents } from "upend/api";
import Icon from "../utils/Icon.svelte";
const dispatch = createEventDispatcher();
export let columns: string | undefined = undefined;
@ -27,7 +30,7 @@
export let columnWidths: string[] | undefined = undefined;
export let entries: UpEntry[];
export let editable = false;
export let attributes: string[] = [];
export let attributeOptions: string[] | undefined = undefined;
// Display
@ -209,14 +212,25 @@
return String(value);
}
}
// Unused attributes
let unusedAttributes = [];
$: (async () => {
unusedAttributes = await Promise.all(
attributes
.filter((attr) => !entries.some((entry) => entry.attribute === attr))
.map(async (attribute) => {
const components = new AddressComponents("Attribute", attribute);
return await api.componentsToAddress(components);
}),
);
})();
</script>
<div class="container">
<table>
<colgroup>
{#if editable}
<col class="action-col" />
{/if}
{#each displayColumns as column, idx}
{#if columnWidths?.length}
<col
@ -227,30 +241,21 @@
<col class="{column}-col" />
{/if}
{/each}
<col class="action-col" />
</colgroup>
{#if header}
<tr>
{#if editable}
<th />
{/if}
{#each displayColumns as column}
<th>{COLUMN_LABELS[column] || $attributeLabels[column] || column}</th>
{/each}
<th />
</tr>
{/if}
{#each sortedEntries as entry (entry.address)}
<tr data-address={entry.address} use:observe>
{#if visible.has(entry.address)}
{#if editable}
<td class="attr-action">
<IconButton
name="x-circle"
on:click={() => removeEntry(entry.address)}
/>
</td>
{/if}
{#each displayColumns as column}
{#if column == TIMESTAMP_COL}
<td title={entry.timestamp}
@ -292,7 +297,6 @@
{:else if column == VALUE_COL}
<td class="value mark-value">
<Editable
{editable}
attribute={entry.attribute}
value={entry.value}
on:edit={(ev) =>
@ -327,6 +331,13 @@
<td>?</td>
{/if}
{/each}
<td class="attr-action">
<IconButton
subdued
name="x-circle"
on:click={() => removeEntry(entry.address)}
/>
</td>
{:else}
<tr>
<td colspan="99">
@ -337,11 +348,26 @@
</tr>
{/each}
{#if editable}
{#each unusedAttributes as attributeAddress}
<tr>
{#each displayColumns as column}
{#if column == ATTR_COL}
<td>
<UpObject link address={attributeAddress} />
</td>
{:else if column == VALUE_COL}
<!-- <Selector type="value" bind:value={newEntryValue} /> -->
<td class="unset">{$i18n.t("(unset)")}</td>
{:else}
<td></td>
{/if}
{/each}
<td class="attr-action"></td>
</tr>
{/each}
{#if !attributes.length}
<tr class="add-row">
<td class="attr-action">
<IconButton name="plus-circle" on:click={addEntry} />
</td>
{#if displayColumns.includes(ATTR_COL)}
<td>
<Selector
@ -356,6 +382,9 @@
<Selector type="value" bind:value={newEntryValue} />
</td>
{/if}
<td class="attr-action">
<IconButton name="plus-circle" on:click={addEntry} />
</td>
</tr>
{/if}
</table>
@ -402,5 +431,10 @@
.attribute-col {
width: 33%;
}
.unset {
opacity: 0.66;
pointer-events: none;
}
}
</style>

View File

@ -92,7 +92,7 @@
{
name: "List",
icon: "list-ul",
components: (entries) => [
components: ({ entries }) => [
{
component: EntryList,
props: {
@ -108,7 +108,7 @@
{
name: "Gallery",
icon: "image",
components: (entries) => [
components: ({ entries }) => [
{
component: Gallery,
props: {
@ -124,7 +124,7 @@
{
name: "List",
icon: "list-ul",
components: (entries) => [
components: ({ entries }) => [
{
component: EntryList,
props: {
@ -140,7 +140,7 @@
{
name: "Gallery",
icon: "image",
components: (entries) => [
components: ({ entries }) => [
{
component: Gallery,
props: {

View File

@ -37,7 +37,7 @@
}
$: objects = ($result?.entries || []).filter(
(e) => e.attribute === ATTR_LABEL
(e) => e.attribute === ATTR_LABEL,
);
$: sortedObjects = matchSorter(objects, debouncedQuery, {
keys: ["value.c"],
@ -51,7 +51,7 @@
.then((labelListing) => {
exactHits = labelListing.entries
.filter(
(e) => String(e.value.c).toLowerCase() === query.toLowerCase()
(e) => String(e.value.c).toLowerCase() === query.toLowerCase(),
)
.map((e) => e.entity);
});
@ -68,7 +68,7 @@
{
name: "List",
icon: "list-ul",
components: (entries) => [
components: ({ entries }) => [
{
component: Gallery,
props: {
@ -82,7 +82,7 @@
{
name: "Gallery",
icon: "image",
components: (entries) => [
components: ({ entries }) => [
{
component: Gallery,
props: {