diff --git a/app/routes/_index.tsx b/app/customRoutes/_index.tsx similarity index 100% rename from app/routes/_index.tsx rename to app/customRoutes/_index.tsx diff --git a/app/routes/categories.$category.errorDialog.tsx b/app/customRoutes/categories.$category.errorDialog.tsx similarity index 64% rename from app/routes/categories.$category.errorDialog.tsx rename to app/customRoutes/categories.$category.errorDialog.tsx index 2275dce1..262ca3be 100644 --- a/app/routes/categories.$category.errorDialog.tsx +++ b/app/customRoutes/categories.$category.errorDialog.tsx @@ -4,7 +4,8 @@ import { useLoaderData, useSubmit } from "@remix-run/react"; import { ErrorDialog } from "~/components/ErrorDialog"; import { useFocus } from "~/hooks/useFocus"; import type { FocusElement } from "~/types/focusElement"; -import { useCallback, useEffect } from "react"; +import type { ElementRef } from "react"; +import { useCallback, useEffect, useRef } from "react"; import { useGamepadButtonPressEvent, useKeyboardEvent, @@ -34,6 +35,7 @@ export const ErrorBoundary = ({ error }: { error: Error }) => { export default function RenderComponent() { const { errorDialog } = useLoaderData(); + const listRef = useRef>(null); const submit = useSubmit(); const { switchFocusBack, switchFocus, isInFocus } = useFocus("errorDialog"); @@ -55,6 +57,28 @@ export default function RenderComponent() { } }, [switchFocusBack, submit, isInFocus]); + const handleScrollDown = useCallback(() => { + if (isInFocus && listRef?.current) { + const scrollHeight = listRef.current.clientHeight; + const scrollTop = listRef.current.scrollTop; + listRef.current.scroll({ + behavior: "smooth", + top: scrollTop + scrollHeight / 2, + }); + } + }, [isInFocus]); + + const handleScrollUp = useCallback(() => { + if (isInFocus && listRef?.current) { + const scrollHeight = listRef.current.clientHeight; + const scrollTop = listRef.current.scrollTop; + listRef.current.scroll({ + behavior: "smooth", + top: scrollTop - scrollHeight / 2, + }); + } + }, [isInFocus]); + useGamepadButtonPressEvent(layout.buttons.X, handleClose); useGamepadButtonPressEvent(layout.buttons.B, handleClose); useGamepadButtonPressEvent(layout.buttons.Start, handleClose); @@ -62,6 +86,10 @@ export default function RenderComponent() { useKeyboardEvent("Enter", handleClose); useKeyboardEvent("Escape", handleClose); useKeyboardEvent("Backspace", handleClose); + useKeyboardEvent("ArrowDown", handleScrollDown); + useKeyboardEvent("ArrowUp", handleScrollUp); - return ; + return ( + + ); } diff --git a/app/routes/categories.$category.settings._index.tsx b/app/customRoutes/categories.$category.settings._index.tsx similarity index 100% rename from app/routes/categories.$category.settings._index.tsx rename to app/customRoutes/categories.$category.settings._index.tsx diff --git a/app/routes/categories.$category.settings.appearance.tsx b/app/customRoutes/categories.$category.settings.appearance.tsx similarity index 100% rename from app/routes/categories.$category.settings.appearance.tsx rename to app/customRoutes/categories.$category.settings.appearance.tsx diff --git a/app/routes/categories.$category.settings.general.tsx b/app/customRoutes/categories.$category.settings.general.tsx similarity index 85% rename from app/routes/categories.$category.settings.general.tsx rename to app/customRoutes/categories.$category.settings.general.tsx index 43008b12..235887c7 100644 --- a/app/routes/categories.$category.settings.general.tsx +++ b/app/customRoutes/categories.$category.settings.general.tsx @@ -121,81 +121,85 @@ type ActionReturn = { }; export const action = async ({ request }: ActionFunctionArgs) => { - const form = await request.formData(); - const _actionId = form.get("_actionId"); - const applicationsPath = form.get("applicationsPath")?.toString(); - const categoriesPath = form.get("categoriesPath")?.toString(); - - if (_actionId === actionIds.importAll) { - const errors: Errors = {}; - - if (isWindows()) { - const errorApplicationsPath = validatePath( - applicationsPathLabel, - applicationsPath, + try { + const form = await request.formData(); + const _actionId = form.get("_actionId"); + const applicationsPath = form.get("applicationsPath")?.toString(); + const categoriesPath = form.get("categoriesPath")?.toString(); + + if (_actionId === actionIds.importAll) { + const errors: Errors = {}; + + if (isWindows()) { + const errorApplicationsPath = validatePath( + applicationsPathLabel, + applicationsPath, + ); + if (errorApplicationsPath) { + errors.applicationsPath = errorApplicationsPath; + } + } + const errorCategoriesPath = validatePath( + categoriesPathLabel, + categoriesPath, ); - if (errorApplicationsPath) { - errors.applicationsPath = errorApplicationsPath; + if (errorCategoriesPath) { + errors.categoriesPath = errorCategoriesPath; } - } - const errorCategoriesPath = validatePath( - categoriesPathLabel, - categoriesPath, - ); - if (errorCategoriesPath) { - errors.categoriesPath = errorCategoriesPath; - } - if (Object.keys(errors).length > 0) { - return json({ errors }); - } + if (Object.keys(errors).length > 0) { + return json({ errors }); + } - const fields: General = { - applicationsPath: - isWindows() && typeof applicationsPath === "string" - ? applicationsPath - : undefined, - categoriesPath, - }; + const fields: General = { + applicationsPath: + isWindows() && typeof applicationsPath === "string" + ? applicationsPath + : undefined, + categoriesPath, + }; - writeGeneral(fields); - await importCategories(); + writeGeneral(fields); + await importCategories(); - const categories = readCategories(); + const categories = readCategories(); - if (categories?.length > 0) { - throw redirect(`/categories/${categories[0].id}/settings/general`); + if (categories?.length > 0) { + throw redirect(`/categories/${categories[0].id}/settings/general`); + } } - } - if (_actionId === actionIds.installMissingApplications) { - await installMissingApplicationsOnLinux(); - } + if (_actionId === actionIds.installMissingApplications) { + await installMissingApplicationsOnLinux(); + } - if (_actionId === actionIds.chooseApplicationsPath) { - const newApplicationsPath = openFolderDialog( - "Select Emulators Folder", - typeof applicationsPath === "string" ? applicationsPath : undefined, - ); - if (newApplicationsPath) { - return json({ - applicationsPath: newApplicationsPath, - categoriesPath, - }); + if (_actionId === actionIds.chooseApplicationsPath) { + const newApplicationsPath = openFolderDialog( + "Select Emulators Folder", + typeof applicationsPath === "string" ? applicationsPath : undefined, + ); + if (newApplicationsPath) { + return json({ + applicationsPath: newApplicationsPath, + categoriesPath, + }); + } } - } - if (_actionId === actionIds.chooseCategoriesPath) { - const newCategoriesPath = openFolderDialog( - "Select Roms Folder", - categoriesPath, - ); - if (newCategoriesPath) { - return json({ - applicationsPath, - categoriesPath: newCategoriesPath, - }); + if (_actionId === actionIds.chooseCategoriesPath) { + const newCategoriesPath = openFolderDialog( + "Select Roms Folder", + categoriesPath, + ); + if (newCategoriesPath) { + return json({ + applicationsPath, + categoriesPath: newCategoriesPath, + }); + } } + } catch (e) { + return redirect("errorDialog"); } return null; diff --git a/app/routes/categories.$category.settings.tsx b/app/customRoutes/categories.$category.settings.tsx similarity index 100% rename from app/routes/categories.$category.settings.tsx rename to app/customRoutes/categories.$category.settings.tsx diff --git a/app/routes/categories.$category.tsx b/app/customRoutes/categories.$category.tsx similarity index 87% rename from app/routes/categories.$category.tsx rename to app/customRoutes/categories.$category.tsx index 3ca7a586..0d8efaa8 100644 --- a/app/routes/categories.$category.tsx +++ b/app/customRoutes/categories.$category.tsx @@ -51,7 +51,7 @@ export const loader = ({ params }: DataFunctionArgs) => { const categoryData = readCategory(category as SystemId); if (!categoryData?.name) { - throw redirect("settings"); + return redirect("settings"); } const { alwaysGameNames } = readAppearance(); @@ -64,40 +64,40 @@ const actionIds = { }; export const action: ActionFunction = async ({ request, params }) => { - const { category } = params; - if (!category) { - console.log("category empty"); - throw Error("category empty"); - } + try { + const { category } = params; + if (!category) { + console.log("category empty"); + throw Error("category empty"); + } - const general = readGeneral(); - const categoryData = readCategory(category as SystemId); + const general = readGeneral(); + const categoryData = readCategory(category as SystemId); - if ( - !general?.categoriesPath || - !categoryData?.name || - !fs.existsSync(nodepath.join(general.categoriesPath, categoryData.name)) - ) { - throw redirect("settings"); - } + if ( + !general?.categoriesPath || + !categoryData?.name || + !fs.existsSync(nodepath.join(general.categoriesPath, categoryData.name)) + ) { + return redirect("settings"); + } - const form = await request.formData(); - const _actionId = form.get("_actionId"); + const form = await request.formData(); + const _actionId = form.get("_actionId"); - if (_actionId === actionIds.launch) { - const game = form.get("game"); - if (typeof game === "string") { - try { + if (_actionId === actionIds.launch) { + const game = form.get("game"); + if (typeof game === "string") { executeApplication(category as SystemId, game); return { ok: true }; - } catch (e) { - throw redirect("errorDialog"); } } - } - if (_actionId === actionIds.import) { - await importEntries(category as SystemId); + if (_actionId === actionIds.import) { + await importEntries(category as SystemId); + } + } catch (e) { + return redirect("errorDialog"); } return null; @@ -188,14 +188,15 @@ export default function Category() { switchFocus("main"); enableGamepads(); } - }, [isInFocus, enableGamepads, switchFocus]); - const onSelectEntryByGamepad = useCallback(() => { - if (listRef?.current) { - // Add scrollPadding if entry was selected by gamepad to center the element. - listRef.current.style.scrollPadding = scrollPadding; - } - }, []); + setTimeout(() => { + if (listRef?.current) { + // Add scrollPadding if entry was selected by gamepad to center the element. + // Needs to be in a timeout to reactivate the feature afterwards + listRef.current.style.scrollPadding = scrollPadding; + } + }, 10); + }, [isInFocus, enableGamepads, switchFocus]); if (!categoryData) { return null; @@ -228,7 +229,6 @@ export default function Category() { onBack={onBack} isInFocus={isInFocus} onGameClick={onEntryClick} - onSelectGameByGamepad={onSelectEntryByGamepad} {...getTestId("entries")} /> ) diff --git a/app/routes/categories.tsx b/app/customRoutes/categories.tsx similarity index 100% rename from app/routes/categories.tsx rename to app/customRoutes/categories.tsx diff --git a/buildConfig/remix.config.js b/buildConfig/remix.config.js new file mode 100644 index 00000000..1aaa9dc5 --- /dev/null +++ b/buildConfig/remix.config.js @@ -0,0 +1,46 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var createErrorDialog = function (route, id) { + route("errorDialog", "customRoutes/categories.$category.errorDialog.tsx", { + id: "".concat(id, "ErrorDialog"), + }); +}; +var createSettingsRoutes = function (route, id) { + route("settings", "customRoutes/categories.$category.settings.tsx", { + id: id, + }, function () { + route("", "customRoutes/categories.$category.settings._index.tsx", { + index: true, + id: "".concat(id, "SettingsIndex"), + }); + route("general", "customRoutes/categories.$category.settings.general.tsx", { + id: "".concat(id, "SettingsGeneral"), + }, function () { + createErrorDialog(route, "".concat(id, "SettingsGeneral")); + }); + route("appearance", "customRoutes/categories.$category.settings.appearance.tsx", { + id: "".concat(id, "SettingsAppearance"), + }); + }); +}; +var createCategoriesRoutes = function (route) { + route("categories", "customRoutes/categories.tsx", {}, function () { + route(":category", "customRoutes/categories.$category.tsx", function () { + createSettingsRoutes(route, "category"); + createErrorDialog(route, "category"); + }); + }); +}; +var appConfig = { + appDirectory: "app", + serverModuleFormat: "cjs", + routes: function (defineRoutes) { + return defineRoutes(function (route) { + route("/", "customRoutes/_index.tsx", { index: true }); + createCategoriesRoutes(route); + createSettingsRoutes(route, "initial"); + }); + }, + postcss: true, +}; +exports.default = appConfig; diff --git a/configsToTs/remix.config.ts b/configsToTs/remix.config.ts new file mode 100644 index 00000000..8c3307ef --- /dev/null +++ b/configsToTs/remix.config.ts @@ -0,0 +1,65 @@ +import type { AppConfig } from "@remix-run/dev"; +import type { DefineRouteFunction } from "@remix-run/dev/dist/config/routes"; + +const createErrorDialog = (route: DefineRouteFunction, id: string) => { + route("errorDialog", "customRoutes/categories.$category.errorDialog.tsx", { + id: `${id}ErrorDialog`, + }); +}; + +const createSettingsRoutes = (route: DefineRouteFunction, id: string) => { + route( + "settings", + "customRoutes/categories.$category.settings.tsx", + { + id, + }, + () => { + route("", "customRoutes/categories.$category.settings._index.tsx", { + index: true, + id: `${id}SettingsIndex`, + }); + route( + "general", + "customRoutes/categories.$category.settings.general.tsx", + { + id: `${id}SettingsGeneral`, + }, + () => { + createErrorDialog(route, `${id}SettingsGeneral`); + }, + ); + route( + "appearance", + "customRoutes/categories.$category.settings.appearance.tsx", + { + id: `${id}SettingsAppearance`, + }, + ); + }, + ); +}; + +const createCategoriesRoutes = (route: DefineRouteFunction) => { + route("categories", "customRoutes/categories.tsx", {}, () => { + route(":category", "customRoutes/categories.$category.tsx", () => { + createSettingsRoutes(route, "category"); + createErrorDialog(route, "category"); + }); + }); +}; + +const appConfig: AppConfig = { + appDirectory: "app", + serverModuleFormat: "cjs", + routes(defineRoutes) { + return defineRoutes((route) => { + route("/", "customRoutes/_index.tsx", { index: true }); + createCategoriesRoutes(route); + createSettingsRoutes(route, "initial"); + }); + }, + postcss: true, +}; + +module.exports = appConfig; diff --git a/configsToTs/tsconfig.json b/configsToTs/tsconfig.json new file mode 100644 index 00000000..7eef139a --- /dev/null +++ b/configsToTs/tsconfig.json @@ -0,0 +1,12 @@ +{ + "include": ["remix.config.ts"], + "compilerOptions": { + "esModuleInterop": true, + "module": "commonjs", + "noImplicitAny": true, + "strict": true, + "baseUrl": ".", + "skipLibCheck": true, + "outDir": "../buildConfig" + } +} diff --git a/remix.config.js b/remix.config.js index 74a4028b..dff74ff7 100644 --- a/remix.config.js +++ b/remix.config.js @@ -1,29 +1,46 @@ -/** @type {import('@remix-run/dev/config').AppConfig} */ -module.exports = { - appDirectory: "app", - serverModuleFormat: "cjs", - routes(defineRoutes) { - return defineRoutes((route) => { - route( - "/settings", - "routes/categories.$category.settings.tsx", - { - id: "initial", - }, - () => { - route("general", "routes/categories.$category.settings.general.tsx", { - id: "initialGeneral", - }); - route( - "appearance", - "routes/categories.$category.settings.appearance.tsx", - { - id: "initialAppearance", - }, - ); - }, - ); +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var createErrorDialog = function (route, id) { + route("errorDialog", "customRoutes/categories.$category.errorDialog.tsx", { + id: "".concat(id, "ErrorDialog"), }); - }, - postcss: true, }; +var createSettingsRoutes = function (route, id) { + route("settings", "customRoutes/categories.$category.settings.tsx", { + id: id, + }, function () { + route("", "customRoutes/categories.$category.settings._index.tsx", { + index: true, + id: "".concat(id, "SettingsIndex"), + }); + route("general", "customRoutes/categories.$category.settings.general.tsx", { + id: "".concat(id, "SettingsGeneral"), + }, function () { + createErrorDialog(route, "".concat(id, "SettingsGeneral")); + }); + route("appearance", "customRoutes/categories.$category.settings.appearance.tsx", { + id: "".concat(id, "SettingsAppearance"), + }); + }); +}; +var createCategoriesRoutes = function (route) { + route("categories", "customRoutes/categories.tsx", {}, function () { + route(":category", "customRoutes/categories.$category.tsx", function () { + createSettingsRoutes(route, "category"); + createErrorDialog(route, "category"); + }); + }); +}; +var appConfig = { + appDirectory: "app", + serverModuleFormat: "cjs", + routes: function (defineRoutes) { + return defineRoutes(function (route) { + route("/", "customRoutes/_index.tsx", { index: true }); + createCategoriesRoutes(route); + createSettingsRoutes(route, "initial"); + }); + }, + postcss: true, +}; +module.exports = appConfig;