This commit is contained in:
loucass003
2025-02-04 22:48:58 +01:00
parent 1c9bca0f43
commit 07f4955e1f
12 changed files with 191 additions and 103 deletions

View File

@@ -80,7 +80,7 @@ function Layout() {
<Route
path="/"
element={
<MainLayout isMobile={isMobile}>
<MainLayout isMobile={isMobile} full>
<Home />
</MainLayout>
}
@@ -88,7 +88,7 @@ function Layout() {
<Route
path="/firmware-update"
element={
<MainLayout isMobile={isMobile} widgets={false}>
<MainLayout isMobile={isMobile}>
<FirmwareUpdate />
</MainLayout>
}

View File

@@ -5,11 +5,13 @@
's c' calc(100% - var(--topbar-h))
/ var(--navbar-w) calc(100% - var(--navbar-w));
&:has(.widgets) {
&.full {
grid-template:
't t t' var(--topbar-h)
's c w' calc(100% - var(--topbar-h))
/ var(--navbar-w) calc(100% - var(--navbar-w) - var(--widget-w)) var(--widget-w);
's r r' var(--toolbar-h)
's c l' calc(70% - var(--topbar-h) - var(--toolbar-h))
's c p' calc(30%)
/ var(--navbar-w) calc(75% - var(--navbar-w)) calc(25%);
}
@screen mobile {

View File

@@ -9,19 +9,21 @@ import {
import { Navbar } from './Navbar';
import { TopBar } from './TopBar';
import { useWebsocketAPI } from '@/hooks/websocket-api';
import { WidgetsComponent } from './WidgetsComponent';
import './MainLayout.scss';
import { Toolbar } from './Toolbar';
import { WidgetsComponent } from './WidgetsComponent';
import { SkeletonVisualizerWidget } from './widgets/SkeletonVisualizerWidget';
export function MainLayout({
children,
background = true,
widgets = true,
full = false,
isMobile = undefined,
}: {
children: ReactNode;
background?: boolean;
isMobile?: boolean;
widgets?: boolean;
full?: boolean;
}) {
const { sendRPCPacket } = useWebsocketAPI();
const [ProportionsLastPageOpen, setProportionsLastPageOpen] = useState(true);
@@ -58,33 +60,50 @@ export function MainLayout({
});
return (
<div className="">
<div className="main-layout w-full h-screen">
<div style={{ gridArea: 't' }}>
<TopBar></TopBar>
</div>
<div style={{ gridArea: 's' }} className="overflow-y-auto">
<Navbar></Navbar>
</div>
<div
style={{ gridArea: 'c' }}
className={classNames(
'overflow-y-auto mr-2 my-2 mobile:m-0',
'flex flex-col rounded-xl',
background && 'bg-background-70'
)}
>
{children}
</div>
{!isMobile && widgets && (
<div
style={{ gridArea: 'w' }}
className="overflow-y-auto mr-2 my-2 rounded-xl bg-background-70 flex flex-col gap-2 p-2 widgets"
>
<WidgetsComponent></WidgetsComponent>
</div>
)}
<div
className={classNames(
'main-layout w-full h-screen',
!isMobile && full && 'full'
)}
>
<div style={{ gridArea: 't' }}>
<TopBar></TopBar>
</div>
<div style={{ gridArea: 's' }} className="overflow-y-auto">
<Navbar></Navbar>
</div>
<div
style={{ gridArea: 'c' }}
className={classNames(
'overflow-y-auto mr-2 my-2 mobile:m-0',
'flex flex-col rounded-md',
background && 'bg-background-70'
)}
>
{children}
</div>
{!isMobile && full && (
<>
<div style={{ gridArea: 'r' }}>
<Toolbar></Toolbar>
</div>
<div
style={{ gridArea: 'p' }}
className="overflow-y-auto mr-2 mb-2 rounded-md bg-background-70 flex flex-col"
>
{/* <WidgetsComponent></WidgetsComponent> */}
<SkeletonVisualizerWidget height={'100%'} maxHeight={'auto'} />
</div>
<div
style={{ gridArea: 'l' }}
className="overflow-y-auto mr-2 my-2 rounded-md bg-background-70 flex flex-col gap-2 p-2"
>
{/* <WidgetsComponent></WidgetsComponent> */}
{/* <SkeletonVisualizerWidget height={'100%'} maxHeight={'auto'} /> */}
</div>
</>
)}
</div>
);
}

View File

@@ -2,14 +2,15 @@ import { useLocalization } from '@fluent/react';
import classnames from 'classnames';
import { ReactNode } from 'react';
import { NavLink, useMatch } from 'react-router-dom';
import { CubeIcon } from './commons/icon/CubeIcon';
import { GearIcon } from './commons/icon/GearIcon';
import { HumanIcon } from './commons/icon/HumanIcon';
import { RulerIcon } from './commons/icon/RulerIcon';
import { SparkleIcon } from './commons/icon/SparkleIcon';
import { WrenchIcon } from './commons/icon/WrenchIcons';
import { useBreakpoint } from '@/hooks/breakpoint';
import { useConfig } from '@/hooks/config';
import { Tooltip } from './commons/Tooltip';
import { HomeIcon } from './commons/icon/HomeIcon';
import { SkiIcon } from './commons/icon/SkiIcon';
export function NavButton({
to,
@@ -24,43 +25,43 @@ export function NavButton({
state?: any;
icon: ReactNode;
}) {
const { isMobile } = useBreakpoint('mobile');
const doesMatch = useMatch({
path: match || to,
});
return (
<NavLink
to={to}
state={state}
className={classnames(
'flex flex-col justify-center xs:gap-4 mobile:gap-2',
'xs:w-[85px] mobile:w-[80px] mobile:h-[80px]',
'xs:py-3 mobile:py-4 rounded-md mobile:rounded-b-none group select-text',
{
'bg-accent-background-50 fill-accent-background-20': doesMatch,
'hover:bg-background-70': !doesMatch,
}
)}
<Tooltip
disabled={isMobile}
preferedDirection={'right'}
content={children}
variant="floating"
>
<div className="flex justify-around">
<div
className={classnames('scale-150', {
'fill-accent-lighter': doesMatch,
'fill-background-50': !doesMatch,
})}
>
{icon}
</div>
</div>
<div
className={classnames('text-center', {
'text-accent-background-10': doesMatch,
'text-background-10': !doesMatch,
})}
<NavLink
to={to}
state={state}
className={classnames(
'flex flex-col justify-center xs:gap-4 mobile:gap-2',
'xs:w-[50px] xs:h-[50px] mobile:w-[50px] mobile:h-[50px]',
'xs:py-3 mobile:py-4 rounded-md mobile:rounded-b-none group select-text',
{
'bg-accent-background-50 fill-accent-background-20': doesMatch,
'hover:bg-background-70': !doesMatch,
}
)}
>
{children}
</div>
</NavLink>
<div className="flex justify-around">
<div
className={classnames('scale-150', {
'fill-accent-lighter': doesMatch,
'fill-background-50': !doesMatch,
})}
>
{icon}
</div>
</div>
</NavLink>
</Tooltip>
);
}
@@ -70,7 +71,7 @@ export function MainLinks() {
return (
<>
<NavButton to="/" icon={<CubeIcon></CubeIcon>}>
<NavButton to="/" icon={<HomeIcon></HomeIcon>}>
{l10n.getString('navbar-home')}
</NavButton>
<NavButton
@@ -84,7 +85,7 @@ export function MainLinks() {
to="/onboarding/mounting/choose"
match="/onboarding/mounting/*"
state={{ alonePage: true }}
icon={<WrenchIcon></WrenchIcon>}
icon={<SkiIcon></SkiIcon>}
>
{l10n.getString('navbar-mounting')}
</NavButton>

View File

@@ -0,0 +1,13 @@
import { ResetType } from 'solarxr-protocol';
import { ResetButton } from './home/ResetButton';
export function Toolbar() {
return (
<div className="flex p-2 gap-2 bg-background-70 rounded-md mr-2 my-2">
<ResetButton type={ResetType.Yaw} variant="small"></ResetButton>
<ResetButton type={ResetType.Full} variant="small"></ResetButton>
<ResetButton type={ResetType.Mounting} variant="small"></ResetButton>
{/* <ClearMountingButton></ClearMountingButton> */}
</div>
);
}

View File

@@ -13,11 +13,15 @@ import { createPortal } from 'react-dom';
import { Typography } from './Typography';
import { CloseIcon } from './icon/CloseIcon';
type Direction = 'top' | 'left' | 'right' | 'bottom';
interface TooltipProps {
content: ReactNode;
children: ReactElement;
preferedDirection: 'top' | 'left' | 'right' | 'bottom';
preferedDirection: Direction;
blockedDirections?: Direction[];
mode?: 'corner' | 'center';
variant?: 'auto' | 'drawer' | 'floating';
disabled?: boolean;
}
interface TooltipPos {
@@ -76,6 +80,7 @@ const clamp = (v: number, min: number, max: number) =>
const getFloatingTooltipPosition = (
preferedDirection: TooltipProps['preferedDirection'],
blockedDirections: Direction[],
mode: TooltipProps['mode'],
childrenRect: DOMRect,
tooltipRect: DOMRect
@@ -134,9 +139,10 @@ const getFloatingTooltipPosition = (
const pos = getPosition(preferedDirection);
if (isNotInside({ ...pos, height: tooltipRect.height }, windowRect)) {
const [firstPos] = ['left', 'top', 'right', 'bottom']
.filter((dir) => !blockedDirections.includes(dir as Direction))
.map((dir) => ({
dir,
area: getPosition(dir as TooltipProps['preferedDirection']),
area: getPosition(dir as Direction),
}))
.toSorted(
(a, b) =>
@@ -225,12 +231,13 @@ const getFloatingTooltipPosition = (
export function FloatingTooltip({
childRef,
preferedDirection,
blockedDirections = [],
mode,
children,
}: {
childRef: MutableRefObject<HTMLDivElement | null>;
children: ReactNode;
} & Pick<TooltipProps, 'mode' | 'preferedDirection'>) {
} & Pick<TooltipProps, 'mode' | 'preferedDirection' | 'blockedDirections'>) {
const tooltipRef = useRef<HTMLDivElement | null>(null);
const [tooltipStyle, setTooltipStyle] = useState<TooltipPos | undefined>();
@@ -244,6 +251,7 @@ export function FloatingTooltip({
setTooltipStyle(
getFloatingTooltipPosition(
preferedDirection,
blockedDirections,
mode,
childrenRect,
tooltipRect
@@ -372,15 +380,15 @@ export function DrawerTooltip({
if (childRef.current && childRef.current.children[0]) {
const elem = childRef.current.children[0] as HTMLElement;
elem.addEventListener('mousedown', touchStart); // for debug on desktop
elem.addEventListener('mouseup', touchEnd); // for debug on desktop
elem.addEventListener('click', touchEnd);
// elem.addEventListener('mousedown', touchStart); // for debug on desktop
// elem.addEventListener('mouseup', touchEnd); // for debug on desktop
// elem.addEventListener('click', touchEnd);
elem.addEventListener('touchstart', touchStart);
elem.addEventListener('touchend', touchEnd);
return () => {
elem.removeEventListener('mousedown', touchStart); // for debug on desktop
elem.removeEventListener('mouseup', touchEnd); // for debug on desktop
// elem.removeEventListener('mousedown', touchStart); // for debug on desktop
// elem.removeEventListener('mouseup', touchEnd); // for debug on desktop
elem.removeEventListener('touchstart', touchStart);
elem.removeEventListener('touchend', touchEnd);
clearTimeout(touchTimeout.current);
@@ -435,30 +443,51 @@ export function Tooltip({
content,
children,
preferedDirection,
blockedDirections = [],
mode = 'center',
variant = 'auto',
disabled = false,
}: TooltipProps) {
const childRef = useRef<HTMLDivElement | null>(null);
const { isMobile } = useBreakpoint('mobile');
let portal = null;
if (variant === 'auto') {
portal = isMobile ? (
<DrawerTooltip childRef={childRef}>{content}</DrawerTooltip>
) : (
<FloatingTooltip
preferedDirection={preferedDirection}
blockedDirections={blockedDirections}
mode={mode}
childRef={childRef}
>
{content}
</FloatingTooltip>
);
}
if (variant === 'drawer')
portal = <DrawerTooltip childRef={childRef}>{content}</DrawerTooltip>;
if (variant === 'floating')
portal = (
<FloatingTooltip
blockedDirections={blockedDirections}
preferedDirection={preferedDirection}
mode={mode}
childRef={childRef}
>
{content}
</FloatingTooltip>
);
return (
<>
<div className="contents" ref={childRef}>
{children}
</div>
{createPortal(
isMobile ? (
<DrawerTooltip childRef={childRef}>{content}</DrawerTooltip>
) : (
<FloatingTooltip
preferedDirection={preferedDirection}
mode={mode}
childRef={childRef}
>
{content}
</FloatingTooltip>
),
document.body
)}
{!disabled && createPortal(portal, document.body)}
</>
);
}

View File

@@ -0,0 +1,12 @@
export function HomeIcon() {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
height="24px"
viewBox="0 -960 960 960"
width="24px"
>
<path d="M160-120v-480l320-240 320 240v480H560v-280H400v280H160Z" />
</svg>
);
}

View File

@@ -0,0 +1,12 @@
export function SkiIcon() {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
height="24px"
viewBox="0 -960 960 960"
width="24px"
>
<path d="M740-40q-26 0-50.5-4T642-56L80-261l20-57 276 101 69-178-143-149q-27-28-21.5-66.5T320-669l139-80q17-10 34.5-11.5T528-755q17 6 29.5 19t18.5 31l13 43q13 43 42.5 76t70.5 50l21-64 57 18-45 138q-74-12-131-58t-84-114l-101 58 121 138-89 230 124 45 84-257q14 5 28 9t29 7l-85 262 31 11q18 6 37.5 9.5T740-100q26 0 49.5-5t45.5-15l45 45q-32 17-67 26t-73 9Zm-80-660q-33 0-56.5-23.5T580-780q0-33 23.5-56.5T660-860q33 0 56.5 23.5T740-780q0 33-23.5 56.5T660-700Z" />
</svg>
);
}

View File

@@ -34,8 +34,6 @@ export function OnboardingLayout({ children }: { children: ReactNode }) {
</div>
</div>
) : (
<MainLayout widgets={false} isMobile={isMobile}>
{children}
</MainLayout>
<MainLayout isMobile={isMobile}>{children}</MainLayout>
);
}

View File

@@ -32,7 +32,7 @@ declare module '@react-three/fiber' {
}
}
const GROUND_COLOR = '#4444aa';
const GROUND_COLOR = '#2c2c6b';
const FRUSTUM_SIZE = 10;
const FACTOR = 2;
// Not currently used but nice to have
@@ -196,7 +196,7 @@ export function SkeletonVisualizerWidget({
if (!skeleton.current) return <></>;
return (
<div className="bg-background-60 flex flex-col p-3 rounded-lg gap-2">
<div className="bg-background-70 flex flex-col rounded-lg gap-2 h-full">
<ErrorBoundary
fallback={
<Typography color="primary" textAlign="text-center">
@@ -205,8 +205,8 @@ export function SkeletonVisualizerWidget({
}
>
<Canvas
className={classNames('container mx-auto')}
style={{ height, background: 'transparent', maxHeight }}
className={classNames('container mx-auto h-full')}
style={{ background: 'transparent' }}
>
<gridHelper args={[10, 50, GROUND_COLOR, GROUND_COLOR]} />
<group position={[0, heightOffset, 0]} quaternion={yawReset}>

View File

@@ -87,13 +87,15 @@ body {
// overflow: hidden; -- NEVER EVER BRING THIS BACK <3
background: theme('colors.background.20');
--navbar-w: 101px;
--widget-w: 274px;
--navbar-w: 66px;
--topbar-h: 38px;
--toolbar-h: 65px;
--preview-w: 400px;
--flightlist-w: 272px;
@screen mobile {
--topbar-h: 44px;
--navbar-h: 90px;
--navbar-h: 60px;
}
}

View File

@@ -192,7 +192,7 @@ export class BoneKind extends Bone {
case BodyPart.NECK:
return new Color('silver');
case BodyPart.UPPER_CHEST:
return new Color('blue');
return new Color('chartreuse');
case BodyPart.CHEST:
return new Color('purple');
case BodyPart.WAIST:
@@ -201,13 +201,13 @@ export class BoneKind extends Bone {
return new Color('orange');
case BodyPart.LEFT_UPPER_LEG:
case BodyPart.RIGHT_UPPER_LEG:
return new Color('blue');
return new Color('chartreuse');
case BodyPart.LEFT_LOWER_LEG:
case BodyPart.RIGHT_LOWER_LEG:
return new Color('teal');
case BodyPart.LEFT_FOOT:
case BodyPart.RIGHT_FOOT:
return new Color('#00ffcc');
return new Color('gold');
case BodyPart.LEFT_LOWER_ARM:
case BodyPart.RIGHT_LOWER_ARM:
return new Color('red');