upend/webui/src/components/display/UpObject.svelte

173 lines
4.2 KiB
Svelte

<script lang="ts">
import { createEventDispatcher, getContext } from "svelte";
import { BLOB_TYPE_ADDR } from "upend/constants";
import HashBadge from "./HashBadge.svelte";
import Ellipsis from "../utils/Ellipsis.svelte";
import UpLink from "./UpLink.svelte";
import { useEntity } from "../../lib/entity";
import { Readable, readable } from "svelte/store";
import { notify, UpNotification } from "../../notifications";
import IconButton from "../utils/IconButton.svelte";
import { useParams } from "svelte-navigator";
const dispatch = createEventDispatcher();
const params = useParams();
export let address: string;
export let labels: string[] = [];
export let link = false;
export let resolve = true;
export let banner = false;
let entity = readable(undefined);
$: if (resolve) ({ entity } = useEntity(address));
// isFile
$: isFile = $entity?.get("IS") === BLOB_TYPE_ADDR;
// Identification
let inferredIds: string[] = [];
$: inferredIds = $entity?.identify() || [];
$: resolving = inferredIds.concat(labels).length == 0 && !$entity;
$: displayLabel =
Array.from(new Set(inferredIds.concat(labels))).join(" | ") || address;
$: dispatch("resolved", inferredIds);
// Navigation highlights
const index =
(getContext("browse") as { index: Readable<number> })?.index || undefined;
$: addresses = $params.addresses?.split(",") || [];
// Native open
function nativeOpen() {
notify.emit(
"notification",
new UpNotification(
`Opening ${
inferredIds[0] || address
} in a default native application...`
)
);
fetch(`/api/raw/${address}?native=1`)
.then(async (response) => {
if (!response.ok) {
throw new Error(`${response.statusText} - ${await response.text()}`);
}
if (response.headers.has("warning")) {
const warningText = response.headers
.get("warning")
.split(" ")
.slice(2)
.join(" ");
notify.emit(
"notification",
new UpNotification(warningText, "warning")
);
}
})
.catch((err) => {
notify.emit(
"notification",
new UpNotification(
`Failed to open in native application! (${err})`,
"error"
)
);
});
}
</script>
<div
class="upobject"
class:left-active={address == addresses[$index - 1]}
class:right-active={address == addresses[$index + 1]}
>
<div class="address" class:identified={Boolean(inferredIds)} class:banner>
<HashBadge {address} />
<div class="separator" />
<div class="label" class:resolving title={displayLabel}>
{#if banner && isFile}
<a href="/api/raw/{address}">
<Ellipsis value={displayLabel} />
</a>
{:else if link}
<UpLink to={{ entity: address }}>
<Ellipsis value={displayLabel} />
</UpLink>
{:else}
<Ellipsis value={displayLabel} />
{/if}
</div>
{#if banner && isFile}
<div class="icon">
<IconButton
name="window-open"
on:click={nativeOpen}
title="Open in default application..."
/>
</div>
{/if}
</div>
</div>
<style scoped lang="scss">
@use "../../styles/colors";
.upobject {
border-radius: 4px;
&.left-active {
background: linear-gradient(90deg, colors.$orange 0%, transparent 100%);
padding: 2px 0 2px 2px;
}
&.right-active {
background: linear-gradient(90deg, transparent 0%, colors.$orange 100%);
padding: 2px 2px 2px 0;
}
}
.address {
display: flex;
align-items: center;
padding: 0.1em 0.25em;
font-family: var(--monospace-font);
line-break: anywhere;
background: var(--background-lighter);
border: 0.1em solid var(--foreground-lighter);
border-radius: 0.2em;
&.banner {
border: 0.12em solid var(--foreground);
padding: 0.5em 0.25em;
}
&.identified {
font-family: var(--default-font);
font-size: 0.95em;
line-break: auto;
}
}
.label {
flex-grow: 1;
min-width: 0;
}
.separator {
width: 0.5em;
}
.icon {
margin: 0 0.25em;
}
.resolving {
opacity: 0.7;
}
</style>