diff --git a/gui/src/components/onboarding/pages/usage-reason/MocapDataChoose.tsx b/gui/src/components/onboarding/pages/usage-reason/MocapDataChoose.tsx index 052efd0e78..84e50abfcf 100644 --- a/gui/src/components/onboarding/pages/usage-reason/MocapDataChoose.tsx +++ b/gui/src/components/onboarding/pages/usage-reason/MocapDataChoose.tsx @@ -83,8 +83,8 @@ export function MocapDataChoose() { return (
-
-
+
+
{l10n.getString('onboarding-usage-mocap-data_choose')} diff --git a/gui/src/components/onboarding/pages/usage-reason/MocapVMCSetup.tsx b/gui/src/components/onboarding/pages/usage-reason/MocapVMCSetup.tsx index f14b7a368d..23208f88cb 100644 --- a/gui/src/components/onboarding/pages/usage-reason/MocapVMCSetup.tsx +++ b/gui/src/components/onboarding/pages/usage-reason/MocapVMCSetup.tsx @@ -1,3 +1,233 @@ +import { CheckBox } from '@/components/commons/Checkbox'; +import { FileInput } from '@/components/commons/FileInput'; +import { VMCIcon } from '@/components/commons/icon/VMCIcon'; +import { Input } from '@/components/commons/Input'; +import { Typography } from '@/components/commons/Typography'; +import { + DEFAULT_VMC_VALUES, + parseVRMFile, + VMCSettingsForm, +} from '@/components/settings/pages/VMCSettings'; +import { SettingsPagePaneLayout } from '@/components/settings/SettingsPageLayout'; +import { useWebsocketAPI } from '@/hooks/websocket-api'; +import { Localized, useLocalization } from '@fluent/react'; +import { useEffect, useState } from 'react'; +import { useForm } from 'react-hook-form'; +import { + ChangeSettingsRequestT, + OSCSettingsT, + RpcMessage, + SettingsRequestT, + SettingsResponseT, + VMCOSCSettingsT, +} from 'solarxr-protocol'; + export function MocapVMCSetup() { - return
; + const { l10n } = useLocalization(); + const { sendRPCPacket, useRPCPacket } = useWebsocketAPI(); + const [modelName, setModelName] = useState(null); + + const { reset, control, watch, handleSubmit } = useForm({ + defaultValues: { + ...DEFAULT_VMC_VALUES, + vmc: { oscSettings: { enabled: true }, anchorHip: false }, + }, + }); + + const onSubmit = async (values: VMCSettingsForm) => { + const settings = new ChangeSettingsRequestT(); + + if (values.vmc) { + const vmcOsc = new VMCOSCSettingsT(); + + vmcOsc.oscSettings = Object.assign( + new OSCSettingsT(), + values.vmc.oscSettings + ); + + if (values.vmc.vrmJson !== undefined) { + if (values.vmc.vrmJson.length > 0) { + vmcOsc.vrmJson = await parseVRMFile(values.vmc.vrmJson[0]); + if (vmcOsc.vrmJson) { + setModelName( + JSON.parse(vmcOsc.vrmJson)?.extensions?.VRM?.meta?.title || '' + ); + } + } else { + vmcOsc.vrmJson = ''; + setModelName(null); + } + } + vmcOsc.anchorHip = values.vmc.anchorHip; + vmcOsc.mirrorTracking = values.vmc.mirrorTracking; + + settings.vmcOsc = vmcOsc; + } + sendRPCPacket(RpcMessage.ChangeSettingsRequest, settings); + }; + + useEffect(() => { + const subscription = watch(() => handleSubmit(onSubmit)()); + return () => subscription.unsubscribe(); + }, []); + + useEffect(() => { + sendRPCPacket(RpcMessage.SettingsRequest, new SettingsRequestT()); + }, []); + + useRPCPacket(RpcMessage.SettingsResponse, (settings: SettingsResponseT) => { + const formData: VMCSettingsForm = DEFAULT_VMC_VALUES; + if (settings.vmcOsc) { + if (settings.vmcOsc.oscSettings) { + formData.vmc.oscSettings.enabled = settings.vmcOsc.oscSettings.enabled; + if (settings.vmcOsc.oscSettings.portIn) + formData.vmc.oscSettings.portIn = settings.vmcOsc.oscSettings.portIn; + if (settings.vmcOsc.oscSettings.portOut) + formData.vmc.oscSettings.portOut = + settings.vmcOsc.oscSettings.portOut; + if (settings.vmcOsc.oscSettings.address) + formData.vmc.oscSettings.address = + settings.vmcOsc.oscSettings.address.toString(); + } + const vrmJson = settings.vmcOsc.vrmJson?.toString(); + if (vrmJson) { + setModelName(JSON.parse(vrmJson)?.extensions?.VRM?.meta?.title || ''); + } + + formData.vmc.anchorHip = settings.vmcOsc.anchorHip; + formData.vmc.mirrorTracking = settings.vmcOsc.mirrorTracking; + } + + reset(formData); + }); + + return ( +
+ } id="vmc"> + + {l10n.getString('settings-osc-vmc')} + +
+ <> + {l10n + .getString('settings-osc-vmc-description') + .split('\n') + .map((line, i) => ( + + {line} + + ))} + +
+ + {l10n.getString('settings-osc-vmc-network')} + +
+ <> + {l10n + .getString('settings-osc-vmc-network-description') + .split('\n') + .map((line, i) => ( + + {line} + + ))} + +
+
+ + + + + + +
+ + {l10n.getString('settings-osc-vmc-network-address')} + +
+ + {l10n.getString('settings-osc-vmc-network-address-description')} + +
+
+ +
+ {l10n.getString('settings-osc-vmc-vrm')} +
+ + {l10n.getString('settings-osc-vmc-vrm-description')} + +
+
+ + {/* For some reason, linux (GNOME) is detecting the VRM file is a VRML */} +
+ + {l10n.getString('settings-osc-vmc-mirror_tracking')} + +
+ + {l10n.getString('settings-osc-vmc-mirror_tracking-description')} + +
+
+ +
+
+
+ ); } diff --git a/gui/src/components/settings/pages/VMCSettings.tsx b/gui/src/components/settings/pages/VMCSettings.tsx index 83b77c8677..8f6a7297ae 100644 --- a/gui/src/components/settings/pages/VMCSettings.tsx +++ b/gui/src/components/settings/pages/VMCSettings.tsx @@ -22,7 +22,7 @@ import { } from '@/components/settings/SettingsPageLayout'; import { error } from '@/utils/logging'; -interface VMCSettingsForm { +export interface VMCSettingsForm { vmc: { oscSettings: { enabled: boolean; @@ -36,7 +36,7 @@ interface VMCSettingsForm { }; } -const defaultValues = { +export const DEFAULT_VMC_VALUES = { vmc: { oscSettings: { enabled: false, @@ -55,7 +55,7 @@ export function VMCSettings() { const [modelName, setModelName] = useState(null); const { reset, control, watch, handleSubmit } = useForm({ - defaultValues, + defaultValues: DEFAULT_VMC_VALUES, }); const onSubmit = async (values: VMCSettingsForm) => { @@ -99,7 +99,7 @@ export function VMCSettings() { }, []); useRPCPacket(RpcMessage.SettingsResponse, (settings: SettingsResponseT) => { - const formData: VMCSettingsForm = defaultValues; + const formData: VMCSettingsForm = DEFAULT_VMC_VALUES; if (settings.vmcOsc) { if (settings.vmcOsc.oscSettings) { formData.vmc.oscSettings.enabled = settings.vmcOsc.oscSettings.enabled; @@ -299,7 +299,7 @@ export function VMCSettings() { const gltfHeaderStart = 0; const gltfHeaderEnd = 20; -async function parseVRMFile(vrm: File): Promise { +export async function parseVRMFile(vrm: File): Promise { const headerView = new DataView( await vrm.slice(gltfHeaderStart, gltfHeaderEnd).arrayBuffer() );