feat: initial romm web ui architecture, bento grids, and spatial gamepad bindings

This commit is contained in:
roormonger
2026-03-23 15:31:41 -04:00
commit 9e8f148a10
40 changed files with 9935 additions and 0 deletions

View File

@@ -0,0 +1,48 @@
import { createContext, useContext, useState, ReactNode } from 'react';
import { useNavigate } from 'react-router-dom';
import { rommApiClient } from '../api/client';
interface AuthContextType {
isAuthenticated: boolean;
login: (u: string, p: string) => Promise<boolean>;
logout: () => void;
}
const AuthContext = createContext<AuthContextType | undefined>(undefined);
export const AuthProvider = ({ children }: { children: ReactNode }) => {
const [isAuthenticated, setIsAuthenticated] = useState(!!rommApiClient.token);
const navigate = useNavigate();
const login = async (user: string, pass: string) => {
try {
await rommApiClient.login(user, pass);
setIsAuthenticated(true);
navigate('/');
return true;
} catch (e) {
console.error(e);
return false;
}
};
const logout = () => {
setIsAuthenticated(false);
navigate('/login');
};
return (
<AuthContext.Provider value={{ isAuthenticated, login, logout }}>
{children}
</AuthContext.Provider>
);
};
export const useAuth = () => {
const context = useContext(AuthContext);
if (context === undefined) {
throw new Error('useAuth must be used within an AuthProvider');
}
return context;
};

View File

@@ -0,0 +1,60 @@
import React, { createContext, useContext, useEffect, useState } from 'react';
type InputMode = 'mouse' | 'gamepad';
interface InputModeContextType {
mode: InputMode;
setMode: (mode: InputMode) => void;
}
const InputModeContext = createContext<InputModeContextType | undefined>(undefined);
export const InputModeProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const [mode, setModeState] = useState<InputMode>('mouse');
const setMode = (newMode: InputMode) => {
if (newMode !== mode) {
setModeState(newMode);
}
};
useEffect(() => {
// Spatial Engine Override
if (mode === 'mouse') {
document.body.classList.remove('gamepad-active');
} else {
document.body.classList.add('gamepad-active');
}
}, [mode]);
useEffect(() => {
const onMouseMove = () => {
setMode('mouse');
};
const onMouseDown = () => {
setMode('mouse');
};
window.addEventListener('mousemove', onMouseMove, { passive: true });
window.addEventListener('mousedown', onMouseDown, { passive: true });
return () => {
window.removeEventListener('mousemove', onMouseMove);
window.removeEventListener('mousedown', onMouseDown);
};
}, []);
return (
<InputModeContext.Provider value={{ mode, setMode }}>
{children}
</InputModeContext.Provider>
);
};
export const useInputMode = () => {
const context = useContext(InputModeContext);
if (context === undefined) {
throw new Error('useInputMode must be used within an InputModeProvider');
}
return context;
};