Skip to content

Commit afc8b03

Browse files
committed
Sketch of server rendered table
1 parent aa006df commit afc8b03

File tree

2 files changed

+178
-0
lines changed

2 files changed

+178
-0
lines changed
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
"use client";
2+
import {
3+
HEADING_HEIGHT,
4+
SCREENSHOT_WIDTH,
5+
SKIN_RATIO,
6+
} from "../../../legacy-client/src/constants.js";
7+
import {
8+
useScrollbarWidth,
9+
useWindowSize,
10+
} from "../../../legacy-client/src/hooks.js";
11+
import React, { useEffect, useMemo, useState } from "react";
12+
import { FixedSizeGrid as Grid } from "react-window";
13+
14+
function ClientOnly({ children }: { children: React.ReactNode }) {
15+
const [mounted, setMounted] = useState(false);
16+
useEffect(() => setMounted(true), []);
17+
return mounted ? <>{children}</> : null;
18+
}
19+
20+
export default function WrappedTable({ initialSkins, skinCount }) {
21+
return (
22+
<ClientOnly>
23+
<Table initialSkins={initialSkins} skinCount={skinCount} />
24+
</ClientOnly>
25+
);
26+
}
27+
28+
function Table({ initialSkins, skinCount }) {
29+
const [skins, setSkins] = useState(initialSkins);
30+
31+
const scale = 0.5; // This can be adjusted based on your needs
32+
function getSkinData(data: {
33+
columnIndex: number;
34+
rowIndex: number;
35+
columnCount: number;
36+
}) {
37+
const index = data.rowIndex * columnCount + data.columnIndex;
38+
const skin = skins[index];
39+
return { requestToken: skin?.md5, skin };
40+
}
41+
const scrollbarWidth = useScrollbarWidth();
42+
const { windowWidth: windowWidthWithScrollabar, windowHeight } =
43+
useWindowSize();
44+
45+
const { columnWidth, rowHeight, columnCount } = getTableDimensions(
46+
windowWidthWithScrollabar - scrollbarWidth,
47+
scale
48+
);
49+
function Cell(props) {
50+
const index = props.rowIndex * columnCount + props.columnIndex;
51+
const skin = skins[index];
52+
if (skin == null) {
53+
if (index < skinCount) {
54+
// Fetch more skins!
55+
}
56+
return <div style={props.style}></div>;
57+
}
58+
if (skin == null) {
59+
return <div style={props.style}>Loading...</div>;
60+
}
61+
const imageUrl = `https://r2.webampskins.org/screenshots/${skin.md5}.png`;
62+
return (
63+
<div style={props.style}>
64+
<img
65+
src={imageUrl}
66+
style={{ width: props.data.width, height: props.data.height }}
67+
/>
68+
</div>
69+
);
70+
}
71+
return (
72+
<div className="flex flex-col gap-4">
73+
<SkinTableUnbound
74+
columnCount={columnCount}
75+
columnWidth={columnWidth}
76+
rowHeight={rowHeight}
77+
windowHeight={windowHeight}
78+
windowWidth={windowWidthWithScrollabar}
79+
skinCount={skinCount}
80+
getSkinData={getSkinData}
81+
Cell={Cell}
82+
/>
83+
</div>
84+
);
85+
}
86+
const getTableDimensions = (windowWidth: number, scale: number) => {
87+
const columnCount = Math.round(windowWidth / (SCREENSHOT_WIDTH * scale));
88+
const columnWidth = windowWidth / columnCount; // TODO: Consider flooring this to get things aligned to the pixel
89+
const rowHeight = columnWidth * SKIN_RATIO;
90+
return { columnWidth, rowHeight, columnCount };
91+
};
92+
93+
function SkinTableUnbound({
94+
columnCount,
95+
columnWidth,
96+
rowHeight,
97+
windowHeight,
98+
skinCount,
99+
windowWidth,
100+
getSkinData,
101+
Cell,
102+
}) {
103+
function itemKey({ columnIndex, rowIndex }) {
104+
const { requestToken, data: skin } = getSkinData({
105+
columnIndex,
106+
rowIndex,
107+
columnCount,
108+
});
109+
if (skin == null && requestToken == null) {
110+
return `empty-cell-${columnIndex}-${rowIndex}`;
111+
}
112+
return skin ? skin.hash : `unfectched-index-${requestToken}`;
113+
}
114+
const gridRef = React.useRef();
115+
const itemRef = React.useRef();
116+
React.useLayoutEffect(() => {
117+
if (gridRef.current == null) {
118+
return;
119+
}
120+
gridRef.current.scrollTo({ scrollLeft: 0, scrollTop: 0 });
121+
}, [skinCount]);
122+
123+
React.useLayoutEffect(() => {
124+
if (gridRef.current == null) {
125+
return;
126+
}
127+
128+
const itemRow = Math.floor(itemRef.current / columnCount);
129+
130+
gridRef.current.scrollTo({ scrollLeft: 0, scrollTop: rowHeight * itemRow });
131+
}, [rowHeight, columnCount]);
132+
133+
const onScroll = useMemo(() => {
134+
const half = Math.round(columnCount / 2);
135+
return (scrollData) => {
136+
itemRef.current =
137+
Math.round(scrollData.scrollTop / rowHeight) * columnCount + half;
138+
};
139+
}, [columnCount, rowHeight]);
140+
141+
return (
142+
<div id="infinite-skins" style={{ marginTop: HEADING_HEIGHT }}>
143+
<Grid
144+
ref={gridRef}
145+
itemKey={itemKey}
146+
itemData={{ columnCount, width: columnWidth, height: rowHeight }}
147+
columnCount={columnCount}
148+
columnWidth={columnWidth}
149+
height={windowHeight - HEADING_HEIGHT}
150+
rowCount={Math.ceil(skinCount / columnCount)}
151+
rowHeight={rowHeight}
152+
width={windowWidth}
153+
overscanRowCount={5}
154+
onScroll={onScroll}
155+
style={{ overflowY: "scroll" }}
156+
>
157+
{Cell}
158+
</Grid>
159+
</div>
160+
);
161+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import * as Skins from "../../../data/skins";
2+
import Table from "./Table";
3+
4+
export default async function TablePage() {
5+
const skins = await Skins.getMuseumPage({
6+
offset: 0,
7+
first: 100,
8+
});
9+
10+
const skinCount = await Skins.getClassicSkinCount();
11+
12+
return (
13+
<div className="flex flex-col gap-4">
14+
<Table initialSkins={skins} skinCount={skinCount} />
15+
</div>
16+
);
17+
}

0 commit comments

Comments
 (0)