wip(webui): use (new) attr constants

feat/type-attributes
Tomáš Mládek 2023-06-24 16:26:14 +02:00
parent 0eec69b219
commit 641f42f785
14 changed files with 79 additions and 31 deletions

View File

@ -0,0 +1,31 @@
/**
* Attribute denoting (hierarchical) relation, in the "upwards" direction. For example, a file `IN` a group, an image `IN` photos, etc.
*/
export const ATTR_IN = "IN";
/**
* Attribute denoting that an entry belongs to the set relating to a given (hierarchical) relation.
* For example, a data blob may have a label entry, and to qualify that label within the context of belonging to a given hierarchical group, that label entry and the hierarchical entry will be linked with `BY`.
*/
export const ATTR_BY = "BY";
/**
* Attribute denoting that an attribute belongs to a given "tagging" entity. If an entity belongs to (`IN`) a "tagging" entity, it is expected to have attributes that are `OF` that entity.
*/
export const ATTR_OF = "OF";
/**
* Attribute denoting a human readable label.
*/
export const ATTR_LABEL = "LBL";
/**
* Attribute denoting the date & time an entity was noted in the database.
* (TODO: This info can be trivially derived from existing entry timestamps, while at the same time the "Introduction problem" is still open.)
*/
export const ATTR_ADDED = "ADDED";
/**
* Attribute for cross-vault unambiguous referencing of non-hashable (e.g. UUID) entities.
*/
export const ATTR_KEY = "KEY";

View File

