mirror of
https://github.com/SlimeVR/SlimeVR-Server.git
synced 2026-04-06 02:01:58 +02:00
Update to form and ui.
This commit is contained in:
@@ -17,6 +17,7 @@
|
||||
},
|
||||
"scripts": {
|
||||
"start": "vite --force",
|
||||
"dev:clean": "rm -rf node_modules/.vite && pnpm run gui",
|
||||
"gui": "electron-vite dev --config electron.vite.config.ts --watch",
|
||||
"build": "electron-vite build --config electron.vite.config.ts",
|
||||
"package": "electron-builder",
|
||||
|
||||
@@ -605,9 +605,22 @@ settings-stay_aligned-debug-copy-label = Copy settings to clipboard
|
||||
|
||||
settings-keybinds = Keybind settings
|
||||
settings-keybinds-description = Change keybinds for various shortcuts
|
||||
keybind_config-keybind_name = Keybind
|
||||
keybind_config-keybind_value = Combination
|
||||
keybind_config-keybind_delay = Delay before trigger (s)
|
||||
settings-keybinds_full-reset = Full Reset
|
||||
settings-keybinds_yaw-reset = Yaw Reset
|
||||
settings-keybinds_mounting-reset = Mounting Reset
|
||||
settings-keybinds_feet-mounting-reset = Feet Mounting Reset
|
||||
settings-keybinds_pause-tracking = Pause Tracking
|
||||
settings-keybinds_record-keybind = Click to record
|
||||
settings-keybinds_now-recording = Recording…
|
||||
settings-keybinds_reset-button = Reset
|
||||
settings-keybinds_reset-all-button = Reset all
|
||||
settings-keybinds-wayland-description = You appear to be using wayland, Please change your shortcuts in your system settings.
|
||||
settings-keybinds-wayland-open-system-settings-button = Open system settings
|
||||
settings-sidebar-keybinds = Keybinds
|
||||
settings-sidebar_keybinds_record-keybind = Click to record
|
||||
settings-sidebar_keybinds_now-recording = Recording…
|
||||
|
||||
## FK/Tracking settings
|
||||
settings-general-fk_settings = Tracking settings
|
||||
|
||||
|
||||
@@ -14,12 +14,24 @@ export const KeybindRecorder = forwardRef<
|
||||
|
||||
return (
|
||||
<div className="relative w-full">
|
||||
<style>
|
||||
{`
|
||||
@keyframes keyslot {
|
||||
0%, 100% { transform: scale(1); opacity: 0.6; }
|
||||
50% { transform: scale(1.08); opacity: 1; }
|
||||
}
|
||||
|
||||
.keyslot-animate {
|
||||
animation: keyslot 1s ease-in-out infinite;
|
||||
}
|
||||
`}
|
||||
</style>
|
||||
<input
|
||||
ref={ref}
|
||||
className="opacity-0 absolute inset-0 cursor-pointer"
|
||||
onFocus={() => {
|
||||
setOldKeys(keys);
|
||||
onKeysChange([]);
|
||||
onKeysChange(['CTRL', 'ALT']);
|
||||
setIsRecording(true);
|
||||
}}
|
||||
onBlur={() => {
|
||||
@@ -33,21 +45,29 @@ export const KeybindRecorder = forwardRef<
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
||||
<div className="flex gap-2 min-h-[42px] items-center px-3 py-2 rounded-lg bg-background-80">
|
||||
<div className="flex flex-grow gap-2 flex-wrap">
|
||||
{keys.map((key, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className="bg-accent-background-50 px-3 py-1 rounded-md text-sm"
|
||||
>
|
||||
{key}
|
||||
</div>
|
||||
))}
|
||||
<div className="flex flex-grow gap-2 min-w-[180px]">
|
||||
{Array.from({ length: 4 }).map((_, i) => {
|
||||
const key = keys[i];
|
||||
const isActive = isRecording && i === keys.length;
|
||||
return (
|
||||
<div
|
||||
key={i}
|
||||
className={`
|
||||
px-2 py-1 rounded-md text-sm min-w-[32px] text-center
|
||||
${key ? 'bg-accent-background-50' : 'bg-accent-background-50/30'}
|
||||
${isActive ? 'keyslot-animate ring-2 ring-accent' : ''}
|
||||
`}
|
||||
>
|
||||
{key ?? ''}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
<div className="text-accent-background-10 text-right text-sm font-medium">
|
||||
{keys.length < 4 && isRecording ? l10n.getString('settings-sidebar_keybinds_now-recording') : l10n.getString('settings-sidebar_keybinds_record-keybind') }
|
||||
<div className="w-40 flex-shrink-0 text-accent-background-10 text-right text-sm font-medium">
|
||||
{keys.length < 4 && isRecording
|
||||
? l10n.getString('settings-keybinds_now-recording')
|
||||
: l10n.getString('settings-keybinds_record-keybind')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
16
gui/src/components/commons/KeybindRow.scss
Normal file
16
gui/src/components/commons/KeybindRow.scss
Normal file
@@ -0,0 +1,16 @@
|
||||
:root {
|
||||
--row-h: 80px;
|
||||
}
|
||||
|
||||
.keybind-row {
|
||||
display: grid;
|
||||
grid-column: 1 / -1;
|
||||
grid-template-columns: subgrid;
|
||||
height: var(--row-h);
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.number-selector::after {
|
||||
gap: 0;
|
||||
}
|
||||
@@ -3,19 +3,22 @@ import { Button } from './Button';
|
||||
import { NumberSelector } from './NumberSelector';
|
||||
import { KeybindRecorder } from './KeybindRecorder';
|
||||
import { useLocaleConfig } from '@/i18n/config';
|
||||
import { Typography } from './Typography';
|
||||
import './KeybindRow.scss';
|
||||
|
||||
export function KeybindRow({
|
||||
label,
|
||||
id,
|
||||
control,
|
||||
resetField,
|
||||
bindingName,
|
||||
delayName,
|
||||
name,
|
||||
delay,
|
||||
}: {
|
||||
label: string;
|
||||
id?: string;
|
||||
label?: string;
|
||||
control: Control<any>;
|
||||
resetField: UseFormResetField<any>;
|
||||
bindingName: string;
|
||||
delayName: string;
|
||||
name: string;
|
||||
delay: string;
|
||||
}) {
|
||||
const { currentLocales } = useLocaleConfig();
|
||||
const secondsFormat = new Intl.NumberFormat(currentLocales, {
|
||||
@@ -24,50 +27,41 @@ export function KeybindRow({
|
||||
unitDisplay: 'narrow',
|
||||
maximumFractionDigits: 2,
|
||||
});
|
||||
|
||||
return (
|
||||
<tr className="border-b border-background-60 h-20">
|
||||
<td className="px-6 py-4 pr-4">
|
||||
<label className="text-sm font-medium text-background-10">
|
||||
{label}
|
||||
</label>
|
||||
</td>
|
||||
<td className="px-4">
|
||||
<Controller
|
||||
control={control}
|
||||
name={bindingName}
|
||||
render={({ field }) => (
|
||||
<KeybindRecorder
|
||||
keys={field.value ?? []}
|
||||
onKeysChange={field.onChange}
|
||||
ref={field.ref}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</td>
|
||||
<td className="px-4">
|
||||
<div className="keybind-row">
|
||||
<label className="text-sm font-medium text-background-10">
|
||||
<Typography id={id} />
|
||||
</label>
|
||||
<Controller
|
||||
control={control}
|
||||
name={name}
|
||||
render={({ field }) => (
|
||||
<KeybindRecorder
|
||||
keys={field.value}
|
||||
onKeysChange={field.onChange}
|
||||
ref={field.ref}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<NumberSelector
|
||||
control={control}
|
||||
name={delayName}
|
||||
name={delay}
|
||||
valueLabelFormat={(value) => secondsFormat.format(value)}
|
||||
min={0}
|
||||
max={10}
|
||||
step={0.2}
|
||||
/>
|
||||
</td>
|
||||
|
||||
<td className="px-2">
|
||||
<div className="flex gap-2 justify-center px-4">
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={() => {
|
||||
resetField(bindingName);
|
||||
resetField(delayName);
|
||||
}}
|
||||
>
|
||||
Reset
|
||||
</Button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<div className='max-w-[45px]'>
|
||||
<Button
|
||||
id="settings-keybinds_reset-button"
|
||||
variant="primary"
|
||||
onClick={() => {
|
||||
resetField(name);
|
||||
resetField(delay);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -8,17 +8,17 @@ import { useWebsocketAPI } from '@/hooks/websocket-api';
|
||||
import { RpcMessage, InstalledInfoResponseT } from 'solarxr-protocol';
|
||||
import { useConfig } from '@/hooks/config';
|
||||
import { useLocalization } from '@fluent/react';
|
||||
import { useAppContext } from '@/hooks/app';
|
||||
|
||||
export function UdevRulesModal() {
|
||||
const { config, setConfig } = useConfig();
|
||||
const { useRPCPacket, sendRPCPacket } = useWebsocketAPI();
|
||||
const electron = useElectron();
|
||||
const [udevContent, setUdevContent] = useState('');
|
||||
const [isUdevInstalledResponse, setIsUdevInstalledResponse] = useState(true);
|
||||
const [showUdevWarning, setShowUdevWarning] = useState(false);
|
||||
const [dontShowThisSession, setDontShowThisSession] = useState(false);
|
||||
const [dontShowAgain, setDontShowAgain] = useState(false);
|
||||
const { l10n } = useLocalization();
|
||||
const { installInfo } = useAppContext();
|
||||
|
||||
const handleUdevContent = async () => {
|
||||
if (electron.isElectron) {
|
||||
@@ -38,28 +38,15 @@ export function UdevRulesModal() {
|
||||
if (!config) throw 'Invalid state!';
|
||||
if (electron.isElectron) {
|
||||
const isLinux = electron.data().os.type === 'linux';
|
||||
const udevMissing = !isUdevInstalledResponse;
|
||||
const udevMissing = !installInfo?.isUdevInstalled;
|
||||
const notHiddenGlobally = !config.dontShowUdevModal;
|
||||
const notHiddenThisSession = !dontShowThisSession;
|
||||
const shouldShow =
|
||||
isLinux && udevMissing && notHiddenGlobally && notHiddenThisSession;
|
||||
setShowUdevWarning(shouldShow);
|
||||
}
|
||||
}, [config, isUdevInstalledResponse, dontShowThisSession]);
|
||||
}, [config, dontShowThisSession]);
|
||||
|
||||
useEffect(() => {
|
||||
sendRPCPacket(
|
||||
RpcMessage.InstalledInfoRequest,
|
||||
new InstalledInfoResponseT()
|
||||
);
|
||||
}, []);
|
||||
|
||||
useRPCPacket(
|
||||
RpcMessage.InstalledInfoResponse,
|
||||
({ isUdevInstalled }: InstalledInfoResponseT) => {
|
||||
setIsUdevInstalledResponse(isUdevInstalled);
|
||||
}
|
||||
);
|
||||
|
||||
const handleModalClose = () => {
|
||||
if (!config) throw 'Invalid State!';
|
||||
|
||||
22
gui/src/components/settings/pages/KeybindSettings.scss
Normal file
22
gui/src/components/settings/pages/KeybindSettings.scss
Normal file
@@ -0,0 +1,22 @@
|
||||
:root {
|
||||
--keybindrow-w: 1fr;
|
||||
--top-row-h: 10px;
|
||||
--keybindrow-h: 10px;
|
||||
--buttonrow-h: 10px;
|
||||
}
|
||||
|
||||
.keybind-settings {
|
||||
display: grid;
|
||||
|
||||
grid-template:
|
||||
'n v d e' auto
|
||||
'k k k k' auto
|
||||
'b . . .' auto
|
||||
/ 200px 1fr 120px auto;
|
||||
|
||||
grid-template-columns: subgrid;
|
||||
grid-template-rows: subgrid;
|
||||
|
||||
place-items: center;
|
||||
|
||||
}
|
||||
@@ -4,8 +4,9 @@ import {
|
||||
SettingsPagePaneLayout,
|
||||
} from '@/components/settings/SettingsPageLayout';
|
||||
import { Typography } from '@/components/commons/Typography';
|
||||
import { Localized, useLocalization } from '@fluent/react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useLocalization } from '@fluent/react';
|
||||
import { DefaultValues, useForm } from 'react-hook-form';
|
||||
import './KeybindSettings.scss';
|
||||
import { ReactNode, useEffect } from 'react';
|
||||
import { KeybindRow } from '@/components/commons/KeybindRow';
|
||||
import { Button } from '@/components/commons/Button';
|
||||
@@ -17,7 +18,30 @@ import {
|
||||
KeybindT,
|
||||
KeybindName,
|
||||
ChangeKeybindRequestT,
|
||||
OpenUriRequestT,
|
||||
} from 'solarxr-protocol';
|
||||
import { useAppContext } from '@/hooks/app';
|
||||
|
||||
function Table({ children }: { children: ReactNode }) {
|
||||
return (
|
||||
<table className="min-w-full divide-y divide-background-50">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col" className="px-6 py-3 text-start">
|
||||
<Typography id="keybind_config-keybind_name" />
|
||||
</th>
|
||||
<th scope="col" className="px-6 py-3 text-middle">
|
||||
<Typography id="keybind_config-keybind_value" />
|
||||
</th>
|
||||
<th scope="col" className="px-6 py-3 text-middle">
|
||||
<Typography id="keybind_config-keybind_delay" />
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>{children}</tbody>
|
||||
</table>
|
||||
);
|
||||
}
|
||||
|
||||
export type KeybindsForm = {
|
||||
names: {
|
||||
@@ -67,39 +91,17 @@ const defaultValues: KeybindsForm = {
|
||||
},
|
||||
};
|
||||
|
||||
export function useKeybindsForm() {
|
||||
const {
|
||||
register,
|
||||
reset,
|
||||
handleSubmit,
|
||||
formState,
|
||||
control,
|
||||
getValues,
|
||||
resetField,
|
||||
watch,
|
||||
} = useForm<KeybindsForm>({
|
||||
defaultValues,
|
||||
});
|
||||
|
||||
return {
|
||||
control,
|
||||
register,
|
||||
reset,
|
||||
handleSubmit,
|
||||
formState,
|
||||
getValues,
|
||||
resetField,
|
||||
watch,
|
||||
};
|
||||
}
|
||||
|
||||
export function KeybindSettings() {
|
||||
const { l10n } = useLocalization();
|
||||
const { control, reset, handleSubmit, watch, getValues, resetField } =
|
||||
useKeybindsForm();
|
||||
const { control, reset, handleSubmit, watch, resetField, getValues } =
|
||||
useForm<KeybindsForm>({
|
||||
defaultValues,
|
||||
});
|
||||
const { sendRPCPacket, useRPCPacket } = useWebsocketAPI();
|
||||
const { installInfo } = useAppContext();
|
||||
|
||||
const onSubmit = (values: KeybindsForm) => {
|
||||
console.log('Onsubmit in KeybindSettings');
|
||||
const keybinds = new ChangeKeybindRequestT();
|
||||
|
||||
const fullResetKeybind = new KeybindT();
|
||||
@@ -134,7 +136,6 @@ export function KeybindSettings() {
|
||||
feetResetKeybind.keybindDelay = values.delays.pauseTrackingDelay;
|
||||
keybinds.keybind.push(feetResetKeybind);
|
||||
|
||||
console.log(`Delay ${Number(fullResetKeybind.keybindDelay)}`);
|
||||
sendRPCPacket(RpcMessage.ChangeKeybindRequest, keybinds);
|
||||
};
|
||||
|
||||
@@ -150,162 +151,151 @@ export function KeybindSettings() {
|
||||
useRPCPacket(RpcMessage.KeybindResponse, ({ keybind }: KeybindResponseT) => {
|
||||
if (!keybind) return;
|
||||
|
||||
const keybindValues: KeybindsForm = {
|
||||
names: {
|
||||
fullResetName: KeybindName.FULL_RESET,
|
||||
yawResetName: KeybindName.YAW_RESET,
|
||||
mountingResetName: KeybindName.MOUNTING_RESET,
|
||||
pauseTrackingName: KeybindName.PAUSE_TRACKING,
|
||||
feetResetName: KeybindName.FEET_MOUNTING_RESET,
|
||||
reset(
|
||||
{
|
||||
names: {
|
||||
fullResetName: KeybindName.FULL_RESET,
|
||||
yawResetName: KeybindName.YAW_RESET,
|
||||
mountingResetName: KeybindName.MOUNTING_RESET,
|
||||
pauseTrackingName: KeybindName.PAUSE_TRACKING,
|
||||
feetResetName: KeybindName.FEET_MOUNTING_RESET,
|
||||
},
|
||||
bindings: {
|
||||
fullResetBinding:
|
||||
(typeof keybind[KeybindName.FULL_RESET].keybindValue === 'string'
|
||||
? keybind[KeybindName.FULL_RESET].keybindValue
|
||||
: ''
|
||||
).split('+') || defaultValues.bindings.fullResetBinding,
|
||||
yawResetBinding:
|
||||
(typeof keybind[KeybindName.YAW_RESET].keybindValue === 'string'
|
||||
? keybind[KeybindName.YAW_RESET].keybindValue
|
||||
: ''
|
||||
).split('+') || defaultValues.bindings.yawResetBinding,
|
||||
mountingResetBinding:
|
||||
(typeof keybind[KeybindName.MOUNTING_RESET].keybindValue ===
|
||||
'string'
|
||||
? keybind[KeybindName.MOUNTING_RESET].keybindValue
|
||||
: ''
|
||||
).split('+') || defaultValues.bindings.mountingResetBinding,
|
||||
pauseTrackingBinding:
|
||||
(typeof keybind[KeybindName.PAUSE_TRACKING].keybindValue ===
|
||||
'string'
|
||||
? keybind[KeybindName.PAUSE_TRACKING].keybindValue
|
||||
: ''
|
||||
).split('+') || defaultValues.bindings.pauseTrackingBinding,
|
||||
feetResetBinding:
|
||||
(typeof keybind[KeybindName.FEET_MOUNTING_RESET].keybindValue ===
|
||||
'string'
|
||||
? keybind[KeybindName.FEET_MOUNTING_RESET].keybindValue
|
||||
: ''
|
||||
).split('+') || defaultValues.bindings.feetResetBinding,
|
||||
},
|
||||
delays: {
|
||||
fullResetDelay:
|
||||
keybind[KeybindName.FULL_RESET].keybindDelay ||
|
||||
defaultValues.delays.fullResetDelay,
|
||||
yawResetDelay:
|
||||
keybind[KeybindName.YAW_RESET].keybindDelay ||
|
||||
defaultValues.delays.yawResetDelay,
|
||||
mountingResetDelay:
|
||||
keybind[KeybindName.MOUNTING_RESET].keybindDelay ||
|
||||
defaultValues.delays.mountingResetDelay,
|
||||
pauseTrackingDelay:
|
||||
keybind[KeybindName.PAUSE_TRACKING].keybindDelay ||
|
||||
defaultValues.delays.pauseTrackingDelay,
|
||||
feetResetDelay:
|
||||
keybind[KeybindName.FEET_MOUNTING_RESET].keybindDelay ||
|
||||
defaultValues.delays.feetResetDelay,
|
||||
},
|
||||
},
|
||||
bindings: {
|
||||
fullResetBinding:
|
||||
(typeof keybind[KeybindName.FULL_RESET].keybindValue === 'string'
|
||||
? keybind[KeybindName.FULL_RESET].keybindValue
|
||||
: ''
|
||||
).split('+') || defaultValues.bindings.fullResetBinding,
|
||||
|
||||
yawResetBinding:
|
||||
(typeof keybind[KeybindName.YAW_RESET].keybindValue === 'string'
|
||||
? keybind[KeybindName.YAW_RESET].keybindValue
|
||||
: ''
|
||||
).split('+') || defaultValues.bindings.yawResetBinding,
|
||||
|
||||
mountingResetBinding:
|
||||
(typeof keybind[KeybindName.MOUNTING_RESET].keybindValue === 'string'
|
||||
? keybind[KeybindName.MOUNTING_RESET].keybindValue
|
||||
: ''
|
||||
).split('+') || defaultValues.bindings.mountingResetBinding,
|
||||
|
||||
pauseTrackingBinding:
|
||||
(typeof keybind[KeybindName.PAUSE_TRACKING].keybindValue === 'string'
|
||||
? keybind[KeybindName.PAUSE_TRACKING].keybindValue
|
||||
: ''
|
||||
).split('+') || defaultValues.bindings.pauseTrackingBinding,
|
||||
|
||||
feetResetBinding:
|
||||
(typeof keybind[KeybindName.FEET_MOUNTING_RESET].keybindValue ===
|
||||
'string'
|
||||
? keybind[KeybindName.FEET_MOUNTING_RESET].keybindValue
|
||||
: ''
|
||||
).split('+') || defaultValues.bindings.feetResetBinding,
|
||||
},
|
||||
delays: {
|
||||
fullResetDelay:
|
||||
keybind[KeybindName.FULL_RESET].keybindDelay ||
|
||||
defaultValues.delays.fullResetDelay,
|
||||
yawResetDelay:
|
||||
keybind[KeybindName.YAW_RESET].keybindDelay ||
|
||||
defaultValues.delays.yawResetDelay,
|
||||
mountingResetDelay:
|
||||
keybind[KeybindName.MOUNTING_RESET].keybindDelay ||
|
||||
defaultValues.delays.mountingResetDelay,
|
||||
pauseTrackingDelay:
|
||||
keybind[KeybindName.PAUSE_TRACKING].keybindDelay ||
|
||||
defaultValues.delays.mountingResetDelay,
|
||||
feetResetDelay:
|
||||
keybind[KeybindName.FEET_MOUNTING_RESET].keybindDelay ||
|
||||
defaultValues.delays.feetResetDelay,
|
||||
},
|
||||
};
|
||||
|
||||
reset({ ...getValues(), ...keybindValues });
|
||||
{
|
||||
keepDefaultValues: true,
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
const handleResetButton = () => {
|
||||
reset(defaultValues);
|
||||
const handleOpenSystemSettingsButton = () => {
|
||||
sendRPCPacket(RpcMessage.OpenUriRequest, new OpenUriRequestT());
|
||||
};
|
||||
|
||||
function Table({ children }: { children: ReactNode }) {
|
||||
return (
|
||||
<table className="min-w-full divide-y divide-background-50">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col" className="px-6 py-3 text-start">
|
||||
<Localized id={'keybind_config-keybind_name'}>
|
||||
<Typography />
|
||||
</Localized>
|
||||
Keybind
|
||||
</th>
|
||||
<th scope="col" className="px-6 py-3 text-middle">
|
||||
<Localized id={'keybind_config-keybind_value'}>
|
||||
<Typography />
|
||||
</Localized>
|
||||
Combination
|
||||
</th>
|
||||
<th scope="col" className="px-6 py-3 text-middle">
|
||||
<Localized id={'keybind_config-keybind_delay'}>
|
||||
<Typography />
|
||||
</Localized>
|
||||
Delay before trigger (S)
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>{children}</tbody>
|
||||
</table>
|
||||
);
|
||||
}
|
||||
const handleResetAllButton = () => {
|
||||
reset(defaultValues);
|
||||
};
|
||||
|
||||
return (
|
||||
<SettingsPageLayout>
|
||||
<form className="flex flex-col gap-2 w-full">
|
||||
<SettingsPagePaneLayout icon={<WrenchIcon />} id="keybinds">
|
||||
<>
|
||||
<Typography variant="main-title" id="settings-keybinds" />
|
||||
<div className="flex flex-col pt-2 pb-4">
|
||||
{l10n
|
||||
.getString('settings-keybinds-description')
|
||||
.split('\n')
|
||||
.map((line, i) => (
|
||||
<Typography key={i}>{line}</Typography>
|
||||
))}
|
||||
<Typography variant="main-title" id="settings-keybinds" />
|
||||
<div className="flex flex-col pt-2 pb-4">
|
||||
{l10n
|
||||
.getString('settings-keybinds-description')
|
||||
.split('\n')
|
||||
.map((line, i) => (
|
||||
<Typography key={i}>{line}</Typography>
|
||||
))}
|
||||
</div>
|
||||
{!installInfo?.isWayland ? (
|
||||
<div className="flex flex-col gap-4">
|
||||
<Typography id="settings-keybinds-wayland-description" />
|
||||
<div>
|
||||
<Button
|
||||
id="settings-keybinds-wayland-open-system-settings-button"
|
||||
className="flex flex-col"
|
||||
onClick={handleOpenSystemSettingsButton}
|
||||
variant="primary"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<Table>
|
||||
<KeybindRow
|
||||
label="Full Reset"
|
||||
control={control}
|
||||
resetField={resetField}
|
||||
bindingName="bindings.fullResetBinding"
|
||||
delayName="delays.fullResetDelay"
|
||||
/>
|
||||
<KeybindRow
|
||||
label="Yaw Reset"
|
||||
control={control}
|
||||
resetField={resetField}
|
||||
bindingName="bindings.yawResetBinding"
|
||||
delayName="delays.yawResetDelay"
|
||||
/>
|
||||
<KeybindRow
|
||||
label="Mounting Reset"
|
||||
control={control}
|
||||
resetField={resetField}
|
||||
bindingName="bindings.mountingResetBinding"
|
||||
delayName="delays.mountingResetDelay"
|
||||
/>
|
||||
<KeybindRow
|
||||
label="Feet Mounting Reset"
|
||||
control={control}
|
||||
resetField={resetField}
|
||||
bindingName="bindings.feetResetBinding"
|
||||
delayName="delays.feetResetDelay"
|
||||
/>
|
||||
<KeybindRow
|
||||
label="Pause Tracking"
|
||||
control={control}
|
||||
resetField={resetField}
|
||||
bindingName="bindings.pauseTrackingBinding"
|
||||
delayName="delays.pauseTrackingDelay"
|
||||
/>
|
||||
</Table>
|
||||
<div className="flex flex-col pt-4" />
|
||||
<Button
|
||||
className="flex flex-col"
|
||||
onClick={handleResetButton}
|
||||
variant="primary"
|
||||
>
|
||||
Reset All
|
||||
</Button>
|
||||
</>
|
||||
) : (
|
||||
<div className="keybind-settings">
|
||||
<Typography id="keybind_config-keybind_name" />
|
||||
<Typography id="keybind_config-keybind_value" />
|
||||
<Typography id="keybind_config-keybind_delay" />
|
||||
<div />
|
||||
<KeybindRow
|
||||
id="settings-keybinds_full-reset"
|
||||
control={control}
|
||||
resetField={resetField}
|
||||
name="bindings.fullResetBinding"
|
||||
delay="delays.fullResetDelay"
|
||||
/>
|
||||
<KeybindRow
|
||||
id="settings-keybinds_yaw-reset"
|
||||
control={control}
|
||||
resetField={resetField}
|
||||
name="bindings.yawResetBinding"
|
||||
delay="delays.yawResetDelay"
|
||||
/>
|
||||
<KeybindRow
|
||||
id="settings-keybinds_mounting-reset"
|
||||
control={control}
|
||||
resetField={resetField}
|
||||
name="bindings.mountingResetBinding"
|
||||
delay="delays.mountingResetDelay"
|
||||
/>
|
||||
<KeybindRow
|
||||
id="settings-keybinds_feet-mounting-reset"
|
||||
control={control}
|
||||
resetField={resetField}
|
||||
name="bindings.feetResetBinding"
|
||||
delay="delays.feetResetDelay"
|
||||
/>
|
||||
<KeybindRow
|
||||
id="settings-keybinds_pause-tracking"
|
||||
control={control}
|
||||
resetField={resetField}
|
||||
name="bindings.pauseTrackingBinding"
|
||||
delay="delays.pauseTrackingDelay"
|
||||
/>
|
||||
<Button
|
||||
id="settings-keybinds_reset-all-button"
|
||||
className="justify-self-start"
|
||||
onClick={handleResetAllButton}
|
||||
variant="primary"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</SettingsPagePaneLayout>
|
||||
</form>
|
||||
</SettingsPageLayout>
|
||||
|
||||
@@ -39,8 +39,8 @@ function StaAlignedPoseModal({
|
||||
<Typography variant="main-title">{l10n.getString(title)}</Typography>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
{descriptionKeys.map((descriptionKey) => (
|
||||
<Typography>{l10n.getString(descriptionKey)}</Typography>
|
||||
{descriptionKeys.map((descriptionKey, i) => (
|
||||
<Typography key={i}>{l10n.getString(descriptionKey)}</Typography>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex pt-1 items-center fill-background-50 justify-center px-12">
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
ResetResponseT,
|
||||
RpcMessage,
|
||||
StartDataFeedT,
|
||||
InstalledInfoResponseT
|
||||
} from 'solarxr-protocol';
|
||||
import { handleResetSounds } from '@/sounds/sounds';
|
||||
import { useConfig } from './config';
|
||||
@@ -18,10 +19,11 @@ import { DEFAULT_LOCALE, LangContext } from '@/i18n/config';
|
||||
|
||||
export interface AppContext {
|
||||
currentFirmwareRelease: FirmwareRelease | null;
|
||||
installInfo: InstalledInfoResponseT | null;
|
||||
}
|
||||
|
||||
export function useProvideAppContext(): AppContext {
|
||||
const { useRPCPacket, sendDataFeedPacket, useDataFeedPacket, isConnected } =
|
||||
const { useRPCPacket, sendRPCPacket, sendDataFeedPacket, useDataFeedPacket, isConnected } =
|
||||
useWebsocketAPI();
|
||||
const { changeLocales } = useContext(LangContext);
|
||||
const { config } = useConfig();
|
||||
@@ -34,6 +36,8 @@ export function useProvideAppContext(): AppContext {
|
||||
const [currentFirmwareRelease, setCurrentFirmwareRelease] =
|
||||
useState<FirmwareRelease | null>(null);
|
||||
|
||||
const [installInfo, setInstallInfo] = useState<InstalledInfoResponseT | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
if (isConnected) {
|
||||
const startDataFeed = new StartDataFeedT();
|
||||
@@ -50,6 +54,24 @@ export function useProvideAppContext(): AppContext {
|
||||
}
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
sendRPCPacket(
|
||||
RpcMessage.InstalledInfoRequest,
|
||||
new InstalledInfoResponseT()
|
||||
);
|
||||
}, []);
|
||||
|
||||
useRPCPacket(
|
||||
RpcMessage.InstalledInfoResponse,
|
||||
({ isUdevInstalled, isWayland }: InstalledInfoResponseT) => {
|
||||
|
||||
setInstallInfo(new InstalledInfoResponseT(
|
||||
isUdevInstalled,
|
||||
isWayland
|
||||
));
|
||||
}
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
updateSentryContext(devices);
|
||||
}, [devices]);
|
||||
@@ -85,6 +107,7 @@ export function useProvideAppContext(): AppContext {
|
||||
|
||||
return {
|
||||
currentFirmwareRelease,
|
||||
installInfo
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"gui"
|
||||
],
|
||||
"scripts": {
|
||||
"gui": "pnpm run update-solarxr && cd gui && pnpm run gui",
|
||||
"gui": "pnpm run update-solarxr && cd gui && pnpm run dev:clean",
|
||||
"lint:fix": "cd gui && pnpm lint:fix",
|
||||
"skipbundler": "cd gui && pnpm run skipbundler",
|
||||
"build": "cd gui && pnpm build",
|
||||
|
||||
@@ -79,7 +79,7 @@ dependencies {
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2")
|
||||
implementation("com.mayakapps.kache:kache:2.1.1")
|
||||
|
||||
implementation("com.github.HannahPadd:DbusGlobalShortcutsWayland:5010e75bd4")
|
||||
implementation("com.github.HannahPadd:DbusGlobalShortcutsWayland:v0.1.0")
|
||||
|
||||
api("com.github.loucass003:EspflashKotlin:v0.11.0")
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ package dev.slimevr
|
||||
import com.melloware.jintellitype.HotkeyListener
|
||||
import com.melloware.jintellitype.JIntellitype
|
||||
import dev.hannah.portals.PortalManager
|
||||
import dev.hannah.portals.Shortcut
|
||||
import dev.hannah.portals.globalShortcuts.Shortcut
|
||||
import dev.hannah.portals.globalShortcuts.ShortcutTuple
|
||||
import dev.slimevr.config.KeybindingsConfig
|
||||
import dev.slimevr.tracking.trackers.TrackerUtils
|
||||
|
||||
@@ -12,6 +12,7 @@ import dev.slimevr.protocol.rpc.firmware.RPCFirmwareUpdateHandler
|
||||
import dev.slimevr.protocol.rpc.games.vrchat.RPCVRChatHandler
|
||||
import dev.slimevr.protocol.rpc.installinfo.RPCInstallInfoHandler
|
||||
import dev.slimevr.protocol.rpc.keybinds.RPCKeybindHandler
|
||||
import dev.slimevr.protocol.rpc.openuri.RPCOpenUriHandler
|
||||
import dev.slimevr.protocol.rpc.reset.RPCResetHandler
|
||||
import dev.slimevr.protocol.rpc.serial.RPCProvisioningHandler
|
||||
import dev.slimevr.protocol.rpc.serial.RPCSerialHandler
|
||||
@@ -54,6 +55,8 @@ class RPCHandler(private val api: ProtocolAPI) : ProtocolHandler<RpcMessageHeade
|
||||
RPCVRChatHandler(this, api)
|
||||
RPCTrackingChecklistHandler(this, api)
|
||||
RPCUserHeightCalibration(this, api)
|
||||
RPCInstallInfoHandler(this, api)
|
||||
RPCOpenUriHandler(this, api)
|
||||
try {
|
||||
RPCKeybindHandler(this, api)
|
||||
} catch (e: Exception) {
|
||||
|
||||
@@ -32,12 +32,13 @@ class RPCInstallInfoHandler(var rpcHandler: RPCHandler, var api: ProtocolAPI) {
|
||||
LogManager.warning("Server couldn't verify if udev is installed")
|
||||
return
|
||||
}
|
||||
val response = udevResponse.contains("slime")
|
||||
val isUdevInstalled = udevResponse.contains("slime")
|
||||
val isWayland = System.getenv("XDG_SESSION_TYPE").lowercase().contains("wayland")
|
||||
val fbb = FlatBufferBuilder(1024)
|
||||
val outbound = this.rpcHandler.createRPCMessage(
|
||||
fbb,
|
||||
RpcMessage.InstalledInfoResponse,
|
||||
createInstalledInfoResponse(fbb, response),
|
||||
createInstalledInfoResponse(fbb, isUdevInstalled, isWayland),
|
||||
)
|
||||
fbb.finish(outbound)
|
||||
conn.send(fbb.dataBuffer())
|
||||
|
||||
@@ -39,6 +39,7 @@ class RPCKeybindHandler(
|
||||
|
||||
|
||||
private fun onKeybindRequest(conn: GenericConnection, messageHeader: RpcMessageHeader) {
|
||||
println("Received KeybindsRequest")
|
||||
val fbb = FlatBufferBuilder(32)
|
||||
val response = buildKeybindResponse(fbb)
|
||||
val outbound = rpcHandler.createRPCMessage(
|
||||
@@ -51,6 +52,7 @@ class RPCKeybindHandler(
|
||||
}
|
||||
|
||||
private fun onChangeKeybindRequest(conn: GenericConnection, messageHeader: RpcMessageHeader) {
|
||||
println("Received Keybinds Change request")
|
||||
val req = (messageHeader.message(ChangeKeybindRequest()) as ChangeKeybindRequest).unpack()
|
||||
|
||||
keybindingConfig.fullResetBinding = req.keybind[KeybindName.FULL_RESET].keybindValue
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
package dev.slimevr.protocol.rpc.openuri
|
||||
|
||||
import dev.slimevr.protocol.GenericConnection
|
||||
import dev.slimevr.protocol.ProtocolAPI
|
||||
import dev.slimevr.protocol.rpc.RPCHandler
|
||||
import solarxr_protocol.rpc.RpcMessage
|
||||
import solarxr_protocol.rpc.RpcMessageHeader
|
||||
import dev.hannah.portals.PortalManager
|
||||
import dev.slimevr.SLIMEVR_IDENTIFIER
|
||||
|
||||
class RPCOpenUriHandler(var rpcHandler: RPCHandler, var api: ProtocolAPI) {
|
||||
|
||||
init {
|
||||
rpcHandler.registerPacketListener(RpcMessage.OpenUriRequest, ::onOpenUriRequest)
|
||||
}
|
||||
|
||||
fun onOpenUriRequest(conn: GenericConnection, messageHeader: RpcMessageHeader?) {
|
||||
val portalManager = PortalManager(SLIMEVR_IDENTIFIER)
|
||||
portalManager.openGlobalShortcutsSettings()
|
||||
}
|
||||
}
|
||||
@@ -73,8 +73,7 @@ tasks.shadowJar {
|
||||
exclude(dependency("com.fazecast:jSerialComm:.*"))
|
||||
exclude(dependency("net.java.dev.jna:.*:.*"))
|
||||
exclude(dependency("com.google.flatbuffers:flatbuffers-java:.*"))
|
||||
exclude(dependency("com.github.HannahPadd:DbusGlobalShortcutsWayland:5010e75bd4"))
|
||||
|
||||
exclude(dependency("com.github.HannahPadd:DbusGlobalShortcutsWayland:v0.1.0"))
|
||||
exclude(project(":solarxr-protocol"))
|
||||
}
|
||||
archiveBaseName.set("slimevr")
|
||||
|
||||
Reference in New Issue
Block a user