2021-11-11 23:37:42 +01:00
|
|
|
<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";
|
|
|
|
|
2021-12-01 15:39:26 +01:00
|
|
|
export let columns = "attribute, value";
|
|
|
|
export let header = true;
|
2021-11-30 22:59:34 +01:00
|
|
|
|
2021-11-11 23:37:42 +01:00
|
|
|
export let attributes: OrderedListing;
|
|
|
|
export let editable = false;
|
|
|
|
|
2021-12-01 15:39:26 +01:00
|
|
|
// Display
|
|
|
|
$: showEntity = columns.includes("entity");
|
|
|
|
$: showAttribute = columns.includes("attribute");
|
|
|
|
$: showValue = columns.includes("value");
|
|
|
|
|
2021-11-30 23:18:39 +01:00
|
|
|
// Pagination
|
2021-11-11 23:37:42 +01:00
|
|
|
let currentDisplay = 999;
|
|
|
|
const MAX_DISPLAY = 50;
|
|
|
|
|
2021-11-30 23:18:39 +01:00
|
|
|
// Editing
|
|
|
|
let newEntryAttribute = "'";
|
|
|
|
let newEntryValue = "";
|
|
|
|
|
2021-11-11 23:37:42 +01:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2021-11-30 23:18:39 +01:00
|
|
|
// 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
|
2021-11-11 23:37:42 +01:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
2021-11-30 23:18:39 +01:00
|
|
|
|
|
|
|
// Optimizations
|
|
|
|
let resolve = [];
|
2021-11-11 23:37:42 +01:00
|
|
|
</script>
|
|
|
|
|
2021-11-30 22:59:34 +01:00
|
|
|
<table>
|
|
|
|
<colgroup>
|
|
|
|
{#if editable}
|
|
|
|
<col class="attr-action-col" />
|
|
|
|
{/if}
|
2021-12-01 15:39:26 +01:00
|
|
|
{#if showEntity}
|
2021-11-30 22:59:34 +01:00
|
|
|
<col class="entity-col" />
|
|
|
|
{/if}
|
2021-12-01 15:39:26 +01:00
|
|
|
{#if showAttribute}
|
2021-11-30 22:59:34 +01:00
|
|
|
<col class="attr-col" />
|
|
|
|
{/if}
|
2021-12-01 15:39:26 +01:00
|
|
|
{#if showValue}
|
2021-11-30 22:59:34 +01:00
|
|
|
<col class="val-col" />
|
|
|
|
{/if}
|
|
|
|
</colgroup>
|
|
|
|
|
2021-12-01 15:39:26 +01:00
|
|
|
{#if header}
|
|
|
|
<tr>
|
|
|
|
{#if editable}
|
|
|
|
<th />
|
|
|
|
{/if}
|
|
|
|
{#if showEntity}
|
|
|
|
<th>Entity</th>
|
|
|
|
{/if}
|
|
|
|
{#if showAttribute}
|
|
|
|
<th>Attribute</th>
|
|
|
|
{/if}
|
|
|
|
{#if showValue}
|
|
|
|
<th>Value</th>
|
|
|
|
{/if}
|
|
|
|
</tr>
|
|
|
|
{/if}
|
2021-11-30 22:59:34 +01:00
|
|
|
|
2021-11-30 23:18:39 +01:00
|
|
|
{#each sortedAttributes as [id, entry] (id)}
|
2021-11-30 22:59:34 +01:00
|
|
|
<tr>
|
2021-11-11 23:37:42 +01:00
|
|
|
{#if editable}
|
2021-11-30 22:59:34 +01:00
|
|
|
<td class="attr-action">
|
|
|
|
<sl-icon-button name="x-circle" on:click={removeEntry(id)} />
|
|
|
|
</td>
|
2021-11-11 23:37:42 +01:00
|
|
|
{/if}
|
2021-11-30 22:59:34 +01:00
|
|
|
|
2021-12-01 15:39:26 +01:00
|
|
|
{#if showEntity}
|
2021-11-30 22:59:34 +01:00
|
|
|
<td>
|
|
|
|
<Address link address={entry.entity} />
|
|
|
|
</td>
|
2021-11-11 23:37:42 +01:00
|
|
|
{/if}
|
2021-11-30 22:59:34 +01:00
|
|
|
|
2021-12-01 15:39:26 +01:00
|
|
|
{#if showAttribute}
|
2021-11-30 22:59:34 +01:00
|
|
|
<td class:formatted={Boolean(formatAttribute(entry.attribute))}>
|
|
|
|
<Marquee>
|
|
|
|
{formatAttribute(entry.attribute) || entry.attribute}
|
|
|
|
</Marquee>
|
|
|
|
</td>
|
2021-11-11 23:37:42 +01:00
|
|
|
{/if}
|
2021-11-30 22:59:34 +01:00
|
|
|
|
2021-12-01 15:39:26 +01:00
|
|
|
{#if showValue}
|
2021-11-30 22:59:34 +01:00
|
|
|
<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}
|
2021-11-30 23:18:39 +01:00
|
|
|
on:resolved={(event) => {
|
|
|
|
resolvedValues[id] = event.detail[0];
|
|
|
|
}}
|
2021-11-30 22:59:34 +01:00
|
|
|
/>
|
|
|
|
{: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>
|
2021-12-01 15:39:26 +01:00
|
|
|
{#if showAttribute}
|
2021-11-30 22:59:34 +01:00
|
|
|
<td>
|
|
|
|
<sl-input v-sl-model:newEntryAttribute size="small" />
|
|
|
|
</td>
|
|
|
|
{/if}
|
2021-12-01 15:39:26 +01:00
|
|
|
{#if showValue}
|
2021-11-30 22:59:34 +01:00
|
|
|
<td>
|
|
|
|
<sl-input v-sl-model:newEntryValue size="small" />
|
|
|
|
</td>
|
|
|
|
{/if}
|
|
|
|
</tr>
|
|
|
|
{/if}
|
|
|
|
</table>
|
2021-11-11 23:37:42 +01:00
|
|
|
|
|
|
|
<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;
|
|
|
|
}
|
|
|
|
|
2021-12-01 15:43:32 +01:00
|
|
|
&.formatted {
|
2021-11-11 23:37:42 +01:00
|
|
|
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>
|