diff --git a/README.md b/README.md
index 040a745..e3b2c46 100644
--- a/README.md
+++ b/README.md
@@ -96,3 +96,4 @@ Focused on building a simple user experience and intuitive UI as a curated commu
- UI Sounds
- [CC BY 4.0 - Credit: JC Sounds](https://opengameart.org/content/jc-sounds-ui-utility-pack-vol-1)
- [Sounds by: Chhoff](https://chhoffmusic.itch.io/classic-ui-sfx)
+ - [UI Sound Effects by lolurio](https://lolurio.itch.io/lolurios-free-cozy-ui-sfx)
diff --git a/scripts/generate-audio-sprites.ts b/scripts/generate-audio-sprites.ts
index bcce3e8..1625362 100644
--- a/scripts/generate-audio-sprites.ts
+++ b/scripts/generate-audio-sprites.ts
@@ -1,24 +1,31 @@
import audioSprite from 'audiosprite';
-import { $, which } from 'bun';
-import fs from "node:fs/promises";
+import { $ } from 'bun';
import path from 'node:path';
+import { soundMap } from '../src/mainview/scripts/audio/audioConstants';
-var files = await Array.fromAsync(new Bun.Glob('*.{ogg,wav}').scan({ cwd: './src/sounds' }));
+var allFiles = await Array.fromAsync(new Bun.Glob('*.{ogg,wav}').scan({ cwd: './src/sounds' }));
+const files = Object.values(soundMap).map(v =>
+{
+ const existingFile = allFiles.find(f => f.startsWith(v.key));
+ if (!existingFile) throw new Error(`Could not find file for sound ${v.key}`);
+ const filePath = path.join(path.resolve('./src/sounds'), existingFile);
+ return filePath;
+});
console.log("Loaded", files.join(","));
await new Promise((resolve) =>
{
- audioSprite(
- files.map(f => path.join(path.resolve('./src/sounds'), f)),
+ audioSprite(files,
{
output: path.resolve('./src/mainview/assets/sounds'),
path: path.resolve('./src/sounds'),
format: 'howler',
export: 'ogg'
- }, async function (err, obj: any)
- {
- if (err) return console.error(err);
- delete obj.urls;
- Bun.file('./src/mainview/assets/sounds.json').write(JSON.stringify(obj, null, 2)).then(r => resolve(true));
- });
+ },
+ async function (err, obj: any)
+ {
+ if (err) return console.error(err);
+ delete obj.urls;
+ Bun.file('./src/mainview/assets/sounds.json').write(JSON.stringify(obj, null, 2)).then(r => resolve(true));
+ });
});
\ No newline at end of file
diff --git a/src/mainview/App.tsx b/src/mainview/App.tsx
index fb0d2db..76dcda3 100644
--- a/src/mainview/App.tsx
+++ b/src/mainview/App.tsx
@@ -1,7 +1,7 @@
import { getCurrentFocusKey } from "@noriginmedia/norigin-spatial-navigation";
import { Router } from ".";
import { useEffect } from "react";
-import audioCallbacks from "./scripts/audio/audioCallbacks";
+import audioCallbacks from "./scripts/feedbackCallbacks";
import { client as rommClient } from "../clients/romm/client.gen";
import { RPC_URL } from "@/shared/constants";
diff --git a/src/mainview/assets/intro.ogg b/src/mainview/assets/intro.ogg
new file mode 100644
index 0000000..e1505f9
--- /dev/null
+++ b/src/mainview/assets/intro.ogg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:231ac69f71f4a0a770ae4bbfd42db9ea136dad6813ddae68a211c74a16e21778
+size 74296
diff --git a/src/mainview/assets/sounds.json b/src/mainview/assets/sounds.json
index ae3bae3..97b63f9 100644
--- a/src/mainview/assets/sounds.json
+++ b/src/mainview/assets/sounds.json
@@ -1,304 +1,64 @@
{
"sprite": {
- "Classic UI SFX - Chords #1": [
+ "Classic UI SFX - Chords #2": [
0,
4005.215419501134
],
- "Classic UI SFX - Chords #10": [
- 6000,
- 4005.215419501134
- ],
- "Classic UI SFX - Chords #11": [
- 12000,
- 4005.215419501134
- ],
- "Classic UI SFX - Chords #12": [
- 18000,
- 4005.215419501134
- ],
- "Classic UI SFX - Chords #13": [
- 24000,
- 4005.215419501134
- ],
- "Classic UI SFX - Chords #14": [
- 30000,
- 4005.215419501134
- ],
- "Classic UI SFX - Chords #15": [
- 36000,
- 4005.215419501134
- ],
- "Classic UI SFX - Chords #16": [
- 42000,
- 4005.215419501134
- ],
- "Classic UI SFX - Chords #17": [
- 48000,
- 4005.215419501134
- ],
- "Classic UI SFX - Chords #18": [
- 54000,
- 4005.215419501134
- ],
- "Classic UI SFX - Chords #19": [
- 60000,
- 4005.215419501127
- ],
- "Classic UI SFX - Chords #2": [
- 66000,
- 4005.215419501127
- ],
- "Classic UI SFX - Chords #20": [
- 72000,
- 4005.215419501127
- ],
- "Classic UI SFX - Chords #3": [
- 78000,
- 4005.215419501127
- ],
- "Classic UI SFX - Chords #4": [
- 84000,
- 4005.215419501127
- ],
- "Classic UI SFX - Chords #5": [
- 90000,
- 4005.215419501127
- ],
- "Classic UI SFX - Chords #6": [
- 96000,
- 4005.215419501127
- ],
- "Classic UI SFX - Chords #7": [
- 102000,
- 4005.215419501127
- ],
- "Classic UI SFX - Chords #8": [
- 108000,
- 4005.215419501127
- ],
- "Classic UI SFX - Chords #9": [
- 114000,
- 4005.215419501127
- ],
- "Classic UI SFX - Short - High #1": [
- 120000,
- 2546.893424036284
- ],
- "Classic UI SFX - Short - High #10": [
- 124000,
- 2552.0861678004535
- ],
- "Classic UI SFX - Short - High #11": [
- 128000,
- 2927.0975056689394
- ],
- "Classic UI SFX - Short - High #12": [
- 132000,
- 2927.0975056689394
- ],
- "Classic UI SFX - Short - High #13": [
- 136000,
- 3000
- ],
- "Classic UI SFX - Short - High #14": [
- 140000,
- 2802.0861678004394
- ],
- "Classic UI SFX - Short - High #15": [
- 144000,
- 2723.9455782312803
- ],
- "Classic UI SFX - Short - High #16": [
- 148000,
- 2927.0975056689394
- ],
- "Classic UI SFX - Short - High #17": [
- 152000,
- 2880.226757369627
- ],
- "Classic UI SFX - Short - High #18": [
- 156000,
- 2359.387755102034
- ],
- "Classic UI SFX - Short - High #19": [
- 160000,
- 3052.0861678004394
- ],
- "Classic UI SFX - Short - High #2": [
- 165000,
- 2843.7641723355964
- ],
- "Classic UI SFX - Short - High #20": [
- 169000,
- 2015.6462585034092
- ],
- "Classic UI SFX - Short - High #21": [
- 173000,
- 2005.215419501127
- ],
- "Classic UI SFX - Short - High #22": [
- 177000,
- 2489.5918367346894
- ],
- "Classic UI SFX - Short - High #23": [
- 181000,
- 2458.3446712018144
- ],
- "Classic UI SFX - Short - High #24": [
- 185000,
- 2093.7641723355964
- ],
- "Classic UI SFX - Short - High #25": [
- 189000,
- 2005.215419501127
- ],
- "Classic UI SFX - Short - High #3": [
- 193000,
- 2864.6031746031613
- ],
- "Classic UI SFX - Short - High #4": [
- 197000,
- 3031.2698412698464
- ],
- "Classic UI SFX - Short - High #5": [
- 202000,
- 2598.9795918367236
- ],
- "Classic UI SFX - Short - High #6": [
- 206000,
- 2427.0975056689394
- ],
- "Classic UI SFX - Short - High #7": [
- 210000,
- 2468.752834467125
- ],
- "Classic UI SFX - Short - High #8": [
- 214000,
- 2916.666666666657
- ],
- "Classic UI SFX - Short - High #9": [
- 218000,
- 2250
- ],
- "Classic UI SFX - Short - Low #1": [
- 222000,
- 2010.4308390022538
- ],
- "Classic UI SFX - Short - Low #10": [
- 226000,
- 3020.8390022675644
- ],
- "Classic UI SFX - Short - Low #11": [
- 231000,
- 2458.3446712018144
- ],
- "Classic UI SFX - Short - Low #12": [
- 235000,
- 2901.0430839002197
- ],
- "Classic UI SFX - Short - Low #13": [
- 239000,
- 2843.7641723355964
- ],
- "Classic UI SFX - Short - Low #14": [
- 243000,
- 3135.4195011337824
- ],
- "Classic UI SFX - Short - Low #15": [
- 248000,
- 2703.1292517006877
- ],
- "Classic UI SFX - Short - Low #16": [
- 252000,
- 2875.011337868472
- ],
- "Classic UI SFX - Short - Low #17": [
- 256000,
- 2927.0975056689394
- ],
- "Classic UI SFX - Short - Low #18": [
- 260000,
- 3057.2789115646515
- ],
- "Classic UI SFX - Short - Low #19": [
- 265000,
- 2473.9455782312803
- ],
"Classic UI SFX - Short - Low #2": [
- 269000,
- 2583.3333333333144
- ],
- "Classic UI SFX - Short - Low #20": [
- 273000,
- 2515.646258503409
- ],
- "Classic UI SFX - Short - Low #21": [
- 277000,
- 2604.172335600879
- ],
- "Classic UI SFX - Short - Low #22": [
- 281000,
- 3031.2698412698182
- ],
- "Classic UI SFX - Short - Low #23": [
- 286000,
- 2937.50566893425
- ],
- "Classic UI SFX - Short - Low #24": [
- 290000,
- 2609.387755102034
- ],
- "Classic UI SFX - Short - Low #25": [
- 294000,
- 2625.0113378685
- ],
- "Classic UI SFX - Short - Low #3": [
- 298000,
- 2828.140589569159
- ],
- "Classic UI SFX - Short - Low #4": [
- 302000,
- 2614.6031746031895
+ 6000,
+ 2583.333333333334
],
"Classic UI SFX - Short - Low #5": [
- 306000,
- 3161.4739229024735
+ 10000,
+ 3161.473922902495
],
- "Classic UI SFX - Short - Low #6": [
- 311000,
- 2333.3333333333144
+ "Classic UI SFX - Short - High #9": [
+ 15000,
+ 2250
],
- "Classic UI SFX - Short - Low #7": [
- 315000,
- 2536.4625850340303
+ "UI_TwoNote Up_Set 11_01": [
+ 21000,
+ 129.16099773242706
],
- "Classic UI SFX - Short - Low #8": [
- 319000,
- 2630.2267573695985
+ "UI_TwoNote Up_Set 11_02": [
+ 23000,
+ 250
],
- "Classic UI SFX - Short - Low #9": [
- 323000,
- 2697.936507936504
+ "Classic UI SFX - Short - High #3": [
+ 25000,
+ 2864.6031746031754
],
- "UI_Single_Set 16_01": [
- 327000,
- 309.5918367346826
+ "Classic UI SFX - Short - High #19": [
+ 29000,
+ 3052.0861678004535
],
- "UI_Single_Set 16_02": [
- 329000,
- 309.5918367346826
+ "Classic UI SFX - Short - High #22": [
+ 34000,
+ 2489.5918367346967
+ ],
+ "Classic UI SFX - Chords #16": [
+ 38000,
+ 4005.215419501134
+ ],
+ "Classic UI SFX - Short - High #8": [
+ 44000,
+ 2916.6666666666642
],
"UI_Single_Set 16_03": [
- 331000,
- 309.5918367346826
+ 48000,
+ 309.5918367346968
],
- "UI_TwoNote_Set 15_01": [
- 333000,
- 335.2380952380827
+ "UI_Single_Set 16_01": [
+ 50000,
+ 309.5918367346968
],
- "UI_TwoNote_Set 15_02": [
- 335000,
- 309.5918367346826
+ "Classic UI SFX - Short - Low #6": [
+ 52000,
+ 2333.3333333333358
+ ],
+ "UI SFX_InGameMenu_Open": [
+ 56000,
+ 2614.104308390026
]
}
}
\ No newline at end of file
diff --git a/src/mainview/assets/sounds.ogg b/src/mainview/assets/sounds.ogg
index b8e00d5..0b7ac9b 100644
--- a/src/mainview/assets/sounds.ogg
+++ b/src/mainview/assets/sounds.ogg
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:4a3bb2f9a59e20e5ea49fec7fca68cda5c9167df332ff25d24c29870af834af7
-size 2229386
+oid sha256:c5dd2b1e23a878efe84694fa354e92e07f9394d88217b0f1d925f3b16f044e55
+size 353897
diff --git a/src/mainview/components/CardList.tsx b/src/mainview/components/CardList.tsx
index 0518d2c..2744585 100644
--- a/src/mainview/components/CardList.tsx
+++ b/src/mainview/components/CardList.tsx
@@ -8,6 +8,8 @@ import CardElement, { GameCardFocusHandler, GameCardParams } from "./CardElement
import { JSX } from "react";
import { twMerge } from "tailwind-merge";
import { GamePadButtonCode, useShortcuts } from "../scripts/shortcuts";
+import { oneShot } from "../scripts/audio/audio";
+import { GamepadButtonEvent } from "../scripts/gamepads";
export interface GameMetaExtra extends GameMeta
{
@@ -24,10 +26,11 @@ function LocalCardElement (data: { game: GameMetaExtra, i: number; } & FocusPara
preview = data.game.previewUrl;
}
- const handleAction = () =>
+ const handleAction = (e?: Event) =>
{
data.game.onSelect?.();
data.onAction?.();
+ oneShot('click');
};
useShortcuts(data.game.focusKey, () => [{ label: "Select", button: GamePadButtonCode.A, action: handleAction }]);
diff --git a/src/mainview/components/CollectionsDetail.tsx b/src/mainview/components/CollectionsDetail.tsx
index ac0437f..717e986 100644
--- a/src/mainview/components/CollectionsDetail.tsx
+++ b/src/mainview/components/CollectionsDetail.tsx
@@ -3,9 +3,9 @@ import { StickyHeaderUI } from './Header';
import { GameList } from './GameList';
import { Search, Settings2 } from 'lucide-react';
import { JSX, Suspense } from 'react';
-import Shortcuts from './Shortcuts';
+import { FloatingShortcuts } from './Shortcuts';
import { AutoFocus } from './AutoFocus';
-import { GamePadButtonCode, useShortcutContext, useShortcuts } from '../scripts/shortcuts';
+import { GamePadButtonCode, useShortcuts } from '../scripts/shortcuts';
import { GameListFilterType } from '@/shared/constants';
import { GameCardFocusHandler } from './CardElement';
import { HandleGoBack } from '../scripts/utils';
@@ -13,6 +13,7 @@ import LoadingCardList from './LoadingCardList';
import { useQuery, useQueryClient } from '@tanstack/react-query';
import { gameQuery } from '../scripts/queries/romm';
import { useRouter } from '@tanstack/react-router';
+import SelectMenu from './SelectMenu';
export interface CollectionsDetailParams
{
@@ -43,8 +44,7 @@ export function CollectionsDetail (data: CollectionsDetailParams)
preferredChildFocusKey: `${focusKey}-list`
});
- useShortcuts(focusKey, () => [{ label: "Back", button: GamePadButtonCode.B, action: () => HandleGoBack(router) }], [router]);
- const { shortcuts } = useShortcutContext();
+ useShortcuts(focusKey, () => [{ label: "Back", button: GamePadButtonCode.B, action: (e) => HandleGoBack(router, e) }], [router]);
const handleScroll: GameCardFocusHandler = (cardId, node, details) =>
{
@@ -83,9 +83,10 @@ export function CollectionsDetail (data: CollectionsDetailParams)
{data.footer}
-
+
+
);
}
\ No newline at end of file
diff --git a/src/mainview/components/ContextDialog.tsx b/src/mainview/components/ContextDialog.tsx
index 94d31a8..6024311 100644
--- a/src/mainview/components/ContextDialog.tsx
+++ b/src/mainview/components/ContextDialog.tsx
@@ -7,6 +7,7 @@ import { GamePadButtonCode, Shortcut, useShortcuts } from "../scripts/shortcuts"
import { ContextDialogContext } from "../scripts/contexts";
import { FOCUS_KEYS } from "../scripts/types";
import { oneShot } from "../scripts/audio/audio";
+import { oneShotRumble } from "../scripts/gamepads";
export function ContextList (data: {
options?: DialogEntry[];
@@ -18,7 +19,7 @@ export function ContextList (data: {
const context = useContext(ContextDialogContext);
return
{data.options?.map(o => )}
-
+ {data.showCloseButton !== false && }
{data.showCloseButton !== false && } action={() => context.close()} id="close-context-dialog" content="Close" />}
;
}
@@ -85,9 +86,9 @@ export interface DialogEntry
shortcuts?: Shortcut[];
}
-export function useContextDialog (id: string, data: { content?: JSX.Element; className?: string; preferredChildFocusKey?: string; onClose?: () => void; canClose?: boolean; })
+export function useContextDialog (id: string, data: { content?: JSX.Element; className?: string; preferredChildFocusKey?: string; onClose?: () => void; canClose?: boolean; defaultOpen?: boolean; backdropClassName?: string; })
{
- const [open, setOpen] = useState(false);
+ const [open, setOpen] = useState(data.defaultOpen ?? false);
const [sourceFocusKey, setSourceFocusKey] = useState(undefined);
const handleClose = (value: boolean, newSourceFocusKey?: string) =>
{
@@ -111,7 +112,7 @@ export function useContextDialog (id: string, data: { content?: JSX.Element; cla
}
};
- const dialog =
+ const dialog =
{data.content}
;
return {
@@ -127,12 +128,13 @@ export function ContextDialog (data: {
open: boolean,
close: (open: boolean) => void;
className?: string;
+ backdropClassName?: string;
preferredChildFocusKey?: string;
})
{
const { ref, focusKey, focusSelf } = useFocusable({
focusable: data.open,
- focusKey: `${data.id}-context-dialog`,
+ focusKey: FOCUS_KEYS.CONTEXT_DIALOG(data.id),
isFocusBoundary: true,
saveLastFocusedChild: !data.preferredChildFocusKey,
preferredChildFocusKey: data.preferredChildFocusKey
@@ -148,6 +150,7 @@ export function ContextDialog (data: {
{
focusSelf({ instant: true });
oneShot('openContext');
+ oneShotRumble('openContext', { all: true });
}
}, [data.open]);
@@ -159,7 +162,7 @@ export function ContextDialog (data: {
return