upend/webui/src/components/Inspect.svelte

273 lines
6.7 KiB
Svelte
Raw Normal View History

2021-11-11 23:37:42 +01:00
<script lang="ts">
import AttributeView from "./AttributeView.svelte";
import { query, useEntity } from "../lib/entity";
2021-12-21 20:02:47 +01:00
import UpObject from "./display/UpObject.svelte";
2021-11-11 23:37:42 +01:00
import { UpType } from "../lib/types";
2021-12-21 20:02:47 +01:00
import BlobPreview from "./display/BlobPreview.svelte";
import { setContext } from "svelte";
import { writable } from "svelte/store";
import type { UpEntry } from "upend";
2022-01-21 15:57:53 +01:00
import Spinner from "./utils/Spinner.svelte";
import NotesEditor from "./utils/NotesEditor.svelte";
import type { AttributeChange } from "../types/base";
import Selector from "./utils/Selector.svelte";
import type { IValue } from "upend/types";
2021-11-11 23:37:42 +01:00
export let address: string;
export let index: number | undefined;
2021-11-11 23:37:42 +01:00
export let editable = false;
let indexStore = writable(index);
$: $indexStore = index;
setContext("browse", { index: indexStore });
$: ({ entity, error, revalidate } = useEntity(address));
2021-11-11 23:37:42 +01:00
$: allTypeAddresses = ($entity?.attr["IS"] || []).map((attr) => attr.value.c);
2021-11-11 23:37:42 +01:00
$: allTypeEntries = query(
() =>
`(matches (in ${allTypeAddresses
.map((addr) => `"${addr}"`)
.join(" ")}) ? ?)`
).result;
let allTypes: { [key: string]: UpType } = {};
$: {
allTypes = {};
($allTypeEntries?.entries || []).forEach((entry) => {
2021-11-11 23:37:42 +01:00
if (allTypes[entry.entity] === undefined) {
allTypes[entry.entity] = new UpType(entry.entity);
}
switch (entry.attribute) {
case "TYPE":
allTypes[entry.entity].name = String(entry.value.c);
2021-11-11 23:37:42 +01:00
break;
2022-01-28 23:27:09 +01:00
case "LBL":
allTypes[entry.entity].label = String(entry.value.c);
break;
2021-11-11 23:37:42 +01:00
case "TYPE_HAS":
allTypes[entry.entity].attributes.push(String(entry.value.c));
2021-11-11 23:37:42 +01:00
break;
}
});
allTypes = allTypes;
}
let typedAttributes = {} as { [key: string]: UpEntry[] };
let untypedAttributes = [] as UpEntry[];
2021-11-11 23:37:42 +01:00
$: {
typedAttributes = {};
untypedAttributes = [];
($entity?.attributes || []).forEach((entry) => {
2021-11-11 23:37:42 +01:00
const entryTypes = Object.entries(allTypes).filter(([_, t]) =>
t.attributes.includes(entry.attribute)
);
if (entryTypes.length > 0) {
entryTypes.forEach(([addr, _]) => {
if (typedAttributes[addr] == undefined) {
typedAttributes[addr] = [];
}
typedAttributes[addr].push(entry);
2021-11-11 23:37:42 +01:00
});
} else {
untypedAttributes.push(entry);
2021-11-11 23:37:42 +01:00
}
});
typedAttributes = typedAttributes;
2022-01-21 15:57:53 +01:00
untypedAttributes = untypedAttributes;
2021-11-11 23:37:42 +01:00
}
2022-01-21 15:57:53 +01:00
$: filteredUntypedAttributes = untypedAttributes.filter(
(entry) => !["IS", "LBL", "NOTE"].includes(entry.attribute)
2022-01-21 15:57:53 +01:00
);
$: currentUntypedAttributes = editable
? untypedAttributes
: filteredUntypedAttributes;
2022-01-28 22:39:08 +01:00
$: currentBacklinks =
(editable
? $entity?.backlinks
: $entity?.backlinks.filter(
(entry) => !["HAS"].includes(entry.attribute)
)) || [];
2022-01-28 23:32:13 +01:00
$: groups = ($entity?.backlinks || [])
2022-01-28 22:39:08 +01:00
.filter((e) => e.attribute === "HAS")
.map((e) => e.entity)
.sort(); // TODO
2022-01-28 22:39:08 +01:00
async function onChange(ev: CustomEvent<AttributeChange>) {
const change = ev.detail;
switch (change.type) {
case "create":
await fetch(`/api/obj`, {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
entity: address,
attribute: change.attribute,
value: change.value,
}),
});
break;
case "delete":
await fetch(`/api/obj/${change.address}`, { method: "DELETE" });
break;
case "update":
// TODO
await fetch(`/api/obj/${change.address}`, { method: "DELETE" });
await fetch(`/api/obj`, {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
entity: address,
attribute: change.attribute,
value: change.value,
}),
});
break;
default:
console.error("Unimplemented AttributeChange", change);
return;
}
revalidate();
}
let groupToAdd: IValue | undefined;
async function addGroup() {
if (!groupToAdd) {
return;
}
await fetch(`/api/obj`, {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
entity: String(groupToAdd.c),
attribute: "HAS",
value: {
t: "Address",
c: address,
},
}),
});
revalidate();
groupToAdd = undefined;
}
2021-11-11 23:37:42 +01:00
</script>
<div class="inspect">
2022-01-28 22:39:08 +01:00
<header>
<h2>
{#if $entity}
<UpObject banner {address} />
{:else}
<Spinner />
{/if}
</h2>
2022-01-28 23:32:13 +01:00
<section class="groups labelborder">
<header><h3>Groups</h3></header>
2022-01-28 22:39:08 +01:00
<div class="content">
2022-01-28 23:32:13 +01:00
{#each groups as address}
2022-01-28 22:39:08 +01:00
<UpObject {address} link />
{/each}
{#if editable}
<div class="selector">
<Selector
type="entity"
bind:value={groupToAdd}
on:input={addGroup}
placeholder="Choose an entity..."
/>
</div>
{/if}
2022-01-28 22:39:08 +01:00
</div>
</section>
</header>
2021-11-30 23:26:40 +01:00
<BlobPreview {address} />
<NotesEditor {address} {editable} on:change={onChange} />
2021-11-11 23:37:42 +01:00
{#if !$error}
{#if Boolean($allTypeEntries)}
<div class="attributes">
{#each Object.entries(typedAttributes) as [typeAddr, entries] (typeAddr)}
<AttributeView
{entries}
type={allTypes[typeAddr]}
{editable}
on:change={onChange}
/>
{/each}
2022-01-21 15:57:53 +01:00
{#if currentUntypedAttributes.length > 0 || editable}
<AttributeView
title="Other attributes"
{editable}
2022-01-21 15:57:53 +01:00
entries={currentUntypedAttributes}
on:change={onChange}
/>
{/if}
2022-01-28 22:39:08 +01:00
{#if currentBacklinks.length > 0}
<AttributeView
title={`Referred to (${$entity.backlinks.length})`}
entries={$entity.backlinks}
reverse
on:change={onChange}
/>
{/if}
</div>
{:else}
<Spinner />
{/if}
2021-11-11 23:37:42 +01:00
{:else}
<div class="error">
{JSON.stringify($error)}
</div>
{/if}
</div>
<style scoped lang="scss">
2022-01-28 22:39:08 +01:00
@use "./util";
2021-11-30 00:29:27 +01:00
.inspect {
flex: auto;
display: flex;
flex-direction: column;
2022-01-28 22:39:08 +01:00
gap: 0.5rem;
h2 {
margin-bottom: 0;
}
2021-11-30 00:29:27 +01:00
}
2022-01-28 23:32:13 +01:00
.groups {
2022-01-28 22:39:08 +01:00
margin: 0.25rem 0;
.content {
display: flex;
flex-wrap: wrap;
gap: 0.5rem 0.5rem;
align-items: center;
}
.selector {
width: 100%;
}
2022-01-28 22:39:08 +01:00
}
2021-11-30 00:29:27 +01:00
.attributes {
flex: auto;
height: 0; // https://stackoverflow.com/a/14964944
overflow-y: auto;
}
2021-11-11 23:37:42 +01:00
.error {
color: red;
}
</style>