Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve setup wizard #639

Merged
merged 20 commits into from
Mar 31, 2023
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
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
8 changes: 7 additions & 1 deletion gui/public/i18n/en/translation.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,12 @@ settings-osc-vmc-anchor_hip-label = Anchor at hips
onboarding-skip = Skip setup
onboarding-continue = Continue
onboarding-wip = Work in progress
onboarding-previous_step = Previous step
onboarding-setup_warning =
<b>Warning:</b> The setup is needed for good tracking,
this is required if this is your first time using SlimeVR.
ImUrX marked this conversation as resolved.
Show resolved Hide resolved
onboarding-setup_warning-skip = Skip setup
onboarding-setup_warning-cancel = Continue setup

## Wi-Fi setup
onboarding-wifi_creds-back = Go Back to introduction
Expand Down Expand Up @@ -606,7 +612,7 @@ onboarding-automatic_mounting-next = Next step
onboarding-automatic_mounting-prev_step = Previous step
onboarding-automatic_mounting-done-title = Mounting rotations calibrated.
onboarding-automatic_mounting-done-description = Your mounting calibration is complete!
onboarding-automatic_mounting-done-restart = Return to start
onboarding-automatic_mounting-done-restart = Try again
onboarding-automatic_mounting-mounting_reset-title = Mounting Reset
onboarding-automatic_mounting-mounting_reset-step-0 = 1. Squat in a "skiing" pose with your legs bent, your upper body tilted forwards, and your arms bent.
onboarding-automatic_mounting-mounting_reset-step-1 = 2. Press the "Reset Mounting" button and wait for 3 seconds before the trackers' mounting rotations will reset.
Expand Down
121 changes: 121 additions & 0 deletions gui/public/models/tracker.gltf

Large diffs are not rendered by default.