@ -19,6 +19,7 @@
import EntryList from "./widgets/EntryList.svelte"; import EntryList from "./widgets/EntryList.svelte";
import api from "../lib/api"; import api from "../lib/api";
import Gallery from "./widgets/Gallery.svelte"; import Gallery from "./widgets/Gallery.svelte";
import { ATTR_IN, ATTR_LABEL, ATTR_KEY } from "upend/constants";
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
const params = useParams(); const params = useParams();
@ -76,9 +77,9 @@
$: filteredUntypedAttributes = untypedAttributes.filter( $: filteredUntypedAttributes = untypedAttributes.filter(
(entry) => (entry) =>
![ ![
"LBL", ATTR_LABEL,
"OF", ATTR_IN,
"KEY", ATTR_KEY,
"NOTE", "NOTE",
"LAST_VISITED", "LAST_VISITED",
"NUM_VISITED", "NUM_VISITED",
@ -94,14 +95,14 @@
(editable (editable
? $entity?.backlinks ? $entity?.backlinks
: $entity?.backlinks.filter( : $entity?.backlinks.filter(
(entry) => !["OF"].includes(entry.attribute) (entry) => ![ATTR_IN].includes(entry.attribute)
)) || []; )) || [];
$: groups = ($entity?.attr["OF"] || []).map((e) => [ $: groups = ($entity?.attr[ATTR_IN] || []).map((e) => [
e.value.c as string, e.value.c as string,
e.address, e.address,
]); ]);
$: tagged = $entity?.attr["~OF"] || []; $: tagged = $entity?.attr[`~${ATTR_IN}`] || [];
let attributesUsed: UpEntry[] = []; let attributesUsed: UpEntry[] = [];
$: { $: {
@ -149,7 +150,7 @@
await api.putEntry([ await api.putEntry([
{ {
entity: address, entity: address,
attribute: "OF", attribute: ATTR_IN,
value: { value: {
t: "Address", t: "Address",
c: String(groupToAdd.c), c: String(groupToAdd.c),

View File

@ -10,6 +10,7 @@
import { getTypes } from "../../util/mediatypes"; import { getTypes } from "../../util/mediatypes";
import { formatDuration } from "../../util/fragments/time"; import { formatDuration } from "../../util/fragments/time";
import { concurrentImage } from "../imageQueue"; import { concurrentImage } from "../imageQueue";
import { ATTR_IN } from "upend/constants";
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
export let address: string; export let address: string;
@ -38,14 +39,14 @@
let failedChildren: string[] = []; let failedChildren: string[] = [];
let loadedChildren: string[] = []; let loadedChildren: string[] = [];
$: groupChildren = $entity?.backlinks $: groupChildren = $entity?.backlinks
.filter((e) => e.attribute === "OF") .filter((e) => e.attribute === ATTR_IN)
.map((e) => String(e.entity)) .map((e) => String(e.entity))
.filter( .filter(
(addr) => (addr) =>
!failedChildren !failedChildren
.slice( .slice(
0, 0,
$entity?.backlinks.filter((e) => e.attribute === "OF").length - 4 $entity?.backlinks.filter((e) => e.attribute === ATTR_IN).length - 4
) )
.includes(addr) .includes(addr)
) )

View File

@ -11,6 +11,7 @@
import UpObject from "../../display/UpObject.svelte"; import UpObject from "../../display/UpObject.svelte";
import Spinner from "../../utils/Spinner.svelte"; import Spinner from "../../utils/Spinner.svelte";
import { i18n } from "../../../i18n"; import { i18n } from "../../../i18n";
import { ATTR_LABEL } from "upend/constants";
export let address: string; export let address: string;
export let detail: boolean; export let detail: boolean;
@ -51,7 +52,7 @@
color: annotation.get("COLOR") || DEFAULT_ANNOTATION_COLOR, color: annotation.get("COLOR") || DEFAULT_ANNOTATION_COLOR,
attributes: { attributes: {
"upend-address": annotation.address, "upend-address": annotation.address,
label: annotation.get("LBL"), label: annotation.get(ATTR_LABEL),
}, },
data: (annotation.attr["NOTE"] || [])[0]?.value, data: (annotation.attr["NOTE"] || [])[0]?.value,
...fragment, ...fragment,
@ -92,7 +93,7 @@
} as any); // incorrect types, `update()` does take `attributes` } as any); // incorrect types, `update()` does take `attributes`
} }
await api.putEntityAttribute(entity, "LBL", { await api.putEntityAttribute(entity, ATTR_LABEL, {
t: "String", t: "String",
c: region.attributes["label"], c: region.attributes["label"],
}); });

View File

@ -6,6 +6,7 @@
import IconButton from "../../utils/IconButton.svelte"; import IconButton from "../../utils/IconButton.svelte";
import Spinner from "../../utils/Spinner.svelte"; import Spinner from "../../utils/Spinner.svelte";
import UpObject from "../UpObject.svelte"; import UpObject from "../UpObject.svelte";
import { ATTR_LABEL } from "upend/constants";
export let address: string; export let address: string;
export let editable: boolean; export let editable: boolean;
@ -58,7 +59,7 @@
if (annotation.get("W3C_FRAGMENT_SELECTOR")) { if (annotation.get("W3C_FRAGMENT_SELECTOR")) {
anno.addAnnotation({ anno.addAnnotation({
type: "Annotation", type: "Annotation",
body: annotation.attr["LBL"].map((e) => { body: annotation.attr[ATTR_LABEL].map((e) => {
return { return {
type: "TextualBody", type: "TextualBody",
value: String(e.value.c), value: String(e.value.c),
@ -134,7 +135,7 @@
...annotation.body.map((body) => { ...annotation.body.map((body) => {
return { return {
entity: uuid, entity: uuid,
attribute: "LBL", attribute: ATTR_LABEL,
value: { value: {
t: "String", t: "String",
c: body.value, c: body.value,
@ -146,9 +147,9 @@
anno.on("updateAnnotation", async (annotation) => { anno.on("updateAnnotation", async (annotation) => {
const annotationObject = await api.fetchEntity(annotation.id); const annotationObject = await api.fetchEntity(annotation.id);
await Promise.all( await Promise.all(
annotationObject.attr["LBL"] annotationObject.attr[ATTR_LABEL].concat(
.concat(annotationObject.attr["W3C_FRAGMENT_SELECTOR"]) annotationObject.attr["W3C_FRAGMENT_SELECTOR"]
.map(async (e) => api.deleteEntry(e.address)) ).map(async (e) => api.deleteEntry(e.address))
); );
await api.putEntry([ await api.putEntry([
{ {
@ -162,7 +163,7 @@
...annotation.body.map((body) => { ...annotation.body.map((body) => {
return { return {
entity: annotation.id, entity: annotation.id,
attribute: "LBL", attribute: ATTR_LABEL,
value: { value: {
t: "String", t: "String",
c: body.value, c: body.value,

View File

@ -10,6 +10,7 @@
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
import { matchSorter } from "match-sorter"; import { matchSorter } from "match-sorter";
import api from "../../lib/api"; import api from "../../lib/api";
import { ATTR_LABEL } from "upend/constants";
const MAX_OPTIONS = 25; const MAX_OPTIONS = 25;
@ -150,7 +151,7 @@
} }
const validOptions = searchResult.entries const validOptions = searchResult.entries
.filter((e) => e.attribute === "LBL") .filter((e) => e.attribute === ATTR_LABEL)
.filter((e) => !exactHits.includes(e.entity)); .filter((e) => !exactHits.includes(e.entity));
const sortedOptions = matchSorter(validOptions, inputValue, { const sortedOptions = matchSorter(validOptions, inputValue, {
@ -197,7 +198,7 @@
entity: { t: "Attribute", c: option.attribute.name }, entity: { t: "Attribute", c: option.attribute.name },
}); });
// Second, label it. // Second, label it.
await api.putEntityAttribute(address, "LBL", { await api.putEntityAttribute(address, ATTR_LABEL, {
t: "String", t: "String",
c: option.labelToCreate, c: option.labelToCreate,
}); });

View File

@ -17,6 +17,7 @@
import { formatDuration } from "../../util/fragments/time"; import { formatDuration } from "../../util/fragments/time";
import { i18n } from "../../i18n"; import { i18n } from "../../i18n";
import UpLink from "../display/UpLink.svelte"; import UpLink from "../display/UpLink.svelte";
import { ATTR_ADDED, ATTR_LABEL } from "upend/constants";
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
export let columns: string | undefined = undefined; export let columns: string | undefined = undefined;
@ -87,7 +88,9 @@
const addressesString = addresses.map((addr) => `@${addr}`).join(" "); const addressesString = addresses.map((addr) => `@${addr}`).join(" ");
labelListing = query(`(matches (in ${addressesString}) "LBL" ? )`).result; labelListing = query(
`(matches (in ${addressesString}) "${ATTR_LABEL}" ? )`
).result;
} }
// Sorting // Sorting
@ -159,7 +162,7 @@
switch (attribute) { switch (attribute) {
case "FILE_SIZE": case "FILE_SIZE":
return filesize(parseInt(String(value), 10), { base: 2 }); return filesize(parseInt(String(value), 10), { base: 2 });
case "ADDED": case ATTR_ADDED:
case "LAST_VISITED": case "LAST_VISITED":
return formatRelative( return formatRelative(
fromUnixTime(parseInt(String(value), 10)), fromUnixTime(parseInt(String(value), 10)),

View File

@ -5,6 +5,7 @@
import { query } from "../../lib/entity"; import { query } from "../../lib/entity";
import UpObject from "../display/UpObject.svelte"; import UpObject from "../display/UpObject.svelte";
import UpObjectCard from "../display/UpObjectCard.svelte"; import UpObjectCard from "../display/UpObjectCard.svelte";
import { ATTR_LABEL } from "upend/constants";
export let entities: Address[]; export let entities: Address[];
export let thumbnails = true; export let thumbnails = true;
@ -49,7 +50,9 @@
const addressesString = addresses.map((addr) => `@${addr}`).join(" "); const addressesString = addresses.map((addr) => `@${addr}`).join(" ");
labelListing = query(`(matches (in ${addressesString}) "LBL" ? )`).result; labelListing = query(
`(matches (in ${addressesString}) "${ATTR_LABEL}" ? )`
).result;
} }
function sortEntities() { function sortEntities() {

View File

@ -5,7 +5,7 @@
"ADDED": "Added at", "ADDED": "Added at",
"LAST_VISITED": "Last visited at", "LAST_VISITED": "Last visited at",
"NUM_VISITED": "Times visited", "NUM_VISITED": "Times visited",
"LBL": "Label", "ATTR_LABEL": "Label",
"IS": "Type", "IS": "Type",
"TYPE": "Type ID", "TYPE": "Type ID",
"MEDIA_DURATION": "Duration" "MEDIA_DURATION": "Duration"

View File

@ -1,5 +1,6 @@
import type { EntityInfo } from "upend/types"; import type { EntityInfo } from "upend/types";
import type { UpObject } from "upend"; import type { UpObject } from "upend";
import { ATTR_IN } from "upend/constants";
export function getTypes(entity: UpObject, entityInfo: EntityInfo) { export function getTypes(entity: UpObject, entityInfo: EntityInfo) {
const mimeType = String(entity?.get("FILE_MIME")); const mimeType = String(entity?.get("FILE_MIME"));
@ -22,7 +23,7 @@ export function getTypes(entity: UpObject, entityInfo: EntityInfo) {
const web = entityInfo?.t == "Url"; const web = entityInfo?.t == "Url";
const fragment = Boolean(entity?.get("ANNOTATES")); const fragment = Boolean(entity?.get("ANNOTATES"));
const group = entity?.backlinks.some((e) => e.attribute == "OF"); const group = entity?.backlinks.some((e) => e.attribute == ATTR_IN);
return { return {
mimeType, mimeType,

View File

@ -1,6 +1,7 @@
import type { PutInput } from "upend/types"; import type { PutInput } from "upend/types";
import { query as queryFn } from "../lib/entity"; import { query as queryFn } from "../lib/entity";
import api from "../lib/api"; import api from "../lib/api";
import { ATTR_LABEL } from "upend/constants";
export function baseSearch(query: string) { export function baseSearch(query: string) {
return queryFn( return queryFn(
@ -26,7 +27,7 @@ export async function createLabelled(label: string) {
} else { } else {
// TODO - don't create invariants, create UUIDs instead, maybe with keys? // TODO - don't create invariants, create UUIDs instead, maybe with keys?
body = { body = {
attribute: "LBL", attribute: ATTR_LABEL,
value: { value: {
t: "String", t: "String",
c: label, c: label,

View File

@ -12,12 +12,13 @@
import { vaultInfo } from "../util/info"; import { vaultInfo } from "../util/info";
import { updateTitle } from "../util/title"; import { updateTitle } from "../util/title";
import { i18n } from "../i18n"; import { i18n } from "../i18n";
import { ATTR_ADDED, ATTR_LABEL } from "upend/constants";
const roots = (async () => { const roots = (async () => {
const data = await api.fetchRoots(); const data = await api.fetchRoots();
const listing = new UpListing(data); const listing = new UpListing(data);
return Object.values(listing.objects) return Object.values(listing.objects)
.filter((obj) => Boolean(obj.attr["LBL"])) .filter((obj) => Boolean(obj.attr[ATTR_LABEL]))
.map((obj) => [obj.address, obj.identify().join(" | ")]) .map((obj) => [obj.address, obj.identify().join(" | ")])
.sort(([_, i1], [__, i2]) => i1.localeCompare(i2)); .sort(([_, i1], [__, i2]) => i1.localeCompare(i2));
})(); })();
@ -34,7 +35,7 @@
.sort((a, b) => (b.value.c as number) - (a.value.c as number)) .sort((a, b) => (b.value.c as number) - (a.value.c as number))
.slice(0, 25); .slice(0, 25);
const { result: latestQuery } = query(`(matches ? "ADDED" ?)`); const { result: latestQuery } = query(`(matches ? "${ATTR_ADDED}" ?)`);
$: latest = ($latestQuery?.entries || []) $: latest = ($latestQuery?.entries || [])
.filter((e) => e.value.t == "Number") .filter((e) => e.value.t == "Number")
.sort((a, b) => (b.value.c as number) - (a.value.c as number)) .sort((a, b) => (b.value.c as number) - (a.value.c as number))

View File

@ -13,6 +13,7 @@
import api from "../lib/api"; import api from "../lib/api";
import Gallery from "../components/widgets/Gallery.svelte"; import Gallery from "../components/widgets/Gallery.svelte";
import { matchSorter } from "match-sorter"; import { matchSorter } from "match-sorter";
import { ATTR_LABEL } from "upend/constants";
const navigate = useNavigate(); const navigate = useNavigate();
export let query: string; export let query: string;
@ -35,7 +36,9 @@
exactHits = []; exactHits = [];
} }
$: objects = ($result?.entries || []).filter((e) => e.attribute === "LBL"); $: objects = ($result?.entries || []).filter(
(e) => e.attribute === ATTR_LABEL
);
$: sortedObjects = matchSorter(objects, debouncedQuery, { $: sortedObjects = matchSorter(objects, debouncedQuery, {
keys: ["value.c"], keys: ["value.c"],
}); });
@ -44,7 +47,7 @@
$: { $: {
const addressesString = objects.map((e) => `@${e.entity}`).join(" "); const addressesString = objects.map((e) => `@${e.entity}`).join(" ");
api api
.query(`(matches (in ${addressesString}) "LBL" ? )`) .query(`(matches (in ${addressesString}) "${ATTR_LABEL}" ? )`)
.then((labelListing) => { .then((labelListing) => {
exactHits = labelListing.entries exactHits = labelListing.entries
.filter( .filter(

View File

@ -12585,11 +12585,11 @@ __metadata:
"upend@file:../tools/upend_js::locator=upend-kestrel%40workspace%3A.": "upend@file:../tools/upend_js::locator=upend-kestrel%40workspace%3A.":
version: 0.0.1 version: 0.0.1
resolution: "upend@file:../tools/upend_js#../tools/upend_js::hash=88616b&locator=upend-kestrel%40workspace%3A." resolution: "upend@file:../tools/upend_js#../tools/upend_js::hash=cda57f&locator=upend-kestrel%40workspace%3A."
dependencies: dependencies:
debug: ^4.3.4 debug: ^4.3.4
lru-cache: ^7.0.0 lru-cache: ^7.0.0
checksum: c73ce133f42c9669f15b5d38b2d552722d9df56ec1daa61d31b41d8a0ec6c9064a9424c6e5589d53edeebfc75c447464f10110539b8237c19bf9597987bd6d06 checksum: 551abb5f6c2d07e1350993d27ca835fea005172ad66889e41fa5a9793ad414788ecfce2c966f526d3347d08e1731f1945c695e95aae15dd334ca17b1bc1fd195
languageName: node languageName: node
linkType: hard linkType: hard