From bf079a55b92d6c01b5955efda244d34e8a822220 Mon Sep 17 00:00:00 2001 From: Juliet Date: Sat, 14 Mar 2026 11:37:47 +0100 Subject: [PATCH] PDS list virtual scrolling --- package.json | 1 + pnpm-lock.yaml | 18 ++++ src/views/pds.tsx | 206 ++++++++++++++++++++++++++++++---------------- 3 files changed, 156 insertions(+), 69 deletions(-) diff --git a/package.json b/package.json index 1e8d764..158db4a 100644 --- a/package.json +++ b/package.json @@ -54,6 +54,7 @@ "@solidjs/router": "^0.15.4", "@takumi-rs/image-response": "^0.72.0", "@takumi-rs/wasm": "^0.72.0", + "@tanstack/solid-virtual": "^3.13.22", "codemirror": "^6.0.2", "solid-js": "^1.9.11" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6294439..8c5070b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -98,6 +98,9 @@ importers: '@takumi-rs/wasm': specifier: ^0.72.0 version: 0.72.0 + '@tanstack/solid-virtual': + specifier: ^3.13.22 + version: 3.13.22(solid-js@1.9.11) codemirror: specifier: ^6.0.2 version: 6.0.2 @@ -1017,6 +1020,14 @@ packages: '@takumi-rs/wasm@0.72.0': resolution: {integrity: sha512-5IoekEovpjzpoHd5LM4hRLRgRwW8RdFdF9TR2kmysB1rJF34z0ynIC/Fr0yhraxvFIP0fCzdinz14YzyTq6nuQ==} + '@tanstack/solid-virtual@3.13.22': + resolution: {integrity: sha512-HoFRbuNfMz4IckIJAQ9RY8uJfT28AjL040cmwBZW8c1dD+VJVKRL0leZo90bU7BK4Mnhq6vt8801BZjyrY8Ffg==} + peerDependencies: + solid-js: ^1.3.0 + + '@tanstack/virtual-core@3.13.22': + resolution: {integrity: sha512-isuUGKsc5TAPDoHSbWTbl1SCil54zOS2MiWz/9GCWHPUQOvNTQx8qJEWC7UWR0lShhbK0Lmkcf0SZYxvch7G3g==} + '@types/babel__core@7.20.5': resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} @@ -2440,6 +2451,13 @@ snapshots: '@takumi-rs/wasm@0.72.0': {} + '@tanstack/solid-virtual@3.13.22(solid-js@1.9.11)': + dependencies: + '@tanstack/virtual-core': 3.13.22 + solid-js: 1.9.11 + + '@tanstack/virtual-core@3.13.22': {} + '@types/babel__core@7.20.5': dependencies: '@babel/parser': 7.29.0 diff --git a/src/views/pds.tsx b/src/views/pds.tsx index 6ad0a2a..5748bd6 100644 --- a/src/views/pds.tsx +++ b/src/views/pds.tsx @@ -3,6 +3,7 @@ import { Client, simpleFetchHandler } from "@atcute/client"; import { InferXRPCBodyOutput } from "@atcute/lexicons"; import * as TID from "@atcute/tid"; import { A, useLocation, useParams } from "@solidjs/router"; +import { createWindowVirtualizer } from "@tanstack/solid-virtual"; import { createResource, createSignal, For, Show } from "solid-js"; import { Button } from "../components/button"; import DidHoverCard from "../components/hover-card/did"; @@ -12,6 +13,93 @@ import { localDateFromTimestamp } from "../utils/date"; const LIMIT = 1000; +const RepoCard = (props: { + repo: ComAtprotoSyncListRepos.Repo; + expanded: boolean; + onToggle: () => void; +}) => { + const expanded = () => props.expanded; + + return ( +
+
+ + + + + + +
+
+
+
+ + {props.repo.head} + + +
+ {props.repo.rev} + • + {localDateFromTimestamp(TID.parse(props.repo.rev).timestamp / 1000)} +
+
+
+
+
+
+ ); +}; + export const PdsView = () => { const params = useParams(); const location = useLocation(); @@ -36,9 +124,10 @@ export const PdsView = () => { else setServerInfos(res.data); }; + getVersion(); + describeServer(); + const fetchRepos = async () => { - getVersion(); - describeServer(); const res = await rpc.get("com.atproto.sync.listRepos", { params: { limit: LIMIT, cursor: cursor() }, }); @@ -51,71 +140,19 @@ export const PdsView = () => { const [response, { refetch }] = createResource(fetchRepos); const [repos, setRepos] = createSignal(); - const RepoCard = (repo: ComAtprotoSyncListRepos.Repo) => { - const [expanded, setExpanded] = createSignal(false); - const [hovering, setHovering] = createSignal(false); + const [expandedIndex, setExpandedIndex] = createSignal(null); - return ( -
-
canHover && setHovering(true)} - onMouseLeave={() => canHover && setHovering(false)} - > - - - - - - -
- -
- - {repo.head} - - -
- {repo.rev} - • - {localDateFromTimestamp(TID.parse(repo.rev).timestamp / 1000)} -
-
-
-
-
- ); - }; + let containerRef: HTMLDivElement | undefined; + const virtualizer = createWindowVirtualizer({ + get count() { + return repos()?.length ?? 0; + }, + estimateSize: () => 32, + overscan: 10, + get scrollMargin() { + return containerRef?.offsetTop ?? 0; + }, + }); const Tab = (props: { tab: "repos" | "info" | "firehose"; label: string }) => ( { -
- {(repo) => } +
+ + {(virtualItem) => ( +
virtualizer.measureElement(el)} + classList={{ "z-10": expandedIndex() === virtualItem.index }} + style={{ + position: "absolute", + top: `${virtualItem.start - virtualizer.options.scrollMargin}px`, + left: 0, + width: "100%", + }} + > + + setExpandedIndex( + expandedIndex() === virtualItem.index ? null : virtualItem.index, + ) + } + /> +
+ )} +
@@ -256,7 +321,10 @@ export const PdsView = () => {