Files
webamp/packages/skin-database/app/(modern)/scroll/gridSkin.ts
T
Jordan EldredgeandClaude Opus 5.5 9999094dde Make the new UI's grid and feed two views of the same list
The feed was its own shuffled list (museum favorites plus random skins).
Now it continues through whatever the grid shows, the museum or a search:
tapping a skin opens the feed on it with the list before and after it,
and going back lands the grid on the skin you were on. Feed URLs are
plain permalinks; the list lives in the tab (listPosition.ts), and a
permalink opened fresh is followed by the museum. /feed redirects to the
museum's first skin.

- Event logging v3: one session per tab visit spanning both views, with
  view_switch events and each feed view's list, list index and depth.
  Older events are still accepted.
- The feed loads readmes and like counts on demand.
- Keyboard paging steps from the skin actually on screen, so it can't get
  stuck after a scroll is interrupted.
- Going back to the grid animates the skin into its tile. React's
  <ViewTransition> can't animate Back with Next's router, so it's done
  by hand.
- Grid tiles and view transition snapshots render pixelated.
- Download saves the skin under its file name, not its md5.
- A feed URL for a missing skin returns a 404 status.
- The list position is kept apart from the server actions, so pages with
  only the menu bar don't each compile another copy of the server code;
  this kept the build within the server's memory.
- Declare reselect (used by legacy-client) and give knexfile.ts named
  exports.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01QzNErwA6wVTLaUTNMARPQt
2026-10-15 10:05:25 -07:00

63 lines
1.4 KiB
TypeScript

import {
screenshotUrlFromHash,
skinUrlFromHash,
} from "../../../legacy-client/src/utils";
import { normalizeSkinFileName } from "./skinFileName";
import { skinPath } from "./paths";
export type GridSkin = {
md5: string;
screenshotUrl: string;
fileName: string;
nsfw: boolean;
};
/**
* A skin as the grid shows it. Museum pages, the server's search and the
* browser's all build theirs here, so a search's results look the same
* whichever of them ran it.
*/
export function toGridSkin(
md5: string,
fileName: string,
nsfw: boolean
): GridSkin {
return {
md5,
screenshotUrl: screenshotUrlFromHash(md5),
fileName: normalizeSkinFileName(fileName),
nsfw,
};
}
// What the feed shows of a skin beyond what the grid has, fetched for the
// skins around the one in view.
export type SkinDetails = {
readme: string;
likeCount: number;
};
// A skin as the feed shows it. Until its details arrive, `readme` is null.
export type ClientSkin = GridSkin & {
skinUrl: string;
downloadUrl: string;
shareUrl: string;
readme: string | null;
likeCount: number;
};
export function toClientSkin(
skin: GridSkin,
details: SkinDetails | undefined
): ClientSkin {
const skinUrl = skinUrlFromHash(skin.md5);
return {
...skin,
skinUrl,
downloadUrl: skinUrl,
shareUrl: `https://skins.webamp.org${skinPath(skin.md5, skin.fileName)}`,
readme: details?.readme ?? null,
likeCount: details?.likeCount ?? 0,
};
}