atproto explorer pdsls.dev
atproto tool

MY BRAIN ON 3 HOURS OF SLEEP AND INDUSTRIAL MUSIC (wide layout)

juli.ee 223630d7 b5787c9f

verified
+1 -1
src/components/account.tsx
···
return (
<>
<Modal open={openManager()} onClose={() => setOpenManager(false)}>
-
<div class="dark:bg-dark-300 dark:shadow-dark-800 absolute top-12 left-[50%] w-[22rem] -translate-x-1/2 rounded-lg border-[0.5px] border-neutral-300 bg-neutral-50 p-4 shadow-md transition-opacity duration-300 sm:w-[24rem] dark:border-neutral-700 starting:opacity-0">
<div class="mb-2 flex items-center gap-1 font-semibold">
<span class="iconify lucide--user-round"></span>
<span>Manage accounts</span>
···
return (
<>
<Modal open={openManager()} onClose={() => setOpenManager(false)}>
+
<div class="dark:bg-dark-300 dark:shadow-dark-800 absolute top-16 left-[50%] w-[22rem] -translate-x-1/2 rounded-lg border-[0.5px] border-neutral-300 bg-neutral-50 p-4 shadow-md transition-opacity duration-200 dark:border-neutral-700 starting:opacity-0">
<div class="mb-2 flex items-center gap-1 font-semibold">
<span class="iconify lucide--user-round"></span>
<span>Manage accounts</span>
+1 -1
src/components/create.tsx
···
return (
<>
<Modal open={openDialog()} onClose={() => setOpenDialog(false)} closeOnClick={false}>
-
<div class="dark:bg-dark-300 dark:shadow-dark-800 absolute top-12 left-[50%] w-[22rem] -translate-x-1/2 rounded-lg border-[0.5px] border-neutral-300 bg-neutral-50 p-2 shadow-md transition-opacity duration-300 sm:w-xl sm:p-4 lg:w-[48rem] dark:border-neutral-700 starting:opacity-0">
<div class="mb-2 flex w-full justify-between">
<div class="flex items-center gap-1 font-semibold">
<span
···
return (
<>
<Modal open={openDialog()} onClose={() => setOpenDialog(false)} closeOnClick={false}>
+
<div class="dark:bg-dark-300 dark:shadow-dark-800 absolute top-16 left-[50%] w-screen -translate-x-1/2 rounded-lg border-[0.5px] border-neutral-300 bg-neutral-50 p-4 shadow-md transition-opacity duration-200 sm:w-xl lg:w-[48rem] dark:border-neutral-700 starting:opacity-0">
<div class="mb-2 flex w-full justify-between">
<div class="flex items-center gap-1 font-semibold">
<span
+1 -1
src/components/navbar.tsx
···
});
return (
-
<nav class="flex w-[22rem] flex-col text-sm wrap-anywhere sm:w-[24rem]">
<div class="relative flex items-center justify-between gap-1">
<div class="flex min-h-[1.25rem] basis-full items-center gap-2">
<Tooltip text="PDS">
···
});
return (
+
<nav class="flex w-full flex-col px-2 text-sm wrap-anywhere">
<div class="relative flex items-center justify-between gap-1">
<div class="flex min-h-[1.25rem] basis-full items-center gap-2">
<Tooltip text="PDS">
+4 -4
src/components/search.tsx
···
return (
<button
onclick={() => setShowSearch(!showSearch())}
-
class={`flex items-center gap-0.5 rounded-lg ${isTouchDevice ? "p-1 text-xl hover:bg-neutral-200 active:bg-neutral-300 dark:hover:bg-neutral-700 dark:active:bg-neutral-600" : "dark:bg-dark-200 bg-neutral-200 p-1.5 text-xs hover:bg-neutral-300/80 active:bg-neutral-300 dark:hover:bg-neutral-700 dark:active:bg-neutral-600"}`}
>
<span class="iconify lucide--search"></span>
<Show when={!isTouchDevice}>
···
return (
<form
-
class="relative w-[22rem] sm:w-[24rem]"
onsubmit={(e) => {
e.preventDefault();
processInput(searchInput.value);
···
</Show>
</div>
<Show when={search()?.length && input()}>
-
<div class="dark:bg-dark-300 dark:shadow-dark-800 absolute z-30 mt-1 flex w-full flex-col rounded-lg border-[0.5px] border-neutral-300 bg-neutral-50 p-1 shadow-md transition-opacity duration-200 dark:border-neutral-700 starting:opacity-0">
<For each={search()}>
{(actor) => (
<A
···
>
<img
src={actor.avatar?.replace("img/avatar/", "img/avatar_thumbnail/")}
-
class="size-6 rounded-full"
/>
<span>{actor.handle}</span>
</A>
···
return (
<button
onclick={() => setShowSearch(!showSearch())}
+
class={`flex items-center gap-0.5 rounded-lg ${isTouchDevice ? "p-1 text-xl hover:bg-neutral-200 active:bg-neutral-300 dark:hover:bg-neutral-700 dark:active:bg-neutral-600" : "dark:bg-dark-100 box-border h-7 border-[0.5px] border-neutral-300 bg-neutral-100 p-1.5 text-xs hover:bg-neutral-200 active:bg-neutral-300 dark:border-neutral-700 dark:hover:bg-neutral-700 dark:active:bg-neutral-600"}`}
>
<span class="iconify lucide--search"></span>
<Show when={!isTouchDevice}>
···
return (
<form
+
class="relative w-full"
onsubmit={(e) => {
e.preventDefault();
processInput(searchInput.value);
···
</Show>
</div>
<Show when={search()?.length && input()}>
+
<div class="dark:bg-dark-300 dark:shadow-dark-800 absolute z-30 mt-1 flex w-full flex-col rounded-lg border-[0.5px] border-neutral-300 bg-neutral-50 p-2 shadow-md transition-opacity duration-200 dark:border-neutral-700 starting:opacity-0">
<For each={search()}>
{(actor) => (
<A
···
>
<img
src={actor.avatar?.replace("img/avatar/", "img/avatar_thumbnail/")}
+
class="size-8 rounded-full"
/>
<span>{actor.handle}</span>
</A>
+1 -1
src/components/sticky.tsx
···
/>
<div
-
class="sticky top-2 z-10 flex flex-col items-center justify-center gap-2 rounded-lg p-3 transition-colors"
classList={{
"bg-neutral-50 dark:bg-dark-300 border-[0.5px] border-neutral-300 dark:border-neutral-700 shadow-md":
filterStuck(),
···
/>
<div
+
class="sticky top-2 z-10 flex w-full flex-col items-center justify-center gap-2 rounded-lg p-3 transition-colors"
classList={{
"bg-neutral-50 dark:bg-dark-300 border-[0.5px] border-neutral-300 dark:border-neutral-700 shadow-md":
filterStuck(),
+4 -4
src/layout.tsx
···
return (
<div
id="main"
-
class="m-4 mb-8 flex flex-col items-center text-neutral-900 dark:text-neutral-200"
>
<MetaProvider>
<Show when={location.pathname !== "/"}>
<Meta name="robots" content="noindex, nofollow" />
</Show>
</MetaProvider>
-
<header class="mb-4 flex w-[22rem] items-center justify-between sm:w-[24rem]">
<A
href="/"
style='font-feature-settings: "cv05"'
···
<DropdownMenu
icon="lucide--menu text-xl"
buttonClass="rounded-lg p-1"
-
menuClass="top-8 p-3 text-sm"
>
<NavMenu href="/jetstream" label="Jetstream" icon="lucide--radio-tower" />
<NavMenu href="/firehose" label="Firehose" icon="lucide--waves" />
···
</MenuProvider>
</div>
</header>
-
<div class="flex max-w-full min-w-[22rem] flex-col items-center gap-4 text-pretty sm:min-w-[24rem] md:max-w-[48rem]">
<Show when={showSearch() || location.pathname === "/"}>
<Search />
</Show>
···
return (
<div
id="main"
+
class="mx-auto mb-8 flex max-w-lg flex-col items-center p-4 text-neutral-900 dark:text-neutral-200"
>
<MetaProvider>
<Show when={location.pathname !== "/"}>
<Meta name="robots" content="noindex, nofollow" />
</Show>
</MetaProvider>
+
<header class="dark:shadow-dark-800 dark:bg-dark-300 mb-4 flex w-full items-center justify-between rounded-lg border-[0.5px] border-neutral-300 bg-neutral-50 p-2 shadow-xs dark:border-neutral-700">
<A
href="/"
style='font-feature-settings: "cv05"'
···
<DropdownMenu
icon="lucide--menu text-xl"
buttonClass="rounded-lg p-1"
+
menuClass="top-10 p-3"
>
<NavMenu href="/jetstream" label="Jetstream" icon="lucide--radio-tower" />
<NavMenu href="/firehose" label="Firehose" icon="lucide--waves" />
···
</MenuProvider>
</div>
</header>
+
<div class="flex w-full flex-col items-center gap-4 text-pretty">
<Show when={showSearch() || location.pathname === "/"}>
<Search />
</Show>
+120 -118
src/views/collection.tsx
···
<Show when={records.length || response()}>
<div class="-mt-2 flex w-full flex-col items-center">
<StickyOverlay>
-
<div class="flex w-[22rem] items-center gap-1 sm:w-[24rem]">
-
<Show when={agent() && agent()?.sub === did}>
-
<div class="flex items-center">
-
<Tooltip
-
text={batchDelete() ? "Cancel" : "Delete"}
-
children={
-
<button
-
onclick={() => {
-
setRecords(
-
{ from: 0, to: untrack(() => records.length) - 1 },
-
"toDelete",
-
false,
-
);
-
setLastSelected(undefined);
-
setBatchDelete(!batchDelete());
-
}}
-
class="-ml-1 flex items-center rounded-lg p-1 hover:bg-neutral-200 active:bg-neutral-300 dark:hover:bg-neutral-700 dark:active:bg-neutral-600"
-
>
-
<span
-
class={`iconify text-lg ${batchDelete() ? "lucide--circle-x" : "lucide--trash-2"} `}
-
></span>
-
</button>
-
}
-
/>
-
<Show when={batchDelete()}>
-
<Tooltip
-
text="Select all"
-
children={
-
<button
-
onclick={() => selectAll()}
-
class="flex items-center rounded-lg p-1 hover:bg-neutral-200 active:bg-neutral-300 dark:hover:bg-neutral-700 dark:active:bg-neutral-600"
-
>
-
<span class="iconify lucide--copy-check text-lg"></span>
-
</button>
-
}
-
/>
<Tooltip
-
text="Recreate"
children={
<button
onclick={() => {
-
setRecreate(true);
-
setOpenDelete(true);
}}
-
class="flex items-center rounded-lg p-1 hover:bg-neutral-200 active:bg-neutral-300 dark:hover:bg-neutral-700 dark:active:bg-neutral-600"
>
-
<span class="iconify lucide--recycle text-lg text-green-500 dark:text-green-400"></span>
</button>
}
/>
-
<Tooltip
-
text="Delete"
-
children={
-
<button
-
onclick={() => {
-
setRecreate(false);
-
setOpenDelete(true);
-
}}
-
class="flex items-center rounded-lg p-1 hover:bg-neutral-200 active:bg-neutral-300 dark:hover:bg-neutral-700 dark:active:bg-neutral-600"
>
-
<span class="iconify lucide--trash-2 text-lg text-red-500 dark:text-red-400"></span>
-
</button>
-
}
-
/>
-
</Show>
-
</div>
-
<Modal open={openDelete()} onClose={() => setOpenDelete(false)}>
-
<div class="dark:bg-dark-300 dark:shadow-dark-800 absolute top-70 left-[50%] -translate-x-1/2 rounded-lg border-[0.5px] border-neutral-300 bg-neutral-50 p-4 shadow-md transition-opacity duration-300 dark:border-neutral-700 starting:opacity-0">
-
<h2 class="mb-2 font-semibold">
-
{recreate() ? "Recreate" : "Delete"} {records.filter((r) => r.toDelete).length}{" "}
-
records?
-
</h2>
-
<div class="flex justify-end gap-2">
-
<Button onClick={() => setOpenDelete(false)}>Cancel</Button>
-
<Button
-
onClick={deleteRecords}
-
class={`dark:shadow-dark-800 rounded-lg px-2 py-1.5 text-xs font-semibold text-neutral-200 shadow-xs select-none ${recreate() ? "bg-green-500 hover:bg-green-400 dark:bg-green-600 dark:hover:bg-green-500" : "bg-red-500 hover:bg-red-400 active:bg-red-400"}`}
-
>
-
{recreate() ? "Recreate" : "Delete"}
-
</Button>
</div>
</div>
-
</Modal>
</Show>
-
<Tooltip text="Jetstream">
-
<A
-
href={`/jetstream?collections=${params.collection}&dids=${params.repo}`}
-
class="flex items-center rounded-lg p-1 hover:bg-neutral-200 active:bg-neutral-300 dark:hover:bg-neutral-700 dark:active:bg-neutral-600"
-
>
-
<span class="iconify lucide--radio-tower text-lg"></span>
-
</A>
-
</Tooltip>
-
<TextInput
-
placeholder="Filter by substring"
-
onInput={(e) => setFilter(e.currentTarget.value)}
-
class="grow"
-
/>
</div>
-
<Show when={records.length > 1}>
-
<div class="flex w-[22rem] items-center justify-between gap-x-2 sm:w-[24rem]">
-
<Button
-
onClick={() => {
-
setReverse(!reverse());
-
setRecords([]);
-
setCursor(undefined);
-
refetch();
-
}}
-
>
-
<span
-
class={`iconify ${reverse() ? "lucide--rotate-ccw" : "lucide--rotate-cw"} text-sm`}
-
></span>
-
Reverse
-
</Button>
-
<div>
-
<Show when={batchDelete()}>
-
<span>{records.filter((rec) => rec.toDelete).length}</span>
-
<span>/</span>
-
</Show>
-
<span>{records.length} records</span>
-
</div>
-
<div class="flex w-[5rem] items-center justify-end">
-
<Show when={cursor()}>
-
<Show when={!response.loading}>
-
<Button onClick={() => refetch()}>Load More</Button>
-
</Show>
-
<Show when={response.loading}>
-
<div class="iconify lucide--loader-circle w-[5rem] animate-spin text-xl" />
-
</Show>
-
</Show>
-
</div>
-
</div>
-
</Show>
</StickyOverlay>
<div class="flex max-w-full flex-col font-mono">
<For
···
<Show when={records.length || response()}>
<div class="-mt-2 flex w-full flex-col items-center">
<StickyOverlay>
+
<div class="flex w-full flex-col gap-2">
+
<div class="flex items-center gap-1">
+
<Show when={agent() && agent()?.sub === did}>
+
<div class="flex items-center">
<Tooltip
+
text={batchDelete() ? "Cancel" : "Delete"}
children={
<button
onclick={() => {
+
setRecords(
+
{ from: 0, to: untrack(() => records.length) - 1 },
+
"toDelete",
+
false,
+
);
+
setLastSelected(undefined);
+
setBatchDelete(!batchDelete());
}}
+
class="-ml-1 flex items-center rounded-lg p-1 hover:bg-neutral-200 active:bg-neutral-300 dark:hover:bg-neutral-700 dark:active:bg-neutral-600"
>
+
<span
+
class={`iconify text-lg ${batchDelete() ? "lucide--circle-x" : "lucide--trash-2"} `}
+
></span>
</button>
}
/>
+
<Show when={batchDelete()}>
+
<Tooltip
+
text="Select all"
+
children={
+
<button
+
onclick={() => selectAll()}
+
class="flex items-center rounded-lg p-1 hover:bg-neutral-200 active:bg-neutral-300 dark:hover:bg-neutral-700 dark:active:bg-neutral-600"
+
>
+
<span class="iconify lucide--copy-check text-lg"></span>
+
</button>
+
}
+
/>
+
<Tooltip
+
text="Recreate"
+
children={
+
<button
+
onclick={() => {
+
setRecreate(true);
+
setOpenDelete(true);
+
}}
+
class="flex items-center rounded-lg p-1 hover:bg-neutral-200 active:bg-neutral-300 dark:hover:bg-neutral-700 dark:active:bg-neutral-600"
+
>
+
<span class="iconify lucide--recycle text-lg text-green-500 dark:text-green-400"></span>
+
</button>
+
}
+
/>
+
<Tooltip
+
text="Delete"
+
children={
+
<button
+
onclick={() => {
+
setRecreate(false);
+
setOpenDelete(true);
+
}}
+
class="flex items-center rounded-lg p-1 hover:bg-neutral-200 active:bg-neutral-300 dark:hover:bg-neutral-700 dark:active:bg-neutral-600"
+
>
+
<span class="iconify lucide--trash-2 text-lg text-red-500 dark:text-red-400"></span>
+
</button>
+
}
+
/>
+
</Show>
+
</div>
+
<Modal open={openDelete()} onClose={() => setOpenDelete(false)}>
+
<div class="dark:bg-dark-300 dark:shadow-dark-800 absolute top-70 left-[50%] -translate-x-1/2 rounded-lg border-[0.5px] border-neutral-300 bg-neutral-50 p-4 shadow-md transition-opacity duration-200 dark:border-neutral-700 starting:opacity-0">
+
<h2 class="mb-2 font-semibold">
+
{recreate() ? "Recreate" : "Delete"}{" "}
+
{records.filter((r) => r.toDelete).length} records?
+
</h2>
+
<div class="flex justify-end gap-2">
+
<Button onClick={() => setOpenDelete(false)}>Cancel</Button>
+
<Button
+
onClick={deleteRecords}
+
class={`dark:shadow-dark-800 rounded-lg px-2 py-1.5 text-xs font-semibold text-neutral-200 shadow-xs select-none ${recreate() ? "bg-green-500 hover:bg-green-400 dark:bg-green-600 dark:hover:bg-green-500" : "bg-red-500 hover:bg-red-400 active:bg-red-400"}`}
>
+
{recreate() ? "Recreate" : "Delete"}
+
</Button>
+
</div>
</div>
+
</Modal>
+
</Show>
+
<Tooltip text="Jetstream">
+
<A
+
href={`/jetstream?collections=${params.collection}&dids=${params.repo}`}
+
class="flex items-center rounded-lg p-1 hover:bg-neutral-200 active:bg-neutral-300 dark:hover:bg-neutral-700 dark:active:bg-neutral-600"
+
>
+
<span class="iconify lucide--radio-tower text-lg"></span>
+
</A>
+
</Tooltip>
+
<TextInput
+
placeholder="Filter by substring"
+
onInput={(e) => setFilter(e.currentTarget.value)}
+
class="grow"
+
/>
+
</div>
+
<Show when={records.length > 1}>
+
<div class="flex items-center justify-between gap-x-2">
+
<Button
+
onClick={() => {
+
setReverse(!reverse());
+
setRecords([]);
+
setCursor(undefined);
+
refetch();
+
}}
+
>
+
<span
+
class={`iconify ${reverse() ? "lucide--rotate-ccw" : "lucide--rotate-cw"} text-sm`}
+
></span>
+
Reverse
+
</Button>
+
<div>
+
<Show when={batchDelete()}>
+
<span>{records.filter((rec) => rec.toDelete).length}</span>
+
<span>/</span>
+
</Show>
+
<span>{records.length} records</span>
</div>
+
<div class="flex w-[5rem] items-center justify-end">
+
<Show when={cursor()}>
+
<Show when={!response.loading}>
+
<Button onClick={() => refetch()}>Load More</Button>
+
</Show>
+
<Show when={response.loading}>
+
<div class="iconify lucide--loader-circle w-[5rem] animate-spin text-xl" />
+
</Show>
+
</Show>
+
</div>
+
</div>
</Show>
</div>
</StickyOverlay>
<div class="flex max-w-full flex-col font-mono">
<For
+2 -10
src/views/home.tsx
···
const Home = () => {
return (
-
<div class="flex w-[22rem] flex-col gap-4 break-words sm:w-[24rem]">
<div>
<div>
<span class="text-lg font-semibold">AT Protocol Explorer</span>
···
</div>
<div class="flex items-center gap-1">
<div class="iconify lucide--tag" />
-
<span>
-
<A
-
href="/at://did:plc:ar7c4by46qjdydhdevvrndac/labels"
-
class="text-blue-400 hover:underline active:underline"
-
>
-
Query labels
-
</A>{" "}
-
from moderation services.
-
</span>
</div>
</div>
<div class="flex gap-2 text-xl">
···
const Home = () => {
return (
+
<div class="flex w-full flex-col gap-4 break-words">
<div>
<div>
<span class="text-lg font-semibold">AT Protocol Explorer</span>
···
</div>
<div class="flex items-center gap-1">
<div class="iconify lucide--tag" />
+
<span>Query labels from moderation services.</span>
</div>
</div>
<div class="flex gap-2 text-xl">
+9 -8
src/views/labels.tsx
···
return (
<div class="flex w-full flex-col items-center">
<form
-
class="flex w-[22rem] flex-col items-center gap-y-1 sm:w-[24rem]"
onsubmit={(e) => {
e.preventDefault();
initQuery();
···
<StickyOverlay>
<TextInput
placeholder="Filter by label"
onInput={(e) => setFilter(e.currentTarget.value)}
-
class="w-[22rem] sm:w-[24rem]"
/>
<div class="flex items-center gap-x-2">
<Show when={labelCount() && labels().length}>
···
</div>
</StickyOverlay>
<Show when={labels().length}>
-
<div class="flex max-w-full min-w-[22rem] flex-col gap-2 divide-y-[0.5px] divide-neutral-400 text-sm wrap-anywhere whitespace-pre-wrap sm:min-w-[24rem] dark:divide-neutral-600">
<For each={filterLabels()}>
{(label) => (
<div class="flex items-center justify-between gap-2 pb-2">
<div class="flex flex-col">
<div class="flex items-center gap-x-2">
-
<div class="min-w-[5rem] font-semibold">URI</div>
<A
href={`/at://${label.uri.replace("at://", "")}`}
class="text-blue-400 hover:underline active:underline"
···
</div>
<Show when={label.cid}>
<div class="flex items-center gap-x-2">
-
<div class="min-w-[5rem] font-semibold">CID</div>
{label.cid}
</div>
</Show>
<div class="flex items-center gap-x-2">
-
<div class="min-w-[5rem] font-semibold">Label</div>
{label.val}
</div>
<div class="flex items-center gap-x-2">
-
<div class="min-w-[5rem] font-semibold">Created</div>
{localDateFromTimestamp(new Date(label.cts).getTime())}
</div>
<Show when={label.exp}>
{(exp) => (
<div class="flex items-center gap-x-2">
-
<div class="min-w-[5rem] font-semibold">Expires</div>
{localDateFromTimestamp(new Date(exp()).getTime())}
</div>
)}
···
return (
<div class="flex w-full flex-col items-center">
<form
+
class="flex w-full flex-col items-center gap-y-1"
onsubmit={(e) => {
e.preventDefault();
initQuery();
···
<StickyOverlay>
<TextInput
placeholder="Filter by label"
+
name="filter"
onInput={(e) => setFilter(e.currentTarget.value)}
+
class="w-full"
/>
<div class="flex items-center gap-x-2">
<Show when={labelCount() && labels().length}>
···
</div>
</StickyOverlay>
<Show when={labels().length}>
+
<div class="flex flex-col gap-2 divide-y-[0.5px] divide-neutral-400 text-sm wrap-anywhere whitespace-pre-wrap dark:divide-neutral-600">
<For each={filterLabels()}>
{(label) => (
<div class="flex items-center justify-between gap-2 pb-2">
<div class="flex flex-col">
<div class="flex items-center gap-x-2">
+
<div class="min-w-[4rem] font-semibold">URI</div>
<A
href={`/at://${label.uri.replace("at://", "")}`}
class="text-blue-400 hover:underline active:underline"
···
</div>
<Show when={label.cid}>
<div class="flex items-center gap-x-2">
+
<div class="min-w-[4rem] font-semibold">CID</div>
{label.cid}
</div>
</Show>
<div class="flex items-center gap-x-2">
+
<div class="min-w-[4rem] font-semibold">Label</div>
{label.val}
</div>
<div class="flex items-center gap-x-2">
+
<div class="min-w-[4rem] font-semibold">Created</div>
{localDateFromTimestamp(new Date(label.cts).getTime())}
</div>
<Show when={label.exp}>
{(exp) => (
<div class="flex items-center gap-x-2">
+
<div class="min-w-[4rem] font-semibold">Expires</div>
{localDateFromTimestamp(new Date(exp()).getTime())}
</div>
)}
+63 -27
src/views/pds.tsx
···
import { A, useParams } from "@solidjs/router";
import { createResource, createSignal, For, Show } from "solid-js";
import { Button } from "../components/button";
import { setPDS } from "../components/navbar";
import Tooltip from "../components/tooltip";
import { localDateFromTimestamp } from "../utils/date";
···
const [response, { refetch }] = createResource(fetchRepos);
const [repos, setRepos] = createSignal<ComAtprotoSyncListRepos.Repo[]>();
return (
<Show when={repos() || response()}>
-
<div class="flex w-[22rem] flex-col sm:w-[24rem]">
<Show when={version()}>
{(version) => (
<div class="flex items-baseline gap-x-1">
···
)}
</Show>
<p class="w-full font-semibold">{repos()?.length} Repositories</p>
-
<For each={repos()}>
-
{(repo) => (
-
<A
-
href={`/at://${repo.did}`}
-
classList={{
-
"rounded items-center text-sm gap-1 flex justify-between font-mono relative hover:bg-neutral-200 dark:hover:bg-neutral-700 active:bg-neutral-300 dark:active:bg-neutral-600": true,
-
"text-blue-400": repo.active,
-
"text-neutral-400 dark:text-neutral-500": !repo.active,
-
}}
-
>
-
<Show when={!repo.active}>
-
<div class="absolute -left-4">
-
<Tooltip text={repo.status ?? "Unknown status"}>
-
<span class="iconify lucide--unplug"></span>
-
</Tooltip>
-
</div>
-
</Show>
-
<span class="text-sm">{repo.did}</span>
-
<Show when={TID.validate(repo.rev)}>
-
<span class="text-xs text-neutral-500 dark:text-neutral-400">
-
{localDateFromTimestamp(TID.parse(repo.rev).timestamp / 1000).split(" ")[0]}
-
</span>
-
</Show>
-
</A>
-
)}
-
</For>
</div>
<Show when={cursor()}>
<div class="dark:bg-dark-500 fixed bottom-0 z-5 flex w-screen justify-center bg-neutral-100 py-3">
···
import { A, useParams } from "@solidjs/router";
import { createResource, createSignal, For, Show } from "solid-js";
import { Button } from "../components/button";
+
import { Modal } from "../components/modal";
import { setPDS } from "../components/navbar";
import Tooltip from "../components/tooltip";
import { localDateFromTimestamp } from "../utils/date";
···
const [response, { refetch }] = createResource(fetchRepos);
const [repos, setRepos] = createSignal<ComAtprotoSyncListRepos.Repo[]>();
+
const RepoCard = (repo: ComAtprotoSyncListRepos.Repo) => {
+
const [openInfo, setOpenInfo] = createSignal(false);
+
+
return (
+
<div class="flex items-center gap-1">
+
<A
+
href={`/at://${repo.did}`}
+
class="grow truncate rounded py-0.5 font-mono text-blue-400 hover:bg-neutral-200 active:bg-neutral-300 dark:hover:bg-neutral-700 dark:active:bg-neutral-600"
+
>
+
{repo.did}
+
</A>
+
<Show when={!repo.active}>
+
<Tooltip text={repo.status ?? "Unknown status"}>
+
<span class="iconify lucide--unplug"></span>
+
</Tooltip>
+
</Show>
+
<button
+
onclick={() => setOpenInfo(true)}
+
class="flex items-center rounded-lg p-1.5 hover:bg-neutral-200 active:bg-neutral-300 dark:hover:bg-neutral-700 dark:active:bg-neutral-600"
+
>
+
<span class="iconify lucide--info"></span>
+
</button>
+
<Modal open={openInfo()} onClose={() => setOpenInfo(false)}>
+
<div class="dark:bg-dark-300 dark:shadow-dark-800 absolute top-70 left-[50%] w-max max-w-full -translate-x-1/2 rounded-lg border-[0.5px] border-neutral-300 bg-neutral-50 p-3 break-words shadow-md transition-opacity duration-200 sm:max-w-[32rem] dark:border-neutral-700 starting:opacity-0">
+
<div class="mb-1 flex justify-between gap-2">
+
<div class="flex items-center gap-1">
+
<span class="iconify lucide--info"></span>
+
<span class="font-semibold">{repo.did}</span>
+
</div>
+
<button
+
onclick={() => setOpenInfo(false)}
+
class="flex items-center rounded-lg p-1 hover:bg-neutral-200 active:bg-neutral-300 dark:hover:bg-neutral-700 dark:active:bg-neutral-600"
+
>
+
<span class="iconify lucide--x"></span>
+
</button>
+
</div>
+
<div class="flex flex-col text-sm">
+
<span>
+
Head: <span class="text-xs">{repo.head}</span>
+
</span>
+
<Show when={TID.validate(repo.rev)}>
+
<span>
+
Rev: {repo.rev} ({localDateFromTimestamp(TID.parse(repo.rev).timestamp / 1000)})
+
</span>
+
</Show>
+
<Show when={repo.active !== undefined}>
+
<span>Active: {repo.active ? "true" : "false"}</span>
+
</Show>
+
<Show when={repo.status}>
+
<span>Status: {repo.status}</span>
+
</Show>
+
</div>
+
</div>
+
</Modal>
+
</div>
+
);
+
};
+
return (
<Show when={repos() || response()}>
+
<div class="flex w-full flex-col">
<Show when={version()}>
{(version) => (
<div class="flex items-baseline gap-x-1">
···
)}
</Show>
<p class="w-full font-semibold">{repos()?.length} Repositories</p>
+
<div class="flex flex-col divide-y-[0.5px] divide-neutral-300 dark:divide-neutral-700">
+
<For each={repos()}>{(repo) => <RepoCard {...repo} />}</For>
+
</div>
</div>
<Show when={cursor()}>
<div class="dark:bg-dark-500 fixed bottom-0 z-5 flex w-screen justify-center bg-neutral-100 py-3">
+3 -3
src/views/record.tsx
···
return (
<Show when={record()} keyed>
<div class="flex w-full flex-col items-center">
-
<div class="dark:shadow-dark-800 dark:bg-dark-300 mb-3 flex w-[22rem] justify-between rounded-lg border-[0.5px] border-neutral-300 bg-neutral-50 px-2 py-1.5 shadow-xs sm:w-[24rem] dark:border-neutral-700">
<div class="flex gap-3 text-sm">
<A
classList={{
···
</button>
</Tooltip>
<Modal open={openDelete()} onClose={() => setOpenDelete(false)}>
-
<div class="dark:bg-dark-300 dark:shadow-dark-800 absolute top-70 left-[50%] -translate-x-1/2 rounded-lg border-[0.5px] border-neutral-300 bg-neutral-50 p-4 shadow-md transition-opacity duration-300 dark:border-neutral-700 starting:opacity-0">
<h2 class="mb-2 font-semibold">Delete this record?</h2>
<div class="flex justify-end gap-2">
<Button onClick={() => setOpenDelete(false)}>Cancel</Button>
···
<Show when={validRecord() === false}>
<div class="mb-2 break-words text-red-500 dark:text-red-400">{notice()}</div>
</Show>
-
<div class="w-[22rem] font-mono text-xs wrap-anywhere whitespace-pre-wrap sm:w-full sm:text-sm">
<JSONValue data={record()?.value as any} repo={record()!.uri.split("/")[2]} />
</div>
</Show>
···
return (
<Show when={record()} keyed>
<div class="flex w-full flex-col items-center">
+
<div class="dark:shadow-dark-800 dark:bg-dark-300 mb-3 flex w-full justify-between rounded-lg border-[0.5px] border-neutral-300 bg-neutral-50 px-2 py-1.5 shadow-xs dark:border-neutral-700">
<div class="flex gap-3 text-sm">
<A
classList={{
···
</button>
</Tooltip>
<Modal open={openDelete()} onClose={() => setOpenDelete(false)}>
+
<div class="dark:bg-dark-300 dark:shadow-dark-800 absolute top-70 left-[50%] -translate-x-1/2 rounded-lg border-[0.5px] border-neutral-300 bg-neutral-50 p-4 shadow-md transition-opacity duration-200 dark:border-neutral-700 starting:opacity-0">
<h2 class="mb-2 font-semibold">Delete this record?</h2>
<div class="flex justify-end gap-2">
<Button onClick={() => setOpenDelete(false)}>Cancel</Button>
···
<Show when={validRecord() === false}>
<div class="mb-2 break-words text-red-500 dark:text-red-400">{notice()}</div>
</Show>
+
<div class="w-max max-w-screen px-4 font-mono text-xs wrap-anywhere whitespace-pre-wrap sm:text-sm md:max-w-[48rem]">
<JSONValue data={record()?.value as any} repo={record()!.uri.split("/")[2]} />
</div>
</Show>
+15 -13
src/views/repo.tsx
···
const did = params.repo;
const RepoTab = (props: { tab: Tab; label: string; icon: string }) => (
-
<A
-
classList={{
-
"flex items-center border-b-2 gap-1": true,
-
"border-transparent hover:border-neutral-400 dark:hover:border-neutral-600":
-
(location.hash !== `#${props.tab}` && !!location.hash) ||
-
(!location.hash && props.tab !== "collections"),
-
}}
-
href={`/at://${params.repo}#${props.tab}`}
-
>
-
<div class={"iconify " + props.icon} />
-
{props.label}
</A>
);
···
return (
<Show when={repo()}>
-
<div class="flex w-[22rem] flex-col gap-2 break-words sm:w-[24rem]">
<Show when={error()}>
<div class="rounded-lg bg-red-100 p-2 text-sm text-red-700 dark:bg-red-200 dark:text-red-600">
{error()}
···
</A>
</Tooltip>
<TextInput
placeholder="Filter collections"
onInput={(e) => setFilter(e.currentTarget.value)}
class="grow"
···
</ErrorBoundary>
</span>
</span>
-
<span class="truncate text-xs">{key()}</span>
</li>
)}
</Show>
···
const did = params.repo;
const RepoTab = (props: { tab: Tab; label: string; icon: string }) => (
+
<A class="group flex flex-1 justify-center" href={`/at://${params.repo}#${props.tab}`}>
+
<span
+
classList={{
+
"flex gap-1 items-center border-b-2": true,
+
"border-transparent group-hover:border-neutral-400 dark:group-hover:border-neutral-600":
+
(location.hash !== `#${props.tab}` && !!location.hash) ||
+
(!location.hash && props.tab !== "collections"),
+
}}
+
>
+
<span class={"iconify " + props.icon}></span>
+
{props.label}
+
</span>
</A>
);
···
return (
<Show when={repo()}>
+
<div class="flex w-full flex-col gap-2 break-words">
<Show when={error()}>
<div class="rounded-lg bg-red-100 p-2 text-sm text-red-700 dark:bg-red-200 dark:text-red-600">
{error()}
···
</A>
</Tooltip>
<TextInput
+
name="filter"
placeholder="Filter collections"
onInput={(e) => setFilter(e.currentTarget.value)}
class="grow"
···
</ErrorBoundary>
</span>
</span>
+
<span class="truncate">{key()}</span>
</li>
)}
</Show>
+1 -1
src/views/settings.tsx
···
const Settings = () => {
return (
-
<div class="flex w-[22rem] flex-col gap-3 sm:w-[24rem]">
<div class="flex items-center gap-1 font-semibold">
<span>Settings</span>
</div>
···
const Settings = () => {
return (
+
<div class="flex w-full flex-col gap-3">
<div class="flex items-center gap-1 font-semibold">
<span>Settings</span>
</div>
+3 -3
src/views/stream.tsx
···
onCleanup(() => socket?.close());
return (
-
<div class="flex flex-col items-center">
<div class="flex gap-2 text-sm">
<A
class="flex items-center gap-1 border-b-2 p-1"
···
</A>
</div>
<StickyOverlay>
-
<form ref={formRef} class="flex w-[22rem] flex-col gap-1 text-sm sm:w-[24rem]">
<Show when={!connected()}>
<label class="flex items-center justify-end gap-x-1">
<span class="min-w-[5rem]">Instance</span>
···
<Show when={notice().length}>
<div class="text-red-500 dark:text-red-400">{notice()}</div>
</Show>
-
<div class="flex w-full flex-col gap-2 divide-y-[0.5px] divide-neutral-500 px-4 font-mono text-sm wrap-anywhere whitespace-pre-wrap md:w-[48rem]">
<For each={records().toReversed()}>
{(rec) => (
<div class="pb-2">
···
onCleanup(() => socket?.close());
return (
+
<div class="flex w-full flex-col items-center">
<div class="flex gap-2 text-sm">
<A
class="flex items-center gap-1 border-b-2 p-1"
···
</A>
</div>
<StickyOverlay>
+
<form ref={formRef} class="flex w-full flex-col gap-1 text-sm">
<Show when={!connected()}>
<label class="flex items-center justify-end gap-x-1">
<span class="min-w-[5rem]">Instance</span>
···
<Show when={notice().length}>
<div class="text-red-500 dark:text-red-400">{notice()}</div>
</Show>
+
<div class="flex w-full flex-col gap-2 divide-y-[0.5px] divide-neutral-500 font-mono text-sm wrap-anywhere whitespace-pre-wrap md:w-[48rem]">
<For each={records().toReversed()}>
{(rec) => (
<div class="pb-2">