12 changes: 11 additions & 1 deletion gui/src/components/commons/BodyDisplay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ interface SlotDot {
type DotParams = {
dotSize: number;
trackers: FlatDeviceTracker[];
hidden: boolean;
} & SlotDot;

function Tracker({
Expand Down Expand Up @@ -56,6 +57,7 @@ function Dot({
left,
dotSize,
trackers,
hidden,
}: DotParams) {
const [velocities, setVelocities] = useState<number[]>([]);

Expand Down Expand Up @@ -83,7 +85,9 @@ function Dot({
>
<div
className={classNames(
'rounded-full outline outline-2 outline-accent-background-20 bg-background-10 transition-transform'
'rounded-full outline outline-2 outline-background-20',
'bg-background-10 transition-transform',
hidden && 'opacity-0'
)}
style={{
width: dotSize,
Expand All @@ -109,13 +113,15 @@ export function BodyDisplay({
width = 228,
dotsSize = 20,
variant = 'tracker-select',
hideUnassigned = false,
}: {
leftControls?: ReactNode;
rightControls?: ReactNode;
width?: number;
dotsSize?: number;
variant?: 'dots' | 'tracker-select';
trackers: FlatDeviceTracker[];
hideUnassigned: boolean;
}) {
const personRef = useRef<HTMLDivElement | null>(null);
const [slotsButtonsPos, setSlotsButtonPos] = useState<SlotDot[]>([]);
Expand Down Expand Up @@ -187,6 +193,10 @@ export function BodyDisplay({
{...dotData}
dotSize={dotsSize}
key={dotData.id}
hidden={
hideUnassigned &&
trackerPartGrouped[(BodyPart as any)[dotData.id]] === undefined
}
trackers={trackerPartGrouped[(BodyPart as any)[dotData.id]]}
/>
))}
Expand Down
9 changes: 7 additions & 2 deletions gui/src/components/commons/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -91,10 +91,15 @@ export function Button({
},
props.className
);
}, [variant, disabled, rounded]);
}, [variant, disabled, rounded, props.className]);

return to ? (
<NavLink to={to} className={classes} state={state}>
<NavLink
to={to}
className={classes}
state={state}
onClick={(ev) => disabled && ev.preventDefault()}
>
<ButtonContent icon={icon} loading={loading}>
{children}
</ButtonContent>
Expand Down
76 changes: 52 additions & 24 deletions gui/src/components/commons/ProgressBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,39 +5,67 @@ export function ProgressBar({
progress,
parts = 1,
height = 10,
colorClass = 'bg-accent-background-20',
animated = false,
}: {
progress: number;
parts?: number;
height?: number;
colorClass?: string;
animated?: boolean;
}) {
const Bar = ({ index }: { index: number }) => {
const value = useMemo(
() => Math.min(Math.max((progress * parts) / 1 - index, 0), 1),
[index, progress]
);
return (
<div
className="flex relative flex-grow bg-background-50 rounded-lg overflow-hidden"
style={{ height: `${height}px` }}
>
<div
className={classNames(
'bg-accent-background-20 rounded-lg overflow-hidden absolute top-0'
)}
style={{
width: `${value * 100}%`,
height: `${height}px`,
}}
></div>
</div>
);
};

return (
<div className="flex w-full flex-row gap-2">
{Array.from({ length: parts }).map((_, key) => (
<Bar index={key} key={key}></Bar>
<Bar
index={key}
key={key}
progress={progress}
height={height}
colorClass={colorClass}
animated={animated}
parts={parts}
></Bar>
))}
</div>
);
}

export function Bar({
index,
progress,
parts,
height,
animated,
colorClass,
}: {
index: number;
progress: number;
parts: number;
height: number;
colorClass: string;
animated: boolean;
}) {
const value = useMemo(
() => Math.min(Math.max((progress * parts) / 1 - index, 0), 1),
[index, progress]
);
return (
<div
className="flex relative flex-grow bg-background-50 rounded-lg overflow-hidden"
style={{ height: `${height}px` }}
>
<div
className={classNames(
'rounded-lg overflow-hidden absolute top-0',
animated && 'transition-[width,background-color]',
colorClass
)}
style={{
width: `${value * 100}%`,
height: `${height}px`,
}}
></div>
</div>
);
}
24 changes: 24 additions & 0 deletions gui/src/components/commons/icon/EscapeIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
export function EscapeIcon({
size = 24,
className = '',
}: {
size?: number;
className?: string;
}) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
width={size}
viewBox="0 0 24 24"
strokeWidth={1.5}
className={className}
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M9.75 9.75l4.5 4.5m0-4.5l-4.5 4.5M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
);
}
4 changes: 2 additions & 2 deletions gui/src/components/commons/icon/LoaderIcon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ export function LoaderIcon({
}) {
return (
<svg
width="19"
height="19"
width="20"
height="20"
viewBox="0 0 19 19"
xmlns="http://www.w3.org/2000/svg"
className={classNames({
Expand Down
1 change: 0 additions & 1 deletion gui/src/components/home/ResetButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ export function ResetButton({
case ResetType.Full:
return l10n.getString('reset-full');
}
return l10n.getString('reset-full');
}, [type]);

const getIcon = () => {
Expand Down
43 changes: 43 additions & 0 deletions gui/src/components/onboarding/SkipSetupButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import classNames from 'classnames';
import { useEffect } from 'react';
import { EscapeIcon } from '../commons/icon/EscapeIcon';

export function SkipSetupButton({
modalVisible,
onClick,
visible,
}: {
onClick: () => void;
modalVisible: boolean;
visible: boolean;
}) {
if (!visible) return <div></div>;
useEffect(() => {
if (modalVisible) return;

function onEscape(ev: KeyboardEvent) {
if (ev.key === 'Escape') onClick();
}

document.addEventListener('keydown', onEscape, { passive: true });

return () => document.removeEventListener('keydown', onEscape);
}, [modalVisible]);

return (
<button
type="button"
className={classNames(
'text-background-40 hover:text-background-30',
'stroke-background-40 hover:stroke-background-30',
'absolute -top-10 right-0'
)}
onClick={onClick}
>
<div className="flex flex-col justify-center items-center">
<EscapeIcon size={42}></EscapeIcon>
<p className="text-standard">ESC</p>
</div>
</button>
);
}
68 changes: 68 additions & 0 deletions gui/src/components/onboarding/SkipSetupWarningModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { Button } from '../commons/Button';
import { WarningBox } from '../commons/TipBox';
import { Localized, useLocalization } from '@fluent/react';
import { BaseModal } from '../commons/BaseModal';
import ReactModal from 'react-modal';
import { useNavigate } from 'react-router-dom';

export function SkipSetupWarningModal({
isOpen = true,
onClose,
accept,
...props
}: {
/**
* Is the parent/sibling component opened?
*/
isOpen: boolean;
/**
* Function to trigger when the warning hasn't been accepted
*/
onClose: () => void;
/**
* Function when you press `i understand`
*/
accept: () => void;
} & ReactModal.Props) {
const { l10n } = useLocalization();
const navigate = useNavigate();

// isOpen is checked by checking if the parent modal is opened + our bodyPart is the
// neck and we havent showed this warning yet
return (
<BaseModal
isOpen={isOpen}
shouldCloseOnOverlayClick
shouldCloseOnEsc
onRequestClose={onClose}
className={props.className}
overlayClassName={props.overlayClassName}
>
<div className="flex w-full h-full flex-col ">
<div className="flex w-full flex-col flex-grow items-center gap-3">
<Localized id="onboarding-setup_warning" elems={{ b: <b></b> }}>
<WarningBox>
<b>Warning:</b> The setup is needed for good tracking, this is
ImUrX marked this conversation as resolved.
Show resolved Hide resolved
required if this is your first time using SlimeVR.
</WarningBox>
</Localized>

<div className="flex flex-row gap-3 pt-5 place-content-center">
<Button variant="primary" onClick={onClose}>
{l10n.getString('onboarding-setup_warning-cancel')}
</Button>
<Button
variant="tiertiary"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know this is misspelled in the code for Button, but tiertiary? ;w; It should be tertiary

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this will get fixed in another PR tbh

onClick={() => {
accept();
navigate('/');
}}
>
{l10n.getString('onboarding-setup_warning-skip')}
</Button>
</div>
</div>
</div>
</BaseModal>
);
}
5 changes: 4 additions & 1 deletion gui/src/components/onboarding/StepperSlider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@ type StepComponentType = FC<{
resetSteps: () => void;
variant: 'alone' | 'onboarding';
}>;
type Step = { type: 'numbered' | 'fullsize'; component: StepComponentType };
export type Step = {
type: 'numbered' | 'fullsize';
component: StepComponentType;
};

export function StepContainer({
children,
Expand Down
Loading