mirror of
https://github.com/SlimeVR/SlimeVR-Server.git
synced 2026-04-06 02:01:58 +02:00
Lots of misc gui fixes (#516)
Co-authored-by: Louka <marioluigivideo@gmail.com>
This commit is contained in:
@@ -11,14 +11,9 @@
|
||||
"@tauri-apps/api": "^1.2.0",
|
||||
"@vitejs/plugin-react": "^3.0.0",
|
||||
"browserslist": "^4.18.1",
|
||||
"camelcase": "^6.2.1",
|
||||
"classnames": "^2.3.1",
|
||||
"dotenv": "^10.0.0",
|
||||
"dotenv-expand": "^5.1.0",
|
||||
"eslint-config-react-app": "^7.0.0",
|
||||
"file-loader": "^6.2.0",
|
||||
"flatbuffers": "^22.10.26",
|
||||
"fs-extra": "^10.0.0",
|
||||
"identity-obj-proxy": "^3.0.0",
|
||||
"intl-pluralrules": "^1.3.1",
|
||||
"ip-num": "^1.4.1",
|
||||
@@ -32,7 +27,6 @@
|
||||
"react-hook-form": "^7.29.0",
|
||||
"react-modal": "3.15.1",
|
||||
"react-router-dom": "^6.2.2",
|
||||
"semver": "^7.3.5",
|
||||
"solarxr-protocol": "file:../solarxr-protocol",
|
||||
"three": "^0.148.0",
|
||||
"typescript": "^4.6.3"
|
||||
@@ -68,7 +62,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/forms": "^0.5.0",
|
||||
"@tauri-apps/cli": "^1.2.2",
|
||||
"@tauri-apps/cli": "^1.2.3",
|
||||
"@types/react": "18.0.25",
|
||||
"@types/react-dom": "^18.0.5",
|
||||
"@types/react-modal": "3.13.1",
|
||||
|
||||
@@ -202,6 +202,14 @@ tracker_selection_menu-unassigned = unassyigned twackaws
|
||||
tracker_selection_menu-assigned = assyigned twackaws
|
||||
tracker_selection_menu-dont_assign = do nawt assyign
|
||||
|
||||
# This line cares about multilines.
|
||||
# <b>text</b> means that the text should be bold.
|
||||
tracker_selection_menu-neck_warning =
|
||||
<b>wawning:</b> a neck twackew can be deadwy if adjusted too tightwy,
|
||||
the stwap (collar) couwd cut the ciwcuwation to youw head!
|
||||
tracker_selection_menu-neck_warning-done = i undewstand the wisks~
|
||||
tracker_selection_menu-neck_warning-cancel = cancew :o
|
||||
|
||||
## Mounting menu
|
||||
mounting_selection_menu = whewe doo yew want dis twayckaw to be?
|
||||
mounting_selection_menu-close = cwose
|
||||
@@ -325,6 +333,13 @@ settings-serial-description =
|
||||
settings-serial-connection_lost = connyection to shewyaw wost, weconnyecting... >~<
|
||||
settings-serial-reboot = weboot
|
||||
settings-serial-factory_reset = fawctowy reset
|
||||
# This cares about multilines
|
||||
# <b>text</b> means that the text should be bold
|
||||
settings-serial-factory_reset-warning =
|
||||
<b>wawning:</b> this wiww weset the twackew to factowy settings.
|
||||
which means wi-fi and cawibwation settings <b>wiww aww be wost!</b>
|
||||
settings-serial-factory_reset-warning-ok = i know what I'm doing :3
|
||||
settings-serial-factory_reset-warning-cancel = cancew
|
||||
settings-serial-get_infos = get infows
|
||||
settings-serial-serial_select = sewect a shewyaw pawt
|
||||
settings-serial-auto_dropdown_item = awto
|
||||
|
||||
@@ -202,6 +202,14 @@ tracker_selection_menu-unassigned = Unassigned Trackers
|
||||
tracker_selection_menu-assigned = Assigned Trackers
|
||||
tracker_selection_menu-dont_assign = Do not assign
|
||||
|
||||
# This line cares about multilines.
|
||||
# <b>text</b> means that the text should be bold.
|
||||
tracker_selection_menu-neck_warning =
|
||||
<b>Warning:</b> A neck tracker can be deadly if adjusted too tightly,
|
||||
the strap could cut the circulation to your head!
|
||||
tracker_selection_menu-neck_warning-done = I understand the risks
|
||||
tracker_selection_menu-neck_warning-cancel = Cancel
|
||||
|
||||
## Mounting menu
|
||||
mounting_selection_menu = Where do you want this tracker to be?
|
||||
mounting_selection_menu-close = Close
|
||||
@@ -325,6 +333,13 @@ settings-serial-description =
|
||||
settings-serial-connection_lost = Connection to serial lost, Reconnecting...
|
||||
settings-serial-reboot = Reboot
|
||||
settings-serial-factory_reset = Factory Reset
|
||||
# This cares about multilines
|
||||
# <b>text</b> means that the text should be bold
|
||||
settings-serial-factory_reset-warning =
|
||||
<b>Warning:</b> This will reset the tracker to factory settings.
|
||||
Which means Wi-Fi and calibration settings <b>will all be lost!</b>
|
||||
settings-serial-factory_reset-warning-ok = I know what I'm doing
|
||||
settings-serial-factory_reset-warning-cancel = Cancel
|
||||
settings-serial-get_infos = Get Infos
|
||||
settings-serial-serial_select = Select a serial port
|
||||
settings-serial-auto_dropdown_item = Auto
|
||||
|
||||
@@ -202,6 +202,14 @@ tracker_selection_menu-unassigned = Capteurs non assignés
|
||||
tracker_selection_menu-assigned = Capteurs assignés
|
||||
tracker_selection_menu-dont_assign = Ne pas attribuer
|
||||
|
||||
# This line cares about multilines.
|
||||
# <b>text</b> means that the text should be bold.
|
||||
tracker_selection_menu-neck_warning =
|
||||
<b>Attention:</b> Un capteur de cou peut être mortel s'il est ajusté trop serré,
|
||||
la sangle pourrait couper la circulation à la tête !
|
||||
tracker_selection_menu-neck_warning-done = Je suis conscient des risques
|
||||
tracker_selection_menu-neck_warning-cancel = Annuler
|
||||
|
||||
## Mounting menu
|
||||
mounting_selection_menu = Dans quelle direction pointe ce capteur?
|
||||
mounting_selection_menu-close = Fermer
|
||||
@@ -325,6 +333,13 @@ settings-serial-description =
|
||||
settings-serial-connection_lost = Connexion à l'appareil perdue, reconnexion...
|
||||
settings-serial-reboot = Redémarrer
|
||||
settings-serial-factory_reset = Remise à zéro
|
||||
# This cares about multilines
|
||||
# <b>text</b> means that the text should be bold
|
||||
settings-serial-factory_reset-warning =
|
||||
<b>Attention:</b> Cela réinitialisera les paramètres du capteur à zéro.
|
||||
Ce qui signifie que les paramètres de Wi-Fi et de calibration <b>seront tous perdus!</b>
|
||||
settings-serial-factory_reset-warning-ok = Je sais ce que je fais
|
||||
settings-serial-factory_reset-warning-cancel = Annuler
|
||||
settings-serial-get_infos = Obtenir des informations
|
||||
settings-serial-serial_select = Sélectionnez un port série
|
||||
settings-serial-auto_dropdown_item = Automatique
|
||||
|
||||
@@ -14,13 +14,19 @@ export function BaseModal({
|
||||
{...props}
|
||||
shouldCloseOnOverlayClick
|
||||
shouldCloseOnEsc
|
||||
overlayClassName={classNames(
|
||||
'fixed top-0 right-0 left-0 bottom-0 flex flex-col justify-center items-center w-full h-full bg-background-90 bg-opacity-60 z-20'
|
||||
)}
|
||||
className={classNames(
|
||||
props.className as string,
|
||||
'items-center focus:ring-transparent focus:ring-offset-transparent focus:outline-transparent outline-none bg-background-60 p-6 rounded-lg text-white'
|
||||
)}
|
||||
overlayClassName={
|
||||
props.overlayClassName ||
|
||||
classNames(
|
||||
'fixed top-0 right-0 left-0 bottom-0 flex flex-col justify-center items-center w-full h-full bg-background-90 bg-opacity-60 z-20'
|
||||
)
|
||||
}
|
||||
className={
|
||||
props.className ||
|
||||
classNames(
|
||||
props.className as string,
|
||||
'items-center focus:ring-transparent focus:ring-offset-transparent focus:outline-transparent outline-none bg-background-60 p-6 rounded-lg text-white'
|
||||
)
|
||||
}
|
||||
>
|
||||
{children}
|
||||
</ReactModal>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { ReactNode } from 'react';
|
||||
import { BulbIcon } from './icon/BulbIcon';
|
||||
import { WarningIcon } from './icon/WarningIcon';
|
||||
import { Typography } from './Typography';
|
||||
|
||||
export function TipBox({ children }: { children: ReactNode }) {
|
||||
@@ -14,3 +15,21 @@ export function TipBox({ children }: { children: ReactNode }) {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Will respect new lines and spacing given in text
|
||||
*/
|
||||
export function WarningBox({ children }: { children: ReactNode }) {
|
||||
return (
|
||||
<div className="flex flex-row gap-4 bg-red-900 p-4 rounded-md">
|
||||
<div className="fill-red-500 flex flex-col justify-center">
|
||||
<WarningIcon></WarningIcon>
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
<Typography color="text-red-300" whitespace="whitespace-pre">
|
||||
{children}
|
||||
</Typography>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -5,12 +5,19 @@ export function Typography({
|
||||
variant = 'standard',
|
||||
bold = false,
|
||||
color = 'primary',
|
||||
whitespace = 'whitespace-normal',
|
||||
children,
|
||||
}: {
|
||||
variant?: 'main-title' | 'section-title' | 'standard' | 'vr-accessible';
|
||||
bold?: boolean;
|
||||
block?: boolean;
|
||||
color?: 'primary' | 'secondary' | string;
|
||||
whitespace?:
|
||||
| 'whitespace-normal'
|
||||
| 'whitespace-nowrap'
|
||||
| 'whitespace-pre'
|
||||
| 'whitespace-pre-line'
|
||||
| 'whitespace-pre-wrap';
|
||||
children: ReactNode;
|
||||
}) {
|
||||
const tag = useMemo(() => {
|
||||
@@ -36,6 +43,7 @@ export function Typography({
|
||||
color === 'primary' && 'text-background-10',
|
||||
color === 'secondary' && 'text-background-30',
|
||||
typeof color === 'string' && color,
|
||||
whitespace,
|
||||
]),
|
||||
},
|
||||
children
|
||||
|
||||
94
gui/src/components/onboarding/NeckWarningModal.tsx
Normal file
94
gui/src/components/onboarding/NeckWarningModal.tsx
Normal file
@@ -0,0 +1,94 @@
|
||||
import { BodyPart } from 'solarxr-protocol';
|
||||
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';
|
||||
|
||||
export function NeckWarningModal({
|
||||
isOpen = true,
|
||||
hasShown = false,
|
||||
onClose,
|
||||
setShown,
|
||||
bodyPart,
|
||||
...props
|
||||
}: {
|
||||
/**
|
||||
* Is the parent/sibling component opened?
|
||||
*/
|
||||
isOpen: boolean;
|
||||
/**
|
||||
* Has this warning be shown
|
||||
* We want the parent/sibling component to tell us this because they also should
|
||||
* know about the state
|
||||
*/
|
||||
hasShown: boolean;
|
||||
/**
|
||||
* The current body part chosen
|
||||
*/
|
||||
bodyPart: BodyPart | null;
|
||||
/**
|
||||
* Function to trigger when the neck warning hasn't been accepted
|
||||
*/
|
||||
onClose: () => void;
|
||||
/**
|
||||
* Function to change the hasShown value
|
||||
*/
|
||||
setShown: (arg0: boolean) => void;
|
||||
} & ReactModal.Props) {
|
||||
const { l10n } = useLocalization();
|
||||
// Skip popup if bodyPart isn't neck or we already showed the warning in this session
|
||||
if (
|
||||
isOpen &&
|
||||
!hasShown &&
|
||||
(bodyPart !== BodyPart.NECK || sessionStorage.getItem('neckWarning'))
|
||||
) {
|
||||
setShown(true);
|
||||
}
|
||||
// Reset shown to false when no longer opened
|
||||
if (!isOpen && hasShown) {
|
||||
setShown(false);
|
||||
}
|
||||
|
||||
// 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 && bodyPart === BodyPart.NECK && !hasShown}
|
||||
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="tracker_selection_menu-neck_warning"
|
||||
elems={{ b: <b></b> }}
|
||||
>
|
||||
<WarningBox>
|
||||
<b>Warning:</b> A neck tracker can be deadly if adjusted too
|
||||
tightly, the strap could cut the circulation to your head!
|
||||
</WarningBox>
|
||||
</Localized>
|
||||
|
||||
<div className="flex flex-row gap-3 pt-5 place-content-center">
|
||||
<Button variant="secondary" onClick={onClose}>
|
||||
{l10n.getString('tracker_selection_menu-neck_warning-cancel')}
|
||||
</Button>
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={() => {
|
||||
setShown(true);
|
||||
sessionStorage.setItem('neckWarning', 'true');
|
||||
}}
|
||||
>
|
||||
{l10n.getString('tracker_selection_menu-neck_warning-done')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</BaseModal>
|
||||
);
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import { ReactNode } from 'react';
|
||||
import {
|
||||
OnboardingContextC,
|
||||
useProvideOnboarding
|
||||
useProvideOnboarding,
|
||||
} from '../../hooks/onboarding';
|
||||
|
||||
export function OnboardingContextProvider({
|
||||
|
||||
@@ -9,6 +9,8 @@ import { TipBox } from '../../../commons/TipBox';
|
||||
import { Typography } from '../../../commons/Typography';
|
||||
import { TrackerCard } from '../../../tracker/TrackerCard';
|
||||
import { useLocalization } from '@fluent/react';
|
||||
import { useState } from 'react';
|
||||
import { NeckWarningModal } from '../../NeckWarningModal';
|
||||
|
||||
export function TrackerSelectionMenu({
|
||||
isOpen = true,
|
||||
@@ -26,101 +28,116 @@ export function TrackerSelectionMenu({
|
||||
useLayout<HTMLDivElement>();
|
||||
const { ref: refOptions, height: optionsHeight } =
|
||||
useElemSize<HTMLDivElement>();
|
||||
|
||||
// This is true when the neck warning has been accepted
|
||||
// It is used for showing or not showing the actual modal
|
||||
const [neckVerified, setNeckVerified] = useState(false);
|
||||
const { useAssignedTrackers, useUnassignedTrackers } = useTrackers();
|
||||
|
||||
const unassignedTrackers = useUnassignedTrackers();
|
||||
const assignedTrackers = useAssignedTrackers();
|
||||
|
||||
return (
|
||||
<ReactModal
|
||||
isOpen={isOpen}
|
||||
shouldCloseOnOverlayClick
|
||||
shouldCloseOnEsc
|
||||
onRequestClose={onClose}
|
||||
overlayClassName={classNames(
|
||||
'fixed top-0 right-0 left-0 bottom-0 flex flex-col items-center w-full h-full bg-black bg-opacity-90 z-20'
|
||||
)}
|
||||
className={classNames(
|
||||
'focus:ring-transparent focus:ring-offset-transparent focus:outline-transparent outline-none mt-20 z-10'
|
||||
)}
|
||||
>
|
||||
<div className="flex w-full h-full flex-col ">
|
||||
<div className="flex w-full flex-col flex-grow items-center gap-3">
|
||||
<Typography variant="main-title" bold>
|
||||
{l10n.getString('tracker_selection_menu-' + BodyPart[bodyPart])}
|
||||
</Typography>
|
||||
<div className="relative">
|
||||
<div
|
||||
className="w-full h-full min-w-[700px] overflow-y-auto p-2 flex flex-col gap-6"
|
||||
ref={refTrackers}
|
||||
style={{ height: trackersHeight - optionsHeight }}
|
||||
>
|
||||
<div className="flex flex-col gap-3">
|
||||
{unassignedTrackers.length && (
|
||||
<div className="flex flex-col gap-3">
|
||||
<Typography>
|
||||
{l10n.getString('tracker_selection_menu-unassigned')}
|
||||
</Typography>
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
{unassignedTrackers.map((fd, index) => (
|
||||
<TrackerCard
|
||||
key={index}
|
||||
tracker={fd.tracker}
|
||||
device={fd.device}
|
||||
onClick={() => onTrackerSelected(fd)}
|
||||
smol
|
||||
interactable
|
||||
outlined={
|
||||
bodyPart ===
|
||||
(fd.tracker.info?.bodyPart || BodyPart.NONE)
|
||||
}
|
||||
></TrackerCard>
|
||||
))}
|
||||
<>
|
||||
<ReactModal
|
||||
isOpen={isOpen && neckVerified}
|
||||
shouldCloseOnOverlayClick
|
||||
shouldCloseOnEsc
|
||||
onRequestClose={onClose}
|
||||
overlayClassName={classNames(
|
||||
'fixed top-0 right-0 left-0 bottom-0 flex flex-col items-center w-full h-full bg-black bg-opacity-90 z-20'
|
||||
)}
|
||||
className={classNames(
|
||||
'focus:ring-transparent focus:ring-offset-transparent focus:outline-transparent outline-none mt-20 z-10'
|
||||
)}
|
||||
>
|
||||
<div className="flex w-full h-full flex-col ">
|
||||
<div className="flex w-full flex-col flex-grow items-center gap-3">
|
||||
<Typography variant="main-title" bold>
|
||||
{l10n.getString('tracker_selection_menu-' + BodyPart[bodyPart])}
|
||||
</Typography>
|
||||
<div className="relative">
|
||||
<div
|
||||
className="w-full h-full min-w-[700px] overflow-y-auto p-2 flex flex-col gap-6"
|
||||
ref={refTrackers}
|
||||
style={{ height: trackersHeight - optionsHeight }}
|
||||
>
|
||||
<div className="flex flex-col gap-3">
|
||||
{unassignedTrackers.length && (
|
||||
<div className="flex flex-col gap-3">
|
||||
<Typography>
|
||||
{l10n.getString('tracker_selection_menu-unassigned')}
|
||||
</Typography>
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
{unassignedTrackers.map((fd, index) => (
|
||||
<TrackerCard
|
||||
key={index}
|
||||
tracker={fd.tracker}
|
||||
device={fd.device}
|
||||
onClick={() => onTrackerSelected(fd)}
|
||||
smol
|
||||
interactable
|
||||
outlined={
|
||||
bodyPart ===
|
||||
(fd.tracker.info?.bodyPart || BodyPart.NONE)
|
||||
}
|
||||
></TrackerCard>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<Typography>
|
||||
{l10n.getString('tracker_selection_menu-assigned')}
|
||||
</Typography>
|
||||
<div className=" grid grid-cols-2 gap-3">
|
||||
{assignedTrackers.map((fd, index) => (
|
||||
<TrackerCard
|
||||
key={index}
|
||||
tracker={fd.tracker}
|
||||
device={fd.device}
|
||||
onClick={() => onTrackerSelected(fd)}
|
||||
smol
|
||||
interactable
|
||||
outlined={
|
||||
bodyPart ===
|
||||
(fd.tracker.info?.bodyPart || BodyPart.NONE)
|
||||
}
|
||||
></TrackerCard>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
<Typography>
|
||||
{l10n.getString('tracker_selection_menu-assigned')}
|
||||
</Typography>
|
||||
<div className=" grid grid-cols-2 gap-3">
|
||||
{assignedTrackers.map((fd, index) => (
|
||||
<TrackerCard
|
||||
key={index}
|
||||
tracker={fd.tracker}
|
||||
device={fd.device}
|
||||
onClick={() => onTrackerSelected(fd)}
|
||||
smol
|
||||
interactable
|
||||
outlined={
|
||||
bodyPart ===
|
||||
(fd.tracker.info?.bodyPart || BodyPart.NONE)
|
||||
}
|
||||
></TrackerCard>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="absolute px-2 pr-4 bottom-0 h-10 w-full border-b-[1px] border-background-40">
|
||||
<div className="w-full h-full bg-gradient-to-b from-transparent to-black opacity-50"></div>
|
||||
<div className="absolute px-2 pr-4 bottom-0 h-10 w-full border-b-[1px] border-background-40">
|
||||
<div className="w-full h-full bg-gradient-to-b from-transparent to-black opacity-50"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="flex w-full justify-between absolute bottom-0 left-0 p-10 z-0"
|
||||
onClick={onClose}
|
||||
ref={refOptions}
|
||||
>
|
||||
<div className="w-full max-w-sm">
|
||||
<TipBox>{l10n.getString('tips-find_tracker')}</TipBox>
|
||||
<div
|
||||
className="flex w-full justify-between absolute bottom-0 left-0 p-10 z-0"
|
||||
onClick={onClose}
|
||||
ref={refOptions}
|
||||
>
|
||||
<div className="w-full max-w-sm">
|
||||
<TipBox>{l10n.getString('tips-find_tracker')}</TipBox>
|
||||
</div>
|
||||
<div className="flex flex-col justify-end pointer-events-auto">
|
||||
<Button variant="primary" onClick={() => onTrackerSelected(null)}>
|
||||
{l10n.getString('tracker_selection_menu-dont_assign')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col justify-end pointer-events-auto">
|
||||
<Button variant="primary" onClick={() => onTrackerSelected(null)}>
|
||||
{l10n.getString('tracker_selection_menu-dont_assign')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</ReactModal>
|
||||
</ReactModal>
|
||||
{/**
|
||||
* This one is simple, we simply pass everything directly
|
||||
* the NeckWarningModal explain how everything works.
|
||||
*/}
|
||||
<NeckWarningModal
|
||||
isOpen={isOpen}
|
||||
hasShown={neckVerified}
|
||||
bodyPart={bodyPart}
|
||||
onClose={onClose}
|
||||
setShown={(bool) => setNeckVerified(bool)}
|
||||
></NeckWarningModal>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -18,7 +18,9 @@ import { useWebsocketAPI } from '../../../hooks/websocket-api';
|
||||
import { Button } from '../../commons/Button';
|
||||
import { Dropdown } from '../../commons/Dropdown';
|
||||
import { Typography } from '../../commons/Typography';
|
||||
import { useLocalization } from '@fluent/react';
|
||||
import { Localized, useLocalization } from '@fluent/react';
|
||||
import { BaseModal } from '../../commons/BaseModal';
|
||||
import { WarningBox } from '../../commons/TipBox';
|
||||
|
||||
export interface SerialForm {
|
||||
port: string;
|
||||
@@ -46,6 +48,8 @@ export function Serial() {
|
||||
Omit<SerialDeviceT, 'pack'>[]
|
||||
>([]);
|
||||
|
||||
const [tryFactoryReset, setTryFactoryReset] = useState(false);
|
||||
|
||||
const { control, watch, handleSubmit, reset } = useForm<SerialForm>({
|
||||
defaultValues: { port: 'Auto' },
|
||||
});
|
||||
@@ -143,6 +147,8 @@ export function Serial() {
|
||||
RpcMessage.SerialTrackerFactoryResetRequest,
|
||||
new SerialTrackerFactoryResetRequestT()
|
||||
);
|
||||
|
||||
setTryFactoryReset(false);
|
||||
};
|
||||
const getInfos = () => {
|
||||
sendRPCPacket(
|
||||
@@ -152,67 +158,94 @@ export function Serial() {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col bg-background-70 h-full p-5 rounded-md">
|
||||
<div className="flex flex-col pb-2">
|
||||
<Typography variant="main-title">
|
||||
{l10n.getString('settings-serial')}
|
||||
</Typography>
|
||||
<>
|
||||
{l10n
|
||||
.getString('settings-serial-description')
|
||||
.split('\n')
|
||||
.map((line, i) => (
|
||||
<Typography color="secondary" key={i}>
|
||||
{line}
|
||||
</Typography>
|
||||
))}
|
||||
</>
|
||||
</div>
|
||||
<div className="bg-background-80 rounded-lg flex flex-col p-2">
|
||||
<div
|
||||
ref={consoleRef}
|
||||
className="overflow-x-auto overflow-y-auto"
|
||||
style={{
|
||||
height: layoutHeight - height - 30,
|
||||
width: layoutWidth - 24,
|
||||
}}
|
||||
<>
|
||||
<BaseModal
|
||||
isOpen={tryFactoryReset}
|
||||
onRequestClose={() => setTryFactoryReset(false)}
|
||||
>
|
||||
<Localized
|
||||
id="settings-serial-factory_reset-warning"
|
||||
elems={{ b: <b></b> }}
|
||||
>
|
||||
<div className="flex select-text px-3">
|
||||
<pre>
|
||||
{isSerialOpen
|
||||
? consoleContent
|
||||
: l10n.getString('settings-serial-connection_lost')}
|
||||
</pre>
|
||||
</div>
|
||||
<WarningBox>
|
||||
<b>Warning:</b> This will reset the tracker to factory settings.
|
||||
Which means Wi-Fi and calibration settings <b>will all be lost!</b>
|
||||
</WarningBox>
|
||||
</Localized>
|
||||
<div className="flex flex-row gap-3 pt-5 place-content-center">
|
||||
<Button variant="secondary" onClick={() => setTryFactoryReset(false)}>
|
||||
{l10n.getString('settings-serial-factory_reset-warning-cancel')}
|
||||
</Button>
|
||||
<Button variant="primary" onClick={factoryReset}>
|
||||
{l10n.getString('settings-serial-factory_reset-warning-ok')}
|
||||
</Button>
|
||||
</div>
|
||||
<div className="" ref={toolbarRef}>
|
||||
<div className="border-t-2 pt-2 border-background-60 border-solid m-2 gap-2 flex flex-row">
|
||||
<div className="flex flex-grow gap-2">
|
||||
<Button variant="quaternary" onClick={reboot}>
|
||||
{l10n.getString('settings-serial-reboot')}
|
||||
</Button>
|
||||
<Button variant="quaternary" onClick={factoryReset}>
|
||||
{l10n.getString('settings-serial-factory_reset')}
|
||||
</Button>
|
||||
<Button variant="quaternary" onClick={getInfos}>
|
||||
{l10n.getString('settings-serial-get_infos')}
|
||||
</Button>
|
||||
</BaseModal>
|
||||
<div className="flex flex-col bg-background-70 h-full p-5 rounded-md">
|
||||
<div className="flex flex-col pb-2">
|
||||
<Typography variant="main-title">
|
||||
{l10n.getString('settings-serial')}
|
||||
</Typography>
|
||||
<>
|
||||
{l10n
|
||||
.getString('settings-serial-description')
|
||||
.split('\n')
|
||||
.map((line, i) => (
|
||||
<Typography color="secondary" key={i}>
|
||||
{line}
|
||||
</Typography>
|
||||
))}
|
||||
</>
|
||||
</div>
|
||||
<div className="bg-background-80 rounded-lg flex flex-col p-2">
|
||||
<div
|
||||
ref={consoleRef}
|
||||
className="overflow-x-auto overflow-y-auto"
|
||||
style={{
|
||||
height: layoutHeight - height - 30,
|
||||
width: layoutWidth - 24,
|
||||
}}
|
||||
>
|
||||
<div className="flex select-text px-3">
|
||||
<pre>
|
||||
{isSerialOpen
|
||||
? consoleContent
|
||||
: l10n.getString('settings-serial-connection_lost')}
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
<div className="" ref={toolbarRef}>
|
||||
<div className="border-t-2 pt-2 border-background-60 border-solid m-2 gap-2 flex flex-row">
|
||||
<div className="flex flex-grow gap-2">
|
||||
<Button variant="quaternary" onClick={reboot}>
|
||||
{l10n.getString('settings-serial-reboot')}
|
||||
</Button>
|
||||
<Button
|
||||
variant="quaternary"
|
||||
onClick={() => setTryFactoryReset(true)}
|
||||
>
|
||||
{l10n.getString('settings-serial-factory_reset')}
|
||||
</Button>
|
||||
<Button variant="quaternary" onClick={getInfos}>
|
||||
{l10n.getString('settings-serial-get_infos')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end">
|
||||
<Dropdown
|
||||
control={control}
|
||||
name="port"
|
||||
placeholder={l10n.getString('settings-serial-serial_select')}
|
||||
items={serialDevices.map((device) => ({
|
||||
label: device.name?.toString() || 'error',
|
||||
value: device.port?.toString() || 'error',
|
||||
}))}
|
||||
></Dropdown>
|
||||
<div className="flex justify-end">
|
||||
<Dropdown
|
||||
control={control}
|
||||
name="port"
|
||||
placeholder={l10n.getString('settings-serial-serial_select')}
|
||||
items={serialDevices.map((device) => ({
|
||||
label: device.name?.toString() || 'error',
|
||||
value: device.port?.toString() || 'error',
|
||||
}))}
|
||||
></Dropdown>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -7,6 +7,8 @@ import { CheckBox } from '../commons/Checkbox';
|
||||
import { Typography } from '../commons/Typography';
|
||||
import { BodyAssignment } from '../onboarding/BodyAssignment';
|
||||
import { useLocalization } from '@fluent/react';
|
||||
import { useState } from 'react';
|
||||
import { NeckWarningModal } from '../onboarding/NeckWarningModal';
|
||||
|
||||
export function SingleTrackerBodyAssignmentMenu({
|
||||
isOpen = true,
|
||||
@@ -21,67 +23,104 @@ export function SingleTrackerBodyAssignmentMenu({
|
||||
const { control, watch } = useForm<{ advanced: boolean }>({
|
||||
defaultValues: { advanced: false },
|
||||
});
|
||||
const [bodyPart, setBodyPart] = useState<null | BodyPart>(null);
|
||||
const [neckVerified, setNeckVerified] = useState(true);
|
||||
const { advanced } = watch();
|
||||
|
||||
return (
|
||||
<ReactModal
|
||||
isOpen={isOpen}
|
||||
shouldCloseOnOverlayClick
|
||||
shouldCloseOnEsc
|
||||
onRequestClose={onClose}
|
||||
overlayClassName={classNames(
|
||||
'fixed top-0 right-0 left-0 bottom-0 flex flex-col items-center w-full h-full justify-center bg-black bg-opacity-90 z-20'
|
||||
)}
|
||||
className={classNames(
|
||||
'focus:ring-transparent focus:ring-offset-transparent focus:outline-transparent outline-none mt-20 z-10'
|
||||
)}
|
||||
>
|
||||
<div className="flex w-full h-full flex-col gap-10 px-3">
|
||||
<div className="flex flex-col w-full h-full justify-center items-center">
|
||||
<div className="flex gap-8">
|
||||
<div className="flex flex-col max-w-sm gap-3">
|
||||
<Typography variant="main-title" bold>
|
||||
{l10n.getString('body_assignment_menu')}
|
||||
</Typography>
|
||||
<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"
|
||||
to="/onboarding/trackers-assign"
|
||||
state={{ alonePage: true }}
|
||||
>
|
||||
{l10n.getString('body_assignment_menu-manage_trackers')}
|
||||
</Button>
|
||||
<>
|
||||
<ReactModal
|
||||
isOpen={isOpen && neckVerified}
|
||||
shouldCloseOnOverlayClick
|
||||
shouldCloseOnEsc
|
||||
onRequestClose={onClose}
|
||||
overlayClassName={classNames(
|
||||
'fixed top-0 right-0 left-0 bottom-0 flex flex-col items-center w-full h-full justify-center bg-black bg-opacity-90 z-20'
|
||||
)}
|
||||
className={classNames(
|
||||
'focus:ring-transparent focus:ring-offset-transparent focus:outline-transparent outline-none mt-20 z-10'
|
||||
)}
|
||||
>
|
||||
<div className="flex w-full h-full flex-col gap-10 px-3">
|
||||
<div className="flex flex-col w-full h-full justify-center items-center">
|
||||
<div className="flex gap-8">
|
||||
<div className="flex flex-col max-w-sm gap-3">
|
||||
<Typography variant="main-title" bold>
|
||||
{l10n.getString('body_assignment_menu')}
|
||||
</Typography>
|
||||
<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"
|
||||
to="/onboarding/trackers-assign"
|
||||
state={{ alonePage: true }}
|
||||
>
|
||||
{l10n.getString('body_assignment_menu-manage_trackers')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col flex-grow gap-3 rounded-xl fill-background-50">
|
||||
<BodyAssignment
|
||||
onlyAssigned={false}
|
||||
advanced={advanced}
|
||||
onRoleSelected={onRoleSelected}
|
||||
></BodyAssignment>
|
||||
<div className="flex justify-center">
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={() => onRoleSelected(BodyPart.NONE)}
|
||||
>
|
||||
{l10n.getString('body_assignment_menu-unassign_tracker')}
|
||||
</Button>
|
||||
<div className="flex flex-col flex-grow gap-3 rounded-xl fill-background-50">
|
||||
<BodyAssignment
|
||||
onlyAssigned={false}
|
||||
advanced={advanced}
|
||||
onRoleSelected={(b) => {
|
||||
setBodyPart(b);
|
||||
setNeckVerified(false);
|
||||
}}
|
||||
></BodyAssignment>
|
||||
<div className="flex justify-center">
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={() => onRoleSelected(BodyPart.NONE)}
|
||||
>
|
||||
{l10n.getString('body_assignment_menu-unassign_tracker')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ReactModal>
|
||||
</ReactModal>
|
||||
{/**
|
||||
* This is a nightmare so, we pass the overlay class to respect styling of the
|
||||
* other modal.
|
||||
* `onClose`: we want to update the `bodyPart` to null because we need to know if the
|
||||
* `bodyPart` is no longer being chosen in the `NeckWarningModal`, if we don't do this
|
||||
* then the state of `bodyPart` (and use `BodyPart.NONE` instead), this will later
|
||||
* propagate to `setShown` and unassign the tracker.
|
||||
* `setShown`: is more simple than what I just explained above, we only want to know
|
||||
* when the `NeckWarningModal` wants to set to true `neckVerified`. We don't want it to
|
||||
* reset to false in any circumstance because then the main modal won't show because we
|
||||
* check for `neckVerified`.
|
||||
*/}
|
||||
<NeckWarningModal
|
||||
isOpen={isOpen}
|
||||
hasShown={neckVerified}
|
||||
bodyPart={bodyPart}
|
||||
overlayClassName={classNames(
|
||||
'fixed top-0 right-0 left-0 bottom-0 flex flex-col items-center w-full h-full justify-center bg-black bg-opacity-90 z-20'
|
||||
)}
|
||||
onClose={() => {
|
||||
setBodyPart(null);
|
||||
setNeckVerified(true);
|
||||
}}
|
||||
setShown={(bool) => {
|
||||
if (bool && bodyPart !== null) {
|
||||
onRoleSelected(bodyPart);
|
||||
setNeckVerified(true);
|
||||
}
|
||||
}}
|
||||
></NeckWarningModal>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -352,7 +352,7 @@ export function TrackersTable({
|
||||
tracker.position && (
|
||||
<Typography color={fontColor}>
|
||||
<span className="whitespace-nowrap">
|
||||
{formatVector3(tracker.position, 1)}
|
||||
{formatVector3(tracker.position, 2)}
|
||||
</span>
|
||||
</Typography>
|
||||
),
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { BodyPart } from 'solarxr-protocol';
|
||||
|
||||
export const bodypartToString = (id: BodyPart) =>
|
||||
BodyPart[id].replace(/_/g, ' ');
|
||||
export const bodypartToString = (id: BodyPart) => BodyPart[id].replace(/_/g, ' ');
|
||||
|
||||
type Vector3 = { x: number; y: number; z: number };
|
||||
export const formatVector3 = ({ x, y, z }: Vector3, precision = 0) =>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
body {
|
||||
font-variant-numeric: tabular-nums;
|
||||
font-family: "Twemoji Country Flags", 'poppins', sans-serif;
|
||||
font-family: 'poppins', sans-serif, emoji, "Twemoji Country Flags";
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
user-select: none;
|
||||
|
||||
@@ -2,18 +2,12 @@ import react from '@vitejs/plugin-react';
|
||||
import { defineConfig, PluginOption } from 'vite';
|
||||
import { execSync } from 'child_process';
|
||||
|
||||
const commitHash = execSync('git rev-parse --verify --short HEAD')
|
||||
.toString()
|
||||
.trim();
|
||||
const versionTag = execSync('git --no-pager tag --points-at HEAD')
|
||||
.toString()
|
||||
.trim();
|
||||
const commitHash = execSync('git rev-parse --verify --short HEAD').toString().trim();
|
||||
const versionTag = execSync('git --no-pager tag --points-at HEAD').toString().trim();
|
||||
// If not empty then it's not clean
|
||||
const gitClean = execSync('git status --porcelain').toString() ? false : true;
|
||||
|
||||
console.log(
|
||||
`version is ${versionTag || commitHash}${gitClean ? '' : '-dirty'}`
|
||||
);
|
||||
console.log(`version is ${versionTag || commitHash}${gitClean ? '' : '-dirty'}`);
|
||||
|
||||
// Detect fluent file changes
|
||||
export function i18nHotReload(): PluginOption {
|
||||
|
||||
5834
package-lock.json
generated
5834
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user