diff --git a/src/bun/api/games/games.ts b/src/bun/api/games/games.ts
index 2e8f957..6196cc2 100644
--- a/src/bun/api/games/games.ts
+++ b/src/bun/api/games/games.ts
@@ -72,6 +72,10 @@ export default new Elysia()
}
return status('Not Found');
}, { query: z.object({ blur: z.coerce.number().optional(), width: z.coerce.number().optional(), height: z.coerce.number().optional() }) })
+ .get('/image', async ({ query }) =>
+ {
+ return processImage(query.url, query);
+ }, { query: z.object({ url: z.url(), blur: z.coerce.number().optional(), width: z.coerce.number().optional(), height: z.coerce.number().optional() }) })
.get('/screenshot/:id', async ({ params: { id }, query, set }) =>
{
const screenshot = await db.query.screenshots.findFirst({ where: eq(schema.screenshots.id, id), columns: { content: true, type: true } });
diff --git a/src/bun/api/games/platforms.ts b/src/bun/api/games/platforms.ts
index 31a73a2..731b026 100644
--- a/src/bun/api/games/platforms.ts
+++ b/src/bun/api/games/platforms.ts
@@ -1,5 +1,5 @@
import Elysia, { status } from "elysia";
-import { getPlatformApiPlatformsIdGet, getPlatformsApiPlatformsGet } from "@clients/romm";
+import { getPlatformApiPlatformsIdGet, getPlatformsApiPlatformsGet, getRomsApiRomsGet } from "@clients/romm";
import z from "zod";
import { count, eq, getTableColumns, notInArray } from "drizzle-orm";
import { db } from "../app";
@@ -22,8 +22,9 @@ export default new Elysia()
if (rommPlatforms)
{
- const frontEndPlatforms = rommPlatforms.map(p =>
+ const frontEndPlatforms = await Promise.all(rommPlatforms.map(async p =>
{
+ const game = await getRomsApiRomsGet({ query: { platform_ids: [p.id] } });
const platform: FrontEndPlatformType = {
slug: p.slug,
name: p.display_name,
@@ -32,18 +33,20 @@ export default new Elysia()
game_count: p.rom_count,
updated_at: new Date(p.updated_at),
id: { source: 'romm', id: p.id },
- hasLocal: localPlatformSet.has(p.slug)
+ hasLocal: localPlatformSet.has(p.slug),
+ paths_screenshots: game.data?.items[0]?.merged_screenshots.map(s => `/api/romm/image/romm/${s}`) ?? []
};
return platform;
- });
+ }));
rommPlatformsSet = new Set(rommPlatforms.map(p => p.slug));
platforms.push(...frontEndPlatforms);
}
- platforms.push(...localPlatforms.filter(p => !rommPlatformsSet?.has(p.slug)).map(p =>
+ platforms.push(...await Promise.all(localPlatforms.filter(p => !rommPlatformsSet?.has(p.slug)).map(async p =>
{
+ const game = await db.query.games.findFirst({ where: eq(schema.games.platform_id, p.id), with: { screenshots: true }, columns: {} });
const platform: FrontEndPlatformType = {
slug: p.slug,
name: p.name,
@@ -52,11 +55,13 @@ export default new Elysia()
game_count: p.game_count,
updated_at: p.created_at,
id: { source: 'local', id: p.id },
- hasLocal: true
+ hasLocal: true,
+ paths_screenshots: game?.screenshots?.map(s => `/api/romm/screenshot/${s.id}`) ?? []
+
};
return platform;
- }));
+ })));
return { platforms };
}).get('/platforms/:source/:id', async ({ params: { source, id } }) =>
diff --git a/src/bun/browser.ts b/src/bun/browser.ts
index 4bc6833..01744f7 100644
--- a/src/bun/browser.ts
+++ b/src/bun/browser.ts
@@ -24,7 +24,7 @@ export default async function init (events: EventEmitter, forceBrowser: boolean)
async function runWebview (events: EventEmitter)
{
- const webviewWorker = new Worker(Bun.env.IS_BINARY ? new URL(`./webview/${os.platform()}`, import.meta.url).href : `./webview/${os.platform()}.ts`, {
+ const webviewWorker = new Worker(new URL(`./webview/${os.platform()}`, import.meta.url).href, {
smol: true,
ref: false
});
diff --git a/src/mainview/components/AnimatedBackground.tsx b/src/mainview/components/AnimatedBackground.tsx
index 97227a4..482a674 100644
--- a/src/mainview/components/AnimatedBackground.tsx
+++ b/src/mainview/components/AnimatedBackground.tsx
@@ -3,6 +3,7 @@ import classNames from 'classnames';
import { createContext, JSX, Ref, useContext, useEffect, useState } from 'react';
import { twMerge } from 'tailwind-merge';
import { useSessionStorage } from 'usehooks-ts';
+import { useLocalSetting } from '../scripts/utils';
export const AnimatedBackgroundContext = createContext({} as { setBackground: (url: string) => void; });
@@ -28,8 +29,13 @@ export function AnimatedBackground (data: {
setBackgroundUrl(data.backgroundUrl ? (data.backgroundUrl instanceof URL ? data.backgroundUrl.href : data.backgroundUrl) : undefined);
}, [data.backgroundUrl]);
- const finalBackgroundUrl = backgroundUrl ? new URL(backgroundUrl) : undefined;
- const blur = localStorage.getItem('background-blur') !== "false";
+ let finalBackgroundUrl;
+ try
+ {
+ finalBackgroundUrl = backgroundUrl ? new URL(backgroundUrl) : undefined;
+ } catch { }
+
+ const blur = useLocalSetting('backgroundBlur');
if (blur)
{
if (!finalBackgroundUrl?.searchParams.has('blur'))
diff --git a/src/mainview/components/ContextDialog.tsx b/src/mainview/components/ContextDialog.tsx
index 1690899..12e9f21 100644
--- a/src/mainview/components/ContextDialog.tsx
+++ b/src/mainview/components/ContextDialog.tsx
@@ -15,7 +15,7 @@ export function ContextList (data: { options?: DialogEntry[]; className?: string
const context = useContext(ContextDialogContext);
return
{data.options?.map(o => )}
- {data.showCloseButton !== false && } action={context.close} id="close" content="Close" />}
+ {data.showCloseButton !== false && } action={() => context.close()} id="close-context-dialog" content="Close" />}
;
}
@@ -30,7 +30,7 @@ export function OptionElement (data: DialogEntry & { onFocus?: () => void; class
const handleAction = data.action ? () => data.action?.({ close: context.close, focus: focusSelf }) : undefined;
const { ref, focused, focusSelf, focusKey, hasFocusedChild } = useFocusable({
focusKey: `${context.id}-list-option-${data.id}`,
- onEnterPress: data.shortcuts ? handleAction : undefined,
+ onEnterPress: data.shortcuts ? undefined : handleAction,
onFocus: handleFocus,
trackChildren: typeof data.content !== 'string'
});
@@ -52,9 +52,9 @@ export function OptionElement (data: DialogEntry & { onFocus?: () => void; class
twMerge("flex cursor-pointer sm:text-sm md:text-base")}>
+ data.className,
+ colors[data.type])}>
{data.icon}
{data.content}
@@ -75,7 +75,8 @@ export interface DialogEntry
export function ContextDialog (data: {
id: string,
children: any | any[],
- open: boolean, close: () => void;
+ open: boolean,
+ close: () => void;
className?: string;
preferredChildFocusKey?: string;
})
diff --git a/src/mainview/components/GameList.tsx b/src/mainview/components/GameList.tsx
index c79c1e9..12c0b3e 100644
--- a/src/mainview/components/GameList.tsx
+++ b/src/mainview/components/GameList.tsx
@@ -8,6 +8,7 @@ import { HardDrive } from "lucide-react";
import { JSX } from "react";
import { GameCardFocusHandler } from "./GameCard";
import { gameQuery } from "../scripts/queries";
+import { useLocalSetting } from "../scripts/utils";
export interface GameListParams
{
@@ -30,6 +31,7 @@ export function GameList (data: GameListParams)
});
const navigator = useNavigate();
const queryClient = useQueryClient();
+ const blur = useLocalSetting('backgroundBlur');
const handleFocus = (id: FrontEndId, source: string | null, sourceId: number | null) =>
{
@@ -40,10 +42,9 @@ export function GameList (data: GameListParams)
{
const screenshotUrl = new URL(`${RPC_URL(__HOST__)}${game.paths_screenshots[new Date().getMinutes() % game.paths_screenshots.length]}`);
const coverUrl = new URL(`${RPC_URL(__HOST__)}${game.path_cover}`);
- const previewUrl = localStorage.getItem('background-blur') !== "false" ? coverUrl : screenshotUrl;
+ const previewUrl = blur ? coverUrl : screenshotUrl;
previewUrl.searchParams.delete('ts');
data.setBackground?.(previewUrl.href);
- //queryClient.prefetchQuery(gameQuery(source ?? id.source, sourceId ?? id.id));
} catch
{
diff --git a/src/mainview/components/PlatformsList.tsx b/src/mainview/components/PlatformsList.tsx
index 8b9c15a..600efc1 100644
--- a/src/mainview/components/PlatformsList.tsx
+++ b/src/mainview/components/PlatformsList.tsx
@@ -42,7 +42,7 @@ export function PlatformsList (data: { id: string, setBackground: (url: string)
previewUrl: "",
badges,
onFocus: () => data.setBackground(
- `https://picsum.photos/id/${10 + i}/100/100.webp?blur=10`,
+ g.paths_screenshots.length > 0 ? `${RPC_URL(__HOST__)}${g.paths_screenshots[new Date().getMinutes() % g.paths_screenshots.length]}` : `${RPC_URL(__HOST__)}/api/romm/image?url=https://picsum.photos/id/${10 + i}/1280/720.webp`,
),
onSelect: () =>
{
diff --git a/src/mainview/components/options/LocalOption.tsx b/src/mainview/components/options/LocalOption.tsx
new file mode 100644
index 0000000..115eea7
--- /dev/null
+++ b/src/mainview/components/options/LocalOption.tsx
@@ -0,0 +1,59 @@
+import { HTMLInputTypeAttribute, JSX } from "react";
+import { LocalSettingsSchema, LocalSettingsType } from "../../../shared/constants";
+import { OptionSpace } from "./OptionSpace";
+import { OptionInput } from "./OptionInput";
+import { useLocalStorage } from "usehooks-ts";
+import { OptionDropdown } from "./OptionDropdown";
+
+export function LocalOption (data: {
+ label: string;
+ id: keyof LocalSettingsType;
+ type: HTMLInputTypeAttribute | 'dropdown';
+ placeholder?: string;
+ values?: string[];
+ icon?: JSX.Element;
+ children?: any;
+})
+{
+ const [localValue, setLocalValue] = useLocalStorage(data.id, LocalSettingsSchema.shape[data.id].parse(undefined), { deserializer: (v) => LocalSettingsSchema.shape[data.id].parse(JSON.parse(v)) });
+
+ return (
+
+ {data.type === 'dropdown' && data.values &&
+ {
+ if (data.type === 'checkbox')
+ {
+ setLocalValue(v);
+ } else
+ {
+ setLocalValue(v);
+ }
+ }}
+ value={localValue} />}
+ {data.type !== 'dropdown' &&
+ {
+ if (data.type === 'checkbox')
+ {
+ setLocalValue(v);
+ } else
+ {
+ setLocalValue(v);
+ }
+ }}
+ value={localValue}
+ />}
+ {data.children}
+
+ );
+}
\ No newline at end of file
diff --git a/src/mainview/components/options/OptionDropdown.tsx b/src/mainview/components/options/OptionDropdown.tsx
new file mode 100644
index 0000000..6b62841
--- /dev/null
+++ b/src/mainview/components/options/OptionDropdown.tsx
@@ -0,0 +1,67 @@
+import classNames from "classnames";
+import { ChangeEventHandler, FocusEventHandler, HTMLInputAutoCompleteAttribute, HTMLInputTypeAttribute, JSX, useRef, useState } from "react";
+import { twMerge } from "tailwind-merge";
+import { useOptionContext } from "./OptionSpace";
+import { FocusContext, useFocusable } from "@noriginmedia/norigin-spatial-navigation";
+import { systemApi } from "../../scripts/clientApi";
+import { ContextDialog, ContextList, DialogEntry } from "../ContextDialog";
+import { ChevronDown } from "lucide-react";
+
+export function OptionDropdown (data: {
+ name: string;
+ type: HTMLInputTypeAttribute;
+ className?: string;
+ placeholder?: string;
+ icon?: JSX.Element;
+ value?: string;
+ values: string[];
+ defaultValue?: string | boolean;
+ autocomplete?: HTMLInputAutoCompleteAttribute;
+ onBlur?: FocusEventHandler;
+ onChange?: (value: any) => void;
+})
+{
+ const [open, setOpen] = useState(false);
+ const handlePress = () =>
+ {
+ setOpen(true);
+ };
+ const handleClose = () => setOpen(false);
+ const { ref, focused, focusKey } = useFocusable({
+ focusKey: data.name, onEnterPress: handlePress
+ });
+ const inputRef = useRef(null);
+ const option = useOptionContext({
+ onOptionEnterPress: handlePress,
+ });
+
+ const valueIndex = data.value ? data.values?.indexOf(data.value) : -1;
+
+ return (
+ <>
+
+ {open &&
+ ({
+ content: v,
+ id: String(i),
+ type: 'primary',
+ action: () =>
+ {
+ data.onChange?.(v);
+ setOpen(false);
+ }
+ } satisfies DialogEntry))} />
+ }
+ >
+ );
+}
\ No newline at end of file
diff --git a/src/mainview/components/options/OptionInput.tsx b/src/mainview/components/options/OptionInput.tsx
index 2f708d8..f279466 100644
--- a/src/mainview/components/options/OptionInput.tsx
+++ b/src/mainview/components/options/OptionInput.tsx
@@ -4,6 +4,7 @@ import { twMerge } from "tailwind-merge";
import { useOptionContext } from "./OptionSpace";
import { useFocusable } from "@noriginmedia/norigin-spatial-navigation";
import { systemApi } from "../../scripts/clientApi";
+import { Check, CheckIcon, X } from "lucide-react";
export function OptionInput (data: {
name: string;
@@ -12,24 +13,28 @@ export function OptionInput (data: {
placeholder?: string;
icon?: JSX.Element;
value?: string;
- defaultValue?: string;
+ defaultValue?: string | boolean;
autocomplete?: HTMLInputAutoCompleteAttribute;
onBlur?: FocusEventHandler;
- onChange?: ChangeEventHandler;
+ onChange?: (value: any) => void;
})
{
- const { ref, focused } = useFocusable({
- focusKey: data.name, onEnterPress: () =>
+ const handlePress = () =>
+ {
+ if (data.type === 'checkbox')
+ {
+ inputRef.current?.click();
+ } else
{
inputRef.current?.focus();
}
+ };
+ const { ref, focused } = useFocusable({
+ focusKey: data.name, onEnterPress: handlePress
});
const inputRef = useRef(null);
const option = useOptionContext({
- onOptionEnterPress ()
- {
- inputRef.current?.focus();
- },
+ onOptionEnterPress: handlePress,
});
const handleFocus = () =>
{
@@ -44,32 +49,60 @@ export function OptionInput (data: {
Height: rect.height
});
}
-
};
return (
);
}
\ No newline at end of file
diff --git a/src/mainview/components/options/OptionSpace.tsx b/src/mainview/components/options/OptionSpace.tsx
index 6148e3b..40764f0 100644
--- a/src/mainview/components/options/OptionSpace.tsx
+++ b/src/mainview/components/options/OptionSpace.tsx
@@ -43,7 +43,7 @@ export function OptionSpace (data: {
className?: string;
focusable?: boolean;
children?: any | any[];
- label?: string | JSX.Element;
+ label?: string | JSX.Element | ((focused: boolean) => JSX.Element);
saveLastFocusedChild?: boolean;
})
{
@@ -62,32 +62,37 @@ export function OptionSpace (data: {
eventTarget.dispatchEvent(new CustomEvent("onEnterPress"));
},
});
+ let labelElement: any = data.label;
+ if (data.label instanceof Function)
+ {
+ labelElement = data.label(focused);
+ } else if (typeof data.label === 'string')
+ {
+ labelElement = ;
+ }
return (
-
- {typeof data.label === "string" ? (
-
- ) : (
- data.label
- )}
-
-
+ {!!labelElement &&
+ {labelElement}
+
}
+
{data.children}
diff --git a/src/mainview/components/options/PathSettingsOption.tsx b/src/mainview/components/options/PathSettingsOption.tsx
index 4ce4a78..f441010 100644
--- a/src/mainview/components/options/PathSettingsOption.tsx
+++ b/src/mainview/components/options/PathSettingsOption.tsx
@@ -119,7 +119,7 @@ export function PathSettingsOptionBase (data: PathSettingsOptionParams & {
onBlur={handleInputBlur}
onChange={(e) =>
{
- data.setLocalValue(e.currentTarget.value);
+ data.setLocalValue(e);
}}
value={data.localValue}
/>
diff --git a/src/mainview/components/options/SettingsAppForm.tsx b/src/mainview/components/options/SettingsAppForm.tsx
index bb36a3e..e957103 100644
--- a/src/mainview/components/options/SettingsAppForm.tsx
+++ b/src/mainview/components/options/SettingsAppForm.tsx
@@ -3,7 +3,6 @@ import { HTMLInputTypeAttribute, JSX } from "react";
import { OptionInput } from "./OptionInput";
import { OptionSpace } from "./OptionSpace";
import classNames from "classnames";
-import { TriangleAlert } from "lucide-react";
// export useFieldContext for use in your custom components
export const { fieldContext, formContext, useFieldContext } =
@@ -30,7 +29,7 @@ function FormOption (data: { type: HTMLInputTypeAttribute, icon?: JSX.Element; l
name={field.name}
value={field.state.value}
type={data.type}
- onChange={e => field.handleChange(e.target.value)}
+ onChange={v => field.handleChange(v)}
placeholder={data.placeholder}
className={classNames({ " flex-3 ring-4 ring-accent": field.getMeta().isDirty })}
/>
diff --git a/src/mainview/components/options/SettingsOption.tsx b/src/mainview/components/options/SettingsOption.tsx
index fab772c..de5fcb7 100644
--- a/src/mainview/components/options/SettingsOption.tsx
+++ b/src/mainview/components/options/SettingsOption.tsx
@@ -61,9 +61,9 @@ export function SettingsOption (data: {
type={data.type}
placeholder={data.placeholder}
onBlur={handleSave}
- onChange={(e) =>
+ onChange={(v) =>
{
- setLocalValue(e.currentTarget.value);
+ setLocalValue(v);
setDirty(true);
}}
value={localValue}
diff --git a/src/mainview/gen/routeTree.gen.ts b/src/mainview/gen/routeTree.gen.ts
index 5ce734a..e78cc2d 100644
--- a/src/mainview/gen/routeTree.gen.ts
+++ b/src/mainview/gen/routeTree.gen.ts
@@ -11,6 +11,7 @@
import { Route as rootRouteImport } from './../routes/__root'
import { Route as SettingsRouteRouteImport } from './../routes/settings/route'
import { Route as IndexRouteImport } from './../routes/index'
+import { Route as SettingsInterfaceRouteImport } from './../routes/settings/interface'
import { Route as SettingsEmulatorsRouteImport } from './../routes/settings/emulators'
import { Route as SettingsDirectoriesRouteImport } from './../routes/settings/directories'
import { Route as SettingsAccountsRouteImport } from './../routes/settings/accounts'
@@ -30,6 +31,11 @@ const IndexRoute = IndexRouteImport.update({
path: '/',
getParentRoute: () => rootRouteImport,
} as any)
+const SettingsInterfaceRoute = SettingsInterfaceRouteImport.update({
+ id: '/interface',
+ path: '/interface',
+ getParentRoute: () => SettingsRouteRoute,
+} as any)
const SettingsEmulatorsRoute = SettingsEmulatorsRouteImport.update({
id: '/emulators',
path: '/emulators',
@@ -79,6 +85,7 @@ export interface FileRoutesByFullPath {
'/settings/accounts': typeof SettingsAccountsRoute
'/settings/directories': typeof SettingsDirectoriesRoute
'/settings/emulators': typeof SettingsEmulatorsRoute
+ '/settings/interface': typeof SettingsInterfaceRoute
'/game/$source/$id': typeof GameSourceIdRoute
'/launcher/$source/$id': typeof LauncherSourceIdRoute
'/platform/$source/$id': typeof PlatformSourceIdRoute
@@ -91,6 +98,7 @@ export interface FileRoutesByTo {
'/settings/accounts': typeof SettingsAccountsRoute
'/settings/directories': typeof SettingsDirectoriesRoute
'/settings/emulators': typeof SettingsEmulatorsRoute
+ '/settings/interface': typeof SettingsInterfaceRoute
'/game/$source/$id': typeof GameSourceIdRoute
'/launcher/$source/$id': typeof LauncherSourceIdRoute
'/platform/$source/$id': typeof PlatformSourceIdRoute
@@ -104,6 +112,7 @@ export interface FileRoutesById {
'/settings/accounts': typeof SettingsAccountsRoute
'/settings/directories': typeof SettingsDirectoriesRoute
'/settings/emulators': typeof SettingsEmulatorsRoute
+ '/settings/interface': typeof SettingsInterfaceRoute
'/game/$source/$id': typeof GameSourceIdRoute
'/launcher/$source/$id': typeof LauncherSourceIdRoute
'/platform/$source/$id': typeof PlatformSourceIdRoute
@@ -118,6 +127,7 @@ export interface FileRouteTypes {
| '/settings/accounts'
| '/settings/directories'
| '/settings/emulators'
+ | '/settings/interface'
| '/game/$source/$id'
| '/launcher/$source/$id'
| '/platform/$source/$id'
@@ -130,6 +140,7 @@ export interface FileRouteTypes {
| '/settings/accounts'
| '/settings/directories'
| '/settings/emulators'
+ | '/settings/interface'
| '/game/$source/$id'
| '/launcher/$source/$id'
| '/platform/$source/$id'
@@ -142,6 +153,7 @@ export interface FileRouteTypes {
| '/settings/accounts'
| '/settings/directories'
| '/settings/emulators'
+ | '/settings/interface'
| '/game/$source/$id'
| '/launcher/$source/$id'
| '/platform/$source/$id'
@@ -172,6 +184,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof IndexRouteImport
parentRoute: typeof rootRouteImport
}
+ '/settings/interface': {
+ id: '/settings/interface'
+ path: '/interface'
+ fullPath: '/settings/interface'
+ preLoaderRoute: typeof SettingsInterfaceRouteImport
+ parentRoute: typeof SettingsRouteRoute
+ }
'/settings/emulators': {
id: '/settings/emulators'
path: '/emulators'
@@ -236,6 +255,7 @@ interface SettingsRouteRouteChildren {
SettingsAccountsRoute: typeof SettingsAccountsRoute
SettingsDirectoriesRoute: typeof SettingsDirectoriesRoute
SettingsEmulatorsRoute: typeof SettingsEmulatorsRoute
+ SettingsInterfaceRoute: typeof SettingsInterfaceRoute
}
const SettingsRouteRouteChildren: SettingsRouteRouteChildren = {
@@ -243,6 +263,7 @@ const SettingsRouteRouteChildren: SettingsRouteRouteChildren = {
SettingsAccountsRoute: SettingsAccountsRoute,
SettingsDirectoriesRoute: SettingsDirectoriesRoute,
SettingsEmulatorsRoute: SettingsEmulatorsRoute,
+ SettingsInterfaceRoute: SettingsInterfaceRoute,
}
const SettingsRouteRouteWithChildren = SettingsRouteRoute._addFileChildren(
diff --git a/src/mainview/index.css b/src/mainview/index.css
index a039004..644e044 100644
--- a/src/mainview/index.css
+++ b/src/mainview/index.css
@@ -2,9 +2,12 @@
@import 'animate.css';
@plugin "daisyui";
+@custom-variant dark (&:where([data-theme=dark], [data-theme=dark] *));
+
@theme {
--breakpoint-sm: 0px;
--breakpoint-md: 1280px;
+ --page-scroll-bg: transparent;
--animate-wiggle: wiggle 0.3s ease-in-out 1;
--animate-rotate: rotate 0.3s ease-in-out 1 0.2s;
diff --git a/src/mainview/index.tsx b/src/mainview/index.tsx
index 4d09964..4087b63 100644
--- a/src/mainview/index.tsx
+++ b/src/mainview/index.tsx
@@ -49,8 +49,6 @@ export const Router = createRouter({
},
});
-
-
// Register things for typesafety
declare module "@tanstack/react-router" {
interface Register
diff --git a/src/mainview/routes/__root.tsx b/src/mainview/routes/__root.tsx
index 943c280..0b6cade 100644
--- a/src/mainview/routes/__root.tsx
+++ b/src/mainview/routes/__root.tsx
@@ -4,7 +4,7 @@ import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
import { RouterContext } from "..";
import Notifications from "../components/Notifications";
import { Toaster } from "react-hot-toast";
-import { mobileCheck } from "../scripts/utils";
+import { mobileCheck, useLocalSetting } from "../scripts/utils";
export const Route = createRootRouteWithContext
()({
component: RootComponent,
@@ -13,9 +13,10 @@ export const Route = createRootRouteWithContext()({
function RootComponent ()
{
const isMobile = mobileCheck();
+ const theme = useLocalSetting('theme');
return (
-
+
diff --git a/src/mainview/routes/settings/accounts.tsx b/src/mainview/routes/settings/accounts.tsx
index 5ce1194..4cd0faa 100644
--- a/src/mainview/routes/settings/accounts.tsx
+++ b/src/mainview/routes/settings/accounts.tsx
@@ -113,6 +113,7 @@ function RouteComponent ()
{
const { focus } = Route.useSearch();
const { ref, focusKey, focusSelf } = useFocusable({
+ focusKey: "accounts",
preferredChildFocusKey: focus
});
diff --git a/src/mainview/routes/settings/directories.tsx b/src/mainview/routes/settings/directories.tsx
index 8cb7050..d0edb9c 100644
--- a/src/mainview/routes/settings/directories.tsx
+++ b/src/mainview/routes/settings/directories.tsx
@@ -41,7 +41,7 @@ function DriveComponent (data: { drive: DownloadsDrive; downloadsSize: number; r
return
void; })
{
- const { ref, focusKey } = useFocusable({ focusKey: 'categories' });
+ const { ref, focused, focusKey } = useFocusable({ focusKey: 'categories' });
return
{[..."ABCDEFGHIJKLMNOPQRSTVWXYZ"].map(c =>
- data.set(c)} content={c} id={c} action={(ctx) => ctx.focus()} type="primary" />
+ data.set(c)} content={c} id={c} action={(ctx) => ctx.focus()} type="primary" />
)}
;
@@ -47,7 +50,7 @@ function EmulatorListType (data: { category: string, action: (e: string) => void
const { ref, focusKey } = useFocusable({ focusKey: 'list-section' });
return
- e.startsWith(data.category)).map(e => ({
+ e.startsWith(data.category)).map(e => ({
id: e,
action: (ctx) =>
{
@@ -152,7 +155,12 @@ function EmulatorPath (data: { id: string; })
};
return (
- {data.id}
{emulators[data.id]}>}>
+ <>
+ {data.id}
+ {emulators[data.id]}
+ >
+ }>
+ onChange={(v) =>
{
- setLocalValue(e.currentTarget.value);
+ setLocalValue(v);
setDirty(true);
}}
value={localValue}
@@ -223,8 +231,9 @@ function EmulatorBadge (data: {
@@ -253,6 +262,7 @@ function RouteComponent ()
{
const { focus } = Route.useSearch();
const { ref, focusKey, focusSelf } = useFocusable({
+ focusKey: "emulators-setting",
preferredChildFocusKey: focus
});
diff --git a/src/mainview/routes/settings/interface.tsx b/src/mainview/routes/settings/interface.tsx
new file mode 100644
index 0000000..a6c74c5
--- /dev/null
+++ b/src/mainview/routes/settings/interface.tsx
@@ -0,0 +1,23 @@
+import { LocalOption } from '@/mainview/components/options/LocalOption';
+import { FocusContext, useFocusable } from '@noriginmedia/norigin-spatial-navigation';
+import { createFileRoute } from '@tanstack/react-router';
+
+export const Route = createFileRoute('/settings/interface')({
+ component: RouteComponent,
+});
+
+function RouteComponent ()
+{
+ const { focus } = Route.useSearch();
+ const { ref, focusKey, focusSelf } = useFocusable({
+ focusKey: "interface-settings",
+ preferredChildFocusKey: focus
+ });
+
+ return
;
+}
diff --git a/src/mainview/routes/settings/route.tsx b/src/mainview/routes/settings/route.tsx
index d3e130d..91f8d01 100644
--- a/src/mainview/routes/settings/route.tsx
+++ b/src/mainview/routes/settings/route.tsx
@@ -83,7 +83,7 @@ function MenuItem (data: {
"group rounded-full p-3 md:pl-5 text-base-content/80",
classNames({
"bg-primary text-primary-content": acitve,
- "font-semibold sm:ring-4 md:ring-7 ring-primary-content": focused && !isPointer,
+ "font-semibold sm:ring-4 md:ring-7 ring-accent": focused && !isPointer,
"bg-secondary text-secondary-content ring-primary": data.return && focused,
}),
data.linkClassName,
@@ -110,7 +110,7 @@ function SettingsMenu (data: {})
return