diff --git a/app/browse/TopCharts.jsx b/app/browse/TopCharts.jsx index ce4868e..f8add3b 100644 --- a/app/browse/TopCharts.jsx +++ b/app/browse/TopCharts.jsx @@ -9,7 +9,7 @@ import { latin, useLocale, useT } from '../i18n/index.js'; import { NO_FILTERS, formatCount, formatDate3, sortGames, sortLabel } from '../state/catalog.js'; import { shuffle } from './picks.js'; import { useNavActions } from '../state/nav.jsx'; -import { useStoreActions } from '../state/store.jsx'; +import { useShowLibrary } from '../ui/useSections.js'; import { Cover } from '../ui/Cover.jsx'; import { FocusFrame } from '../ui/FocusRing.jsx'; import { SectionHeader } from '../ui/SectionHeader.jsx'; @@ -59,14 +59,10 @@ import { Text, View } from '@ps5-react/core'; } function TopCharts() { useT(); - const { openGame, focusLater } = useNavActions(); - const { setFilters, setQuery, setSort } = useStoreActions(); - const seeAll = (id) => { - setFilters(NO_FILTERS); - setQuery(""); - setSort(id); - focusLater("catalog:sort"); - }; + const { openGame } = useNavActions(); + const show = useShowLibrary(); + // "See all" opens the library on that sort, as the header's section of the same name does. + const seeAll = (id) => show(NO_FILTERS, id, "catalog:sort", { rating: "top", newest: "new" }[id] ?? "catalog"); return /* @__PURE__ */ (0, jsx)(View, { scrollAnchor: true, style: { marginBottom: -18, marginTop: -12, flexDirection: "row", gap: 48 }, children: CHARTS.map( (chart) => /* @__PURE__ */ (0, jsxs)(View, { style: { flex: 1 }, children: [ /* @__PURE__ */ (0, jsx)(SectionHeader, { title: sortLabel(chart.id), onSeeAll: () => seeAll(chart.id) }), diff --git a/app/data/pages/CUSA01369.json b/app/data/pages/CUSA01369.json deleted file mode 100644 index 2cade8e..0000000 --- a/app/data/pages/CUSA01369.json +++ /dev/null @@ -1 +0,0 @@ -{"about":["Uncertain of his sister's fate, a boy enters Limbo.","Limbo is an award-winning indie adventure, critically acclaimed for its captivating puzzle design and immersive sound and visuals. Its dark, misty spaces and haunting narrative will stay with you forever.","What the press said: \"Limbo is as close to perfect at what it does as a game can get.\"","Destructoid – 10/10 \"The game is a masterpiece.\"","Giant Bomb – 5/5 \"Limbo is genius. Freaky, weird genius. Disturbing, uncomfortable genius.\"","The Escapist – 5/5 \"Dark, disturbing, yet eerily beautiful, Limbo is a world that deserves to be explored.\""],"shots":[{"url":"https://image.api.playstation.com/vulcan/img/rnd/202010/0813/y7WUbV4HXSRWC1XqJjKhRtmU.jpg?w=1920&thumb=false","thumb":"https://image.api.playstation.com/vulcan/img/rnd/202010/0813/y7WUbV4HXSRWC1XqJjKhRtmU.jpg?w=256&thumb=false"},{"url":"https://image.api.playstation.com/vulcan/img/rnd/202010/0813/XUcaSSyRYyOWRe4Z5mkglIye.jpg?w=1920&thumb=false","thumb":"https://image.api.playstation.com/vulcan/img/rnd/202010/0813/XUcaSSyRYyOWRe4Z5mkglIye.jpg?w=256&thumb=false"},{"url":"https://image.api.playstation.com/vulcan/img/rnd/202010/0813/U6J8nV1CIZO9VdxMSOPj6xUi.jpg?w=1920&thumb=false","thumb":"https://image.api.playstation.com/vulcan/img/rnd/202010/0813/U6J8nV1CIZO9VdxMSOPj6xUi.jpg?w=256&thumb=false"}],"summary":"From the creators of INSIDE"} \ No newline at end of file diff --git a/app/data/releases/CUSA01369.json b/app/data/releases/CUSA01369.json deleted file mode 100644 index 19e7e78..0000000 --- a/app/data/releases/CUSA01369.json +++ /dev/null @@ -1 +0,0 @@ -[{"id":"r2de6f494acad","format":"pkg","filename":"CUSA01369-game-01.01-r2de6f494acad.pkg","url":"https://archive.org/download/ps4-fpkg-collection-english-l/LIMBO%20-%20[EU]%20[EN]%20[1.01].pkg","requiresBrowser":false,"sizeBytes":null,"version":"01.01","category":"game","provider":"archive.org","source":"pegasus-0","flow":"direct","label":"EUR Base (v01.01) - Direct","platform":"ps4","record":"pegasus-0-381","health":"ok","unavailableReason":"Source unavailable: HTTP 401. Choose another source."}] \ No newline at end of file diff --git a/app/ui/Header.jsx b/app/ui/Header.jsx index 8bf9d9c..07f83b6 100644 --- a/app/ui/Header.jsx +++ b/app/ui/Header.jsx @@ -26,7 +26,7 @@ import { FocusScope, Text, View } from '@ps5-react/core'; function Header({ children }) { const t2 = useT(); const { rtl } = useLocale(); - const { section, filters, on, step, keyOf } = useSections(); + const { section, filters, on, step, keyOf, browseFocus } = useSections(); const wishlist = useWishlist(); const favorites = wishlist.filter((id) => getGame(id)).length; const nav = useNavActions(); @@ -98,7 +98,8 @@ import { FocusScope, Text, View } from '@ps5-react/core'; current: section === "catalog", onPress: () => setBrowsing((open) => !open), onLayout: track("catalog"), - nextFocusDown: down + nextFocusDown: down, + ...browseFocus } ), /* @__PURE__ */ (0, jsxs)(Menu, { open: browsing, onClose: () => setBrowsing(false), rows: genres.length + 2, children: [ @@ -108,7 +109,7 @@ import { FocusScope, Text, View } from '@ps5-react/core'; label: t2("nav.allGames"), checked: section === "catalog" && !filters.genre.length, autoFocus: !onlyGenre, - onSelect: () => on.show(NO_FILTERS, "newest") + onSelect: () => on.show(NO_FILTERS, "newest", void 0, "catalog") } ), /* @__PURE__ */ (0, jsx)(MenuDivider, {}), diff --git a/app/ui/useSections.js b/app/ui/useSections.js index 86b956d..5f7b3a6 100644 --- a/app/ui/useSections.js +++ b/app/ui/useSections.js @@ -16,11 +16,20 @@ import { useQueryActive, useStoreActions, useStoreState } from '../state/store.j downloads: "nav:downloads", files: "nav:files" }; - var TABS = ["search", "discover", "top", "new", "favorites", "downloads", "files"]; + // L1/R1 stop on Browse too: the item only takes focus there, and ✕ opens its menu. + var TABS = ["search", "discover", "catalog", "top", "new", "favorites", "downloads", "files"]; + // The section a header item showed, with the filters and sort it set: it names the page while + // they stay as they are, since the library opens on New releases for Discover and Browse too and + // the sort alone can no longer tell them apart. A change made on the page itself falls back to + // what the filters and sort say (sectionOf). + var pinned = { section: "discover", filters: NO_FILTERS, sort: "newest" }; + // Whether the Browse item has focus, so L1/R1 move on from it rather than from the page's section. + var onBrowse = false; function useShowLibrary() { const store = useStoreActions(); const nav = useNavActions(); - const [show] = (0, useState)(() => (filters, sort, target = "catalog:sort") => { + const [show] = (0, useState)(() => (filters, sort, target = "catalog:sort", section = "catalog") => { + pinned = { section, filters, sort }; store.setFilters(filters); store.setQuery(""); store.setSort(sort); @@ -29,6 +38,7 @@ import { useQueryActive, useStoreActions, useStoreState } from '../state/store.j return show; } function sectionOf({ filters, sort }, query) { + if (!query && filters === pinned.filters && sort === pinned.sort) return pinned.section; if (filters.wishlist) return "favorites"; if (sort === "rating") return "top"; if (sort === "newest") return "new"; @@ -48,18 +58,21 @@ import { useQueryActive, useStoreActions, useStoreState } from '../state/store.j const [on] = (0, useState)(() => ({ show, search: () => topName.current !== "search" && nav.push({ name: "search" }, "nav:search"), - discover: () => show(NO_FILTERS, "newest", "spotlight:cta"), - top: () => show(NO_FILTERS, "rating"), - new: () => show(NO_FILTERS, "newest"), - favorites: () => show({ ...NO_FILTERS, wishlist: true }, "newest"), + discover: () => show(NO_FILTERS, "newest", "spotlight:cta", "discover"), + top: () => show(NO_FILTERS, "rating", void 0, "top"), + new: () => show(NO_FILTERS, "newest", void 0, "new"), + favorites: () => show({ ...NO_FILTERS, wishlist: true }, "newest", void 0, "favorites"), downloads: () => topName.current !== "downloads" && nav.push({ name: "downloads" }, "nav:downloads"), files: () => topName.current !== "files" && nav.push({ name: "files" }, "nav:files") })); const step = (forward) => { - const current = top.name === "search" ? "search" : underneath; - const at = current === "catalog" ? 1.5 : TABS.indexOf(current); - const next = TABS[forward ? Math.floor(at) + 1 : Math.ceil(at) - 1]; + const current = onBrowse ? "catalog" : top.name === "search" ? "search" : underneath; + const next = TABS[TABS.indexOf(current) + (forward ? 1 : -1)]; if (!next) return; + if (next === "catalog") { + nav.focusLater(next === section ? "nav:current" : KEYS2.catalog); + return; + } if (next === "downloads" || next === "files") { store.setQuery(""); nav.home(); @@ -73,7 +86,8 @@ import { useQueryActive, useStoreActions, useStoreState } from '../state/store.j step, // The page's top elements go up to `nav:current`, the current section's item, like a PS5 tab // row; the scope's own memory would be stale after L1/R1 or a closed menu. - keyOf: (name) => name === section ? "nav:current" : KEYS2[name] + keyOf: (name) => name === section ? "nav:current" : KEYS2[name], + browseFocus: { onFocus: () => onBrowse = true, onBlur: () => onBrowse = false } }; }