135 lines
3.2 KiB
Svelte
135 lines
3.2 KiB
Svelte
<script lang="ts">
|
|
import UpObjectDisplay from "./display/UpObject.svelte";
|
|
import Selector, { type SelectorValue } from "./utils/Selector.svelte";
|
|
import IconButton from "./utils/IconButton.svelte";
|
|
import api from "../lib/api";
|
|
import { i18n } from "../i18n";
|
|
import type { UpObject, UpEntry } from "@upnd/upend";
|
|
import type { Readable } from "svelte/store";
|
|
import { ATTR_OF } from "@upnd/upend/constants";
|
|
import { createEventDispatcher } from "svelte";
|
|
import LabelBorder from "./utils/LabelBorder.svelte";
|
|
const dispatch = createEventDispatcher();
|
|
|
|
export let entity: Readable<UpObject>;
|
|
|
|
let adding = false;
|
|
let typeSelector: Selector;
|
|
|
|
$: if (adding && typeSelector) typeSelector.focus();
|
|
|
|
$: typeEntries = $entity?.attr[`~${ATTR_OF}`] || [];
|
|
|
|
async function add(ev: CustomEvent<SelectorValue>) {
|
|
if (ev.detail.t !== "Attribute") {
|
|
return;
|
|
}
|
|
|
|
await api.putEntry({
|
|
entity: {
|
|
t: "Attribute",
|
|
c: ev.detail.name,
|
|
},
|
|
attribute: ATTR_OF,
|
|
value: { t: "Address", c: $entity.address },
|
|
});
|
|
dispatch("change");
|
|
}
|
|
|
|
async function remove(entry: UpEntry) {
|
|
let really = confirm(
|
|
$i18n.t('Really remove "{{attributeName}}" from "{{typeName}}"?', {
|
|
attributeName: (await api.addressToComponents(entry.entity)).c,
|
|
typeName: $entity.identify().join("/"),
|
|
}),
|
|
);
|
|
|
|
if (really) {
|
|
await api.deleteEntry(entry.address);
|
|
dispatch("change");
|
|
}
|
|
}
|
|
</script>
|
|
|
|
{#if typeEntries.length || $entity?.attr["~IN"]?.length}
|
|
<LabelBorder hide={typeEntries.length === 0}>
|
|
<span slot="header">{$i18n.t("Type Attributes")}</span>
|
|
{#if adding}
|
|
<div class="selector">
|
|
<Selector
|
|
bind:this={typeSelector}
|
|
types={["Attribute", "NewAttribute"]}
|
|
on:input={add}
|
|
placeholder={$i18n.t("Assign an attribute to this type...")}
|
|
on:focus={(ev) => {
|
|
if (!ev.detail) adding = false;
|
|
}}
|
|
/>
|
|
</div>
|
|
{/if}
|
|
<div class="body">
|
|
<ul class="attributes">
|
|
{#each typeEntries as typeEntry}
|
|
<li class="attribute">
|
|
<div class="label">
|
|
<UpObjectDisplay address={typeEntry.entity} link />
|
|
</div>
|
|
<div class="controls">
|
|
<IconButton name="x-circle" on:click={() => remove(typeEntry)} />
|
|
</div>
|
|
</li>
|
|
{:else}
|
|
<li class="no-attributes">
|
|
{$i18n.t("No attributes assigned to this type.")}
|
|
</li>
|
|
{/each}
|
|
</ul>
|
|
<div class="add-button">
|
|
<IconButton
|
|
outline
|
|
small
|
|
name="plus-circle"
|
|
on:click={() => (adding = true)}
|
|
/>
|
|
</div>
|
|
</div>
|
|
</LabelBorder>
|
|
{/if}
|
|
|
|
<style lang="scss">
|
|
.attributes {
|
|
display: flex;
|
|
align-items: baseline;
|
|
flex-wrap: wrap;
|
|
gap: 0.25em;
|
|
}
|
|
|
|
.attribute {
|
|
display: flex;
|
|
}
|
|
|
|
.body {
|
|
display: flex;
|
|
align-items: start;
|
|
|
|
.attributes {
|
|
flex-grow: 1;
|
|
}
|
|
}
|
|
|
|
.selector {
|
|
width: 100%;
|
|
margin-bottom: 0.5rem;
|
|
}
|
|
|
|
.no-attributes {
|
|
opacity: 0.66;
|
|
}
|
|
|
|
ul {
|
|
list-style: none;
|
|
padding: 0;
|
|
margin: 0;
|
|
}
|
|
</style>
|