Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
106bc67
add manual mounting to stay alligned setup
Aed-1 Dec 19, 2025
096b52a
applied suggestions
Aed-1 Dec 19, 2025
6648444
set Closable prop and handle edge case
Aed-1 Dec 26, 2025
aa08b40
make lint shutup
Aed-1 Dec 26, 2025
99cefe5
add appropriate closing
Aed-1 Dec 26, 2025
baa2f68
naming
Aed-1 Dec 26, 2025
878412c
didnt need that
Aed-1 Dec 26, 2025
3c36d69
Merge branch 'main' into main
loucass003 Dec 26, 2025
ad5a83c
fixed z-height priority
Aed-1 Dec 28, 2025
247cbe9
fix button
Aed-1 Dec 28, 2025
54c4927
Update gui/src/components/onboarding/pages/mounting/ManualMounting.tsx
Aed-1 Dec 29, 2025
374c251
Update gui/src/components/onboarding/pages/stay-aligned/stay-aligned-…
Aed-1 Dec 29, 2025
7674c82
Update gui/src/components/onboarding/pages/stay-aligned/stay-aligned-…
Aed-1 Dec 29, 2025
bbf974b
Update gui/src/components/onboarding/pages/stay-aligned/stay-aligned-…
Aed-1 Dec 29, 2025
fe3144d
Update gui/src/components/onboarding/pages/stay-aligned/stay-aligned-…
Aed-1 Dec 29, 2025
c9daf96
Update gui/src/components/onboarding/pages/stay-aligned/stay-aligned-…
Aed-1 Dec 29, 2025
c35ae4f
Update gui/src/components/onboarding/pages/stay-aligned/stay-aligned-…
Aed-1 Dec 29, 2025
b4092e2
Update gui/src/components/onboarding/pages/stay-aligned/stay-aligned-…
Aed-1 Dec 29, 2025
dc580ad
add suggestion
Aed-1 Dec 29, 2025
d8e288e
handle edgecase
Aed-1 Dec 31, 2025
e4839e4
fix this
Aed-1 Dec 31, 2025
d4723d1
fix lint
Aed-1 Dec 31, 2025
c81beab
bruh we have a function for this already
Aed-1 Dec 31, 2025
810e085
different way for this
Aed-1 Dec 31, 2025
2b386b7
apply requested changes
Aed-1 Dec 31, 2025
e5e2add
fix lint
Aed-1 Dec 31, 2025
8c460d3
change slimevr z height
Aed-1 Dec 31, 2025
a2b7b35
removed empty line
Aed-1 Dec 31, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions gui/public/i18n/en/translation.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -1348,6 +1348,7 @@ onboarding-stay_aligned-previous_step = Previous
onboarding-stay_aligned-next_step = Next
onboarding-stay_aligned-restart = Restart
onboarding-stay_aligned-done = Done
onboarding-stay_aligned-manual_mounting-done = Done

