Use content ids for episode progress keys

This commit is contained in:
ShiGuangAlex
2026-04-15 11:13:18 +08:00
parent 6372ed307d
commit d3eb78d9ce
3 changed files with 207 additions and 52 deletions
+38 -19
View File
@@ -55,14 +55,18 @@ export default function VirtualScrollableGrid({
const measureGridRef = useRef<HTMLDivElement>(null);
const childrenRef = useRef(children);
const rafRef = useRef<number | null>(null);
const needsMeasureRef = useRef(true);
childrenRef.current = children;
const [layout, setLayout] = useState<LayoutMetrics>(() => ({
const initialLayout: LayoutMetrics = {
columns: Math.max(1, mobileColumns),
rowHeight: DEFAULT_ROW_HEIGHT,
totalRows: Math.ceil(children.length / Math.max(1, mobileColumns)),
}));
};
const layoutRef = useRef<LayoutMetrics>(initialLayout);
const [layout, setLayout] = useState<LayoutMetrics>(() => initialLayout);
const [range, setRange] = useState({ startRow: 0, endRow: 0 });
const computeFallbackColumns = () => {
@@ -169,9 +173,23 @@ export default function VirtualScrollableGrid({
return { startRow: clampedStart, endRow: clampedEnd };
};
const syncLayoutAndRange = () => {
const nextLayout = readLayout();
const syncRange = (nextLayout: LayoutMetrics) => {
const nextRange = computeRange(nextLayout);
setRange((prev) => {
if (
prev.startRow === nextRange.startRow &&
prev.endRow === nextRange.endRow
) {
return prev;
}
return nextRange;
});
};
const syncMeasuredLayout = () => {
const nextLayout = readLayout();
layoutRef.current = nextLayout;
setLayout((prev) => {
if (
@@ -185,37 +203,38 @@ export default function VirtualScrollableGrid({
return nextLayout;
});
setRange((prev) => {
if (
prev.startRow === nextRange.startRow &&
prev.endRow === nextRange.endRow
) {
return prev;
}
return nextRange;
});
syncRange(nextLayout);
};
const scheduleUpdate = () => {
const scheduleUpdate = (measure = false) => {
if (typeof window === 'undefined') return;
if (measure) {
needsMeasureRef.current = true;
}
if (rafRef.current != null) return;
rafRef.current = window.requestAnimationFrame(() => {
rafRef.current = null;
syncLayoutAndRange();
if (needsMeasureRef.current) {
needsMeasureRef.current = false;
syncMeasuredLayout();
return;
}
syncRange(layoutRef.current);
});
};
useEffect(() => {
scheduleUpdate();
scheduleUpdate(true);
const handleScroll = () => {
scheduleUpdate();
};
const handleResize = () => {
scheduleUpdate();
scheduleUpdate(true);
};
const bodyEl = document.body;
@@ -230,7 +249,7 @@ export default function VirtualScrollableGrid({
let resizeObserver: ResizeObserver | null = null;
if (typeof ResizeObserver !== 'undefined') {
resizeObserver = new ResizeObserver(() => {
scheduleUpdate();
scheduleUpdate(true);
});
if (containerRef.current) {