Improve GUI consistency/UX (#313)

This commit is contained in:
0forks
2022-11-27 18:51:54 +03:00
committed by GitHub
parent f7d34480de
commit c3e2987e82
12 changed files with 76 additions and 67 deletions

View File

@@ -34,8 +34,12 @@ import { ManualProportionsPage } from './components/onboarding/pages/body-propor
import { TrackerSettingsPage } from './components/tracker/TrackerSettings';
import { DonePage } from './components/onboarding/pages/Done';
import { OSCSettings } from './components/settings/pages/OSCSettings';
import { useConfig } from './hooks/config';
function Layout() {
const { loading } = useConfig();
if (loading) return (<></>);
return (
<>
<Routes>
@@ -154,7 +158,7 @@ function App() {
<>
<TopBar></TopBar>
<div className="flex w-full h-full justify-center items-center p-2">
{websocketAPI.isFistConnection
{websocketAPI.isFirstConnection
? 'Connecting to the server'
: 'Connection lost to the server. Trying to reconnect...'}
</div>

View File

@@ -8,6 +8,8 @@ export function CheckBox({
control,
outlined,
name,
// input props
disabled,
...props
}: {
label: string | ReactChild;
@@ -15,7 +17,7 @@ export function CheckBox({
name: string;
variant?: 'checkbox' | 'toggle';
outlined?: boolean;
}) {
} & React.HTMLProps<HTMLInputElement>) {
const classes = useMemo(() => {
const vriantsMap = {
checkbox: {
@@ -51,7 +53,10 @@ export function CheckBox({
<label
className={classNames(
'w-full py-3 flex gap-2 items-center text-standard-bold',
{ 'px-3': outlined }
{
'px-3': outlined,
'cursor-pointer': !disabled,
}
)}
>
<input

View File

@@ -8,13 +8,16 @@ export function Radio({
label,
value,
desciption,
// input props
disabled,
...props
}: {
control: Control<any>;
name: string;
label: string;
value: string | number;
desciption?: string;
}) {
} & React.HTMLProps<HTMLInputElement>) {
return (
<Controller
control={control}
@@ -26,6 +29,7 @@ export function Radio({
{
'border-accent-background-30': value == checked,
'border-transparent': value != checked,
'cursor-pointer': !disabled,
}
)}
>
@@ -37,6 +41,7 @@ export function Radio({
onChange={onChange}
value={value}
checked={value == checked}
{...props}
></input>
<div className="flex flex-col gap-2">
<Typography bold>{label}</Typography>

View File

@@ -9,17 +9,9 @@ import { TrackersTable } from '../tracker/TrackersTable';
export function Home() {
const { config } = useConfig();
const { useAssignedTrackers, useUnassignedTrackers } = useTrackers();
const { trackers } = useTrackers();
const navigate = useNavigate();
const assignedTrackers = useAssignedTrackers();
const unasignedTrackers = useUnassignedTrackers();
const trackers = useMemo(
() => [...assignedTrackers, ...unasignedTrackers],
[assignedTrackers, unasignedTrackers]
);
const sendToSettings = (tracker: TrackerDataT) => {
navigate(
`/tracker/${tracker.trackerId?.trackerNum}/${tracker.trackerId?.deviceId?.id}`

View File

@@ -1,4 +1,4 @@
import { useRef, useState } from 'react';
import { useState } from 'react';
import { ResetRequestT, ResetType, RpcMessage } from 'solarxr-protocol';
import { useWebsocketAPI } from '../../hooks/websocket-api';
import { BigButton } from '../commons/BigButton';
@@ -9,9 +9,8 @@ import {
} from '../commons/icon/ResetIcon';
export function ResetButton({ type }: { type: ResetType }) {
const timerid = useRef<NodeJS.Timer | null>(null);
const [reseting, setReseting] = useState(false);
const [timer, setTimer] = useState(0);
const [resetting, setResetting] = useState(false);
const [timer, setDisplayTimer] = useState(0);
const { sendRPCPacket } = useWebsocketAPI();
const getText = () => {
@@ -34,46 +33,33 @@ export function ResetButton({ type }: { type: ResetType }) {
return <ResetIcon width={20} />;
};
const reset = () => {
const TIMER_DURATION = 3;
const resetStart = () => {
setResetting(true);
setDisplayTimer(TIMER_DURATION);
if (type !== ResetType.Quick) {
for (let i = 1; i < TIMER_DURATION; i++) {
setTimeout(() => setDisplayTimer(TIMER_DURATION - i), i * 1000);
}
setTimeout(resetEnd, TIMER_DURATION * 1000);
} else {
resetEnd();
}
};
const resetEnd = () => {
const req = new ResetRequestT();
req.resetType = type;
setReseting(true);
setTimer(0);
if (type !== ResetType.Quick) {
if (timerid.current) clearInterval(timerid.current);
timerid.current = setInterval(() => {
setTimer((timer) => {
const newTimer = timer + 1;
if (newTimer >= 3) {
// Stop the current interval
if (timerid.current) clearInterval(timerid.current);
// Only actually reset on exactly 0 so it doesn't repeatedly reset if bugged
if (newTimer === 3) sendRPCPacket(RpcMessage.ResetRequest, req);
else
console.warn(
`Reset timer is still running after 3 seconds (newTimer = ${newTimer})`
);
// Reset the state
// Don't reset the timer in-case the interval keeps running
setReseting(false);
}
return newTimer;
});
}, 1000);
} else {
sendRPCPacket(RpcMessage.ResetRequest, req);
setReseting(false);
}
sendRPCPacket(RpcMessage.ResetRequest, req);
setResetting(false);
};
return (
<BigButton
text={!reseting || timer >= 3 ? getText() : `${3 - timer}`}
text={!resetting ? getText() : String(timer)}
icon={getIcon()}
onClick={reset}
disabled={reseting}
onClick={resetStart}
disabled={resetting}
></BigButton>
);
}

View File

@@ -356,7 +356,7 @@ export function GeneralSettings() {
<>
<Typography variant="main-title">Interface</Typography>
<Typography bold>Developer Mode</Typography>
<div className="flex flex-col">
<div className="flex flex-col pt-2">
<Typography color="secondary">
This mode can be useful if you need in-depth data or to interact
</Typography>

View File

@@ -132,7 +132,7 @@ export function TrackerCard({
onClick={onClick}
className={classNames(
'rounded-lg',
interactable && 'hover:bg-background-50',
interactable && 'hover:bg-background-50 cursor-pointer',
outlined && 'outline outline-2 outline-accent-background-40',
bg
)}

View File

@@ -20,6 +20,7 @@ import { Typography } from '../commons/Typography';
import { MountingSelectionMenu } from '../onboarding/pages/mounting/MountingSelectionMenu';
import { SingleTrackerBodyAssignmentMenu } from './SingleTrackerBodyAssignmentMenu';
import { TrackerCard } from './TrackerCard';
import { bodypartToString } from '../utils/formatting';
const rotationToQuatMap = {
FRONT: 180,
@@ -29,10 +30,10 @@ const rotationToQuatMap = {
};
const rotationsLabels = {
[rotationToQuatMap.BACK]: 'Back',
[rotationToQuatMap.FRONT]: 'Front',
[rotationToQuatMap.LEFT]: 'Left',
[rotationToQuatMap.RIGHT]: 'Right',
[rotationToQuatMap.BACK]: 'BACK',
[rotationToQuatMap.FRONT]: 'FRONT',
[rotationToQuatMap.LEFT]: 'LEFT',
[rotationToQuatMap.RIGHT]: 'RIGHT',
};
export function TrackerSettingsPage() {
@@ -203,7 +204,7 @@ export function TrackerSettingsPage() {
<div className="flex gap-3 items-center">
<FootIcon></FootIcon>
<Typography>
{BodyPart[tracker?.tracker.info?.bodyPart || BodyPart.NONE]}
{bodypartToString(tracker?.tracker.info?.bodyPart || BodyPart.NONE)}
</Typography>
</div>
<div className="flex">

View File

@@ -0,0 +1,4 @@
import { BodyPart } from "solarxr-protocol";
export const bodypartToString = (id: BodyPart) =>
BodyPart[id].replace(/_/g, ' ');

View File

@@ -3,9 +3,10 @@ import {
readTextFile,
writeFile,
createDir,
renameFile,
} from '@tauri-apps/api/fs';
import { createContext, useContext, useState } from 'react';
import { createContext, useContext, useRef, useState } from 'react';
export interface WindowConfig {
width: number;
@@ -30,6 +31,7 @@ export interface ConfigContext {
const initialConfig = { doneOnboarding: false };
export function useConfigProvider(): ConfigContext {
const debounceTimer = useRef<NodeJS.Timeout | null>(null);
const [currConfig, set] = useState<Config | null>(null);
const [loading, setLoading] = useState(false);
@@ -42,11 +44,20 @@ export function useConfigProvider(): ConfigContext {
: null;
set(newConfig as Config);
await createDir('', { dir: BaseDirectory.App, recursive: true });
await writeFile(
{ contents: JSON.stringify(newConfig), path: 'config.json' },
{ dir: BaseDirectory.App }
);
if (!debounceTimer.current) {
debounceTimer.current = setTimeout(async () => {
await createDir('', { dir: BaseDirectory.App, recursive: true });
await writeFile(
{ contents: JSON.stringify(newConfig), path: 'config.json.tmp' },
{ dir: BaseDirectory.App }
);
await renameFile(
'config.json.tmp', 'config.json',
{ dir: BaseDirectory.App }
);
debounceTimer.current = null;
}, 10);
}
};
return {

View File

@@ -1,5 +1,6 @@
import { useEffect, useMemo, useRef, useState } from 'react';
import { BodyPart, TrackerDataT, TrackerStatus } from 'solarxr-protocol';
import { bodypartToString } from '../components/utils/formatting';
import { QuaternionFromQuatT } from '../maths/quaternion';
import { useAppContext } from './app';
@@ -40,7 +41,7 @@ export function useTracker(tracker: TrackerDataT) {
useName: () =>
useMemo(() => {
if (tracker.info?.customName) return tracker.info?.customName;
if (tracker.info?.bodyPart) return BodyPart[tracker.info?.bodyPart];
if (tracker.info?.bodyPart) return bodypartToString(tracker.info?.bodyPart);
return tracker.info?.displayName || 'NONE';
}, [tracker.info]),
useRotation: () =>

View File

@@ -16,7 +16,7 @@ import { useInterval } from './timeout';
export interface WebSocketApi {
isConnected: boolean;
isFistConnection: boolean;
isFirstConnection: boolean;
useRPCPacket: <T>(type: RpcMessage, callback: (packet: T) => void) => void;
useDataFeedPacket: <T>(
type: DataFeedMessage,
@@ -46,7 +46,7 @@ export function useProvideWebsocketApi(): WebSocketApi {
const rpclistenerRef = useRef<EventTarget>(new EventTarget());
const pubsublistenerRef = useRef<EventTarget>(new EventTarget());
const datafeedlistenerRef = useRef<EventTarget>(new EventTarget());
const [isFistConnection, setFirstConnection] = useState(true);
const [isFirstConnection, setFirstConnection] = useState(true);
const [isConnected, setConnected] = useState(false);
useInterval(() => {
@@ -184,7 +184,7 @@ export function useProvideWebsocketApi(): WebSocketApi {
return {
isConnected,
isFistConnection,
isFirstConnection,
useDataFeedPacket: <T>(
type: DataFeedMessage,
callback: (packet: T) => void