upend/ui/src/components/widgets/Table.svelte

264 lines
6.0 KiB
Svelte

<script lang="ts">
import filesize from "filesize";
import { format, fromUnixTime } from "date-fns";
import type { Readable } from "svelte/store";
import type { OrderedListing } from "upend/types";
import Marquee from "../Marquee.svelte";
import Address from "../Address.svelte";
export let entity = false;
export let attribute = true;
export let value = true;
export let attributes: OrderedListing;
export let editable = false;
// Pagination
let currentDisplay = 999;
const MAX_DISPLAY = 50;
// Editing
let newEntryAttribute = "'";
let newEntryValue = "";
async function addEntry() {
// this.$emit("edit", {
// type: "create",
// attribute: this.newEntryAttribute,
// value: this.newEntryValue,
// } as AttributeChange);
// this.newEntryAttribute = "";
// this.newEntryValue = "";
}
async function removeEntry(addr: string) {
// if (confirm("Are you sure you want to remove the attribute?")) {
// this.$emit("edit", { type: "delete", addr } as AttributeChange);
// }
}
async function updateEntry(addr: string, attribute: string, value: string) {
// this.$emit("edit", {
// type: "update",
// addr,
// value
// } as AttributeChange);
// this.$emit("edit", {
// type: "delete",
// addr,
// } as AttributeChange);
// this.$emit("edit", {
// type: "create",
// attribute,
// value,
// } as AttributeChange);
}
// Sorting
let resolvedValues: { [key: string]: string } = {};
$: sortedAttributes = attributes
.concat()
.sort(([aHash, _], [bHash, __]) => {
if (
resolvedValues[aHash] === undefined ||
resolvedValues[bHash] === undefined
) {
return 0;
} else {
return resolvedValues[aHash].localeCompare(resolvedValues[bHash]);
}
})
.sort(([_, aEntry], [__, bEntry]) => {
return aEntry.attribute.localeCompare(bEntry.attribute);
})
.sort(([_, aEntry], [__, bEntry]) => {
return aEntry.entity.localeCompare(bEntry.entity);
});
// Formatting & Display
const ATTRIBUTE_LABELS: { [key: string]: string } = {
FILE_MIME: "MIME type",
FILE_MTIME: "Last modified",
FILE_SIZE: "File size",
};
const VALUE_FORMATTERS: { [key: string]: (val: string) => string } = {
FILE_MTIME: (val) => format(fromUnixTime(parseInt(val, 10)), "PPpp"),
FILE_SIZE: (val) => filesize(parseInt(val, 10), { base: 2 }),
};
function formatAttribute(attribute: string) {
return ATTRIBUTE_LABELS[attribute];
}
function formatValue(value: string, attribute: string): string | undefined {
const handler = VALUE_FORMATTERS[attribute];
if (handler) {
return handler(value);
}
}
// Optimizations
let resolve = [];
</script>
<table>
<colgroup>
{#if editable}
<col class="attr-action-col" />
{/if}
{#if entity}
<col class="entity-col" />
{/if}
{#if attribute}
<col class="attr-col" />
{/if}
{#if value}
<col class="val-col" />
{/if}
</colgroup>
<tr>
{#if editable}
<th />
{/if}
{#if entity}
<th>Entity</th>
{/if}
{#if attribute}
<th>Attribute</th>
{/if}
{#if value}
<th>Value</th>
{/if}
</tr>
{#each sortedAttributes as [id, entry] (id)}
<tr>
{#if editable}
<td class="attr-action">
<sl-icon-button name="x-circle" on:click={removeEntry(id)} />
</td>
{/if}
{#if entity}
<td>
<Address link address={entry.entity} />
</td>
{/if}
{#if attribute}
<td class:formatted={Boolean(formatAttribute(entry.attribute))}>
<Marquee>
{formatAttribute(entry.attribute) || entry.attribute}
</Marquee>
</td>
{/if}
{#if value}
<td class="value">
<text-input
{editable}
value={entry.value.c}
on:edit={(val) => updateEntry(id, entry.attribute, val)}
>
{#if entry.value.t === "Address"}
<Address
link
address={entry.value.c}
resolve={Boolean(resolve[id]) || true}
on:resolved={(event) => {
resolvedValues[id] = event.detail[0];
}}
/>
{:else}
<div
class:formatted={Boolean(
formatValue(entry.value.c, entry.attribute)
)}
>
<Marquee>
{formatValue(entry.value.c, entry.attribute) || entry.value.c}
</Marquee>
</div>
{/if}
</text-input>
</td>
{/if}
</tr>
{/each}
{#if attributes.length > currentDisplay}
<tr>
<td colspan={editable ? 3 : 2}>
<sl-button
class="more-button"
on:click={(currentDisplay += MAX_DISPLAY)}
>
+ {attributes.length - currentDisplay} more...
</sl-button>
</td>
</tr>
{/if}
{#if editable}
<tr>
<td class="attr-action">
<sl-icon-button name="plus-circle" on:click={addEntry()} />
</td>
{#if attribute}
<td>
<sl-input v-sl-model:newEntryAttribute size="small" />
</td>
{/if}
{#if value}
<td>
<sl-input v-sl-model:newEntryValue size="small" />
</td>
{/if}
</tr>
{/if}
</table>
<style lang="scss" scoped>
table {
width: 100%;
table-layout: fixed;
th {
text-align: left;
}
td {
font-family: var(--monospace-font);
padding-right: 1em;
line-height: 1em;
line-break: anywhere;
&.attr-action {
max-width: 1em;
}
.formatted {
font-family: var(--default-font);
}
}
.attr-action-col {
width: 1.5em;
}
.attr-col {
width: 33%;
}
sl-icon-button {
&::part(base) {
padding: 2px;
}
}
.more-button {
width: 100%;
}
}
</style>