feat: add attribute view

feat/type-attributes
Tomáš Mládek 2023-01-07 11:00:55 +01:00
parent 7f889be0db
commit 7579f83585
8 changed files with 121 additions and 33 deletions

View File

@ -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)

View File

@ -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)?;

View File

@ -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}

View File

@ -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>

View File

@ -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}>

View File

@ -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">

View File

@ -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();
}

View File

@ -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