refactor: EntryList uses CSS grid instead of tables
ci/woodpecker/push/woodpecker Pipeline was successful Details

feat/axum
Tomáš Mládek 2023-09-09 14:29:58 +02:00
parent c3305efaaa
commit ae0c588928
2 changed files with 201 additions and 207 deletions

View File

@ -8,6 +8,7 @@
export let outline = false;
export let subdued = false;
export let small = false;
export let plain = false;
export let color: string | undefined = "var(--active-color, var(--primary))";
</script>
@ -17,6 +18,7 @@
class:outline
class:subdued
class:small
class:plain
{disabled}
{title}
style="--color: {color}"
@ -39,6 +41,10 @@
color: inherit;
opacity: 0.66;
&.plain {
padding: 0;
}
display: flex;
align-items: center;

View File

@ -41,6 +41,21 @@
const ATTR_COL = "attribute";
const VALUE_COL = "value";
$: templateColumns = (
(displayColumns || []).map((column, idx) => {
if (columnWidths?.[idx]) return columnWidths[idx];
switch (column) {
case ATTR_COL:
return "minmax(1em, 1fr)";
default:
return "minmax(1em, 2fr)";
}
}) as string[]
)
.concat(["auto"])
.join(" ");
// Editing
let adding = false;
let addHover = false;
@ -229,255 +244,228 @@
})();
</script>
<div class="container">
<table>
<colgroup>
{#each displayColumns as column, idx}
{#if columnWidths?.length}
<col
class="{column}-col"
style="width: {columnWidths[idx] || 'unset'}"
/>
{:else}
<col class="{column}-col" />
{/if}
<div class="entry-list" style:--template-columns={templateColumns}>
{#if header}
<header>
{#each displayColumns as column}
<div class="label">
{COLUMN_LABELS[column] || $attributeLabels[column] || column}
</div>
{/each}
<col class="action-col" />
</colgroup>
<div class="attr-action"></div>
</header>
{/if}
{#if header}
<tr>
{#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)}
{#each displayColumns as column}
{#if column == TIMESTAMP_COL}
<td title={entry.timestamp}
>{formatRelative(parseISO(entry.timestamp), new Date())}</td
>
{:else if column == PROVENANCE_COL}
<td>{entry.provenance}</td>
{:else if column == ENTITY_COL}
<td class="entity mark-entity">
{#each sortedEntries as entry (entry.address)}
{#if visible.has(entry.address)}
{#each displayColumns as column}
{#if column == TIMESTAMP_COL}
<div class="cell" title={entry.timestamp}>
{formatRelative(parseISO(entry.timestamp), new Date())}
</div>
{:else if column == PROVENANCE_COL}
<div class="cell">{entry.provenance}</div>
{:else if column == ENTITY_COL}
<div class="cell entity mark-entity">
<UpObject
link
labels={$labelListing
?.getObject(String(entry.entity))
?.identify() || []}
address={entry.entity}
on:resolved={(event) => {
addSortKeys(entry.entity, event.detail, true);
}}
/>
</div>
{:else if column == ATTR_COL}
<div
class="cell mark-attribute"
class:formatted={Boolean(
Object.keys($attributeLabels).includes(entry.attribute),
)}
>
<UpLink to={{ attribute: entry.attribute }}>
<Ellipsis
value={$attributeLabels[entry.attribute] || entry.attribute}
title={$attributeLabels[entry.attribute]
? `${$attributeLabels[entry.attribute]} (${entry.attribute})`
: entry.attribute}
/>
</UpLink>
</div>
{:else if column == VALUE_COL}
<div class="cell value mark-value">
<Editable
value={entry.value}
on:edit={(ev) =>
updateEntry(entry.address, entry.attribute, ev.detail)}
>
{#if entry.value.t === "Address"}
<UpObject
link
address={String(entry.value.c)}
labels={$labelListing
?.getObject(String(entry.entity))
?.getObject(String(entry.value.c))
?.identify() || []}
address={entry.entity}
on:resolved={(event) => {
addSortKeys(entry.entity, event.detail, true);
addSortKeys(String(entry.value.c), event.detail, true);
}}
/>
</td>
{:else if column == ATTR_COL}
<td
class:formatted={Boolean(
Object.keys($attributeLabels).includes(entry.attribute),
)}
class="mark-attribute"
>
<UpLink to={{ attribute: entry.attribute }}>
<Ellipsis
value={$attributeLabels[entry.attribute] || entry.attribute}
title={$attributeLabels[entry.attribute]
? `${$attributeLabels[entry.attribute]} (${
entry.attribute
})`
: entry.attribute}
/>
</UpLink>
</td>
{:else if column == VALUE_COL}
<td class="value mark-value">
<Editable
value={entry.value}
on:edit={(ev) =>
updateEntry(entry.address, entry.attribute, ev.detail)}
{:else}
<div
class:formatted={Boolean(
formatValue(entry.value.c, entry.attribute),
)}
>
{#if entry.value.t === "Address"}
<UpObject
link
address={String(entry.value.c)}
labels={$labelListing
?.getObject(String(entry.value.c))
?.identify() || []}
on:resolved={(event) => {
addSortKeys(String(entry.value.c), event.detail, true);
}}
/>
{:else}
<div
class:formatted={Boolean(
formatValue(entry.value.c, entry.attribute),
)}
>
<Ellipsis
value={formatValue(entry.value.c, entry.attribute) ||
String(entry.value.c)}
/>
</div>
{/if}
</Editable>
</td>
{:else}
<td>?</td>
{/if}
{/each}
<td class="attr-action">
<IconButton
subdued
name="x-circle"
on:click={() => removeEntry(entry.address)}
/>
</td>
<Ellipsis
value={formatValue(entry.value.c, entry.attribute) ||
String(entry.value.c)}
/>
</div>
{/if}
</Editable>
</div>
{:else}
<tr>
<td colspan="99">
<div class="skeleton" style="text-align: center">...</div>
</td>
</tr>
<div>?</div>
{/if}
</tr>
{/each}
{/each}
<div class="attr-action">
<IconButton
plain
subdued
name="x-circle"
color="#dc322f"
on:click={() => removeEntry(entry.address)}
/>
</div>
{:else}
<div class="skeleton" data-address={entry.address} use:observe>...</div>
{/if}
{/each}
{#each unusedAttributes as attribute}
<tr>
{#each unusedAttributes as attribute}
{#each displayColumns as column}
{#if column == ATTR_COL}
<div
class="cell mark-attribute"
class:formatted={Boolean(
Object.keys($attributeLabels).includes(attribute),
)}
>
<UpLink to={{ attribute }}>
<Ellipsis
value={$attributeLabels[attribute] || attribute}
title={$attributeLabels[attribute]
? `${$attributeLabels[attribute]} (${attribute})`
: attribute}
/>
</UpLink>
</div>
{:else if column == VALUE_COL}
<div class="cell">
<Editable on:edit={(ev) => addEntry(attribute, ev.detail)}>
<span class="unset">{$i18n.t("(unset)")}</span>
</Editable>
</div>
{:else}
<div class="cell"></div>
{/if}
{/each}
<div class="attr-action"></div>
{/each}
{#if !attributes?.length}
{#if adding}
<div
class="add-row"
on:mouseenter={() => (addHover = true)}
on:mouseleave={() => (addHover = false)}
>
{#each displayColumns as column}
{#if column == ATTR_COL}
<td
class:formatted={Boolean(
Object.keys($attributeLabels).includes(attribute),
)}
class="mark-attribute"
>
<UpLink to={{ attribute }}>
<Ellipsis
value={$attributeLabels[attribute] || attribute}
title={$attributeLabels[attribute]
? `${$attributeLabels[attribute]} (${attribute})`
: attribute}
/>
</UpLink>
</td>
{:else if column == VALUE_COL}
<td>
<Editable on:edit={(ev) => addEntry(attribute, ev.detail)}>
<span class="unset">{$i18n.t("(unset)")}</span>
</Editable>
</td>
<div class="cell mark-attribute">
<Selector
type="attribute"
bind:attribute={newEntryAttribute}
{attributeOptions}
on:focus={(ev) => (addFocus = ev.detail)}
bind:this={newAttrSelector}
/>
</div>
{:else if column === VALUE_COL}
<div class="cell mark-value">
<Selector
type="value"
bind:value={newEntryValue}
on:focus={(ev) => (addFocus = ev.detail)}
/>
</div>
{:else}
<td></td>
<div class="cell"></div>
{/if}
{/each}
<td class="attr-action"></td>
</tr>
{/each}
{#if !attributes?.length}
{#if adding}
<tr
class="add-row"
on:mouseenter={() => (addHover = true)}
on:mouseleave={() => (addHover = false)}
>
{#each displayColumns as column}
{#if column == ATTR_COL}
<td>
<Selector
type="attribute"
bind:attribute={newEntryAttribute}
{attributeOptions}
on:focus={(ev) => (addFocus = ev.detail)}
bind:this={newAttrSelector}
/>
</td>
{:else if column === VALUE_COL}
<td>
<Selector
type="value"
bind:value={newEntryValue}
on:focus={(ev) => (addFocus = ev.detail)}
/>
</td>
{:else}
<td></td>
{/if}
{/each}
<td class="attr-action">
<IconButton
name="save"
on:click={() => addEntry(newEntryAttribute, newEntryValue)}
/>
</td>
</tr>
{:else}
<tr>
<td class="fullwidth" colspan={displayColumns.length + 1}>
<IconButton
outline
subdued
name="plus-circle"
on:click={() => (adding = true)}
/>
</td>
</tr>
{/if}
<div class="attr-action">
<IconButton
name="save"
on:click={() => addEntry(newEntryAttribute, newEntryValue)}
/>
</div>
</div>
{:else}
<div class="add-button">
<IconButton
outline
subdued
name="plus-circle"
on:click={() => (adding = true)}
/>
</div>
{/if}
</table>
{/if}
</div>
<style lang="scss" scoped>
.container {
overflow-x: clip;
}
.entry-list {
display: grid;
grid-template-columns: var(--template-columns);
gap: 0.05rem 0.5rem;
table {
table-layout: fixed;
header {
display: contents;
// gaps between columns, but hug the edges
width: calc(100% + 1em);
margin-left: -0.5em;
border-spacing: 0.5em 0;
th {
text-align: left;
.label {
font-weight: 600;
}
}
td {
.cell {
font-family: var(--monospace-font);
line-break: anywhere;
border-radius: 4px;
padding: 2px;
&.attr-action {
max-width: 1em;
}
&.formatted,
.formatted {
font-family: var(--default-font);
}
}
.action-col {
width: 1.5em;
.attr-action {
display: flex;
justify-content: center;
align-items: center;
}
.attribute-col {
width: 33%;
.add-row {
display: contents;
}
:global(td.fullwidth > *) {
width: 100%;
.add-button {
display: flex;
flex-direction: column;
grid-column: 1 / -1;
}
.unset {