import { useQuery } from '@tanstack/react-query';
import { ChevronDown, ChevronRight } from 'lucide-react';
import { useState } from 'react';
import { ago, fileSize } from '#/lib/format.js';
import { identityOf } from '#/lib/identity.js';
import { cn } from '#/lib/utils.js';
// A record arrives as the JSON form of DAG-CBOR, so three of its leaves are
// objects rather than scalars: a CID link, a byte string, and a blob reference.
// Each reads as one thing on the page instead of as a nested object.
/**
* @param {unknown} value
* @returns {value is {$link: string}}
*/
function isLink(value) {
return Boolean(value && typeof value === 'object' && '$link' in value);
}
/**
* @param {unknown} value
* @returns {value is {$bytes: string}}
*/
function isBytes(value) {
return Boolean(value && typeof value === 'object' && '$bytes' in value);
}
/**
* @param {any} value
* @returns {boolean}
*/
function isBlob(value) {
return value?.$type === 'blob' && isLink(value.ref);
}
const ISO_DATE = /^\d{4}-\d{2}-\d{2}T[\d:.]+Z?$/;
/**
* Whoever a DID in a record belongs to, added beside it. A record names an
* account by DID and nothing else, so without this a reader has to copy the
* string somewhere to find out who wrote what.
*/
function IdentityHint({ did }) {
const { data } = useQuery({
queryKey: ['identity', did],
queryFn: () => identityOf(did),
retry: false,
staleTime: Number.POSITIVE_INFINITY,
});
if (!data?.handle) return null;
return (
{data.handle}
{data.verified ? '' : '?'}
);
}
/** One scalar, coloured by what it is. */
function Leaf({ value, onBlob }) {
if (value === null) {
return null;
}
if (typeof value === 'boolean' || typeof value === 'number') {
return {String(value)};
}
if (isBlob(value)) {
return (
);
}
if (isLink(value)) {
return (
{value.$link}
);
}
if (isBytes(value)) {
// The decoded length rather than the base64 text: a hash, a signature and a
// MAC are all opaque here, and their size is the part worth reading.
const bytes = Math.floor((value.$bytes.length * 3) / 4);
return {bytes} bytes;
}
const text = String(value);
// The DID is the value, so it stays whole. The handle is added beside it
// rather than in place of it, because a record says one and a reader knows
// the other.
if (text.startsWith('did:')) {
return (
"{text}"