mirror of
https://github.com/captbaritone/webamp.git
synced 2026-11-07 10:25:59 -10:00
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
63 lines
1.4 KiB
TypeScript
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,
|
|
};
|
|
}
|