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 {
public readonly entries: UpEntry[];
private _objects: { [key: string]: UpObject } = {};
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));
return Array.from(allEntities).map(
(entity) => new UpObject(entity, this.entries)
const result: { [key: string]: UpObject } = {};
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 {
public readonly address;
private entries: UpEntry[];
public listing: UpListing | undefined;
constructor(address: string, entries?: UpEntry[]) {
constructor(address: string, listing?: UpListing) {
this.address = address;
this.entries = entries || [];
this.listing = listing;
}
public bind(entries: UpEntry[]) {
this.entries = entries;
}
public bindAppend(entries: UpEntry[]) {
this.entries.push(...entries);
public bind(listing: UpListing) {
this.listing = listing;
}
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() {
return this.entries.filter((e) => e.value.c === this.address);
return (this.listing?.entries || []).filter(
(e) => e.value.c === this.address
);
}
public get attr() {
@ -70,18 +82,10 @@ export class UpObject {
}
public identify(): string[] {
// Get all places where this Object is "had"
const hasEntries = this.backlinks
const hasAliases = this.backlinks
.filter((entry) => entry.attribute === "HAS")
.map((entry) => entry.address);
// 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));
.map((entry) => entry.get("ALIAS"))
.filter(Boolean) as string[];
const lblValues = (this.attr["LBL"] || []).map((e) => String(e.value.c));
@ -101,8 +105,8 @@ export class UpEntry extends UpObject implements IEntry {
attribute: string;
value: { t: VALUE_TYPE; c: string | number };
constructor(address: string, entry: IEntry) {
super(address);
constructor(address: string, entry: IEntry, listing: UpListing) {
super(address, listing);
this.entity = entry.entity;
this.attribute = entry.attribute;

View File

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

View File

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

View File

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

View File

@ -14,7 +14,7 @@
export let columns = "attribute, value";
export let header = true;
export let attributes: UpEntry[];
export let entries: UpEntry[];
export let editable = false;
// Display
@ -59,7 +59,7 @@
// Sorting
let sortKeys: { [key: string]: string } = {};
$: sortedAttributes = attributes
$: sortedAttributes = entries
.concat()
.sort((aEntry, bEntry) => {
if (
@ -77,6 +77,9 @@
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) => {
return aEntry.attribute.localeCompare(bEntry.attribute);
})
@ -186,6 +189,7 @@
<td class="entity">
<Address
link
labels={entry.listing.getObject(String(entry.entity)).identify()}
address={entry.entity}
on:resolved={(event) => {
sortKeys[entry.entity] = event.detail[0];
@ -213,6 +217,9 @@
<Address
link
address={String(entry.value.c)}
labels={entry.listing
.getObject(String(entry.value.c))
.identify()}
resolve={Boolean(resolve[entry.address]) || true}
on:resolved={(event) => {
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) => {
if ($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 List from "../components/widgets/List.svelte";
import Table from "../components/widgets/Table.svelte";
export class UpType {
address: string;
name: string | null = null;
attributes: string[] = [];
constructor(address: string) {
constructor(address?: string) {
this.address = address;
}
@ -19,6 +18,8 @@ export class UpType {
}
}
export const UNTYPED = new UpType("UNTYPED");
export interface Component {
component: any; // TODO
props?: { [key: string]: unknown };
@ -41,9 +42,9 @@ const TYPE_WIDGETS: { [key: string]: Widget } = {
icon: "folder",
components: [
{
component: List,
component: Table,
props: {
attribute: "HAS",
columns: "value",
},
},
],

View File

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

View File

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