mirror of
https://github.com/SlimeVR/SlimeVR-Server.git
synced 2026-04-06 02:01:58 +02:00
Should be done
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { useState, forwardRef, useRef } from 'react';
|
||||
import { Typography } from './Typography';
|
||||
import classNames from 'classnames';
|
||||
|
||||
const excludedKeys = [' ', 'SPACE', 'META'];
|
||||
const maxKeybindLength = 4;
|
||||
@@ -68,19 +69,12 @@ export const KeybindRecorder = forwardRef<
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="relative w-full justify-center align-center">
|
||||
{showError ? (
|
||||
<div className="absolute bottom isInvalid keyslot-invalid text-red-600">
|
||||
<Typography color="red-600">{errorText}</Typography>
|
||||
</div>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
<div className="flex gap-2 m-2 items-center rounded-lg">
|
||||
<div className="w-full justify-center items-center flex flex-col gap-2">
|
||||
<div className="flex gap-2 p-2 items-center rounded-lg relative">
|
||||
<input
|
||||
autoFocus
|
||||
ref={inputRef}
|
||||
className="opacity-0 absolute inset-0 cursor-pointer"
|
||||
className="opacity-0 absolute cursor-pointer w-full"
|
||||
onFocus={handleOnFocus}
|
||||
onBlur={handleOnBlur}
|
||||
onKeyDown={handleKeyDown}
|
||||
@@ -93,20 +87,15 @@ export const KeybindRecorder = forwardRef<
|
||||
return (
|
||||
<div key={i} className="flex flex-row">
|
||||
<div
|
||||
className={`
|
||||
flex p-2 rounded-lg min-w-[50px] min-h-[50px] text-lg justify-center items-center bg-background-80 mobile:text-sm
|
||||
${
|
||||
isInvalid
|
||||
? 'keyslot-invalid ring-2 ring-red-600'
|
||||
: isActive
|
||||
? 'keyslot-animate ring-2 ring-accent'
|
||||
: 'ring-accent'
|
||||
}
|
||||
`}
|
||||
className={classNames('flex p-2 rounded-lg min-w-[50px] min-h-[50px] text-main-title justify-center items-center bg-background-80 mobile:text-sm', {
|
||||
'keyslot-invalid ring-2 ring-status-critical': isInvalid,
|
||||
'keyslot-animate ring-2 ring-accent': isActive && !isInvalid,
|
||||
'ring-accent': !isInvalid && !isInvalid
|
||||
})}
|
||||
>
|
||||
{key ?? ''}
|
||||
</div>
|
||||
<div className="flex pl-2 text-lg justify-center items-center mobile:text-sm">
|
||||
<div className="flex pl-2 text-main-title justify-center items-center mobile:text-sm">
|
||||
{i < maxKeybindLength - 1 ? '+' : ''}
|
||||
</div>
|
||||
</div>
|
||||
@@ -114,6 +103,11 @@ export const KeybindRecorder = forwardRef<
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
{showError && (
|
||||
<div className="isInvalid keyslot-invalid">
|
||||
<Typography color="text-status-critical">{errorText}</Typography>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -29,49 +29,50 @@ export function KeybindRecorderModal({
|
||||
return (
|
||||
<BaseModal
|
||||
isOpen={isVisisble}
|
||||
appendClasses="w-full max-w-xl h-full max-h-52"
|
||||
onRequestClose={() => onClose()}
|
||||
appendClasses="w-full max-w-xl"
|
||||
>
|
||||
<div className="flex-col gap-4 w-full">
|
||||
<div className="flex flex-col w-full">
|
||||
<div className="flex flex-col gap-3 w-full">
|
||||
<Typography variant="section-title">
|
||||
{l10n.getString('settings-keybinds-recorder-modal-title')}{' '}
|
||||
{l10n.getString(keybindlocalization)}
|
||||
</Typography>
|
||||
<Controller
|
||||
control={control}
|
||||
name={name}
|
||||
render={({ field }) => (
|
||||
<KeybindRecorder
|
||||
keys={field.value ?? []}
|
||||
onKeysChange={field.onChange}
|
||||
ref={field.ref}
|
||||
/>
|
||||
)}
|
||||
<div className="flex flex-col gap-3 w-full justify-between h-full">
|
||||
<Typography variant="section-title">
|
||||
{l10n.getString('settings-keybinds-recorder-modal-title')}{' '}
|
||||
{l10n.getString(keybindlocalization)}
|
||||
</Typography>
|
||||
<Controller
|
||||
control={control}
|
||||
name={name}
|
||||
render={({ field }) => (
|
||||
<KeybindRecorder
|
||||
keys={field.value ?? []}
|
||||
onKeysChange={field.onChange}
|
||||
ref={field.ref}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<div className="flex flex-row justify-between w-full">
|
||||
<div className="flex flex-row justify-start gap-4">
|
||||
<Button
|
||||
id="settings-keybinds-recorder-modal-reset-button"
|
||||
variant="tertiary"
|
||||
onClick={() => {
|
||||
resetField(name);
|
||||
onClose()
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
id="settings-keybinds-recorder-modal-unbind-button"
|
||||
variant="tertiary"
|
||||
onClick={() => {
|
||||
onUnbind()
|
||||
onClose()
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-row justify-end">
|
||||
<Button
|
||||
id="settings-keybinds-recorder-modal-done-button"
|
||||
variant="primary"
|
||||
onClick={onClose}
|
||||
/>
|
||||
<div className="flex flex-row justify-between w-full">
|
||||
<div className="flex flex-row justify-start gap-4">
|
||||
<Button
|
||||
id="settings-keybinds-recorder-modal-reset-button"
|
||||
variant="primary"
|
||||
onClick={() => {
|
||||
resetField(name);
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
id="settings-keybinds-recorder-modal-unbind-button"
|
||||
variant="primary"
|
||||
onClick={onUnbind}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-row justify-end">
|
||||
<Button
|
||||
id="settings-keybinds-recorder-modal-done-button"
|
||||
variant="primary"
|
||||
onClick={onClose}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { Typography } from './Typography';
|
||||
import './KeybindRow.scss';
|
||||
import { ReactNode, useEffect, useState } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Control, UseFormGetValues } from 'react-hook-form';
|
||||
import { NumberSelector } from './NumberSelector';
|
||||
import { useLocaleConfig } from '@/i18n/config';
|
||||
|
||||
const createKeybindDisplay = (keybind: string[]): ReactNode | null => {
|
||||
function KeyBindKeyList({ keybind }: { keybind: string[] }) {
|
||||
if (keybind.length <= 1) {
|
||||
return (
|
||||
<div className="flex min-h-[50px] h-full text-lg items-center justifiy-center">
|
||||
<div className="flex h-full text-section-title items-center justifiy-center">
|
||||
Click to edit keybind
|
||||
</div>
|
||||
);
|
||||
@@ -16,10 +16,10 @@ const createKeybindDisplay = (keybind: string[]): ReactNode | null => {
|
||||
return keybind.map((key, i) => {
|
||||
return (
|
||||
<div key={i} className="flex flex-row">
|
||||
<div className="flex flex-wrap p-2 rounded-lg min-w-[50px] min-h-[50px] text-lg justify-center items-center bg-background-80 mobile:text-sm">
|
||||
<div className="flex flex-wrap p-2 rounded-lg min-w-[50px] text-standard-bold justify-center items-center bg-background-80 mobile:text-sm">
|
||||
{key ?? ''}
|
||||
</div>
|
||||
<div className="flex justify-center items-center text-lg mobile:text-sm gap-2 pl-3">
|
||||
<div className="flex justify-center items-center text-section-title mobile:text-sm gap-2 pl-3">
|
||||
{i < keybind.length - 1 ? '+' : ''}
|
||||
</div>
|
||||
</div>
|
||||
@@ -40,7 +40,6 @@ export function KeybindsRow({
|
||||
getValue: UseFormGetValues<any>;
|
||||
openKeybindRecorderModal: (index: number) => void;
|
||||
}) {
|
||||
const [keybindDisplay, setKeybindDisplay] = useState<ReactNode>(null);
|
||||
const [binding, setBinding] = useState<string[]>();
|
||||
const { currentLocales } = useLocaleConfig();
|
||||
const secondsFormat = new Intl.NumberFormat(currentLocales, {
|
||||
@@ -58,21 +57,17 @@ export function KeybindsRow({
|
||||
setBinding(getValue(`keybinds.${index}.binding`));
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (binding != null) setKeybindDisplay(createKeybindDisplay(binding));
|
||||
}, [binding]);
|
||||
|
||||
return (
|
||||
<div className="keybind-row bg-background-60 rounded-xl h-full hover:ring-2 hover:ring-accent mobile:flex mobile:flex-wrap mobile:justify-center mobile:items-center">
|
||||
<label className="text-sm font-medium text-background-10 p-2">
|
||||
<div className="keybind-row bg-background-60 rounded-xl h-full keybinds-small:flex keybinds-small:flex-col keybinds-small:justify-center keybinds-small:items-center p-2">
|
||||
<label className="text-sm font-medium text-background-10 keybinds-small:flex keybinds-small:py-2 keybinds-small:justify-center keybinds-small:align-middle">
|
||||
<Typography id={`settings-keybinds_${id}`} />
|
||||
</label>
|
||||
<div
|
||||
className="flex gap-2 min-h-[42px] items-center m-2 rounded-lg bg-background-70 hover:bg-background-50"
|
||||
className="flex gap-2 h-full items-center rounded-lg bg-background-70 hover:bg-background-50 w-full"
|
||||
onClick={handleOpenModal}
|
||||
>
|
||||
<div className="flex flex-grow gap-2 m-2 justify-center h-full">
|
||||
{keybindDisplay}
|
||||
<div className="flex flex-grow gap-2 justify-center p-2 h-[50px]">
|
||||
{binding != null && <KeyBindKeyList keybind={binding} />}
|
||||
</div>
|
||||
</div>
|
||||
<NumberSelector
|
||||
|
||||
@@ -6,11 +6,11 @@
|
||||
'n v d ' auto
|
||||
'k k k ' auto
|
||||
'b . . ' auto
|
||||
/ 1fr 1fr 80px;
|
||||
/ 1fr 1fr 90px;
|
||||
|
||||
grid-template-columns: subgrid;
|
||||
grid-template-rows: subgrid;
|
||||
|
||||
align-items: left;
|
||||
gap: 20px;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
@@ -16,9 +16,12 @@ import {
|
||||
KeybindRequestT,
|
||||
KeybindResponseT,
|
||||
KeybindT,
|
||||
OpenUriRequestT,
|
||||
RpcMessage,
|
||||
} from 'solarxr-protocol';
|
||||
import { useFieldArray, useForm } from 'react-hook-form';
|
||||
import { useAppContext } from '@/hooks/app';
|
||||
import { useElectron } from '@/hooks/electron';
|
||||
|
||||
export type KeybindForm = {
|
||||
keybinds: {
|
||||
@@ -30,6 +33,7 @@ export type KeybindForm = {
|
||||
};
|
||||
|
||||
export function KeybindSettings() {
|
||||
const electron = useElectron();
|
||||
const { l10n } = useLocalization();
|
||||
const { sendRPCPacket, useRPCPacket } = useWebsocketAPI();
|
||||
const [isOpen, setIsOpen] = useState<boolean>(false);
|
||||
@@ -39,6 +43,8 @@ export function KeybindSettings() {
|
||||
}
|
||||
);
|
||||
const currentIndex = useRef<number | null>(null);
|
||||
const { installInfo } = useAppContext();
|
||||
|
||||
|
||||
const { control, resetField, handleSubmit, reset, setValue, getValues } =
|
||||
useForm<KeybindForm>({
|
||||
@@ -66,6 +72,10 @@ export function KeybindSettings() {
|
||||
});
|
||||
};
|
||||
|
||||
const handleOpenSystemSettingsButton = () => {
|
||||
sendRPCPacket(RpcMessage.OpenUriRequest, new OpenUriRequestT());
|
||||
};
|
||||
|
||||
useRPCPacket(
|
||||
RpcMessage.KeybindResponse,
|
||||
({ keybind, defaultKeybinds }: KeybindResponseT) => {
|
||||
@@ -127,62 +137,82 @@ export function KeybindSettings() {
|
||||
return (
|
||||
<SettingsPageLayout>
|
||||
<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>
|
||||
))}
|
||||
<div className="flex flex-col gap-2">
|
||||
<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>
|
||||
) : electron.isElectron && electron.data().os.type === 'windows' && (
|
||||
<>
|
||||
<div className="keybind-settings">
|
||||
<Typography
|
||||
id="keybind_config-keybind_name"
|
||||
variant="section-title"
|
||||
/>
|
||||
<Typography
|
||||
id="keybind_config-keybind_value"
|
||||
variant="section-title"
|
||||
/>
|
||||
<Typography
|
||||
id="keybind_config-keybind_delay"
|
||||
variant="section-title"
|
||||
/>
|
||||
{createKeybindRows()}
|
||||
</div>
|
||||
<div className="flex justify-end">
|
||||
<Button
|
||||
id="settings-keybinds_reset-all-button"
|
||||
onClick={() => {
|
||||
reset(defaultKeybindsState);
|
||||
handleSubmit(onSubmit)();
|
||||
}}
|
||||
variant="primary"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<KeybindRecorderModal
|
||||
id={
|
||||
currentIndex.current != null
|
||||
? fields[currentIndex.current].name
|
||||
: ''
|
||||
}
|
||||
control={control}
|
||||
resetField={resetField}
|
||||
name={
|
||||
currentIndex.current != null
|
||||
? `keybinds.${currentIndex.current}.binding`
|
||||
: ''
|
||||
}
|
||||
isVisisble={isOpen}
|
||||
onClose={() => {
|
||||
setIsOpen(false);
|
||||
handleSubmit(onSubmit)();
|
||||
}}
|
||||
onUnbind={() => {
|
||||
if (currentIndex.current != null)
|
||||
setValue(`keybinds.${currentIndex.current}.binding`, []);
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<div className="keybind-settings">
|
||||
<Typography
|
||||
id="keybind_config-keybind_name"
|
||||
variant="section-title"
|
||||
/>
|
||||
<Typography
|
||||
id="keybind_config-keybind_value"
|
||||
variant="section-title"
|
||||
/>
|
||||
<Typography
|
||||
id="keybind_config-keybind_delay"
|
||||
variant="section-title"
|
||||
/>
|
||||
{createKeybindRows()}
|
||||
<Button
|
||||
id="settings-keybinds_reset-all-button"
|
||||
className="justify-self-start"
|
||||
onClick={() => {
|
||||
reset(defaultKeybindsState);
|
||||
handleSubmit(onSubmit)();
|
||||
}}
|
||||
variant="primary"
|
||||
/>
|
||||
</div>
|
||||
<KeybindRecorderModal
|
||||
id={
|
||||
currentIndex.current != null
|
||||
? fields[currentIndex.current].name
|
||||
: ''
|
||||
}
|
||||
control={control}
|
||||
resetField={resetField}
|
||||
name={
|
||||
currentIndex.current != null
|
||||
? `keybinds.${currentIndex.current}.binding`
|
||||
: ''
|
||||
}
|
||||
isVisisble={isOpen}
|
||||
onClose={() => {
|
||||
setIsOpen(false);
|
||||
handleSubmit(onSubmit)();
|
||||
}}
|
||||
onUnbind={() => {
|
||||
if (currentIndex.current != null)
|
||||
setValue(`keybinds.${currentIndex.current}.binding`, []);
|
||||
}}
|
||||
/>
|
||||
</SettingsPagePaneLayout>
|
||||
</SettingsPageLayout>
|
||||
);
|
||||
|
||||
@@ -59,16 +59,6 @@ export function useProvideAppContext(): AppContext {
|
||||
}
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
sendRPCPacket(RpcMessage.InstalledInfoRequest, new InstalledInfoResponseT());
|
||||
}, []);
|
||||
|
||||
useRPCPacket(
|
||||
RpcMessage.InstalledInfoResponse,
|
||||
({ isUdevInstalled, isWayland }: InstalledInfoResponseT) => {
|
||||
setInstallInfo(new InstalledInfoResponseT(isUdevInstalled, isWayland));
|
||||
}
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
updateSentryContext(devices);
|
||||
@@ -90,6 +80,18 @@ export function useProvideAppContext(): AppContext {
|
||||
};
|
||||
}, [config?.uuid]);
|
||||
|
||||
useEffect(() => {
|
||||
sendRPCPacket(RpcMessage.InstalledInfoRequest, new InstalledInfoResponseT());
|
||||
}, []);
|
||||
|
||||
useRPCPacket(
|
||||
RpcMessage.InstalledInfoResponse,
|
||||
({ isUdevInstalled, isWayland }: InstalledInfoResponseT) => {
|
||||
setInstallInfo(new InstalledInfoResponseT(isUdevInstalled, isWayland));
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
useLayoutEffect(() => {
|
||||
changeLocales([config?.lang || DEFAULT_LOCALE]);
|
||||
}, []);
|
||||
|
||||
@@ -183,6 +183,7 @@ const config = {
|
||||
lg: '1300px',
|
||||
xl: '1600px',
|
||||
tall: { raw: '(min-height: 860px)' },
|
||||
'keybinds-small': { raw: 'not (min-width: 1230px)' },
|
||||
},
|
||||
extend: {
|
||||
colors: {
|
||||
|
||||
Reference in New Issue
Block a user