feat: add attribute view
This commit is contained in:
parent
7f889be0db
commit
7579f83585
8 changed files with 121 additions and 33 deletions
|
@ -280,6 +280,7 @@ fn main() -> Result<()> {
|
|||
.service(routes::put_object)
|
||||
.service(routes::put_object_attribute)
|
||||
.service(routes::delete_object)
|
||||
.service(routes::get_address)
|
||||
.service(routes::get_all_attributes)
|
||||
.service(routes::api_refresh)
|
||||
.service(routes::list_hier)
|
||||
|
|
|
@ -619,6 +619,33 @@ pub async fn delete_object(
|
|||
// Ok(HttpResponse::Ok().finish())
|
||||
// }
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct GetAddressRequest {
|
||||
attribute: Option<String>,
|
||||
}
|
||||
|
||||
#[get("/api/address")]
|
||||
pub async fn get_address(
|
||||
web::Query(query): web::Query<GetAddressRequest>,
|
||||
) -> Result<HttpResponse, Error> {
|
||||
let address = match query {
|
||||
GetAddressRequest {
|
||||
attribute: Some(attribute),
|
||||
} => Address::Attribute(attribute),
|
||||
_ => Err(ErrorBadRequest("Specify one of: `attribute`"))?,
|
||||
};
|
||||
|
||||
Ok(HttpResponse::Ok()
|
||||
.set_header(
|
||||
http::header::CACHE_CONTROL,
|
||||
CacheControl(vec![
|
||||
CacheDirective::MaxAge(2678400),
|
||||
CacheDirective::Extension("immutable".into(), None),
|
||||
]),
|
||||
)
|
||||
.json(format!("{}", address)))
|
||||
}
|
||||
|
||||
#[get("/api/all/attributes")]
|
||||
pub async fn get_all_attributes(state: web::Data<State>) -> Result<HttpResponse, Error> {
|
||||
let connection = state.upend.connection().map_err(ErrorInternalServerError)?;
|
||||
|
|
|
@ -15,10 +15,16 @@
|
|||
import type { BrowseContext } from "../util/browse";
|
||||
import { useParams } from "svelte-navigator";
|
||||
import { GROUP_TYPE_ADDR } from "upend/constants";
|
||||
import { deleteEntry, putEntityAttribute, putEntry } from "../lib/api";
|
||||
import {
|
||||
deleteEntry,
|
||||
putEntityAttribute,
|
||||
putEntry,
|
||||
queryOnce,
|
||||
} from "../lib/api";
|
||||
import Icon from "./utils/Icon.svelte";
|
||||
import BlobViewer from "./display/BlobViewer.svelte";
|
||||
import { i18n } from "../i18n";
|
||||
import EntryList from "./widgets/EntryList.svelte";
|
||||
const dispatch = createEventDispatcher();
|
||||
const params = useParams();
|
||||
|
||||
|
@ -40,7 +46,7 @@
|
|||
addresses: addressesStore,
|
||||
} as BrowseContext);
|
||||
|
||||
$: ({ entity, error, revalidate } = useEntity(address));
|
||||
$: ({ entity, entityInfo, error, revalidate } = useEntity(address));
|
||||
|
||||
$: allTypeAddresses = ($entity?.attr["IS"] || []).map((attr) => attr.value.c);
|
||||
|
||||
|
@ -127,6 +133,15 @@
|
|||
.map((e) => [e.address, e.entity])
|
||||
.sort(); // TODO
|
||||
|
||||
let attributesUsed: UpEntry[] = [];
|
||||
$: {
|
||||
if ($entityInfo?.t === "Attribute") {
|
||||
queryOnce(`(matches ? "${$entityInfo.c}" ?)`).then(
|
||||
(result) => (attributesUsed = result.entries)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async function onChange(ev: CustomEvent<AttributeChange>) {
|
||||
const change = ev.detail;
|
||||
switch (change.type) {
|
||||
|
@ -300,6 +315,15 @@
|
|||
on:change={onChange}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
{#if $entityInfo?.t === "Attribute"}
|
||||
<EntryList
|
||||
columns="entity,value"
|
||||
columnWidths={["auto", "33%"]}
|
||||
entries={attributesUsed}
|
||||
orderByValue
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if editable}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script lang="ts">
|
||||
import { getAddress } from "../../lib/api";
|
||||
import { getContext } from "svelte";
|
||||
|
||||
import { useNavigate, useLocation } from "svelte-navigator";
|
||||
import { readable } from "svelte/store";
|
||||
import type { Address, VALUE_TYPE } from "upend/types";
|
||||
|
@ -8,34 +8,42 @@
|
|||
const location = useLocation();
|
||||
const navigate = useNavigate();
|
||||
|
||||
export let passthrough = false;
|
||||
export let to: {
|
||||
entity?: Address;
|
||||
attribute?: string;
|
||||
value?: { t: VALUE_TYPE; c: string };
|
||||
};
|
||||
export let passthrough = false;
|
||||
|
||||
const NOOP = "#";
|
||||
let targetHref = NOOP;
|
||||
if (to.entity) {
|
||||
targetHref = to.entity;
|
||||
} else if (to.attribute) {
|
||||
getAddress({ attribute: to.attribute }).then((address) => {
|
||||
targetHref = address;
|
||||
});
|
||||
}
|
||||
|
||||
const context = getContext("browse") as BrowseContext | undefined;
|
||||
const index = context ? context.index : readable(0);
|
||||
const addresses = context ? context.addresses : readable([]);
|
||||
|
||||
function onClick() {
|
||||
if (to.entity) {
|
||||
if ($location.pathname.startsWith("/browse")) {
|
||||
let newAddresses = $addresses.concat();
|
||||
if ($location.pathname.startsWith("/browse")) {
|
||||
let newAddresses = $addresses.concat();
|
||||
|
||||
const routerTo =
|
||||
"/browse/" +
|
||||
newAddresses
|
||||
.slice(0, $index + 1)
|
||||
.concat([to.entity])
|
||||
.join(",");
|
||||
const routerTo =
|
||||
"/browse/" +
|
||||
newAddresses
|
||||
.slice(0, $index + 1)
|
||||
.concat([targetHref])
|
||||
.join(",");
|
||||
|
||||
navigate(routerTo);
|
||||
return true;
|
||||
} else {
|
||||
navigate(`/browse/${to.entity}`);
|
||||
}
|
||||
navigate(routerTo);
|
||||
return true;
|
||||
} else {
|
||||
navigate(`/browse/${targetHref}`);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
@ -43,7 +51,8 @@
|
|||
<a
|
||||
class="uplink"
|
||||
class:passthrough
|
||||
href="/#/browse/{to.entity}"
|
||||
class:unresolved={targetHref === NOOP}
|
||||
href="/#/browse/{targetHref}"
|
||||
on:click|preventDefault={onClick}
|
||||
>
|
||||
<slot />
|
||||
|
@ -56,4 +65,7 @@
|
|||
:global(.uplink.passthrough) {
|
||||
display: contents;
|
||||
}
|
||||
:global(.uplink.unresolved) {
|
||||
pointer-events: none;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -4,13 +4,15 @@
|
|||
import HashBadge from "./HashBadge.svelte";
|
||||
import Ellipsis from "../utils/Ellipsis.svelte";
|
||||
import UpLink from "./UpLink.svelte";
|
||||
import { useEntity } from "../../lib/entity";
|
||||
import { useEntity, type EntityInfo } from "../../lib/entity";
|
||||
import { API_URL, nativeOpen as nativeOpenApi } from "../../lib/api";
|
||||
import { readable } from "svelte/store";
|
||||
import { readable, type Readable } from "svelte/store";
|
||||
import { notify, UpNotification } from "../../notifications";
|
||||
import IconButton from "../utils/IconButton.svelte";
|
||||
import { vaultInfo } from "../../util/info";
|
||||
import type { BrowseContext } from "../../util/browse";
|
||||
import type { UpObject } from "upend";
|
||||
import { i18n } from "../../i18n";
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
export let address: string;
|
||||
|
@ -18,8 +20,9 @@
|
|||
export let link = false;
|
||||
export let banner = false;
|
||||
|
||||
let entity = readable(undefined);
|
||||
$: if (labels === undefined) ({ entity } = useEntity(address));
|
||||
let entity: Readable<UpObject> = readable(undefined);
|
||||
let entityInfo: Readable<EntityInfo> = readable(undefined);
|
||||
$: if (labels === undefined) ({ entity, entityInfo } = useEntity(address));
|
||||
|
||||
// isFile
|
||||
$: isFile = $entity?.get("IS") === BLOB_TYPE_ADDR;
|
||||
|
@ -29,9 +32,16 @@
|
|||
$: inferredIds = $entity?.identify() || [];
|
||||
$: resolving = inferredIds.concat(labels || []).length == 0 && !$entity;
|
||||
|
||||
$: displayLabel =
|
||||
Array.from(new Set(inferredIds.concat(labels || []))).join(" | ") ||
|
||||
address;
|
||||
let displayLabel = address;
|
||||
$: {
|
||||
displayLabel = Array.from(new Set(inferredIds.concat(labels || []))).join(
|
||||
" | "
|
||||
);
|
||||
if (!displayLabel && $entityInfo?.t === "Attribute") {
|
||||
displayLabel = `${$i18n.t("Attribute")}: ${$entityInfo.c}`;
|
||||
}
|
||||
displayLabel = displayLabel || address;
|
||||
}
|
||||
|
||||
$: dispatch("resolved", inferredIds);
|
||||
|
||||
|
@ -84,7 +94,11 @@
|
|||
class:left-active={address == $addresses[$index - 1]}
|
||||
class:right-active={address == $addresses[$index + 1]}
|
||||
>
|
||||
<div class="address" class:identified={inferredIds.length || labels?.length} class:banner>
|
||||
<div
|
||||
class="address"
|
||||
class:identified={inferredIds.length || labels?.length}
|
||||
class:banner
|
||||
>
|
||||
<HashBadge {address} />
|
||||
<div class="separator" />
|
||||
<div class="label" class:resolving title={displayLabel}>
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
import { attributeLabels } from "../../util/labels";
|
||||
import { formatDuration } from "../../util/fragments/time";
|
||||
import { i18n } from "../../i18n";
|
||||
import UpLink from "../display/UpLink.svelte";
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
export let columns: string | undefined = undefined;
|
||||
|
@ -229,10 +230,12 @@
|
|||
Object.keys($attributeLabels).includes(entry.attribute)
|
||||
)}
|
||||
>
|
||||
<Ellipsis
|
||||
value={$attributeLabels[entry.attribute] || entry.attribute}
|
||||
title={entry.attribute}
|
||||
/>
|
||||
<UpLink to={{ attribute: entry.attribute }}>
|
||||
<Ellipsis
|
||||
value={$attributeLabels[entry.attribute] || entry.attribute}
|
||||
title={entry.attribute}
|
||||
/>
|
||||
</UpLink>
|
||||
</td>
|
||||
{:else if column == VALUE_COL}
|
||||
<td class="value">
|
||||
|
|
|
@ -138,3 +138,10 @@ export async function fetchStoreInfo(): Promise<{ [key: string]: StoreInfo }> {
|
|||
const response = await fetch(`${API_URL}/store`);
|
||||
return await response.json();
|
||||
}
|
||||
|
||||
export async function getAddress(input: { attribute: string }): Promise<string> {
|
||||
const response = await fetch(
|
||||
`${API_URL}/address?attribute=${input.attribute}`
|
||||
);
|
||||
return await response.json();
|
||||
}
|
||||
|
|
|
@ -4068,8 +4068,8 @@ __metadata:
|
|||
|
||||
"upend@file:../tools/upend_js::locator=upend-kestrel%40workspace%3A.":
|
||||
version: 0.0.1
|
||||
resolution: "upend@file:../tools/upend_js#../tools/upend_js::hash=4972b4&locator=upend-kestrel%40workspace%3A."
|
||||
checksum: 9449fce51433df8ce7a4d4e597fbd8811611f15ed3d8c499a6e14fd29bbba40adaf7f441e5cd4f4f3439e9fb46adf269783cef574904975569ba99c9933e711d
|
||||
resolution: "upend@file:../tools/upend_js#../tools/upend_js::hash=b32491&locator=upend-kestrel%40workspace%3A."
|
||||
checksum: 4e4e5c0a6498ad2ea8369a91e3c67c160186a96d47073469d61794c3717824198bd2de7851f3e0786833fa5f8c0eb85c65f7d7dc9c0114b9328b2a1060f645b2
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
|
Loading…
Reference in a new issue