label loading via ALIASes, more complete API

feat/vaults
Tomáš Mládek 2021-12-19 19:20:09 +01:00
parent c44afd0376
commit 78152c94d6
9 changed files with 84 additions and 71 deletions

View File

@ -14,42 +14,54 @@ import type { IEntry, ListingResult, VALUE_TYPE } from "./types";
export class UpListing { export class UpListing {
public readonly entries: UpEntry[]; public readonly entries: UpEntry[];
private _objects: { [key: string]: UpObject } = {};
constructor(listing: ListingResult) { constructor(listing: ListingResult) {
this.entries = Object.entries(listing).map((lr) => new UpEntry(...lr)); this.entries = Object.entries(listing).map(
(lr) => new UpEntry(...lr, this)
);
} }
public get objects(): UpObject[] { public get objects() {
const allEntities = new Set(this.entries.map((e) => e.entity)); const allEntities = new Set(this.entries.map((e) => e.entity));
return Array.from(allEntities).map( const result: { [key: string]: UpObject } = {};
(entity) => new UpObject(entity, this.entries) Array.from(allEntities).forEach(
(entity) => (result[entity] = new UpObject(entity, this))
); );
return result;
}
public getObject(address: string) {
if (!this._objects[address]) {
this._objects[address] = new UpObject(address, this);
}
return this._objects[address];
} }
} }
export class UpObject { export class UpObject {
public readonly address; public readonly address;
private entries: UpEntry[]; public listing: UpListing | undefined;
constructor(address: string, entries?: UpEntry[]) { constructor(address: string, listing?: UpListing) {
this.address = address; this.address = address;
this.entries = entries || []; this.listing = listing;
} }
public bind(entries: UpEntry[]) { public bind(listing: UpListing) {
this.entries = entries; this.listing = listing;
}
public bindAppend(entries: UpEntry[]) {
this.entries.push(...entries);
} }
public get attributes() { public get attributes() {
return this.entries.filter((e) => e.entity === this.address); return (this.listing?.entries || []).filter(
(e) => e.entity === this.address
);
} }
public get backlinks() { public get backlinks() {
return this.entries.filter((e) => e.value.c === this.address); return (this.listing?.entries || []).filter(
(e) => e.value.c === this.address
);
} }
public get attr() { public get attr() {
@ -70,18 +82,10 @@ export class UpObject {
} }
public identify(): string[] { public identify(): string[] {
// Get all places where this Object is "had" const hasAliases = this.backlinks
const hasEntries = this.backlinks
.filter((entry) => entry.attribute === "HAS") .filter((entry) => entry.attribute === "HAS")
.map((entry) => entry.address); .map((entry) => entry.get("ALIAS"))
.filter(Boolean) as string[];
// Out of those relations, retrieve their ALIAS attrs
const hasAliases = this.entries
.filter(
(entry) =>
entry.attribute === "ALIAS" && hasEntries.includes(entry.entity)
)
.map((entry) => String(entry.value.c));
const lblValues = (this.attr["LBL"] || []).map((e) => String(e.value.c)); const lblValues = (this.attr["LBL"] || []).map((e) => String(e.value.c));
@ -101,8 +105,8 @@ export class UpEntry extends UpObject implements IEntry {
attribute: string; attribute: string;
value: { t: VALUE_TYPE; c: string | number }; value: { t: VALUE_TYPE; c: string | number };
constructor(address: string, entry: IEntry) { constructor(address: string, entry: IEntry, listing: UpListing) {
super(address); super(address, listing);
this.entity = entry.entity; this.entity = entry.entity;
this.attribute = entry.attribute; this.attribute = entry.attribute;

View File

@ -5,9 +5,11 @@
import Ellipsis from "./Ellipsis.svelte"; import Ellipsis from "./Ellipsis.svelte";
import UpLink from "./UpLink.svelte"; import UpLink from "./UpLink.svelte";
import { useEntity } from "../lib/entity"; import { useEntity } from "../lib/entity";
import type { UpObject } from "upend";
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
export let address: string; export let address: string;
export let labels: string[] = [];
export let link = false; export let link = false;
export let resolve = true; export let resolve = true;
export let banner = false; export let banner = false;
@ -29,7 +31,8 @@
} }
} }
$: label = inferredIds.join(" | ") || address; $: displayLabel =
Array.from(new Set(inferredIds.concat(labels))).join(" | ") || address;
$: dispatch("resolved", inferredIds); $: dispatch("resolved", inferredIds);
</script> </script>
@ -37,17 +40,17 @@
<div class="address" class:identified={Boolean(inferredIds)} class:banner> <div class="address" class:identified={Boolean(inferredIds)} class:banner>
<HashBadge {address} /> <HashBadge {address} />
<div class="separator" /> <div class="separator" />
<div class="label" class:resolving title={label}> <div class="label" class:resolving title={displayLabel}>
{#if banner && isFile} {#if banner && isFile}
<a href="/api/raw/{address}" target="_blank"> <a href="/api/raw/{address}" target="_blank">
<Ellipsis value={label} /> <Ellipsis value={displayLabel} />
</a> </a>
{:else if link} {:else if link}
<UpLink to={{ entity: address }}> <UpLink to={{ entity: address }}>
<Ellipsis value={label} /> <Ellipsis value={displayLabel} />
</UpLink> </UpLink>
{:else} {:else}
<Ellipsis value={label} /> <Ellipsis value={displayLabel} />
{/if} {/if}
</div> </div>
</div> </div>

View File

@ -1,16 +1,16 @@
<script lang="ts"> <script lang="ts">
import { createEventDispatcher } from "svelte"; import { createEventDispatcher } from "svelte";
import UpLink from "./UpLink.svelte"; import UpLink from "./UpLink.svelte";
import type { Component, UpType, Widget } from "../lib/types"; import { Component, UNTYPED, UpType, Widget } from "../lib/types";
import Table from "./widgets/Table.svelte"; import Table from "./widgets/Table.svelte";
import TableComponent from "./widgets/Table.svelte"; // silence false svelte(reactive-component) warnings import TableComponent from "./widgets/Table.svelte"; // silence false svelte(reactive-component) warnings
import type { AttributeChange } from "../types/base"; import type { AttributeChange } from "../types/base";
import type { UpEntry } from "upend"; import type { UpEntry } from "upend";
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
export let attributes: UpEntry[]; export let address: string;
export let entries: UpEntry[];
export let type: UpType | undefined = undefined; export let type: UpType | undefined = undefined;
export let address: String;
export let title: String | undefined = undefined; export let title: String | undefined = undefined;
export let editable = false; export let editable = false;
export let reverse = false; export let reverse = false;
@ -74,7 +74,7 @@ import type { UpEntry } from "upend";
<section class="attribute-view"> <section class="attribute-view">
<header> <header>
<h3> <h3>
{#if type} {#if type && type !== UNTYPED}
<UpLink to={{ entity: type.address }}> <UpLink to={{ entity: type.address }}>
{#if type.icon} {#if type.icon}
<div class="icon"> <div class="icon">
@ -105,13 +105,13 @@ import type { UpEntry } from "upend";
<svelte:component <svelte:component
this={component.component} this={component.component}
{...component.props || {}} {...component.props || {}}
{attributes} {entries}
{editable} {editable}
on:change={(ev) => onChange(ev.detail)} on:change={(ev) => onChange(ev.detail)}
/> />
{/each} {/each}
{:else} {:else}
<Table columns="entity, attribute" {attributes} /> <Table columns="entity, attribute" {entries} />
{/if} {/if}
</section> </section>
@ -121,7 +121,7 @@ import type { UpEntry } from "upend";
overflow: visible; overflow: visible;
margin-top: 1.66em; margin-top: 1.66em;
padding: 1ex .95ex; padding: 1ex 0.95ex;
border: 0.1em solid var(--foreground); border: 0.1em solid var(--foreground);
border-radius: 4px; border-radius: 4px;

View File

@ -3,7 +3,6 @@
import { query, useEntity } from "../lib/entity"; import { query, useEntity } from "../lib/entity";
import Address from "./Address.svelte"; import Address from "./Address.svelte";
import { UpType } from "../lib/types"; import { UpType } from "../lib/types";
import type { IEntry } from "upend/types";
import BlobPreview from "./BlobPreview.svelte"; import BlobPreview from "./BlobPreview.svelte";
import { setContext } from "svelte"; import { setContext } from "svelte";
import { writable } from "svelte/store"; import { writable } from "svelte/store";
@ -42,8 +41,6 @@
allTypes[entry.entity].name = String(entry.value.c); allTypes[entry.entity].name = String(entry.value.c);
break; break;
case "TYPE_HAS": case "TYPE_HAS":
case "TYPE_REQUIRES":
case "TYPE_ID":
allTypes[entry.entity].attributes.push(String(entry.value.c)); allTypes[entry.entity].attributes.push(String(entry.value.c));
break; break;
} }
@ -76,46 +73,46 @@
}); });
typedAttributes = typedAttributes; typedAttributes = typedAttributes;
untypedAttributes = untypedAttributes; untypedAttributes = untypedAttributes.filter(
(entry) => !["IS", "LBL"].includes(entry.attribute)
);
} }
$: filteredUntypedAttributes = untypedAttributes.filter(
(entry) =>
entry.attribute !== "IS" ||
!Object.keys(typedAttributes).includes(String(entry.value.c))
);
</script> </script>
<div class="inspect"> <div class="inspect">
<h2> <h2>
<Address banner {address} /> {#if $entity}
<Address banner {address} />
{/if}
</h2> </h2>
<BlobPreview {address} /> <BlobPreview {address} />
{#if !$error} {#if !$error}
<div class="attributes"> <div class="attributes">
{#each Object.entries(typedAttributes) as [typeAddr, attributes] (typeAddr)} {#each Object.entries(typedAttributes) as [typeAddr, entries] (typeAddr)}
<AttributeView <AttributeView
{editable}
{address} {address}
{entries}
type={allTypes[typeAddr]} type={allTypes[typeAddr]}
{attributes} {editable}
on:changed={revalidate} on:changed={revalidate}
/> />
{/each} {/each}
{#if filteredUntypedAttributes.length > 0 || editable}
{#if untypedAttributes.length > 0 || editable}
<AttributeView <AttributeView
title="Other attributes" title="Other attributes"
{editable} {editable}
{address} {address}
attributes={untypedAttributes} entries={untypedAttributes}
on:changed={revalidate} on:changed={revalidate}
/> />
{/if} {/if}
{#if $entity?.backlinks.length > 0} {#if $entity?.backlinks.length > 0}
<AttributeView <AttributeView
title={`Referred to (${$entity.backlinks.length})`} title={`Referred to (${$entity.backlinks.length})`}
{address} {address}
attributes={$entity.backlinks} entries={$entity.backlinks}
reverse reverse
on:changed={revalidate} on:changed={revalidate}
/> />

View File

@ -14,7 +14,7 @@
export let columns = "attribute, value"; export let columns = "attribute, value";
export let header = true; export let header = true;
export let attributes: UpEntry[]; export let entries: UpEntry[];
export let editable = false; export let editable = false;
// Display // Display
@ -59,7 +59,7 @@
// Sorting // Sorting
let sortKeys: { [key: string]: string } = {}; let sortKeys: { [key: string]: string } = {};
$: sortedAttributes = attributes $: sortedAttributes = entries
.concat() .concat()
.sort((aEntry, bEntry) => { .sort((aEntry, bEntry) => {
if ( if (
@ -77,6 +77,9 @@
return sortKeys[aEntry.value.c].localeCompare(sortKeys[bEntry.value.c]); return sortKeys[aEntry.value.c].localeCompare(sortKeys[bEntry.value.c]);
} }
}) })
.sort((aEntry, bEntry) => {
return String(aEntry.value.c).length - String(bEntry.value.c).length;
})
.sort((aEntry, bEntry) => { .sort((aEntry, bEntry) => {
return aEntry.attribute.localeCompare(bEntry.attribute); return aEntry.attribute.localeCompare(bEntry.attribute);
}) })
@ -186,6 +189,7 @@
<td class="entity"> <td class="entity">
<Address <Address
link link
labels={entry.listing.getObject(String(entry.entity)).identify()}
address={entry.entity} address={entry.entity}
on:resolved={(event) => { on:resolved={(event) => {
sortKeys[entry.entity] = event.detail[0]; sortKeys[entry.entity] = event.detail[0];
@ -213,6 +217,9 @@
<Address <Address
link link
address={String(entry.value.c)} address={String(entry.value.c)}
labels={entry.listing
.getObject(String(entry.value.c))
.identify()}
resolve={Boolean(resolve[entry.address]) || true} resolve={Boolean(resolve[entry.address]) || true}
on:resolved={(event) => { on:resolved={(event) => {
sortKeys[entry.value.c] = event.detail[0]; sortKeys[entry.value.c] = event.detail[0];

View File

@ -16,7 +16,7 @@ export function useEntity(address: string) {
const entity: Readable<UpObject | undefined> = derived(data, ($listing) => { const entity: Readable<UpObject | undefined> = derived(data, ($listing) => {
if ($listing) { if ($listing) {
const listing = new UpListing($listing); const listing = new UpListing($listing);
return listing.objects.find((obj) => obj.address == address); return listing.getObject(address);
} }
}); });

View File

@ -1,12 +1,11 @@
import type { SvelteComponent, SvelteComponentTyped } from "svelte"; import Table from "../components/widgets/Table.svelte";
import List from "../components/widgets/List.svelte";
export class UpType { export class UpType {
address: string; address: string;
name: string | null = null; name: string | null = null;
attributes: string[] = []; attributes: string[] = [];
constructor(address: string) { constructor(address?: string) {
this.address = address; this.address = address;
} }
@ -19,6 +18,8 @@ export class UpType {
} }
} }
export const UNTYPED = new UpType("UNTYPED");
export interface Component { export interface Component {
component: any; // TODO component: any; // TODO
props?: { [key: string]: unknown }; props?: { [key: string]: unknown };
@ -41,9 +42,9 @@ const TYPE_WIDGETS: { [key: string]: Widget } = {
icon: "folder", icon: "folder",
components: [ components: [
{ {
component: List, component: Table,
props: { props: {
attribute: "HAS", columns: "value",
}, },
}, },
], ],

View File

@ -15,8 +15,9 @@
const response = await fetch("/api/hier_roots"); const response = await fetch("/api/hier_roots");
const data = (await response.json()) as ListingResult; const data = (await response.json()) as ListingResult;
const listing = new UpListing(data); const listing = new UpListing(data);
console.log(listing.objects.map((obj) => obj.attr)); return Object.values(listing.objects).filter((obj) =>
return listing.objects.filter((obj) => Boolean(obj.attr["LBL"])); Boolean(obj.attr["LBL"])
);
})(); })();
const latestFiles = (async () => { const latestFiles = (async () => {
@ -45,7 +46,7 @@
<li> <li>
<sl-card class="root"> <sl-card class="root">
<Link to="/browse/{root.address}"> <Link to="/browse/{root.address}">
<h1>{root.get("LBL")}</h1> <h1>{root.identify()}</h1>
</Link> </Link>
<div slot="footer"> <div slot="footer">
{root.attr["HAS"]?.length || 0} children {root.attr["HAS"]?.length || 0} children

View File

@ -3301,8 +3301,8 @@ __metadata:
"upend@file:../tools/upend_js::locator=svelte-app%40workspace%3A.": "upend@file:../tools/upend_js::locator=svelte-app%40workspace%3A.":
version: 0.0.1 version: 0.0.1
resolution: "upend@file:../tools/upend_js#../tools/upend_js::hash=88b9af&locator=svelte-app%40workspace%3A." resolution: "upend@file:../tools/upend_js#../tools/upend_js::hash=28f6da&locator=svelte-app%40workspace%3A."
checksum: 9076efc8b84c0c96f5d48ce092320832be93d18529af48d6ab623a3adb698401b78913ceac95439ddaaf3232a6a41ec886ad263ce2f05646ad6460d98627f8e9 checksum: c0d1171522a5b5756516f5e4b1ff2eab15e9a6be7eb1689afcdecd4da89b4c26aa82293320928e8a60a9bd65de15482c5b1eae323c0205f10c17aac5524418de
languageName: node languageName: node
linkType: hard linkType: hard