mirror of
https://github.com/SlimeVR/SlimeVR-Server.git
synced 2026-04-06 02:01:58 +02:00
GUI - Tracker assignment options (#1009)
Co-authored-by: Erimel <marioluigivideo@gmail.com> Co-authored-by: loucass003 <loucass003@gmail.com>
This commit is contained in:
@@ -688,6 +688,24 @@ onboarding-assign_trackers-assigned = { $assigned } of { $trackers ->
|
||||
onboarding-assign_trackers-advanced = Show advanced assign locations
|
||||
onboarding-assign_trackers-next = I assigned all the trackers
|
||||
onboarding-assign_trackers-mirror_view = Mirror view
|
||||
onboarding-assign_trackers-option-amount = { $trackersCount ->
|
||||
[one] x{ $trackersCount }
|
||||
*[other] x{ $trackersCount }
|
||||
}
|
||||
onboarding-assign_trackers-option-label = { $mode ->
|
||||
[lower-body] Lower-Body Set
|
||||
[core] Core Set
|
||||
[enhanced-core] Enhanced Core Set
|
||||
[full-body] Full-Body Set
|
||||
*[all] All Trackers
|
||||
}
|
||||
onboarding-assign_trackers-option-description = { $mode ->
|
||||
[lower-body] Minimum for VR full-body tracking
|
||||
[core] + Enhanced spine tracking
|
||||
[enhanced-core] + Foot rotation
|
||||
[full-body] + Elbow tracking
|
||||
*[all] All available tracker assignments
|
||||
}
|
||||
|
||||
## Tracker assignment warnings
|
||||
# Note for devs, number is used for representing boolean states per bit.
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import classNames from 'classnames';
|
||||
import { Control, Controller } from 'react-hook-form';
|
||||
import { Typography } from './Typography';
|
||||
import { ReactNode } from 'react';
|
||||
|
||||
export function Radio({
|
||||
control,
|
||||
@@ -8,15 +9,17 @@ export function Radio({
|
||||
label,
|
||||
value,
|
||||
description,
|
||||
children,
|
||||
// input props
|
||||
disabled,
|
||||
...props
|
||||
}: {
|
||||
control: Control<any>;
|
||||
name: string;
|
||||
label: string;
|
||||
label?: string;
|
||||
value: string;
|
||||
description?: string | null;
|
||||
children?: ReactNode;
|
||||
} & React.HTMLProps<HTMLInputElement>) {
|
||||
return (
|
||||
<Controller
|
||||
@@ -27,7 +30,7 @@ export function Radio({
|
||||
className={classNames('w-full p-3 rounded-md flex gap-3 border-2', {
|
||||
'border-accent-background-30': value == checked,
|
||||
'border-transparent': value != checked,
|
||||
'bg-background-60 cursor-pointer': !disabled,
|
||||
'bg-background-60 cursor-pointer hover:bg-background-50': !disabled,
|
||||
'bg-background-80 cursor-not-allowed': disabled,
|
||||
})}
|
||||
>
|
||||
@@ -46,7 +49,7 @@ export function Radio({
|
||||
{...props}
|
||||
/>
|
||||
<div className="flex flex-col gap-2">
|
||||
<Typography bold>{label}</Typography>
|
||||
{children ? children : <Typography bold>{label}</Typography>}
|
||||
{description && (
|
||||
<Typography variant="standard" color="secondary">
|
||||
{description}
|
||||
|
||||
@@ -1,19 +1,29 @@
|
||||
import { useMemo } from 'react';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { BodyPart } from 'solarxr-protocol';
|
||||
import { FlatDeviceTracker } from '@/hooks/app';
|
||||
import { AssignMode } from '@/hooks/config';
|
||||
import { useTrackers } from '@/hooks/tracker';
|
||||
import { BodyInteractions } from '@/components/commons/BodyInteractions';
|
||||
import { TrackerPartCard } from '@/components/tracker/TrackerPartCard';
|
||||
import { BodyPartError } from './pages/trackers-assign/TrackerAssignment';
|
||||
import { SIDES } from '@/components/commons/PersonFrontIcon';
|
||||
|
||||
export const ARMS_PARTS = new Set([
|
||||
BodyPart.LEFT_UPPER_ARM,
|
||||
BodyPart.RIGHT_UPPER_ARM,
|
||||
BodyPart.LEFT_LOWER_ARM,
|
||||
BodyPart.RIGHT_LOWER_ARM,
|
||||
]);
|
||||
export const LEGS_PARTS = new Set([
|
||||
BodyPart.LEFT_UPPER_LEG,
|
||||
BodyPart.RIGHT_UPPER_LEG,
|
||||
BodyPart.LEFT_LOWER_LEG,
|
||||
BodyPart.RIGHT_LOWER_LEG,
|
||||
]);
|
||||
export const LOWER_BODY = new Set([
|
||||
BodyPart.LEFT_FOOT,
|
||||
BodyPart.RIGHT_FOOT,
|
||||
BodyPart.LEFT_LOWER_LEG,
|
||||
BodyPart.RIGHT_LOWER_LEG,
|
||||
BodyPart.LEFT_UPPER_LEG,
|
||||
BodyPart.RIGHT_UPPER_LEG,
|
||||
...LEGS_PARTS,
|
||||
]);
|
||||
export const SPINE_PARTS = [
|
||||
BodyPart.UPPER_CHEST,
|
||||
@@ -44,8 +54,47 @@ export const ASSIGNMENT_RULES: Partial<
|
||||
// Also don't warn if no legs.
|
||||
};
|
||||
|
||||
export const ASSIGNMENT_MODES: Record<AssignMode, BodyPart[]> = {
|
||||
// x5
|
||||
[AssignMode.LowerBody]: [BodyPart.CHEST, ...LEGS_PARTS],
|
||||
// x6 (5 + 1)
|
||||
[AssignMode.Core]: [BodyPart.CHEST, BodyPart.HIP, ...LEGS_PARTS],
|
||||
// x8 (5 + 3)
|
||||
[AssignMode.EnhancedCore]: [
|
||||
BodyPart.CHEST,
|
||||
BodyPart.HIP,
|
||||
...LEGS_PARTS,
|
||||
BodyPart.LEFT_FOOT,
|
||||
BodyPart.RIGHT_FOOT,
|
||||
],
|
||||
// x10 (7 + 3)
|
||||
[AssignMode.FullBody]: [
|
||||
BodyPart.CHEST,
|
||||
BodyPart.HIP,
|
||||
BodyPart.LEFT_UPPER_ARM,
|
||||
BodyPart.RIGHT_UPPER_ARM,
|
||||
...LEGS_PARTS,
|
||||
BodyPart.LEFT_FOOT,
|
||||
BodyPart.RIGHT_FOOT,
|
||||
],
|
||||
// special case with all body parts
|
||||
[AssignMode.All]: [
|
||||
BodyPart.HEAD,
|
||||
BodyPart.NECK,
|
||||
BodyPart.LEFT_SHOULDER,
|
||||
BodyPart.RIGHT_SHOULDER,
|
||||
BodyPart.LEFT_HAND,
|
||||
BodyPart.RIGHT_HAND,
|
||||
BodyPart.LEFT_FOOT,
|
||||
BodyPart.RIGHT_FOOT,
|
||||
...SPINE_PARTS,
|
||||
...ARMS_PARTS,
|
||||
...LEGS_PARTS,
|
||||
],
|
||||
};
|
||||
|
||||
export function BodyAssignment({
|
||||
advanced,
|
||||
assignMode,
|
||||
mirror,
|
||||
onRoleSelected,
|
||||
rolesWithErrors = {},
|
||||
@@ -53,7 +102,7 @@ export function BodyAssignment({
|
||||
onlyAssigned = false,
|
||||
width,
|
||||
}: {
|
||||
advanced: boolean;
|
||||
assignMode: AssignMode;
|
||||
mirror: boolean;
|
||||
onlyAssigned?: boolean;
|
||||
rolesWithErrors?: Partial<Record<BodyPart, BodyPartError>>;
|
||||
@@ -94,6 +143,13 @@ export function BodyAssignment({
|
||||
const left = +!mirror;
|
||||
const right = +mirror;
|
||||
|
||||
const hasBodyPart = useCallback(
|
||||
(part: BodyPart) =>
|
||||
assignMode === AssignMode.All ||
|
||||
ASSIGNMENT_MODES[assignMode].indexOf(part) > -1,
|
||||
[assignMode]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<BodyInteractions
|
||||
@@ -105,7 +161,7 @@ export function BodyAssignment({
|
||||
leftControls={
|
||||
<div className="flex flex-col justify-between h-full text-right">
|
||||
<div className="flex flex-col gap-2">
|
||||
{advanced && (
|
||||
{hasBodyPart(BodyPart.HEAD) && (
|
||||
<TrackerPartCard
|
||||
onlyAssigned={onlyAssigned}
|
||||
roleError={rolesWithErrors[BodyPart.HEAD]?.label}
|
||||
@@ -115,7 +171,8 @@ export function BodyAssignment({
|
||||
direction="right"
|
||||
/>
|
||||
)}
|
||||
{advanced && (
|
||||
|
||||
{hasBodyPart(BodyPart.NECK) && (
|
||||
<TrackerPartCard
|
||||
onlyAssigned={onlyAssigned}
|
||||
roleError={rolesWithErrors[BodyPart.NECK]?.label}
|
||||
@@ -126,9 +183,8 @@ export function BodyAssignment({
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-2">
|
||||
{advanced && (
|
||||
{hasBodyPart(SIDES[left].shoulder) && (
|
||||
<TrackerPartCard
|
||||
onlyAssigned={onlyAssigned}
|
||||
roleError={rolesWithErrors[SIDES[left].shoulder]?.label}
|
||||
@@ -138,26 +194,31 @@ export function BodyAssignment({
|
||||
direction="right"
|
||||
/>
|
||||
)}
|
||||
<TrackerPartCard
|
||||
onlyAssigned={onlyAssigned}
|
||||
roleError={rolesWithErrors[SIDES[left].upperArm]?.label}
|
||||
td={trackerPartGrouped[SIDES[left].upperArm]}
|
||||
role={SIDES[left].upperArm}
|
||||
onClick={() => onRoleSelected(SIDES[left].upperArm)}
|
||||
direction="right"
|
||||
/>
|
||||
|
||||
{hasBodyPart(SIDES[left].upperArm) && (
|
||||
<TrackerPartCard
|
||||
onlyAssigned={onlyAssigned}
|
||||
roleError={rolesWithErrors[SIDES[left].upperArm]?.label}
|
||||
td={trackerPartGrouped[SIDES[left].upperArm]}
|
||||
role={SIDES[left].upperArm}
|
||||
onClick={() => onRoleSelected(SIDES[left].upperArm)}
|
||||
direction="right"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<TrackerPartCard
|
||||
onlyAssigned={onlyAssigned}
|
||||
roleError={rolesWithErrors[SIDES[left].lowerArm]?.label}
|
||||
td={trackerPartGrouped[SIDES[left].lowerArm]}
|
||||
role={SIDES[left].lowerArm}
|
||||
onClick={() => onRoleSelected(SIDES[left].lowerArm)}
|
||||
direction="right"
|
||||
/>
|
||||
{hasBodyPart(SIDES[left].lowerArm) && (
|
||||
<TrackerPartCard
|
||||
onlyAssigned={onlyAssigned}
|
||||
roleError={rolesWithErrors[SIDES[left].lowerArm]?.label}
|
||||
td={trackerPartGrouped[SIDES[left].lowerArm]}
|
||||
role={SIDES[left].lowerArm}
|
||||
onClick={() => onRoleSelected(SIDES[left].lowerArm)}
|
||||
direction="right"
|
||||
/>
|
||||
)}
|
||||
|
||||
{advanced && (
|
||||
{hasBodyPart(SIDES[left].hand) && (
|
||||
<TrackerPartCard
|
||||
onlyAssigned={onlyAssigned}
|
||||
roleError={rolesWithErrors[SIDES[left].hand]?.label}
|
||||
@@ -169,37 +230,44 @@ export function BodyAssignment({
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<TrackerPartCard
|
||||
onlyAssigned={onlyAssigned}
|
||||
roleError={rolesWithErrors[SIDES[left].upperLeg]?.label}
|
||||
td={trackerPartGrouped[SIDES[left].upperLeg]}
|
||||
role={SIDES[left].upperLeg}
|
||||
onClick={() => onRoleSelected(SIDES[left].upperLeg)}
|
||||
direction="right"
|
||||
/>
|
||||
{hasBodyPart(SIDES[left].upperLeg) && (
|
||||
<TrackerPartCard
|
||||
onlyAssigned={onlyAssigned}
|
||||
roleError={rolesWithErrors[SIDES[left].upperLeg]?.label}
|
||||
td={trackerPartGrouped[SIDES[left].upperLeg]}
|
||||
role={SIDES[left].upperLeg}
|
||||
onClick={() => onRoleSelected(SIDES[left].upperLeg)}
|
||||
direction="right"
|
||||
/>
|
||||
)}
|
||||
|
||||
<TrackerPartCard
|
||||
onlyAssigned={onlyAssigned}
|
||||
roleError={rolesWithErrors[SIDES[left].lowerLeg]?.label}
|
||||
td={trackerPartGrouped[SIDES[left].lowerLeg]}
|
||||
role={SIDES[left].lowerLeg}
|
||||
onClick={() => onRoleSelected(SIDES[left].lowerLeg)}
|
||||
direction="right"
|
||||
/>
|
||||
<TrackerPartCard
|
||||
onlyAssigned={onlyAssigned}
|
||||
roleError={rolesWithErrors[SIDES[left].foot]?.label}
|
||||
td={trackerPartGrouped[SIDES[left].foot]}
|
||||
role={SIDES[left].foot}
|
||||
onClick={() => onRoleSelected(SIDES[left].foot)}
|
||||
direction="right"
|
||||
/>
|
||||
{hasBodyPart(SIDES[left].lowerLeg) && (
|
||||
<TrackerPartCard
|
||||
onlyAssigned={onlyAssigned}
|
||||
roleError={rolesWithErrors[SIDES[left].lowerLeg]?.label}
|
||||
td={trackerPartGrouped[SIDES[left].lowerLeg]}
|
||||
role={SIDES[left].lowerLeg}
|
||||
onClick={() => onRoleSelected(SIDES[left].lowerLeg)}
|
||||
direction="right"
|
||||
/>
|
||||
)}
|
||||
|
||||
{hasBodyPart(SIDES[left].foot) && (
|
||||
<TrackerPartCard
|
||||
onlyAssigned={onlyAssigned}
|
||||
roleError={rolesWithErrors[SIDES[left].foot]?.label}
|
||||
td={trackerPartGrouped[SIDES[left].foot]}
|
||||
role={SIDES[left].foot}
|
||||
onClick={() => onRoleSelected(SIDES[left].foot)}
|
||||
direction="right"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
rightControls={
|
||||
<div className="flex flex-col justify-between h-full">
|
||||
{advanced && (
|
||||
{hasBodyPart(BodyPart.UPPER_CHEST) && (
|
||||
<TrackerPartCard
|
||||
onlyAssigned={onlyAssigned}
|
||||
roleError={rolesWithErrors[BodyPart.UPPER_CHEST]?.label}
|
||||
@@ -210,17 +278,19 @@ export function BodyAssignment({
|
||||
/>
|
||||
)}
|
||||
|
||||
<TrackerPartCard
|
||||
onlyAssigned={onlyAssigned}
|
||||
roleError={rolesWithErrors[BodyPart.CHEST]?.label}
|
||||
td={trackerPartGrouped[BodyPart.CHEST]}
|
||||
role={BodyPart.CHEST}
|
||||
onClick={() => onRoleSelected(BodyPart.CHEST)}
|
||||
direction="left"
|
||||
/>
|
||||
{hasBodyPart(BodyPart.CHEST) && (
|
||||
<TrackerPartCard
|
||||
onlyAssigned={onlyAssigned}
|
||||
roleError={rolesWithErrors[BodyPart.CHEST]?.label}
|
||||
td={trackerPartGrouped[BodyPart.CHEST]}
|
||||
role={BodyPart.CHEST}
|
||||
onClick={() => onRoleSelected(BodyPart.CHEST)}
|
||||
direction="left"
|
||||
/>
|
||||
)}
|
||||
|
||||
<div className="flex flex-col gap-2">
|
||||
{advanced && (
|
||||
{hasBodyPart(SIDES[right].shoulder) && (
|
||||
<TrackerPartCard
|
||||
onlyAssigned={onlyAssigned}
|
||||
roleError={rolesWithErrors[SIDES[right].shoulder]?.label}
|
||||
@@ -231,45 +301,53 @@ export function BodyAssignment({
|
||||
/>
|
||||
)}
|
||||
|
||||
<TrackerPartCard
|
||||
onlyAssigned={onlyAssigned}
|
||||
roleError={rolesWithErrors[SIDES[right].upperArm]?.label}
|
||||
td={trackerPartGrouped[SIDES[right].upperArm]}
|
||||
role={SIDES[right].upperArm}
|
||||
onClick={() => onRoleSelected(SIDES[right].upperArm)}
|
||||
direction="left"
|
||||
/>
|
||||
{hasBodyPart(SIDES[right].upperArm) && (
|
||||
<TrackerPartCard
|
||||
onlyAssigned={onlyAssigned}
|
||||
roleError={rolesWithErrors[SIDES[right].upperArm]?.label}
|
||||
td={trackerPartGrouped[SIDES[right].upperArm]}
|
||||
role={SIDES[right].upperArm}
|
||||
onClick={() => onRoleSelected(SIDES[right].upperArm)}
|
||||
direction="left"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-2">
|
||||
<TrackerPartCard
|
||||
onlyAssigned={onlyAssigned}
|
||||
roleError={rolesWithErrors[BodyPart.WAIST]?.label}
|
||||
td={trackerPartGrouped[BodyPart.WAIST]}
|
||||
onClick={() => onRoleSelected(BodyPart.WAIST)}
|
||||
role={BodyPart.WAIST}
|
||||
direction="left"
|
||||
/>
|
||||
<TrackerPartCard
|
||||
onlyAssigned={onlyAssigned}
|
||||
roleError={rolesWithErrors[BodyPart.HIP]?.label}
|
||||
td={trackerPartGrouped[BodyPart.HIP]}
|
||||
onClick={() => onRoleSelected(BodyPart.HIP)}
|
||||
role={BodyPart.HIP}
|
||||
direction="left"
|
||||
/>
|
||||
{hasBodyPart(BodyPart.WAIST) && (
|
||||
<TrackerPartCard
|
||||
onlyAssigned={onlyAssigned}
|
||||
roleError={rolesWithErrors[BodyPart.WAIST]?.label}
|
||||
td={trackerPartGrouped[BodyPart.WAIST]}
|
||||
onClick={() => onRoleSelected(BodyPart.WAIST)}
|
||||
role={BodyPart.WAIST}
|
||||
direction="left"
|
||||
/>
|
||||
)}
|
||||
|
||||
{hasBodyPart(BodyPart.HIP) && (
|
||||
<TrackerPartCard
|
||||
onlyAssigned={onlyAssigned}
|
||||
roleError={rolesWithErrors[BodyPart.HIP]?.label}
|
||||
td={trackerPartGrouped[BodyPart.HIP]}
|
||||
onClick={() => onRoleSelected(BodyPart.HIP)}
|
||||
role={BodyPart.HIP}
|
||||
direction="left"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-2">
|
||||
<TrackerPartCard
|
||||
onlyAssigned={onlyAssigned}
|
||||
roleError={rolesWithErrors[SIDES[right].lowerArm]?.label}
|
||||
td={trackerPartGrouped[SIDES[right].lowerArm]}
|
||||
role={SIDES[right].lowerArm}
|
||||
onClick={() => onRoleSelected(SIDES[right].lowerArm)}
|
||||
direction="left"
|
||||
/>
|
||||
{advanced && (
|
||||
{hasBodyPart(SIDES[right].lowerArm) && (
|
||||
<TrackerPartCard
|
||||
onlyAssigned={onlyAssigned}
|
||||
roleError={rolesWithErrors[SIDES[right].lowerArm]?.label}
|
||||
td={trackerPartGrouped[SIDES[right].lowerArm]}
|
||||
role={SIDES[right].lowerArm}
|
||||
onClick={() => onRoleSelected(SIDES[right].lowerArm)}
|
||||
direction="left"
|
||||
/>
|
||||
)}
|
||||
|
||||
{hasBodyPart(SIDES[right].hand) && (
|
||||
<TrackerPartCard
|
||||
onlyAssigned={onlyAssigned}
|
||||
roleError={rolesWithErrors[SIDES[right].hand]?.label}
|
||||
@@ -280,33 +358,39 @@ export function BodyAssignment({
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-2">
|
||||
<TrackerPartCard
|
||||
onlyAssigned={onlyAssigned}
|
||||
roleError={rolesWithErrors[SIDES[right].upperLeg]?.label}
|
||||
td={trackerPartGrouped[SIDES[right].upperLeg]}
|
||||
role={SIDES[right].upperLeg}
|
||||
onClick={() => onRoleSelected(SIDES[right].upperLeg)}
|
||||
direction="left"
|
||||
/>
|
||||
{hasBodyPart(SIDES[right].upperLeg) && (
|
||||
<TrackerPartCard
|
||||
onlyAssigned={onlyAssigned}
|
||||
roleError={rolesWithErrors[SIDES[right].upperLeg]?.label}
|
||||
td={trackerPartGrouped[SIDES[right].upperLeg]}
|
||||
role={SIDES[right].upperLeg}
|
||||
onClick={() => onRoleSelected(SIDES[right].upperLeg)}
|
||||
direction="left"
|
||||
/>
|
||||
)}
|
||||
|
||||
<TrackerPartCard
|
||||
onlyAssigned={onlyAssigned}
|
||||
roleError={rolesWithErrors[SIDES[right].lowerLeg]?.label}
|
||||
td={trackerPartGrouped[SIDES[right].lowerLeg]}
|
||||
role={SIDES[right].lowerLeg}
|
||||
onClick={() => onRoleSelected(SIDES[right].lowerLeg)}
|
||||
direction="left"
|
||||
/>
|
||||
<TrackerPartCard
|
||||
onlyAssigned={onlyAssigned}
|
||||
roleError={rolesWithErrors[SIDES[right].foot]?.label}
|
||||
td={trackerPartGrouped[SIDES[right].foot]}
|
||||
role={SIDES[right].foot}
|
||||
onClick={() => onRoleSelected(SIDES[right].foot)}
|
||||
direction="left"
|
||||
/>
|
||||
{hasBodyPart(SIDES[right].lowerLeg) && (
|
||||
<TrackerPartCard
|
||||
onlyAssigned={onlyAssigned}
|
||||
roleError={rolesWithErrors[SIDES[right].lowerLeg]?.label}
|
||||
td={trackerPartGrouped[SIDES[right].lowerLeg]}
|
||||
role={SIDES[right].lowerLeg}
|
||||
onClick={() => onRoleSelected(SIDES[right].lowerLeg)}
|
||||
direction="left"
|
||||
/>
|
||||
)}
|
||||
|
||||
{hasBodyPart(SIDES[right].foot) && (
|
||||
<TrackerPartCard
|
||||
onlyAssigned={onlyAssigned}
|
||||
roleError={rolesWithErrors[SIDES[right].foot]?.label}
|
||||
td={trackerPartGrouped[SIDES[right].foot]}
|
||||
role={SIDES[right].foot}
|
||||
onClick={() => onRoleSelected(SIDES[right].foot)}
|
||||
direction="left"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -38,7 +38,9 @@ export function OnboardingLayout({ children }: { children: ReactNode }) {
|
||||
</>
|
||||
) : (
|
||||
<MainLayoutRoute widgets={false} isMobile={isMobile}>
|
||||
<div className="flex-grow xs:pt-10 mobile:pt-2">{children}</div>
|
||||
<div className="flex-grow xs:pt-10 mobile:pt-2 overflow-y-auto">
|
||||
{children}
|
||||
</div>
|
||||
</MainLayoutRoute>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ import { MountingSelectionMenu } from './MountingSelectionMenu';
|
||||
import { useLocalization } from '@fluent/react';
|
||||
import { useBreakpoint } from '@/hooks/breakpoint';
|
||||
import { Quaternion } from 'three';
|
||||
import { defaultConfig, useConfig } from '@/hooks/config';
|
||||
import { AssignMode, defaultConfig, useConfig } from '@/hooks/config';
|
||||
|
||||
export function ManualMountingPage() {
|
||||
const { isMobile } = useBreakpoint('mobile');
|
||||
@@ -104,7 +104,7 @@ export function ManualMountingPage() {
|
||||
width={isMobile ? 160 : undefined}
|
||||
mirror={config?.mirrorView ?? defaultConfig.mirrorView}
|
||||
onlyAssigned={true}
|
||||
advanced={true}
|
||||
assignMode={AssignMode.All}
|
||||
onRoleSelected={setSelectRole}
|
||||
></BodyAssignment>
|
||||
</div>
|
||||
|
||||
@@ -24,15 +24,17 @@ import { CheckBox } from '@/components/commons/Checkbox';
|
||||
import { TipBox } from '@/components/commons/TipBox';
|
||||
import { Typography } from '@/components/commons/Typography';
|
||||
import {
|
||||
ASSIGNMENT_MODES,
|
||||
ASSIGNMENT_RULES,
|
||||
BodyAssignment,
|
||||
LOWER_BODY,
|
||||
} from '@/components/onboarding/BodyAssignment';
|
||||
import { NeckWarningModal } from '@/components/onboarding/NeckWarningModal';
|
||||
import { TrackerSelectionMenu } from './TrackerSelectionMenu';
|
||||
import { useConfig } from '@/hooks/config';
|
||||
import { AssignMode, defaultConfig, useConfig } from '@/hooks/config';
|
||||
import { playTapSetupSound } from '@/sounds/sounds';
|
||||
import { useBreakpoint } from '@/hooks/breakpoint';
|
||||
import { Radio } from '@/components/commons/Radio';
|
||||
|
||||
export type BodyPartError = {
|
||||
label: string | undefined;
|
||||
@@ -46,27 +48,54 @@ interface FlatDeviceTrackerDummy {
|
||||
};
|
||||
}
|
||||
|
||||
// Ordered collection of assign modes with the number of IMU trackers
|
||||
const ASSIGN_MODE_OPTIONS = [
|
||||
AssignMode.LowerBody,
|
||||
AssignMode.Core,
|
||||
AssignMode.EnhancedCore,
|
||||
AssignMode.FullBody,
|
||||
AssignMode.All,
|
||||
].reduce(
|
||||
(options, mode) => ({ ...options, [mode]: ASSIGNMENT_MODES[mode].length }),
|
||||
{} as Record<AssignMode, number>
|
||||
);
|
||||
|
||||
export function TrackersAssignPage() {
|
||||
const { isMobile } = useBreakpoint('mobile');
|
||||
const { l10n } = useLocalization();
|
||||
const { config, setConfig } = useConfig();
|
||||
const { useAssignedTrackers, trackers } = useTrackers();
|
||||
const { useAssignedTrackers, useConnectedIMUTrackers, trackers } =
|
||||
useTrackers();
|
||||
const { applyProgress, state } = useOnboarding();
|
||||
const { sendRPCPacket, useRPCPacket } = useWebsocketAPI();
|
||||
const defaultValues = {
|
||||
advanced: config?.advancedAssign ?? false,
|
||||
mirrorView: config?.mirrorView ?? true,
|
||||
assignMode: config?.assignMode ?? defaultConfig.assignMode,
|
||||
mirrorView: config?.mirrorView ?? defaultConfig.mirrorView,
|
||||
};
|
||||
const { control, watch } = useForm<{
|
||||
advanced: boolean;
|
||||
const { control, watch, setValue } = useForm<{
|
||||
assignMode: AssignMode;
|
||||
mirrorView: boolean;
|
||||
}>({ defaultValues });
|
||||
const { advanced, mirrorView } = watch();
|
||||
const { assignMode, mirrorView } = watch();
|
||||
const [selectedRole, setSelectRole] = useState<BodyPart>(BodyPart.NONE);
|
||||
const assignedTrackers = useAssignedTrackers();
|
||||
useEffect(() => {
|
||||
setConfig({ advancedAssign: advanced, mirrorView });
|
||||
}, [advanced, mirrorView]);
|
||||
setConfig({ assignMode, mirrorView });
|
||||
}, [assignMode, mirrorView]);
|
||||
|
||||
const connectedIMUTrackers = useConnectedIMUTrackers().length;
|
||||
useEffect(() => {
|
||||
if (connectedIMUTrackers <= ASSIGN_MODE_OPTIONS[assignMode]) return;
|
||||
|
||||
const selectedAssignMode =
|
||||
(Object.entries(ASSIGN_MODE_OPTIONS).find(
|
||||
([_, count]) => count >= connectedIMUTrackers
|
||||
)?.[0] as AssignMode) ?? AssignMode.All;
|
||||
|
||||
if (assignMode !== selectedAssignMode) {
|
||||
setValue('assignMode', selectedAssignMode);
|
||||
}
|
||||
}, [connectedIMUTrackers, assignMode]);
|
||||
|
||||
const [tapDetectionSettings, setTapDetectionSettings] = useState<Omit<
|
||||
TapDetectionSettingsT,
|
||||
@@ -290,13 +319,54 @@ export function TrackersAssignPage() {
|
||||
</Typography>
|
||||
</div>
|
||||
<TipBox>{l10n.getString('tips-find_tracker')}</TipBox>
|
||||
<div>
|
||||
<CheckBox
|
||||
control={control}
|
||||
label={l10n.getString('onboarding-assign_trackers-advanced')}
|
||||
name="advanced"
|
||||
variant="toggle"
|
||||
></CheckBox>
|
||||
{!!firstError && (
|
||||
<div className="bg-status-warning text-background-60 px-3 py-2 text-justify rounded-md">
|
||||
<div className="flex flex-col gap-1 whitespace-normal">
|
||||
<span>{firstError.label}</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex flex-col md:gap-4 sm:gap-2 mobile:gap-4">
|
||||
{Object.entries(ASSIGN_MODE_OPTIONS).map(
|
||||
([mode, trackersCount]) => (
|
||||
<Radio
|
||||
key={mode}
|
||||
name="assignMode"
|
||||
control={control}
|
||||
value={mode}
|
||||
disabled={
|
||||
connectedIMUTrackers > trackersCount &&
|
||||
mode !== AssignMode.All
|
||||
}
|
||||
className="hidden"
|
||||
>
|
||||
<div className="flex flex-row md:gap-4 sm:gap-2 mobile:gap-2">
|
||||
<div style={{ width: '2.5rem', textAlign: 'right' }}>
|
||||
<Typography variant="mobile-title">
|
||||
{l10n.getString(
|
||||
'onboarding-assign_trackers-option-amount',
|
||||
{ trackersCount }
|
||||
)}
|
||||
</Typography>
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
<Typography>
|
||||
{l10n.getString(
|
||||
'onboarding-assign_trackers-option-label',
|
||||
{ mode }
|
||||
)}
|
||||
</Typography>
|
||||
<Typography variant="standard" color="secondary">
|
||||
{l10n.getString(
|
||||
'onboarding-assign_trackers-option-description',
|
||||
{ mode }
|
||||
)}
|
||||
</Typography>
|
||||
</div>
|
||||
</div>
|
||||
</Radio>
|
||||
)
|
||||
)}
|
||||
<CheckBox
|
||||
control={control}
|
||||
label={l10n.getString(
|
||||
@@ -306,13 +376,6 @@ export function TrackersAssignPage() {
|
||||
variant="toggle"
|
||||
></CheckBox>
|
||||
</div>
|
||||
{!!firstError && (
|
||||
<div className="bg-status-warning text-background-60 px-3 py-2 text-justify rounded-md">
|
||||
<div className="flex flex-col gap-1 whitespace-normal">
|
||||
<span>{firstError.label}</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex flex-row">
|
||||
{!state.alonePage && (
|
||||
<>
|
||||
@@ -342,7 +405,7 @@ export function TrackersAssignPage() {
|
||||
onlyAssigned={false}
|
||||
highlightedRoles={firstError?.affectedRoles || []}
|
||||
rolesWithErrors={rolesWithErrors}
|
||||
advanced={advanced ?? defaultValues.advanced}
|
||||
assignMode={assignMode ?? defaultValues.assignMode}
|
||||
mirror={mirrorView ?? defaultValues.mirrorView}
|
||||
onRoleSelected={tryOpenChokerWarning}
|
||||
></BodyAssignment>
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import classNames from 'classnames';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import ReactModal from 'react-modal';
|
||||
import { BodyPart } from 'solarxr-protocol';
|
||||
import { Button } from '@/components/commons/Button';
|
||||
import { CheckBox } from '@/components/commons/Checkbox';
|
||||
import { Typography } from '@/components/commons/Typography';
|
||||
import { BodyAssignment } from '@/components/onboarding/BodyAssignment';
|
||||
import { useLocalization } from '@fluent/react';
|
||||
@@ -24,9 +22,6 @@ export function SingleTrackerBodyAssignmentMenu({
|
||||
const { isMobile } = useBreakpoint('mobile');
|
||||
const { l10n } = useLocalization();
|
||||
const { config } = useConfig();
|
||||
const defaultValues = { advanced: false };
|
||||
const { control, watch } = useForm<{ advanced: boolean }>({ defaultValues });
|
||||
const { advanced } = watch();
|
||||
|
||||
const { closeChokerWarning, tryOpenChokerWarning, shouldShowChokerWarn } =
|
||||
useChokerWarning({
|
||||
@@ -56,14 +51,6 @@ export function SingleTrackerBodyAssignmentMenu({
|
||||
<Typography color="secondary">
|
||||
{l10n.getString('body_assignment_menu-description')}
|
||||
</Typography>
|
||||
<CheckBox
|
||||
control={control}
|
||||
label={l10n.getString(
|
||||
'body_assignment_menu-show_advanced_locations'
|
||||
)}
|
||||
name="advanced"
|
||||
variant="toggle"
|
||||
></CheckBox>
|
||||
<div className="flex">
|
||||
<Button
|
||||
variant="secondary"
|
||||
@@ -79,7 +66,7 @@ export function SingleTrackerBodyAssignmentMenu({
|
||||
mirror={config?.mirrorView ?? defaultConfig.mirrorView}
|
||||
width={isMobile ? 160 : undefined}
|
||||
onlyAssigned={false}
|
||||
advanced={advanced ?? defaultValues.advanced}
|
||||
assignMode={config?.assignMode ?? defaultConfig.assignMode}
|
||||
onRoleSelected={tryOpenChokerWarning}
|
||||
></BodyAssignment>
|
||||
<div className="flex justify-center">
|
||||
|
||||
@@ -13,6 +13,14 @@ export interface WindowConfig {
|
||||
y: number;
|
||||
}
|
||||
|
||||
export enum AssignMode {
|
||||
LowerBody = 'lower-body',
|
||||
Core = 'core',
|
||||
EnhancedCore = 'enhanced-core',
|
||||
FullBody = 'full-body',
|
||||
All = 'all',
|
||||
}
|
||||
|
||||
export interface Config {
|
||||
debug: boolean;
|
||||
lang: string;
|
||||
@@ -25,10 +33,10 @@ export interface Config {
|
||||
theme: string;
|
||||
textSize: number;
|
||||
fonts: string[];
|
||||
advancedAssign: boolean;
|
||||
useTray: boolean | null;
|
||||
doneManualMounting: boolean;
|
||||
mirrorView: boolean;
|
||||
assignMode: AssignMode;
|
||||
discordPresence: boolean;
|
||||
}
|
||||
|
||||
@@ -51,10 +59,10 @@ export const defaultConfig: Omit<Config, 'devSettings'> = {
|
||||
theme: 'slime',
|
||||
textSize: 12,
|
||||
fonts: ['poppins'],
|
||||
advancedAssign: false,
|
||||
useTray: null,
|
||||
doneManualMounting: false,
|
||||
mirrorView: true,
|
||||
assignMode: AssignMode.Core,
|
||||
discordPresence: false,
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user