diff --git a/gui/public/i18n/en/translation.ftl b/gui/public/i18n/en/translation.ftl index 30754f16fe..de874051ac 100644 --- a/gui/public/i18n/en/translation.ftl +++ b/gui/public/i18n/en/translation.ftl @@ -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 diff --git a/gui/src/components/TopBar.tsx b/gui/src/components/TopBar.tsx index 94f24c3986..bb5aa785ae 100644 --- a/gui/src/components/TopBar.tsx +++ b/gui/src/components/TopBar.tsx @@ -156,9 +156,9 @@ export function TopBar({ <>
-
+
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({ > + 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({
)}
- +
+ + + setDisableMounting(true)} type={ResetType.Mounting} group="default" - onReseted={nextStep} + onReseted={goNextStep} + onFailed={() => setDisableMounting(false)} />
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(() => {