Skip to content

Commit 944b938

Browse files
feat: save input preference to db
1 parent 673a476 commit 944b938

14 files changed

+408
-164
lines changed

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
"swr": "2.3.3",
6060
"tailwind-merge": "3.0.2",
6161
"tailwindcss-motion": "1.1.0",
62+
"type-fest": "4.37.0",
6263
"waku": "0.21.23",
6364
"zod": "3.24.2"
6465
},

pnpm-lock.yaml

+14-11
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/constants/preference.ts

+61-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type { PartialDeep } from 'type-fest'
12
import type { CoreName } from './core'
23
import type { PlatformName } from './platform'
34

@@ -7,7 +8,29 @@ export type PlatformSortOrder = 'ascending' | 'descending'
78
export interface Preference {
89
emulator: {
910
core: Partial<Record<CoreName, Record<string, string>>>
10-
platform: Record<PlatformName, { core: CoreName; shader?: string }>
11+
keyboardMapping: {
12+
a: string
13+
b: string
14+
down: string
15+
fastforward: string
16+
l1: string
17+
l2: string
18+
l3: string
19+
left: string
20+
pause: string
21+
r1: string
22+
r2: string
23+
r3: string
24+
rewind: string
25+
right: string
26+
select: string
27+
start: string
28+
up: string
29+
x: string
30+
y: string
31+
}
32+
platform: Record<PlatformName, { core: CoreName }>
33+
shader: string
1134
}
1235
ui: {
1336
libraryCoverType: 'boxart'
@@ -17,6 +40,8 @@ export interface Preference {
1740
}
1841
}
1942

43+
export type PreferenceSnippet = PartialDeep<Preference>
44+
2045
export const defaultPreference: Preference = {
2146
emulator: {
2247
core: {
@@ -28,6 +53,41 @@ export const defaultPreference: Preference = {
2853
mgba_skip_bios: 'ON',
2954
},
3055
},
56+
/**
57+
input_player1_a = "x"
58+
input_player1_b = "z"
59+
input_player1_down = "down"
60+
input_player1_l = "q"
61+
input_player1_left = "left"
62+
input_player1_r = "w"
63+
input_player1_right = "right"
64+
input_player1_select = "rshift"
65+
input_player1_start = "enter"
66+
input_player1_up = "up"
67+
input_player1_x = "s"
68+
input_player1_y = "a"
69+
*/
70+
keyboardMapping: {
71+
a: 'x',
72+
b: 'z',
73+
down: 'down',
74+
fastforward: 'space',
75+
l1: 'q',
76+
l2: '',
77+
l3: '',
78+
left: 'left',
79+
pause: 'esc',
80+
r1: 'w',
81+
r2: '',
82+
r3: '',
83+
rewind: 'r',
84+
right: 'right',
85+
select: 'rshift',
86+
start: 'enter',
87+
up: 'up',
88+
x: 's',
89+
y: 'a',
90+
},
3191
platform: {
3292
arcade: { core: 'fbneo' },
3393
atari2600: { core: 'stella2014' },

src/controllers/update-preference.ts

+12-9
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
11
import { eq, type InferInsertModel } from 'drizzle-orm'
22
import { getContextData } from 'waku/middleware/context'
33
import { platforms } from '@/constants/platform.ts'
4+
import { defaultPreference, type PreferenceSnippet } from '@/constants/preference.ts'
45
import { userPreferenceTable } from '@/databases/library/schema.ts'
56
import { mergePreference } from './utils.ts'
67

78
function normalize(preference) {
8-
preference.ui.platforms = platforms.map(({ name }) => name).filter((name) => preference.ui.platforms.includes(name))
9+
if (preference.ui?.platforms) {
10+
preference.ui.platforms = platforms.map(({ name }) => name).filter((name) => preference.ui.platforms.includes(name))
11+
}
912
}
1013

11-
export async function updatePreference(preference) {
14+
export async function updatePreference(preference: PreferenceSnippet) {
1215
const { currentUser, db } = getContextData()
1316

1417
const where = eq(userPreferenceTable.user_id, currentUser.id)
@@ -20,13 +23,11 @@ export async function updatePreference(preference) {
2023
if (results.length > 0) {
2124
const [{ emulator, ui }] = results
2225
const newPreference: any = {}
23-
if (emulator) {
24-
mergePreference(emulator, preference.emulator)
25-
newPreference.emulator = emulator
26+
if (preference.emulator) {
27+
newPreference.emulator = mergePreference(emulator, preference.emulator)
2628
}
27-
if (ui) {
28-
mergePreference(ui, preference.ui)
29-
newPreference.ui = ui
29+
if (preference.ui) {
30+
newPreference.ui = mergePreference(ui, preference.ui)
3031
}
3132

3233
normalize(newPreference)
@@ -47,5 +48,7 @@ export async function updatePreference(preference) {
4748
.returning(returning)
4849
}
4950

50-
return newPreferenceResults[0]
51+
const updatedPreference = structuredClone(defaultPreference)
52+
mergePreference(updatedPreference, newPreferenceResults[0])
53+
return updatedPreference
5154
}

src/controllers/utils.ts

+13-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { inArray, type InferSelectModel } from 'drizzle-orm'
2-
import { compact, keyBy, mergeWith } from 'es-toolkit'
2+
import { compact, isNil, isPrimitive, keyBy, mergeWith } from 'es-toolkit'
33
import { getContextData } from 'waku/middleware/context'
44
import { launchboxGameTable, libretroGameTable } from '../databases/metadata/schema.ts'
55

@@ -45,9 +45,19 @@ export async function getRomsMetadata<T extends RomModelLike[]>(romResults: T) {
4545
}
4646

4747
export function mergePreference(target: any, source: any) {
48-
mergeWith(target, source || {}, (targetValue, sourceValue) => {
49-
if (Array.isArray(targetValue) && Array.isArray(sourceValue)) {
48+
const safeTarget = target || {}
49+
mergeWith(safeTarget, source || {}, (targetValue, sourceValue) => {
50+
if (isNil(sourceValue)) {
51+
return targetValue
52+
}
53+
if (Array.isArray(sourceValue)) {
54+
return sourceValue
55+
}
56+
if (isPrimitive(sourceValue)) {
5057
return sourceValue
5158
}
59+
mergePreference(targetValue, sourceValue)
60+
return targetValue
5261
})
62+
return safeTarget
5363
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { Code, Select } from '@radix-ui/themes'
2+
3+
export function CoreOptions({ coreOptions }: { coreOptions: { name: string; options: string[] }[] }) {
4+
return (
5+
<>
6+
<h3 className='flex items-center gap-2 py-2 text-lg font-semibold'>
7+
<span className='icon-[mdi--wrench]' /> Options
8+
</h3>
9+
<div className='flex flex-col gap-2 px-6'>
10+
{coreOptions.map(({ name, options }) => {
11+
return (
12+
<label className='flex w-fit items-center gap-4'>
13+
<Code>{name}</Code>
14+
15+
<div>
16+
<Select.Root size='3' value={options[0]}>
17+
<Select.Trigger variant='ghost' />
18+
<Select.Content>
19+
{options.map((option) => (
20+
<Select.Item key={option} value={option}>
21+
{option}
22+
</Select.Item>
23+
))}
24+
</Select.Content>
25+
</Select.Root>
26+
</div>
27+
</label>
28+
)
29+
})}
30+
</div>
31+
</>
32+
)
33+
}

src/pages/library/components/emulating-settings.tsx

+7-28
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
1-
import { Code, Select } from '@radix-ui/themes'
1+
import { Select } from '@radix-ui/themes'
22
import { useState } from 'react'
33
import { coreOptionsMap } from '@/constants/core.ts'
44
import { platformMap } from '@/constants/platform.ts'
55
import { getPlatformIcon } from '@/utils/rom.ts'
66
import { usePreference } from '../hooks/use-preference.ts'
7+
import { CoreOptions } from './core-options.tsx'
78

89
export function EmulatingSettings() {
9-
const preference = usePreference()
10+
const { preference } = usePreference()
1011
const [selectedPlatform, setSelectedPlatform] = useState(preference.ui.platforms?.[0])
1112

1213
if (!preference.ui.platforms?.length) {
@@ -58,7 +59,9 @@ export function EmulatingSettings() {
5859
{platformMap[selectedPlatform].cores.map((core) => (
5960
<Select.Item key={core} value={core}>
6061
<div className='flex items-center gap-2'>
61-
<span className='icon-[mdi--jigsaw]' />
62+
<div className='flex size-6 items-center justify-center'>
63+
<span className='icon-[mdi--jigsaw] size-5' />
64+
</div>
6265
{core}
6366
</div>
6467
</Select.Item>
@@ -68,31 +71,7 @@ export function EmulatingSettings() {
6871
</div>
6972
</label>
7073

71-
<h3 className='flex items-center gap-2 py-2 text-lg font-semibold'>
72-
<span className='icon-[mdi--wrench]' /> Options
73-
</h3>
74-
<div className='flex flex-col gap-2 px-6'>
75-
{coreOptions.map(({ name, options }) => {
76-
return (
77-
<label className='flex w-fit items-center gap-4'>
78-
<Code>{name}</Code>
79-
80-
<div>
81-
<Select.Root size='3' value={options[0]}>
82-
<Select.Trigger variant='ghost' />
83-
<Select.Content>
84-
{options.map((option) => (
85-
<Select.Item key={option} value={option}>
86-
{option}
87-
</Select.Item>
88-
))}
89-
</Select.Content>
90-
</Select.Root>
91-
</div>
92-
</label>
93-
)
94-
})}
95-
</div>
74+
{coreOptions.length > 0 ? <CoreOptions coreOptions={coreOptions} /> : null}
9675
</div>
9776
)
9877
}

0 commit comments

Comments
 (0)