mirror of
https://github.com/SlimeVR/SlimeVR-Server.git
synced 2026-04-06 02:01:58 +02:00
Add "Setup Stay Aligned" flow
This commit is contained in:
@@ -544,6 +544,8 @@ settings-general-tracker_mechanics-use_mag_on_all_trackers-label = Use magnetome
|
||||
|
||||
settings-stay_aligned = Stay Aligned
|
||||
settings-stay_aligned-description = Stay Aligned reduces drift by gradually adjusting your trackers to match your most common poses.
|
||||
settings-stay_aligned-setup-label = Setup Stay Aligned
|
||||
settings-stay_aligned-setup-description = You must complete "Setup Stay Aligned" to enable Stay Aligned.
|
||||
settings-stay_aligned-warnings-drift_compensation = ⚠ Please turn off Drift Compensation! Drift Compensation will conflict with Stay Aligned.
|
||||
settings-stay_aligned-enabled-label = Enabled
|
||||
settings-stay_aligned-extra_yaw_correction-label = Extra correction for low quality IMUs (e.g. BMI160, MPU60XX)
|
||||
@@ -1247,6 +1249,34 @@ onboarding-scaled_proportions-reset_proportion-description = To set your body pr
|
||||
onboarding-scaled_proportions-done-title = Body proportions set
|
||||
onboarding-scaled_proportions-done-description = Your body proportions should now be configured based on your height.
|
||||
|
||||
## Stay Aligned setup
|
||||
onboarding-stay_aligned-title = Stay Aligned
|
||||
onboarding-stay_aligned-description = Configure Stay Aligned to keep your trackers aligned.
|
||||
onboarding-stay_aligned-verify_mounting-title = Check your Mounting
|
||||
onboarding-stay_aligned-verify_mounting-step-0 = Stay Aligned requires good mounting. Otherwise, you won't get a good experience with Stay Aligned.
|
||||
onboarding-stay_aligned-verify_mounting-step-1 = 1. Move around while standing.
|
||||
onboarding-stay_aligned-verify_mounting-step-2 = 2. Sit down and move your legs and feet.
|
||||
onboarding-stay_aligned-verify_mounting-step-3 = 3. If your trackers aren't in the right place, restart the process.
|
||||
onboarding-stay_aligned-relaxed_poses-standing-title = Relaxed Standing Pose
|
||||
onboarding-stay_aligned-relaxed_poses-standing-step-0 = 1. Stand in a comfortable position. Relax!
|
||||
onboarding-stay_aligned-relaxed_poses-standing-step-1 = 2. Check that your trackers match your body. If it does not match, you need to restart this flow.
|
||||
onboarding-stay_aligned-relaxed_poses-standing-step-2 = 3. Press the "Detect pose" button.
|
||||
onboarding-stay_aligned-relaxed_poses-sitting-title = Relaxed Sitting in Chair Pose
|
||||
onboarding-stay_aligned-relaxed_poses-sitting-step-0 = 1. Sit in a comfortable position. Relax!
|
||||
onboarding-stay_aligned-relaxed_poses-sitting-step-1 = 2. Check that your trackers match your body. If it does not match, you need to restart this flow.
|
||||
onboarding-stay_aligned-relaxed_poses-sitting-step-2 = 3. Press the "Detect pose" button.
|
||||
onboarding-stay_aligned-relaxed_poses-flat-title = Relaxed Sitting on Floor Pose
|
||||
onboarding-stay_aligned-relaxed_poses-flat-step-0 = 1. Sit on the floor with your legs in front. Relax!
|
||||
onboarding-stay_aligned-relaxed_poses-flat-step-1 = 2. Check that your trackers match your body. If it does not match, you need to restart this flow.
|
||||
onboarding-stay_aligned-relaxed_poses-flat-step-2 = 3. Press the "Detect pose" button.
|
||||
onboarding-stay_aligned-relaxed_poses-skip_step = Skip
|
||||
onboarding-stay_aligned-done-title = Stay Aligned enabled!
|
||||
onboarding-stay_aligned-done-description = Your Stay Aligned setup is complete!
|
||||
onboarding-stay_aligned-previous_step = Previous
|
||||
onboarding-stay_aligned-next_step = Next
|
||||
onboarding-stay_aligned-restart = Restart
|
||||
onboarding-stay_aligned-done = Done
|
||||
|
||||
## Home
|
||||
home-no_trackers = No trackers detected or assigned
|
||||
|
||||
|
||||
BIN
gui/public/images/relaxed_pose_flat.webp
Normal file
BIN
gui/public/images/relaxed_pose_flat.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
BIN
gui/public/images/relaxed_pose_sitting.webp
Normal file
BIN
gui/public/images/relaxed_pose_sitting.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 13 KiB |
BIN
gui/public/images/relaxed_pose_standing.webp
Normal file
BIN
gui/public/images/relaxed_pose_standing.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
@@ -60,6 +60,7 @@ import { AdvancedSettings } from './components/settings/pages/AdvancedSettings';
|
||||
import { FirmwareUpdate } from './components/firmware-update/FirmwareUpdate';
|
||||
import { ConnectionLost } from './components/onboarding/pages/ConnectionLost';
|
||||
import { VRCWarningsPage } from './components/vrc/VRCWarningsPage';
|
||||
import { StayAlignedSetup } from './components/onboarding/pages/stay-aligned/StayAlignedSetup';
|
||||
|
||||
export const GH_REPO = 'SlimeVR/SlimeVR-Server';
|
||||
export const VersionContext = createContext('');
|
||||
@@ -173,6 +174,7 @@ function Layout() {
|
||||
path="body-proportions/scaled"
|
||||
element={<ScaledProportionsPage />}
|
||||
/>
|
||||
<Route path="stay-aligned" element={<StayAlignedSetup />} />
|
||||
<Route path="done" element={<DonePage />} />
|
||||
</Route>
|
||||
<Route path="*" element={<TopBar></TopBar>}></Route>
|
||||
|
||||
@@ -7,12 +7,18 @@ import { PreparationStep } from './mounting-steps/Preparation';
|
||||
import { PutTrackersOnStep } from './mounting-steps/PutTrackersOn';
|
||||
import { useLocalization } from '@fluent/react';
|
||||
|
||||
const steps: Step[] = [
|
||||
// Auto mounting steps that can be included within other flows
|
||||
export const autoMountingSteps: Step[] = [
|
||||
{ type: 'numbered', component: PutTrackersOnStep },
|
||||
{ type: 'numbered', component: PreparationStep },
|
||||
{ type: 'numbered', component: MountingResetStep },
|
||||
];
|
||||
|
||||
const steps: Step[] = [
|
||||
...autoMountingSteps,
|
||||
{ type: 'fullsize', component: DoneStep },
|
||||
];
|
||||
|
||||
export function AutomaticMountingPage() {
|
||||
const { l10n } = useLocalization();
|
||||
const { applyProgress, state } = useOnboarding();
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
import { useOnboarding } from '@/hooks/onboarding';
|
||||
import { Typography } from '@/components/commons/Typography';
|
||||
import { Step, StepperSlider } from '@/components/onboarding/StepperSlider';
|
||||
import { DoneStep } from './stay-aligned-steps/Done';
|
||||
import { useLocalization } from '@fluent/react';
|
||||
import { autoMountingSteps } from '@/components/onboarding/pages/mounting/AutomaticMounting';
|
||||
import {
|
||||
FlatRelaxedPoseStep,
|
||||
SittingRelaxedPoseStep,
|
||||
StandingRelaxedPoseStep,
|
||||
} from './stay-aligned-steps/RelaxedPoseSteps';
|
||||
import { EnableStayAlignedRequestT, RpcMessage } from 'solarxr-protocol';
|
||||
import { RPCPacketType, useWebsocketAPI } from '@/hooks/websocket-api';
|
||||
import { useEffect } from 'react';
|
||||
import { VerifyMountingStep } from './stay-aligned-steps/VerifyMounting';
|
||||
|
||||
export function enableStayAligned(
|
||||
enable: boolean,
|
||||
sendRPCPacket: (type: RpcMessage, data: RPCPacketType) => void
|
||||
) {
|
||||
const req = new EnableStayAlignedRequestT();
|
||||
req.enable = enable;
|
||||
sendRPCPacket(RpcMessage.EnableStayAlignedRequest, req);
|
||||
}
|
||||
|
||||
const steps: Step[] = [
|
||||
...autoMountingSteps,
|
||||
{ type: 'numbered', component: VerifyMountingStep },
|
||||
{ type: 'numbered', component: StandingRelaxedPoseStep },
|
||||
{ type: 'numbered', component: SittingRelaxedPoseStep },
|
||||
{ type: 'numbered', component: FlatRelaxedPoseStep },
|
||||
{ type: 'fullsize', component: DoneStep },
|
||||
];
|
||||
export function StayAlignedSetup() {
|
||||
const { l10n } = useLocalization();
|
||||
const { state } = useOnboarding();
|
||||
const { sendRPCPacket } = useWebsocketAPI();
|
||||
|
||||
useEffect(() => {
|
||||
// Disable Stay Aligned as soon as we enter the setup flow so that we don't
|
||||
// adjust the trackers while trying to set up the feature
|
||||
enableStayAligned(false, sendRPCPacket);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-2 h-full items-center w-full xs:justify-center relative overflow-y-auto overflow-x-hidden px-4 pb-4">
|
||||
<div className="flex flex-col w-full h-full xs:justify-center xs:max-w-3xl gap-5">
|
||||
<div className="flex flex-col xs:max-w-lg gap-3">
|
||||
<Typography variant="main-title">
|
||||
{l10n.getString('onboarding-stay_aligned-title')}
|
||||
</Typography>
|
||||
<Typography color="secondary">
|
||||
{l10n.getString('onboarding-stay_aligned-description')}
|
||||
</Typography>
|
||||
</div>
|
||||
<div className="flex pb-4">
|
||||
<StepperSlider
|
||||
variant={state.alonePage ? 'alone' : 'onboarding'}
|
||||
steps={steps}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import { Button } from '@/components/commons/Button';
|
||||
import { Typography } from '@/components/commons/Typography';
|
||||
import { SkeletonVisualizerWidget } from '@/components/widgets/SkeletonVisualizerWidget';
|
||||
import { useLocalization } from '@fluent/react';
|
||||
|
||||
export function DoneStep({
|
||||
resetSteps,
|
||||
variant,
|
||||
}: {
|
||||
resetSteps: () => void;
|
||||
variant: 'onboarding' | 'alone';
|
||||
}) {
|
||||
const { l10n } = useLocalization();
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-center w-full justify-center gap-5">
|
||||
<div className="flex gap-1 flex-col justify-center items-center">
|
||||
<Typography variant="section-title">
|
||||
{l10n.getString('onboarding-stay_aligned-done-title')}
|
||||
</Typography>
|
||||
<Typography color="secondary">
|
||||
{l10n.getString('onboarding-stay_aligned-done-description')}
|
||||
</Typography>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-3">
|
||||
<Button
|
||||
variant={variant === 'onboarding' ? 'secondary' : 'tertiary'}
|
||||
onClick={resetSteps}
|
||||
>
|
||||
{l10n.getString('onboarding-stay_aligned-restart')}
|
||||
</Button>
|
||||
<Button
|
||||
variant="primary"
|
||||
to="/settings/trackers"
|
||||
state={{ scrollTo: 'stayaligned' }}
|
||||
>
|
||||
{l10n.getString('onboarding-stay_aligned-done')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<SkeletonVisualizerWidget />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
import { Button } from '@/components/commons/Button';
|
||||
import { Typography } from '@/components/commons/Typography';
|
||||
import {
|
||||
CurrentRelaxedPose,
|
||||
DetectRelaxedPoseButton,
|
||||
ResetRelaxedPoseButton,
|
||||
} from '@/components/stay-aligned/RelaxedPose';
|
||||
import { useLocalization } from '@fluent/react';
|
||||
import { StayAlignedRelaxedPose } from 'solarxr-protocol';
|
||||
import { enableStayAligned } from '@/components/onboarding/pages/stay-aligned/StayAlignedSetup';
|
||||
import { useWebsocketAPI } from '@/hooks/websocket-api';
|
||||
|
||||
function makeRelaxedPoseStep(
|
||||
titleKey: string,
|
||||
descriptionKeys: string[],
|
||||
imageUrl: string,
|
||||
relaxedPose: StayAlignedRelaxedPose,
|
||||
lastStep: boolean
|
||||
) {
|
||||
return ({
|
||||
nextStep,
|
||||
prevStep,
|
||||
variant,
|
||||
}: {
|
||||
nextStep: () => void;
|
||||
prevStep: () => void;
|
||||
variant: 'onboarding' | 'alone';
|
||||
}) => {
|
||||
const { l10n } = useLocalization();
|
||||
const { sendRPCPacket } = useWebsocketAPI();
|
||||
|
||||
return (
|
||||
<div className="flex mobile:flex-col">
|
||||
<div className="flex flex-grow flex-col gap-4 max-w-sm">
|
||||
<Typography variant="main-title" bold>
|
||||
{l10n.getString(titleKey)}
|
||||
</Typography>
|
||||
<div className="flex flex-col gap-2">
|
||||
{descriptionKeys.map((descriptionKey) => (
|
||||
<Typography color="secondary">
|
||||
{l10n.getString(descriptionKey)}
|
||||
</Typography>
|
||||
))}
|
||||
</div>
|
||||
<CurrentRelaxedPose />
|
||||
<div className="flex gap-3 mobile:justify-between">
|
||||
<Button
|
||||
variant={variant === 'onboarding' ? 'secondary' : 'tertiary'}
|
||||
onClick={prevStep}
|
||||
>
|
||||
{l10n.getString('onboarding-stay_aligned-previous_step')}
|
||||
</Button>
|
||||
<DetectRelaxedPoseButton
|
||||
onClick={() => {
|
||||
if (lastStep) {
|
||||
enableStayAligned(true, sendRPCPacket);
|
||||
}
|
||||
nextStep();
|
||||
}}
|
||||
pose={relaxedPose}
|
||||
/>
|
||||
<ResetRelaxedPoseButton
|
||||
onClick={() => {
|
||||
if (lastStep) {
|
||||
enableStayAligned(true, sendRPCPacket);
|
||||
}
|
||||
nextStep();
|
||||
}}
|
||||
pose={relaxedPose}
|
||||
>
|
||||
{l10n.getString(
|
||||
'onboarding-stay_aligned-relaxed_poses-skip_step'
|
||||
)}
|
||||
</ResetRelaxedPoseButton>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col pt-1 items-center fill-background-50 justify-center px-12">
|
||||
<img src={imageUrl} width={200} alt="Reset position" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
export const StandingRelaxedPoseStep = makeRelaxedPoseStep(
|
||||
'onboarding-stay_aligned-relaxed_poses-standing-title',
|
||||
[
|
||||
'onboarding-stay_aligned-relaxed_poses-standing-step-0',
|
||||
'onboarding-stay_aligned-relaxed_poses-standing-step-1',
|
||||
'onboarding-stay_aligned-relaxed_poses-standing-step-2',
|
||||
],
|
||||
'/images/relaxed_pose_standing.webp',
|
||||
StayAlignedRelaxedPose.STANDING,
|
||||
false
|
||||
);
|
||||
|
||||
export const SittingRelaxedPoseStep = makeRelaxedPoseStep(
|
||||
'onboarding-stay_aligned-relaxed_poses-sitting-title',
|
||||
[
|
||||
'onboarding-stay_aligned-relaxed_poses-sitting-step-0',
|
||||
'onboarding-stay_aligned-relaxed_poses-sitting-step-1',
|
||||
'onboarding-stay_aligned-relaxed_poses-sitting-step-2',
|
||||
],
|
||||
'/images/relaxed_pose_sitting.webp',
|
||||
StayAlignedRelaxedPose.SITTING,
|
||||
false
|
||||
);
|
||||
|
||||
export const FlatRelaxedPoseStep = makeRelaxedPoseStep(
|
||||
'onboarding-stay_aligned-relaxed_poses-flat-title',
|
||||
[
|
||||
'onboarding-stay_aligned-relaxed_poses-flat-step-0',
|
||||
'onboarding-stay_aligned-relaxed_poses-flat-step-1',
|
||||
'onboarding-stay_aligned-relaxed_poses-flat-step-2',
|
||||
],
|
||||
'/images/relaxed_pose_flat.webp',
|
||||
StayAlignedRelaxedPose.FLAT,
|
||||
true
|
||||
);
|
||||
@@ -0,0 +1,50 @@
|
||||
import { Button } from '@/components/commons/Button';
|
||||
import { Typography } from '@/components/commons/Typography';
|
||||
import { useLocalization } from '@fluent/react';
|
||||
|
||||
export function VerifyMountingStep({
|
||||
nextStep,
|
||||
resetSteps,
|
||||
variant,
|
||||
}: {
|
||||
nextStep: () => void;
|
||||
resetSteps: () => void;
|
||||
variant: 'onboarding' | 'alone';
|
||||
}) {
|
||||
const { l10n } = useLocalization();
|
||||
|
||||
return (
|
||||
<div className="flex mobile:flex-col">
|
||||
<div className="flex flex-grow flex-col gap-4 max-w-sm">
|
||||
<Typography variant="main-title" bold>
|
||||
{l10n.getString('onboarding-stay_aligned-verify_mounting-title')}
|
||||
</Typography>
|
||||
<div className="flex flex-col gap-2">
|
||||
<Typography color="secondary">
|
||||
{l10n.getString('onboarding-stay_aligned-verify_mounting-step-0')}
|
||||
</Typography>
|
||||
<Typography color="secondary">
|
||||
{l10n.getString('onboarding-stay_aligned-verify_mounting-step-1')}
|
||||
</Typography>
|
||||
<Typography color="secondary">
|
||||
{l10n.getString('onboarding-stay_aligned-verify_mounting-step-2')}
|
||||
</Typography>
|
||||
<Typography color="secondary">
|
||||
{l10n.getString('onboarding-stay_aligned-verify_mounting-step-3')}
|
||||
</Typography>
|
||||
</div>
|
||||
<div className="flex gap-3 mobile:justify-between">
|
||||
<Button
|
||||
variant={variant === 'onboarding' ? 'secondary' : 'tertiary'}
|
||||
onClick={resetSteps}
|
||||
>
|
||||
{l10n.getString('onboarding-stay_aligned-restart')}
|
||||
</Button>
|
||||
<Button variant="primary" onClick={nextStep}>
|
||||
{l10n.getString('onboarding-stay_aligned-next_step')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -96,6 +96,18 @@ export function StayAlignedSettings({
|
||||
<Typography color="secondary">
|
||||
{l10n.getString('settings-stay_aligned-description')}
|
||||
</Typography>
|
||||
<Typography color="secondary">
|
||||
{l10n.getString('settings-stay_aligned-setup-description')}
|
||||
</Typography>
|
||||
<div className="flex mt-2">
|
||||
<Button
|
||||
variant="primary"
|
||||
to="/onboarding/stay-aligned"
|
||||
state={{ alonePage: true }}
|
||||
>
|
||||
{l10n.getString('settings-stay_aligned-setup-label')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-4">
|
||||
<Typography bold>
|
||||
|
||||
Reference in New Issue
Block a user