label loading via ALIASes, more complete API
parent
c44afd0376
commit
78152c94d6
|
@ -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;
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -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];
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -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",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue