Skip to content

Commit 1deef25

Browse files
authored
Without virtualisation (#266)
Withheld working on the virtualisation element to get a viable working product out. Throttled getListSize to prevent server overload. Updated with css changes from #264. List count appears to the right of of the list name. resolves #255. ![Screenshot 2025-01-14 at 17 38 38](https://github.com/user-attachments/assets/bf70d00d-5c2a-4861-92b6-110a9ca71258)
2 parents bd2db09 + 48505f9 commit 1deef25

File tree

10 files changed

+223
-98
lines changed

10 files changed

+223
-98
lines changed

package-lock.json

+44-8
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,13 @@
2121
"@mui/icons-material": "^5.14.16",
2222
"@mui/material": "^5.14.20",
2323
"@mui/styles": "^5.14.20",
24-
"@tanstack/react-query": "^5.61.4",
24+
"@tanstack/react-query": "^5.64.1",
2525
"@yornaath/batshit": "^0.10.1",
2626
"ag-grid-community": "^31.0.2",
2727
"ag-grid-react": "^31.0.3",
2828
"fuse.js": "^7.0.0",
2929
"iso-web": "^1.0.6",
30+
"p-queue": "npm:@nmann/p-queue@^8.1.1",
3031
"react": "^18.2.0",
3132
"react-dom": "^18.2.0",
3233
"react-router-dom": "^6.28.0"

src/api/lists.js

+27-7
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { unwrapShortHandle } from '.';
44
import { fetchClearskyApi, unwrapClearskyURL } from './core';
55
import { useResolveHandleOrDid } from './resolve-handle-or-did';
66
import { useInfiniteQuery, useQuery } from '@tanstack/react-query';
7+
import PQueue from 'p-queue';
78

89
const PAGE_SIZE = 100;
910

@@ -26,16 +27,18 @@ export function useList(handleOrDID) {
2627
* Look up the total number of lists to which a given handle/DID belongs
2728
* @param {string} handleOrDID
2829
*/
29-
export function useListTotal(handleOrDID) {
30+
export function useListCount(handleOrDID) {
3031
const profileQuery = useResolveHandleOrDid(handleOrDID);
3132
const shortHandle = profileQuery.data?.shortHandle;
3233
return useQuery({
3334
enabled: !!shortHandle,
3435
queryKey: ['list-total', shortHandle],
35-
queryFn: () => getListTotal(shortHandle),
36+
queryFn: () => getListCount(shortHandle),
3637
});
3738
}
3839

40+
const TWELVE_HOURS = 1000 * 60 * 60 * 12;
41+
3942
/**
4043
* Gets the size (length) of a given user list
4144
* @param {string} listUrl
@@ -44,7 +47,9 @@ export function useListSize(listUrl) {
4447
return useQuery({
4548
enabled: !!listUrl,
4649
queryKey: ['list-size', listUrl],
47-
queryFn: () => getListSize(listUrl),
50+
queryFn: ({ signal }) => getListSize(listUrl, signal),
51+
staleTime: TWELVE_HOURS,
52+
gcTime: TWELVE_HOURS,
4853
});
4954
}
5055

@@ -84,9 +89,8 @@ async function getList(shortHandle, currentPage = 1) {
8489
* Gets the total number of lists to which a given handle belongs
8590
* @param {string} shortHandle
8691
*/
87-
async function getListTotal(shortHandle) {
92+
async function getListCount(shortHandle) {
8893
const handleURL = 'get-list/total/' + unwrapShortHandle(shortHandle);
89-
9094
/** @type {{ data: { count: number; pages: number } }} */
9195
const re = await fetchClearskyApi('v1', handleURL);
9296
return re.data;
@@ -95,13 +99,18 @@ async function getListTotal(shortHandle) {
9599
/**
96100
* Gets the size (length) of a given user list
97101
* @param {string} listUrl
102+
* @param {AbortSignal} signal
98103
* @returns {Promise<{ count: number } | null>} null if response is a 400/404
99104
*/
100-
async function getListSize(listUrl) {
105+
async function getListSize(listUrl, signal) {
101106
const apiUrl = unwrapClearskyURL(
102107
`/api/v1/anon/get-list/specific/total/${encodeURIComponent(listUrl)}`
103108
);
104-
const resp = await fetch(apiUrl);
109+
signal.throwIfAborted;
110+
const resp = await listSizeQueue.add(() => fetch(apiUrl, { signal }), {
111+
signal,
112+
throwOnTimeout: true,
113+
});
105114
if (resp.ok) {
106115
/** @type {{ data: { count: number }, list_uri: string }} */
107116
const respData = await resp.json();
@@ -112,3 +121,14 @@ async function getListSize(listUrl) {
112121
}
113122
throw new Error('getListSize error: ' + resp.statusText);
114123
}
124+
125+
/**
126+
* create a queue where only one request can be in flight at a time,
127+
* and at most 1 may be sent in any 250 millisecond interval
128+
*/
129+
const listSizeQueue = new PQueue({
130+
concurrency: 1,
131+
intervalCap: 1,
132+
interval: 200,
133+
timeout: 500,
134+
});

src/common-components/account-short-entry.jsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ function getAvatarDelay(account) {
140140
const rnd = Math.abs(hash) - Math.floor(Math.abs(hash));
141141
delay = (rnd * 40).toFixed(3) + 's';
142142
avatarDelays[avatarUrl] = delay;
143-
console.log('Avatar delay', account.shortHandle, { delay, hash, rnd });
143+
//console.log('Avatar delay', account.shortHandle, { delay, hash, rnd });
144144
return delay;
145145
}
146146

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// @ts-check
2+
import { useState, cloneElement } from 'react';
3+
import { Visible } from './visible';
4+
5+
const INITIAL_SIZE = 20;
6+
const GROW_BLOCK_SIZE = 29;
7+
8+
/**
9+
* @template ItemType
10+
* @param {{ items: Array<ItemType>, renderItem(item: ItemType): import('react').ReactElement }} props
11+
* Given a list of items, only renders the first 20 by default,
12+
* and then more whenever the last item is close to being on screen
13+
*/
14+
export function ProgressiveRender(props) {
15+
const [listSize, setListSize] = useState(INITIAL_SIZE);
16+
const showSize = Math.min(props.items.length, listSize);
17+
18+
return (
19+
<>
20+
{props.items.slice(0, showSize).map((item, index) => {
21+
const entry = cloneElement(props.renderItem(item), { key: index });
22+
23+
return index < showSize - 1 ? (
24+
entry
25+
) : (
26+
<Visible
27+
key={index}
28+
rootMargin="0px 0px 300px 0px"
29+
onVisible={handleBottomVisible}
30+
>
31+
{entry}
32+
</Visible>
33+
);
34+
})}
35+
</>
36+
);
37+
38+
function handleBottomVisible() {
39+
const incrementListSize = listSize + GROW_BLOCK_SIZE;
40+
setListSize(incrementListSize);
41+
if (incrementListSize > props.items.length) {
42+
// TODO: control fetch more from here?
43+
}
44+
}
45+
}

src/detail-panels/block-panel-generic/list-view.jsx

+5-31
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,24 @@
11
// @ts-check
22
/// <reference path="../../types.d.ts" />
33
import { FormatTimestamp } from '../../common-components/format-timestamp';
4-
import { Visible } from '../../common-components/visible';
5-
import { useState } from 'react';
4+
import { ProgressiveRender } from '../../common-components/progressive-render';
65
import { AccountShortEntry } from '../../common-components/account-short-entry';
76
import { localise } from '../../localisation';
87

9-
const INITIAL_SIZE = 20;
10-
const GROW_BLOCK_SIZE = 29;
11-
128
/**
139
* @param {{
1410
* blocklist: (BlockedByRecord | { did: string; blocked_date: string })[];
1511
* }} _
1612
*/
1713
export function ListView({ blocklist }) {
18-
const [listSize, setListSize] = useState(INITIAL_SIZE);
19-
const showSize = Math.min(blocklist.length, listSize);
20-
2114
return (
2215
<ul className="block-list">
23-
{blocklist.slice(0, showSize).map((block, index) => {
24-
const entry = <ListViewEntry key={index} {...block} />;
25-
26-
return index < showSize - 1 ? (
27-
entry
28-
) : (
29-
<Visible
30-
key={index}
31-
rootMargin="0px 0px 300px 0px"
32-
onVisible={handleBottomVisible}
33-
>
34-
{entry}
35-
</Visible>
36-
);
37-
})}
16+
<ProgressiveRender
17+
items={blocklist}
18+
renderItem={(item) => <ListViewEntry {...item} />}
19+
/>
3820
</ul>
3921
);
40-
41-
function handleBottomVisible() {
42-
const incrementListSize = listSize + GROW_BLOCK_SIZE;
43-
setListSize(incrementListSize);
44-
if (incrementListSize > blocklist.length) {
45-
// TODO: fetch more
46-
}
47-
}
4822
}
4923

5024
/**

src/detail-panels/lists/list-view.css

+13-7
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
.lists-as-list-view {
22
margin: 0;
3-
padding: 0
3+
padding: 0;
44
}
55

66
.lists-as-list-view .lists-entry {
77
list-style-type: none;
8-
height: 3.5em;
8+
height: auto;
99
border-top: solid 1px #dbdbdb;
1010
padding-top: 0.4em;
1111
padding-left: 0.7em;
@@ -39,15 +39,21 @@
3939
.lists-as-list-view .lists-entry .list-name {
4040
grid-row: 1/ 2;
4141
grid-column: 1 / 2;
42-
padding-left: 1.7em;
42+
padding-left: 0.5em;
4343
font-weight: bold;
4444
}
4545

4646
.lists-as-list-view .lists-entry .list-description {
4747
grid-row: 1 / 2;
4848
grid-column: 2 / 3;
4949
opacity: 0.6;
50-
overflow: hidden;
51-
text-overflow: ellipsis;
52-
padding-left: 0.5em;
53-
}
50+
padding-left: 1.7em;
51+
white-space: normal;
52+
word-wrap: break-word;
53+
}
54+
55+
.lists-as-list-view .lists-entry .list-count {
56+
padding-left: 0.3em;
57+
color: green;
58+
}
59+

0 commit comments

Comments
 (0)