Improve scroll UI

This commit is contained in:
Jordan Eldredge 2025-12-29 17:03:29 -08:00
parent e062a51a88
commit 8d4ff41f42
13 changed files with 73 additions and 58 deletions

View file

@ -1,5 +1,6 @@
import App from "../App";
export default function Layout() {
console.log("Render legacy layout");
return <App />;
}

View file

@ -135,18 +135,16 @@ export default function BottomMenuBar() {
}}
>
<MenuButton
href="/scroll/grid"
href="/scroll"
icon={<Grid3x3 size={24} />}
label="Grid"
isActive={pathname === "/scroll/grid"}
isActive={pathname === "/scroll"}
/>
<MenuButton
href="/scroll"
href="/scroll/skin"
icon={<Smartphone size={24} />}
label="Feed"
isActive={
pathname === "/scroll" || pathname.startsWith("/scroll/skin")
}
isActive={pathname.startsWith("/scroll/skin")}
/>
<MenuButton

View file

@ -10,11 +10,11 @@ import React, {
import Link from "next/link";
import { FixedSizeGrid as Grid } from "react-window";
import { useWindowSize } from "../../../../legacy-client/src/hooks";
import { useWindowSize } from "../../../legacy-client/src/hooks";
import {
SCREENSHOT_WIDTH,
SKIN_RATIO,
} from "../../../../legacy-client/src/constants";
} from "../../../legacy-client/src/constants";
import { getMuseumPageSkins, GridSkin } from "./getMuseumPageSkins";
type CellData = {

View file

@ -10,11 +10,11 @@ import {
} from "react";
import { FixedSizeGrid as Grid } from "react-window";
import { useRouter } from "next/navigation";
import { ClientSkin } from "../SkinScroller";
import { ClientSkin } from "./SkinScroller";
import {
SCREENSHOT_WIDTH,
SKIN_RATIO,
} from "../../../../legacy-client/src/constants";
} from "../../../legacy-client/src/constants";
type Props = {
initialSkins: ClientSkin[];

View file

@ -1,7 +1,7 @@
"use client";
// @ts-expect-error - unstable_ViewTransition is not yet in @types/react
import { unstable_ViewTransition as ViewTransition } from "react";
import { useEffect, unstable_ViewTransition as ViewTransition } from "react";
import { ClientSkin } from "./SkinScroller";
import SkinActionIcons from "./SkinActionIcons";
@ -12,6 +12,9 @@ type Props = {
};
export default function SkinPage({ skin, index, sessionId }: Props) {
useEffect(() => {
console.log("Mount SkinPage");
}, []);
return (
<div
key={skin.md5}

View file

@ -109,9 +109,21 @@ export default function SkinScroller({
}, []);
useEffect(() => {
// We want the URL and title to update as you scroll, but
// we can't trigger a NextJS navigation since that would remount the
// component. So, here we replicate the metadata behavior of the route.
const skinMd5 = skins[visibleSkinIndex].md5;
const newUrl = `/scroll/skin/${skinMd5}`;
window.document.title = `${skins[visibleSkinIndex].fileName} - Winamp Skin Museum`;
window.history.replaceState(
{ ...window.history.state, as: newUrl, url: newUrl },
"",
newUrl
);
logUserEvent(sessionId, {
type: "skin_view_start",
skinMd5: skins[visibleSkinIndex].md5,
skinMd5,
});
const startTime = Date.now();
return () => {

View file

@ -1,6 +1,6 @@
"use server";
import { getMuseumPage, getScreenshotUrl } from "../../../../data/skins";
import { getMuseumPage, getScreenshotUrl } from "../../../data/skins";
export type GridSkin = {
md5: string;

View file

@ -1,9 +0,0 @@
import { ReactNode } from "react";
type LayoutProps = {
children: ReactNode;
};
export default function Layout({ children }: LayoutProps) {
return <div style={{}}>{children}</div>;
}

View file

@ -1,13 +0,0 @@
import React from "react";
import Grid from "./Grid";
import { getMuseumPageSkins } from "./getMuseumPageSkins";
import * as Skins from "../../../../data/skins";
export default async function SkinTable() {
const [initialSkins, skinCount] = await Promise.all([
getMuseumPageSkins(0, 50),
Skins.getClassicSkinCount(),
]);
return <Grid initialSkins={initialSkins} initialTotal={skinCount} />;
}

View file

@ -1,24 +1,13 @@
import SessionModel from "../../../data/SessionModel";
import { getClientSkins } from "./getClientSkins";
import SkinScroller from "./SkinScroller";
import React from "react";
import Grid from "./Grid";
import { getMuseumPageSkins } from "./getMuseumPageSkins";
import * as Skins from "../../..//data/skins";
// Ensure each page load gets a new session
export const dynamic = "force-dynamic";
/**
* A tik-tok style scroll page where we display one skin at a time in full screen
*/
export default async function ScrollPage() {
// Create the session in the database
const sessionId = await SessionModel.create();
const initialSkins = await getClientSkins(sessionId);
return (
<SkinScroller
initialSkins={initialSkins}
getSkins={getClientSkins}
sessionId={sessionId}
/>
);
export default async function SkinTable() {
const [initialSkins, skinCount] = await Promise.all([
getMuseumPageSkins(0, 50),
Skins.getClassicSkinCount(),
]);
console.log("SERVER RENDER generic");
return <Grid initialSkins={initialSkins} initialTotal={skinCount} />;
}

View file

@ -1,7 +1,14 @@
import { Metadata } from "next";
import SessionModel from "../../../../../data/SessionModel";
import UserContext from "../../../../../data/UserContext";
import { getClientSkins, getSkinForSession } from "../../getClientSkins";
import SkinScroller from "../../SkinScroller";
import { generateSkinPageMetadata } from "../../../../(legacy)/skin/[hash]/skinMetadata";
export async function generateMetadata({ params }): Promise<Metadata> {
const { md5 } = await params;
return generateSkinPageMetadata(md5);
}
// Ensure each page load gets a new session
export const dynamic = "force-dynamic";

View file

@ -0,0 +1,25 @@
import SessionModel from "../../../../data/SessionModel";
import { getClientSkins } from "../getClientSkins";
import SkinScroller from "../SkinScroller";
// Ensure each page load gets a new session
export const dynamic = "force-dynamic";
/**
* A tik-tok style scroll page where we display one skin at a time in full screen
*/
export default async function ScrollPage() {
// Create the session in the database
const sessionId = await SessionModel.create();
const initialSkins = await getClientSkins(sessionId);
console.log("SERVER RENDER generic");
return (
<SkinScroller
initialSkins={initialSkins}
getSkins={getClientSkins}
sessionId={sessionId}
/>
);
}

View file

@ -99,7 +99,7 @@ export default function BulkDownloadPage() {
completedSkins: prev.completedSkins + 1,
}));
return;
} catch (error) {
} catch (_) {
// File doesn't exist, continue with download
}
@ -277,6 +277,7 @@ export default function BulkDownloadPage() {
}
for (const skin of skins) {
// eslint-disable-next-line max-depth
if (abortController.current.signal.aborted) break;
await waitForAvailableSlot(
@ -284,6 +285,7 @@ export default function BulkDownloadPage() {
abortController.current.signal
);
// eslint-disable-next-line max-depth
if (abortController.current.signal.aborted) break;
const downloadPromise = downloadSkin(