From 622115706d2ecbf3a2aeae972f2a87d66763da56 Mon Sep 17 00:00:00 2001 From: zk Date: Mon, 18 May 2026 13:21:43 +0000 Subject: [PATCH] fix: folder resolution and remove smooth scrolling --- .../_playtype/folders/SpecificFolderPage.tsx | 3 +- .../src/components/layout/CustomScrollbar.tsx | 4 +- .../client/src/styles/components/navbar.scss | 1 - .../server/src/lib/db-formats/folders.ts | 38 ++++++++-- .../_playtype/folders/router.test.ts | 70 +++++++++++++++++++ 5 files changed, 104 insertions(+), 12 deletions(-) diff --git a/typescript/client/src/app/pages/dashboard/users/games/_game/_playtype/folders/SpecificFolderPage.tsx b/typescript/client/src/app/pages/dashboard/users/games/_game/_playtype/folders/SpecificFolderPage.tsx index c63bda27b..6aeb74ba2 100644 --- a/typescript/client/src/app/pages/dashboard/users/games/_game/_playtype/folders/SpecificFolderPage.tsx +++ b/typescript/client/src/app/pages/dashboard/users/games/_game/_playtype/folders/SpecificFolderPage.tsx @@ -124,7 +124,7 @@ export default function SpecificFolderPage({ reqUser, game }: Props) { anchor.getBoundingClientRect().top + window.scrollY - FOLDER_BREAKDOWN_TABLE_SCROLL_EXTRA_OFFSET_PX; - window.scrollTo({ behavior: "smooth", top: Math.max(0, y) }); + window.scrollTo({ top: Math.max(0, y) }); }); }, []); @@ -928,7 +928,6 @@ function TierlistInfoLadder({ }); queueMicrotask(() => { document.getElementById(`tierlist-tier-section-${i}`)?.scrollIntoView({ - behavior: "smooth", block: "start", }); }); diff --git a/typescript/client/src/components/layout/CustomScrollbar.tsx b/typescript/client/src/components/layout/CustomScrollbar.tsx index 057239eed..1b4cfeeb1 100644 --- a/typescript/client/src/components/layout/CustomScrollbar.tsx +++ b/typescript/client/src/components/layout/CustomScrollbar.tsx @@ -104,9 +104,9 @@ export function CustomScrollbar() { const mid = thumbRect.top + thumbRect.height / 2; const page = clientHeight * 0.85; if (e.clientY < mid) { - document.documentElement.scrollBy({ top: -page, behavior: "smooth" }); + document.documentElement.scrollBy({ top: -page }); } else { - document.documentElement.scrollBy({ top: page, behavior: "smooth" }); + document.documentElement.scrollBy({ top: page }); } }; diff --git a/typescript/client/src/styles/components/navbar.scss b/typescript/client/src/styles/components/navbar.scss index a20e2c8ad..36d55426d 100644 --- a/typescript/client/src/styles/components/navbar.scss +++ b/typescript/client/src/styles/components/navbar.scss @@ -6,7 +6,6 @@ background-color: var(--bs-secondary-bg); overflow-x: auto; overflow-y: hidden; - scroll-behavior: smooth; @extend .scrollbar-hide; &-item { diff --git a/typescript/server/src/lib/db-formats/folders.ts b/typescript/server/src/lib/db-formats/folders.ts index aff599ccc..4c7ed8b30 100644 --- a/typescript/server/src/lib/db-formats/folders.ts +++ b/typescript/server/src/lib/db-formats/folders.ts @@ -68,21 +68,45 @@ export async function LoadFolderDocumentsByIds( return out; } +/** + * Loads a folder for this GPT. The key may be **`slug`**, primary **`folder.id`**, or + * **`legacy_id`** (in that order of precedence when keys could theoretically collide). + */ export async function LoadFolderDocumentByGameAndSlug( game: V3Game, - slug: string, + slugOrFolderKey: string, ): Promise { - const row = await DB.selectFrom("folder") + const bySlug = await DB.selectFrom("folder") .select(SELECT_FOLDER) - .where("game", "=", game) - .where("slug", "=", slug) + .where("folder.game", "=", game) + .where("folder.slug", "=", slugOrFolderKey) .executeTakeFirst(); - if (!row) { - return undefined; + if (bySlug) { + return ToFolderDocument(bySlug); } - return ToFolderDocument(row); + const byId = await DB.selectFrom("folder") + .select(SELECT_FOLDER) + .where("folder.game", "=", game) + .where("folder.id", "=", slugOrFolderKey) + .executeTakeFirst(); + + if (byId) { + return ToFolderDocument(byId); + } + + const byLegacy = await DB.selectFrom("folder") + .select(SELECT_FOLDER) + .where("folder.game", "=", game) + .where("folder.legacy_id", "=", slugOrFolderKey) + .executeTakeFirst(); + + if (byLegacy) { + return ToFolderDocument(byLegacy); + } + + return undefined; } /** diff --git a/typescript/server/src/server/router/api/v1/games/_gameGroup/_playtype/folders/router.test.ts b/typescript/server/src/server/router/api/v1/games/_gameGroup/_playtype/folders/router.test.ts index 51e820d3a..5bff887c9 100644 --- a/typescript/server/src/server/router/api/v1/games/_gameGroup/_playtype/folders/router.test.ts +++ b/typescript/server/src/server/router/api/v1/games/_gameGroup/_playtype/folders/router.test.ts @@ -127,6 +127,76 @@ describe("GET /api/v1/games/:game/folders/:folderSlug", () => { expect(res.body.body.charts.length).toBeGreaterThanOrEqual(1); }); + it("returns the folder when the path segment is the primary folder id", async () => { + const res = await mockApi.get("/api/v1/games/iidx-sp/folders/folder-foo2"); + + expect(res.status).toBe(200); + expect(res.body.body.folder.folderID).toBe("folder-foo2"); + expect(res.body.body.folder.slug).toBe("foo"); + expect(res.body.body.charts.length).toBeGreaterThanOrEqual(1); + }); + + it("returns the folder when the path segment is legacy_id and differs from slug", async () => { + const n = Date.now(); + const songId = `fsong-leg-${n}`; + const chartId = `fchart-leg-${n}`; + + await DB.insertInto("song") + .values({ + id: songId, + legacy_id: 880_000 + (n % 10_000), + game_group: "iidx", + title: "T2", + artist: "A2", + search_terms: [], + alt_titles: [], + data: {}, + fts_document: "", + }) + .execute(); + + await DB.insertInto("chart") + .values({ + id: chartId, + legacy_id: chartId, + game: "iidx-sp", + song_id: songId, + difficulty: "ANOTHER", + level: "10", + level_num: 10, + is_primary: true, + versions: [], + data: {}, + }) + .execute(); + + const folderPk = `folder-by-legacy-path-${n}`; + + await DB.insertInto("folder") + .values({ + id: folderPk, + legacy_id: `mongo-folder-${n}`, + game: "iidx-sp", + inactive: false, + title: "legacy key", + slug: `pretty-slug-${n}`, + where: `chart.id = '${chartId}'`, + version_filter: null, + search_terms: [], + }) + .execute(); + + await DB.insertInto("folder_chart_lookup") + .values({ folder_id: folderPk, chart_id: chartId }) + .execute(); + + const res = await mockApi.get(`/api/v1/games/iidx-sp/folders/mongo-folder-${n}`); + + expect(res.status).toBe(200); + expect(res.body.body.folder.folderID).toBe(folderPk); + expect(res.body.body.folder.slug).toBe(`pretty-slug-${n}`); + }); + it("returns 404 when the folder does not exist", async () => { const res = await mockApi.get("/api/v1/games/iidx-sp/folders/bar");