diff --git a/gui/src/components/commons/TipBox.tsx b/gui/src/components/commons/TipBox.tsx
index e92e897b37..93b514e671 100644
--- a/gui/src/components/commons/TipBox.tsx
+++ b/gui/src/components/commons/TipBox.tsx
@@ -10,7 +10,7 @@ export function TipBox({
whitespace = false,
className,
}: {
- children: ReactNode;
+ children?: ReactNode;
hideIcon?: boolean;
whitespace?: boolean;
className?: string;
diff --git a/gui/src/components/home/ResetButton.tsx b/gui/src/components/home/ResetButton.tsx
index 8d8fc52d7d..18da3c5c5f 100644
--- a/gui/src/components/home/ResetButton.tsx
+++ b/gui/src/components/home/ResetButton.tsx
@@ -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 (
@@ -60,7 +65,10 @@ export function ResetButton({
>
}
- onClick={triggerReset}
+ onClick={() => {
+ if (onClick) onClick();
+ triggerReset();
+ }}
className={classNames(
'border-2 py-[5px]',
status === 'finished'
diff --git a/gui/src/components/onboarding/pages/mounting/ManualMounting.tsx b/gui/src/components/onboarding/pages/mounting/ManualMounting.tsx
index c0b314f13e..366b8ba3e1 100644
--- a/gui/src/components/onboarding/pages/mounting/ManualMounting.tsx
+++ b/gui/src/components/onboarding/pages/mounting/ManualMounting.tsx
@@ -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';
@@ -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';
@@ -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();
@@ -103,28 +102,26 @@ export function ManualMountingPage() {
-
- {l10n.getString('onboarding-manual_mounting')}
-
-
- {l10n.getString('onboarding-manual_mounting-description')}
-
-
{l10n.getString('tips-find_tracker')}
+
+
+
+
+
+
+
+ id="onboarding-previous_step"
+ />
{!state.alonePage && (
+ id="onboarding-manual_mounting-next"
+ />
)}
@@ -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.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 (
+ <>
+ setSelectRole(BodyPart.NONE)}
+ onDirectionSelected={onDirectionSelected}
+ />
+
+
+
+
+
+
+
+
+
+ {children}
+
+
+
+
+
+
+ >
+ );
+}
diff --git a/gui/src/components/onboarding/pages/mounting/MountingSelectionMenu.tsx b/gui/src/components/onboarding/pages/mounting/MountingSelectionMenu.tsx
index 0c722be4a8..eaf0aba3dc 100644
--- a/gui/src/components/onboarding/pages/mounting/MountingSelectionMenu.tsx
+++ b/gui/src/components/onboarding/pages/mounting/MountingSelectionMenu.tsx
@@ -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'
diff --git a/gui/src/components/onboarding/pages/stay-aligned/stay-aligned-steps/VerifyMounting.tsx b/gui/src/components/onboarding/pages/stay-aligned/stay-aligned-steps/VerifyMounting.tsx
index 59d3678d88..18f969e872 100644
--- a/gui/src/components/onboarding/pages/stay-aligned/stay-aligned-steps/VerifyMounting.tsx
+++ b/gui/src/components/onboarding/pages/stay-aligned/stay-aligned-steps/VerifyMounting.tsx
@@ -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 (
-
- {l10n.getString(
- 'onboarding-automatic_mounting-mounting_reset-step-0'
- )}
-
-
- {l10n.getString(
- 'onboarding-automatic_mounting-mounting_reset-step-1'
- )}
-
+
+
@@ -50,13 +51,36 @@ export function VerifyMountingStep({
)}
-
+
+
diff --git a/gui/src/hooks/reset.ts b/gui/src/hooks/reset.ts
index f3bdd0e0aa..35b665c329 100644
--- a/gui/src/hooks/reset.ts
+++ b/gui/src/hooks/reset.ts
@@ -27,13 +27,16 @@ export const BODY_PARTS_GROUPS: Record = {
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();
const [status, setStatus] = useState('idle');
const [progress, setProgress] = useState(0);
@@ -62,6 +65,7 @@ export function useReset(options: UseResetOptions, onReseted?: () => void) {
const onResetCanceled = () => {
if (status !== 'finished') setStatus('idle');
+ if (onFailed) onFailed();
};
useEffect(() => {