## Home
home-no_trackers = No trackers detected or assigned
Expand Down
4 changes: 2 additions & 2 deletions gui/src/components/TopBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -156,9 +156,9 @@ export function TopBar({
<>
<div className="flex gap-0 flex-col">
<div className="h-[3px]" />
<div data-tauri-drag-region className="flex gap-2 h-[38px] z-50">
<div data-tauri-drag-region className="flex gap-2 h-[38px] z-49">
<div
className="flex px-2 py-2 justify-around z-50"
className="flex px-2 py-2 justify-around z-49"
data-tauri-drag-region
>
<div className="flex gap-2" data-tauri-drag-region>
Expand Down
2 changes: 1 addition & 1 deletion gui/src/components/commons/TipBox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export function TipBox({
whitespace = false,
className,
}: {
children: ReactNode;
children?: ReactNode;
hideIcon?: boolean;
whitespace?: boolean;
className?: string;
Expand Down
12 changes: 10 additions & 2 deletions gui/src/components/home/ResetButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,18 +28,23 @@ export function ResetButtonIcon(options: UseResetOptions) {
}

export function ResetButton({
onClick,
className,
onReseted,
children,
onFailed,
...options
}: {
onClick?: () => void;
className?: string;
children?: ReactNode;
onReseted?: () => void;
onFailed?: () => void;
} & UseResetOptions) {
const { triggerReset, status, timer, disabled, name, error } = useReset(
options,
onReseted
onReseted,
onFailed
);

return (
Expand All @@ -60,7 +65,10 @@ export function ResetButton({
>
<Button
icon={<ResetButtonIcon {...options} />}
onClick={triggerReset}
onClick={() => {
if (onClick) onClick();
triggerReset();
}}
className={classNames(
'border-2 py-[5px]',
status === 'finished'
Expand Down
135 changes: 119 additions & 16 deletions gui/src/components/onboarding/pages/mounting/ManualMounting.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useCallback, useMemo, useState } from 'react';
import { ReactNode, useCallback, useMemo, useState } from 'react';
import { AssignTrackerRequestT, BodyPart, RpcMessage } from 'solarxr-protocol';
import { useOnboarding } from '@/hooks/onboarding';
import { useWebsocketAPI } from '@/hooks/websocket-api';
Expand All @@ -12,7 +12,7 @@ import { TipBox } from '@/components/commons/TipBox';
import { Typography } from '@/components/commons/Typography';
import { BodyAssignment } from '@/components/onboarding/BodyAssignment';
import { MountingSelectionMenu } from './MountingSelectionMenu';
import { useLocalization } from '@fluent/react';
import { Localized } from '@fluent/react';
import { useBreakpoint } from '@/hooks/breakpoint';
import { Quaternion } from 'three';
import { AssignMode, defaultConfig, useConfig } from '@/hooks/config';
Expand All @@ -22,7 +22,6 @@ import * as Sentry from '@sentry/react';

export function ManualMountingPage() {
const { isMobile } = useBreakpoint('mobile');
const { l10n } = useLocalization();
const { applyProgress, state } = useOnboarding();
const { sendRPCPacket } = useWebsocketAPI();
const { config } = useConfig();
Expand Down Expand Up @@ -103,28 +102,26 @@ export function ManualMountingPage() {
<div className="flex flex-col gap-5 h-full items-center w-full xs:justify-center relative overflow-y-auto">
<div className="flex xs:flex-row mobile:flex-col h-full px-8 xs:w-full xs:justify-center mobile:px-4 items-center">
<div className="flex flex-col w-full xs:max-w-sm gap-3">
<Typography variant="main-title">
{l10n.getString('onboarding-manual_mounting')}
</Typography>
<Typography>
{l10n.getString('onboarding-manual_mounting-description')}
</Typography>
<TipBox>{l10n.getString('tips-find_tracker')}</TipBox>
<Typography variant="main-title" id="onboarding-manual_mounting" />
<Typography id="onboarding-manual_mounting-description" />
<Typography id="tips-find_tracker" />
<Localized id="tips-find_tracker">
<TipBox />
</Localized>

<div className="flex flex-row gap-3 mt-auto">
<Button
variant="secondary"
to="/onboarding/mounting/choose"
state={state}
>
{l10n.getString('onboarding-previous_step')}
</Button>
id="onboarding-previous_step"
/>
{!state.alonePage && (
<Button
variant="primary"
to="/onboarding/body-proportions/scaled"
>
{l10n.getString('onboarding-manual_mounting-next')}
</Button>
id="onboarding-manual_mounting-next"
/>
)}
</div>
</div>
Expand All @@ -142,3 +139,109 @@ export function ManualMountingPage() {
</>
);
}

export function ManualMountingPageStayAligned({
children,
}: {
children: ReactNode;
}) {
const { isMobile } = useBreakpoint('mobile');
const { sendRPCPacket } = useWebsocketAPI();
const { config } = useConfig();

const [selectedRole, setSelectRole] = useState<BodyPart>(BodyPart.NONE);

const assignedTrackers = useAtomValue(assignedTrackersAtom);

const trackerPartGrouped = useMemo(
() =>
assignedTrackers.reduce<{ [key: number]: FlatDeviceTracker[] }>(
(curr, td) => {
const key = td.tracker.info?.bodyPart || BodyPart.NONE;
return {
...curr,
[key]: [...(curr[key] || []), td],
};
},
{}
),
[assignedTrackers]
);

const onDirectionSelected = (mountingOrientationDegrees: Quaternion) => {
(trackerPartGrouped[selectedRole] || []).forEach((td) => {
const assignreq = new AssignTrackerRequestT();

assignreq.bodyPosition = td.tracker.info?.bodyPart || BodyPart.NONE;
assignreq.mountingOrientation = MountingOrientationDegreesToQuatT(
mountingOrientationDegrees
);
assignreq.trackerId = td.tracker.trackerId;
assignreq.allowDriftCompensation = false;

sendRPCPacket(RpcMessage.AssignTrackerRequest, assignreq);
Sentry.metrics.count('manual_mounting_set', 1, {
attributes: {
part: BodyPart[assignreq.bodyPosition],
direction: assignreq.mountingOrientation,
},
});
});

setSelectRole(BodyPart.NONE);
};

const getCurrRotation = useCallback(
(role: BodyPart) => {
if (role === BodyPart.NONE) return undefined;

const trackers = trackerPartGrouped[role] || [];
const [mountingOrientation, ...orientation] = trackers
.map((td) => td.tracker.info?.mountingOrientation)
.filter((orientation) => !!orientation)
.map((orientation) => QuaternionFromQuatT(orientation));

const identicalOrientations =
mountingOrientation !== undefined &&
orientation.every((quat) =>
similarQuaternions(quat, mountingOrientation)
);
return identicalOrientations ? mountingOrientation : undefined;
},
[trackerPartGrouped]
);

return (
<>
<MountingSelectionMenu
bodyPart={selectedRole}
currRotation={getCurrRotation(selectedRole)}
isOpen={selectedRole !== BodyPart.NONE}
onClose={() => setSelectRole(BodyPart.NONE)}
onDirectionSelected={onDirectionSelected}
/>
<div className="flex flex-col gap-5 h-full items-center w-full xs:justify-center relative overflow-y-auto">
<div className="flex xs:flex-row mobile:flex-col h-full px-8 xs:w-full xs:justify-center mobile:px-4 items-center">
<div className="flex flex-col w-full xs:max-w-sm gap-3">
<Typography variant="main-title" id="onboarding-manual_mounting" />
<Typography id="onboarding-manual_mounting-description" />
<Typography id="tips-find_tracker" />
<Localized id="tips-find_tracker">
<TipBox />
</Localized>
{children}
</div>
<div className="flex flex-row justify-center">
<BodyAssignment
width={isMobile ? 160 : undefined}
mirror={config?.mirrorView ?? defaultConfig.mirrorView}
onlyAssigned={true}
assignMode={AssignMode.All}
onRoleSelected={setSelectRole}
/>
</div>
</div>
</div>
</>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ export function MountingSelectionMenu({
shouldCloseOnEsc
onRequestClose={onClose}
overlayClassName={classNames(
'fixed top-0 right-0 left-0 bottom-0 flex flex-col items-center w-full h-full bg-background-90 bg-opacity-90 z-20'
'fixed top-0 right-0 left-0 bottom-0 flex flex-col items-center w-full h-full bg-background-90 bg-opacity-90 z-50'
)}
className={classNames(
'focus:ring-transparent focus:ring-offset-transparent focus:outline-transparent outline-none mt-20 z-10'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,32 +1,33 @@
import { useState } from 'react';
import { Button } from '@/components/commons/Button';
import { Typography } from '@/components/commons/Typography';
import { ResetType } from 'solarxr-protocol';
import { ResetButton } from '@/components/home/ResetButton';
import { useLocalization } from '@fluent/react';
import { useBreakpoint } from '@/hooks/breakpoint';
import { VerticalStepComponentProps } from '@/components/commons/VerticalStepper';

import { BaseModal } from '@/components/commons/BaseModal';
import { ManualMountingPageStayAligned } from '@/components/onboarding/pages/mounting/ManualMounting';
export function VerifyMountingStep({
nextStep,
prevStep,
}: VerticalStepComponentProps) {
const { isMobile } = useBreakpoint('mobile');
const { l10n } = useLocalization();
const [isOpen, setOpen] = useState(false);
const [disableMounting, setDisableMounting] = useState(false);

const goNextStep = () => {
setDisableMounting(false);
setOpen(false);
nextStep();
};

return (
<div className="flex flex-col flex-grow justify-between py-2 gap-2">
<div className="flex flex-col flex-grow">
<div className="flex flex-grow flex-col gap-4 max-w-sm">
<div className="flex flex-col gap-2">
<Typography>
{l10n.getString(
'onboarding-automatic_mounting-mounting_reset-step-0'
)}
</Typography>
<Typography>
{l10n.getString(
'onboarding-automatic_mounting-mounting_reset-step-1'
)}
</Typography>
<Typography id="onboarding-automatic_mounting-mounting_reset-step-0" />
<Typography id="onboarding-automatic_mounting-mounting_reset-step-1" />
</div>
</div>

Expand All @@ -50,13 +51,36 @@ export function VerifyMountingStep({
</div>
)}
<div className="flex gap-3 justify-between">
<Button variant={'secondary'} onClick={prevStep}>
{l10n.getString('onboarding-automatic_mounting-prev_step')}
</Button>
<Button
variant={'secondary'}
onClick={prevStep}
id="onboarding-automatic_mounting-prev_step"
/>
<Button
disabled={disableMounting}
variant={'secondary'}
className="self-start mt-auto"
onClick={() => setOpen(true)}
id="onboarding-automatic_mounting-manual_mounting"
/>
<BaseModal isOpen={isOpen} onRequestClose={() => setOpen(false)}>
<ManualMountingPageStayAligned>
<div className="flex flex-row gap-3 mt-auto">
<Button
variant="primary"
onClick={goNextStep}
id="onboarding-stay_aligned-manual_mounting-done"
/>
</div>
</ManualMountingPageStayAligned>
</BaseModal>

<ResetButton
onClick={() => setDisableMounting(true)}
type={ResetType.Mounting}
group="default"
onReseted={nextStep}
onReseted={goNextStep}
onFailed={() => setDisableMounting(false)}
/>
</div>
</div>
Expand Down
8 changes: 6 additions & 2 deletions gui/src/hooks/reset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,16 @@ export const BODY_PARTS_GROUPS: Record<MountingResetGroup, BodyPart[]> = {
fingers: FINGER_BODY_PARTS,
};

export function useReset(options: UseResetOptions, onReseted?: () => void) {
export function useReset(
options: UseResetOptions,
onReseted?: () => void,
onFailed?: () => void
) {
if (options.type === ResetType.Mounting && !options.group) options.group = 'default';

const serverGuards = useAtomValue(serverGuardsAtom);
const { currentLocales } = useLocaleConfig();
const { sendRPCPacket, useRPCPacket } = useWebsocketAPI();

const finishedTimeoutRef = useRef<NodeJS.Timeout>();
const [status, setStatus] = useState<ResetBtnStatus>('idle');
const [progress, setProgress] = useState(0);
Expand Down Expand Up @@ -62,6 +65,7 @@ export function useReset(options: UseResetOptions, onReseted?: () => void) {

const onResetCanceled = () => {
if (status !== 'finished') setStatus('idle');
if (onFailed) onFailed();
};

useEffect(() => {
Expand Down