Skip to content

Commit daa1b4c

Browse files
committed
Update Party Progress
1 parent 6bd0012 commit daa1b4c

File tree

4 files changed

+163
-16
lines changed

4 files changed

+163
-16
lines changed

web/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
"@tanstack/react-query": "^5.64.1",
2121
"@tanstack/react-query-persist-client": "^5.64.1",
2222
"@tanstack/react-router": "^1.97.1",
23+
"@tanstack/react-virtual": "^3.13.4",
2324
"@tanstack/router-plugin": "^1.97.1",
2425
"@types/react": "^19.0.7",
2526
"@types/react-dom": "^19.0.3",

web/pnpm-lock.yaml

Lines changed: 20 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 140 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,149 @@
1-
import { LISTS, setifyList } from '@/util/lists';
2-
import cx from 'classnames';
1+
import { useCallback, useEffect, useRef, useState, useMemo } from "react";
2+
import { LISTS, setifyList } from "@/util/lists";
3+
import cx from "classnames";
4+
import { useVirtualizer } from '@tanstack/react-virtual';
35

46
export const PartyProgress = () => {
57
const codes = setifyList(LISTS.flatMap(list => list.codes));
6-
8+
const parentRef = useRef<HTMLDivElement>(null);
9+
710
const progress = 10;
11+
12+
// Constants for item sizing
13+
const ITEM_WIDTH = 64;
14+
const ITEM_HEIGHT = 24;
15+
const ITEM_GAP = 2;
16+
const CELL_SIZE = ITEM_WIDTH + ITEM_GAP;
17+
18+
// State for container dimensions
19+
const [containerSize, setContainerSize] = useState({ width: 0, height: 0 });
20+
21+
// Calculate columns based on container width - memoize to prevent recalculation
22+
const columnCount = useMemo(() =>
23+
Math.max(1, Math.floor(containerSize.width / CELL_SIZE)),
24+
[containerSize.width, CELL_SIZE]
25+
);
26+
27+
// Calculate the total number of rows needed - memoize to prevent recalculation
28+
const rowCount = useMemo(() =>
29+
Math.ceil(codes.length / columnCount),
30+
[codes.length, columnCount]
31+
);
32+
33+
// Resize observer callback
34+
const observerCallback = useCallback(() => {
35+
if (parentRef.current) {
36+
const width = parentRef.current.clientWidth;
37+
const height = parentRef.current.clientHeight;
38+
setContainerSize({ width, height });
39+
}
40+
}, []);
41+
42+
// Set up resize observer
43+
useEffect(() => {
44+
if (!parentRef.current) return;
45+
46+
observerCallback();
47+
48+
// Use ResizeObserver for more efficient resize detection
49+
const resizeObserver = new ResizeObserver(observerCallback);
50+
resizeObserver.observe(parentRef.current);
51+
52+
return () => {
53+
if (parentRef.current) {
54+
resizeObserver.unobserve(parentRef.current);
55+
}
56+
resizeObserver.disconnect();
57+
};
58+
}, [observerCallback]);
59+
60+
// Create a virtualizer for rows
61+
const rowVirtualizer = useVirtualizer({
62+
count: rowCount,
63+
getScrollElement: () => parentRef.current,
64+
estimateSize: () => ITEM_HEIGHT + ITEM_GAP,
65+
overscan: 5,
66+
});
67+
68+
// Create a virtualizer for columns
69+
const columnVirtualizer = useVirtualizer({
70+
count: columnCount,
71+
getScrollElement: () => parentRef.current,
72+
horizontal: true,
73+
estimateSize: () => ITEM_WIDTH + ITEM_GAP, // Make sure this uses the updated ITEM_WIDTH
74+
overscan: 5,
75+
});
76+
77+
// Get cell index in the full list
78+
const getCellIndex = useCallback((rowIndex: number, columnIndex: number) => {
79+
return rowIndex * columnCount + columnIndex;
80+
}, [columnCount]);
81+
82+
// Memoize virtual cells to prevent unnecessary re-renders
83+
const virtualCells = useMemo(() => {
84+
return rowVirtualizer.getVirtualItems().flatMap(virtualRow =>
85+
columnVirtualizer.getVirtualItems().map(virtualColumn => {
86+
const cellIndex = getCellIndex(virtualRow.index, virtualColumn.index);
87+
88+
// Skip rendering if cell index is out of bounds
89+
if (cellIndex >= codes.length) return null;
90+
91+
const code = codes[cellIndex];
92+
93+
return {
94+
key: `${virtualRow.index}:${virtualColumn.index}`,
95+
rowStart: virtualRow.start,
96+
columnStart: virtualColumn.start,
97+
code,
98+
cellIndex
99+
};
100+
}).filter(Boolean) // Filter out null entries
101+
);
102+
}, [
103+
rowVirtualizer.getVirtualItems(),
104+
columnVirtualizer.getVirtualItems(),
105+
codes,
106+
getCellIndex
107+
]);
108+
8109
return (
9-
<div className="card">
10-
<div className="max-h-[300px] overflow-y-auto">
11-
<div className="w-full flex flex-wrap gap-0.5 font-bold">
12-
{
13-
codes.map((code, index) => (
14-
<div key={index} className="w-6 h-6 bg-secondary rounded-sm">
15-
<div className={cx("flex justify-center items-center w-full h-full rounded-sm text-[0.5rem]", index < progress ? 'bg-accent text-primary' : 'bg-tertiary text-secondary')}>
16-
{code}
17-
</div>
110+
<div className="card w-full font-mono">
111+
<div
112+
ref={parentRef}
113+
className="max-h-[300px] overflow-auto w-full"
114+
style={{
115+
height: '300px',
116+
width: '100%',
117+
}}
118+
>
119+
<div
120+
style={{
121+
height: `${rowVirtualizer.getTotalSize()}px`,
122+
width: `${columnVirtualizer.getTotalSize()}px`,
123+
position: 'relative',
124+
}}
125+
>
126+
{virtualCells.map(cell => cell && (
127+
<div
128+
key={cell.key}
129+
className="absolute bg-secondary rounded-sm"
130+
style={{
131+
top: cell.rowStart,
132+
left: cell.columnStart,
133+
width: `${ITEM_WIDTH}px`, // This will now use the increased width
134+
height: `${ITEM_HEIGHT}px`,
135+
}}
136+
>
137+
<div className={cx(
138+
"flex justify-center items-center w-full h-full rounded-sm text-[0.8rem]",
139+
cell.cellIndex < progress ? 'bg-accent text-primary' : 'bg-tertiary text-secondary'
140+
)}>
141+
{cell.code}
18142
</div>
19-
))
20-
}
143+
</div>
144+
))}
21145
</div>
22146
</div>
23147
</div>
24-
)
25-
}
148+
);
149+
};

web/src/routes/$partyId/index.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Switch } from '@/components/input/Switch';
22
import { Modal } from '@/components/modal/Modal';
3+
import { PartyProgress } from '@/components/party/codes/PartyProgress';
34
import { PartyInviteCard } from '@/components/party/management/PartyInvite';
45
import { ServerFinder } from '@/components/party/management/ServerFinder';
56
import { PartyMembers } from '@/components/party/PartyMembers';
@@ -41,6 +42,7 @@ function RouteComponent() {
4142
</div>
4243
<PartyMembers />
4344
</div>
45+
<PartyProgress />
4446
</div>);
4547
}
4648

0 commit comments

Comments
 (0)