feat: implemented storage management

fix: Enabled fallback secrets
feat: Made header stats actually work
feat: Made steam deck keyboard auto open for some inputs
fix: Made keybaord also work with shortcuts (no tooltips yet)
This commit is contained in:
Simeon Radivoev 2026-02-24 00:30:16 +02:00
parent 62f16cbcc1
commit e4df8fb9fb
Signed by: simeonradivoev
GPG key ID: C16C2132A7660C8E
55 changed files with 1675 additions and 398 deletions

View file

@ -2,20 +2,19 @@ import { FocusContext, FocusDetails, setFocus, useFocusable } from "@noriginmedi
import classNames from "classnames";
import { createContext, JSX, useContext, useEffect } from "react";
import { twMerge } from "tailwind-merge";
import { useEventListener } from "usehooks-ts";
import { X } from "lucide-react";
import { GamePadButtonCode, useShortcuts } from "../scripts/shortcuts";
import { GamePadButtonCode, Shortcut, useShortcuts } from "../scripts/shortcuts";
const ContextDialogContext = createContext({} as {
close: () => void,
id: string;
});
export function ContextList (data: { options: DialogEntry[]; className?: string; showCloseButton?: boolean; })
export function ContextList (data: { options?: DialogEntry[]; className?: string; showCloseButton?: boolean; })
{
const context = useContext(ContextDialogContext);
return <ul className={twMerge("list max-h-[70vh] overflow-y-auto", data.className)}>
{data.options.map(o => <OptionElement className="list-row" key={o.id} {...o} />)}
return <ul className={twMerge("list", data.className)}>
{data.options?.map(o => <OptionElement className="list-row" key={o.id} {...o} />)}
{data.showCloseButton !== false && <OptionElement className="list-row" type='accent' icon={<X />} action={context.close} id="close" content="Close" />}
</ul>;
}
@ -29,30 +28,37 @@ export function OptionElement (data: DialogEntry & { onFocus?: () => void; class
data.onFocus?.();
};
const handleAction = data.action ? () => data.action?.({ close: context.close, focus: focusSelf }) : undefined;
const { ref, focused, focusSelf } = useFocusable({
const { ref, focused, focusSelf, focusKey, hasFocusedChild } = useFocusable({
focusKey: `${context.id}-list-option-${data.id}`,
onEnterPress: handleAction,
onFocus: handleFocus
onFocus: handleFocus,
trackChildren: typeof data.content !== 'string'
});
const colors = {
primary: classNames("hover:bg-primary/40", { "bg-primary text-primary-content": focused }),
secondary: classNames("hover:bg-secondary/40", { "bg-secondary text-secondary-content": focused }),
accent: classNames("hover:bg-accent/40", { "bg-accent text-accent-content": focused }),
info: classNames("hover:bg-info/40", { "bg-info text-info-content": focused }),
warning: classNames("hover:bg-warning/40", { "bg-warning text-warning-content": focused }),
error: classNames("hover:bg-error/40", { "bg-error text-error-content": focused })
primary: classNames("hover:bg-primary/40", { "bg-primary text-primary-content": focused || hasFocusedChild }),
secondary: classNames("hover:bg-secondary/40", { "bg-secondary text-secondary-content": focused || hasFocusedChild }),
accent: classNames("hover:bg-accent/40", { "bg-accent text-accent-content": focused || hasFocusedChild }),
info: classNames("hover:bg-info/40", { "bg-info text-info-content": focused || hasFocusedChild }),
warning: classNames("hover:bg-warning/40", { "bg-warning text-warning-content": focused || hasFocusedChild }),
error: classNames("hover:bg-error/40", { "bg-error text-error-content": focused || hasFocusedChild })
};
if (data.shortcuts)
{
useShortcuts(focusKey, () => data.shortcuts!, [data.shortcuts]);
}
return <li ref={ref}
onClick={handleAction}
className={
twMerge("flex cursor-pointer")}>
<p className={twMerge("flex w-full h-14 items-center px-5 rounded-2xl transition-all gap-2",
colors[data.type],
classNames({ "font-semibold": focused }),
data.className)}>
{data.icon}
{data.content}
</p>
<FocusContext value={focusKey}>
<div className={twMerge("flex w-full h-14 items-center px-4 rounded-2xl transition-all gap-2",
colors[data.type],
classNames({ "font-semibold": focused || hasFocusedChild }),
data.className)}>
{data.icon}
{data.content}
</div>
</FocusContext>
</li>;
}
@ -63,11 +69,23 @@ export interface DialogEntry
icon?: string | JSX.Element;
type: 'primary' | 'secondary' | 'accent' | 'info' | 'warning' | 'error';
action?: (ctx: { close: () => void, focus: (focusDetails?: FocusDetails | undefined) => void; }) => void;
shortcuts?: Shortcut[];
}
export function ContextDialog (data: { id: string, children: any | any[], open: boolean, close: () => void; })
export function ContextDialog (data: {
id: string,
children: any | any[],
open: boolean, close: () => void;
className?: string;
preferredChildFocusKey?: string;
})
{
const { ref, focusKey, focusSelf } = useFocusable({ focusable: data.open, focusKey: `${data.id}-context-dialog`, isFocusBoundary: true });
const { ref, focusKey, focusSelf } = useFocusable({
focusable: data.open,
focusKey: `${data.id}-context-dialog`,
isFocusBoundary: true,
preferredChildFocusKey: data.preferredChildFocusKey
});
useEffect(() =>
{
if (data.open)
@ -76,14 +94,14 @@ export function ContextDialog (data: { id: string, children: any | any[], open:
}
}, [data.open]);
useShortcuts(focusKey, () => [{
useShortcuts(focusKey, () => data.open ? [{
label: "Close",
button: GamePadButtonCode.B,
action: () =>
{
data.close();
}
}], []);
}] : [], [data.open]);
return <dialog ref={ref} open={data.open} closedby="any" className={
twMerge("absolute modal cursor-pointer bg-base-300/80 backdrop-brightness-50 duration-300 ease-in-out transition-all text-base-content",
@ -96,7 +114,11 @@ export function ContextDialog (data: { id: string, children: any | any[], open:
<FocusContext value={focusKey}>
<ContextDialogContext value={{ id: data.id, close: data.close }} >
<div
className={twMerge("bg-base-100/80 delay-200 rounded-4xl p-6 min-w-[30vw] cursor-auto", data.open ? "animate-scale-delayed" : "opacity-0")}
className={twMerge(
"bg-base-100/80 delay-200 rounded-4xl p-6 min-w-[30vw] cursor-auto",
data.open ? "animate-scale-delayed" : "opacity-0",
data.className)
}
style={{ backdropFilter: 'blur(24px)' }}
onClick={(e) => e.stopPropagation()}
>