mirror of
https://github.com/SlimeVR/SlimeVR-Server.git
synced 2026-04-06 02:01:58 +02:00
Compare commits
8 Commits
hannah/ste
...
support-in
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ca358b6394 | ||
|
|
66ec595c09 | ||
|
|
be8c000b36 | ||
|
|
b2f74640f7 | ||
|
|
8b67ee1a36 | ||
|
|
6293cf4c83 | ||
|
|
8a05572715 | ||
|
|
8232a9342a |
@@ -26,6 +26,7 @@
|
||||
"@twemoji/svg": "^15.0.0",
|
||||
"browser-fs-access": "^0.35.0",
|
||||
"classnames": "^2.5.1",
|
||||
"convert": "^5.10.0",
|
||||
"flatbuffers": "22.10.26",
|
||||
"intl-pluralrules": "^2.0.1",
|
||||
"ip-num": "^1.5.1",
|
||||
|
||||
@@ -676,6 +676,11 @@ settings-interface-appearance-font_size-description = This affects the font size
|
||||
settings-interface-appearance-decorations = Use the system native decorations
|
||||
settings-interface-appearance-decorations-description = This will not render the top bar of the interface and will use the operating system's instead.
|
||||
settings-interface-appearance-decorations-label = Use native decorations
|
||||
settings-interface-appearance-unit_system = Select unit system
|
||||
settings-interface-appearance-unit_system-description = Change if you want to use the metric system or the imperial system for body measurements.
|
||||
settings-interface-appearance-unit_system-placeholder = Select the unit system to use
|
||||
settings-interface-appearance-unit_system-metric = Metric units
|
||||
settings-interface-appearance-unit_system-imperial = Imperial units
|
||||
|
||||
## Notification settings
|
||||
settings-interface-notifications = Notifications
|
||||
|
||||
@@ -72,7 +72,7 @@ export function DropdownItems({
|
||||
<div
|
||||
ref={ref}
|
||||
className={classNames(
|
||||
'z-[1000] fixed rounded shadow',
|
||||
'z-[1000] fixed rounded shadow w-fit',
|
||||
'overflow-y-auto dropdown-scroll overflow-x-hidden text-background-10',
|
||||
variant == 'primary' && 'bg-background-60',
|
||||
variant == 'secondary' && 'bg-background-70',
|
||||
@@ -100,13 +100,13 @@ export function DropdownItems({
|
||||
style={item.fontName ? { fontFamily: item.fontName } : {}}
|
||||
className={classNames(
|
||||
'py-2 px-4 min-w-max cursor-pointer first-of-type:*:pointer-events-none',
|
||||
variant == 'primary' &&
|
||||
'checked-hover:bg-background-50 text-background-20 ' +
|
||||
variant === 'primary' &&
|
||||
'data-checked:bg-background-40 hover:bg-background-50 text-background-20 ' +
|
||||
'checked-hover:text-background-10',
|
||||
variant == 'secondary' &&
|
||||
'checked-hover:bg-background-60 text-background-20 ' +
|
||||
variant === 'secondary' &&
|
||||
'data-checked:bg-background-50 hover:bg-background-60 text-background-20 ' +
|
||||
'checked-hover:text-background-10',
|
||||
variant == 'tertiary' &&
|
||||
variant === 'tertiary' &&
|
||||
'bg-accent-background-30 checked-hover:bg-accent-background-20'
|
||||
)}
|
||||
onClick={() => {
|
||||
|
||||
75
gui/src/components/commons/HeightDisplay.tsx
Normal file
75
gui/src/components/commons/HeightDisplay.tsx
Normal file
@@ -0,0 +1,75 @@
|
||||
import { defaultConfig, UnitType, useConfig } from '@/hooks/config';
|
||||
import { useLocaleConfig } from '@/i18n/config';
|
||||
import convert from 'convert';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
export function HeightDisplay({
|
||||
height,
|
||||
/**
|
||||
* Unit type of only the input height
|
||||
*/
|
||||
heightUnit = 'm',
|
||||
unitDisplay = 'short',
|
||||
className,
|
||||
roundInches = false,
|
||||
}: {
|
||||
heightUnit?: 'm' | 'cm' | 'in';
|
||||
height: number;
|
||||
unitDisplay?: 'narrow' | 'short' | 'long';
|
||||
className?: string;
|
||||
roundInches?: boolean;
|
||||
}) {
|
||||
const { currentLocales } = useLocaleConfig();
|
||||
const { config } = useConfig();
|
||||
|
||||
const cmFormat = useMemo(
|
||||
() =>
|
||||
new Intl.NumberFormat(currentLocales, {
|
||||
style: 'unit',
|
||||
unit: 'centimeter',
|
||||
unitDisplay,
|
||||
maximumFractionDigits: 2,
|
||||
}),
|
||||
[currentLocales, unitDisplay]
|
||||
);
|
||||
|
||||
const [footFormat, inchesFormat] = useMemo(
|
||||
() => [
|
||||
new Intl.NumberFormat(currentLocales, {
|
||||
style: 'unit',
|
||||
unit: 'foot',
|
||||
unitDisplay: 'narrow',
|
||||
maximumFractionDigits: 0,
|
||||
roundingMode: 'trunc',
|
||||
}),
|
||||
new Intl.NumberFormat(currentLocales, {
|
||||
style: 'unit',
|
||||
unit: 'inch',
|
||||
unitDisplay: 'narrow',
|
||||
maximumFractionDigits: 1,
|
||||
roundingMode: 'trunc',
|
||||
}),
|
||||
],
|
||||
[currentLocales]
|
||||
);
|
||||
|
||||
const value = useMemo(() => {
|
||||
const unitSystem = config?.unitSystem ?? defaultConfig.unitSystem;
|
||||
|
||||
if (unitSystem === UnitType.Metric) {
|
||||
return cmFormat.format(convert(height, heightUnit).to('cm'));
|
||||
} else {
|
||||
let totalInches = convert(height, heightUnit).to('inches');
|
||||
if (roundInches) totalInches = Math.round(totalInches);
|
||||
|
||||
const feet = Math.trunc(totalInches / 12);
|
||||
const remainingInches = totalInches % 12;
|
||||
return (
|
||||
(feet ? footFormat.format(feet) : '') +
|
||||
inchesFormat.format(remainingInches)
|
||||
);
|
||||
}
|
||||
}, [config?.unitSystem, height, heightUnit]);
|
||||
|
||||
return <span className={className}>{value}</span>;
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useLocalization } from '@fluent/react';
|
||||
import { useEffect, useMemo, useContext } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useConfig } from '@/hooks/config';
|
||||
import { defaultConfig, useConfig } from '@/hooks/config';
|
||||
import { LangContext } from '@/i18n/config';
|
||||
import { langs } from '@/i18n/names';
|
||||
import { Dropdown, DropdownDirection } from './Dropdown';
|
||||
@@ -17,7 +17,7 @@ export function LangSelector({
|
||||
const { l10n } = useLocalization();
|
||||
const { config, setConfig } = useConfig();
|
||||
const { control, watch, handleSubmit } = useForm<{ lang: string }>({
|
||||
defaultValues: { lang: config?.lang || 'en' },
|
||||
defaultValues: { lang: config?.lang ?? defaultConfig.lang },
|
||||
});
|
||||
|
||||
const languagesItems = useMemo(
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Control, Controller } from 'react-hook-form';
|
||||
import { Button } from './Button';
|
||||
import { Typography } from './Typography';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { ReactNode, useMemo } from 'react';
|
||||
import { useLocaleConfig } from '@/i18n/config';
|
||||
|
||||
export function NumberSelector({
|
||||
@@ -14,34 +14,33 @@ export function NumberSelector({
|
||||
step,
|
||||
doubleStep,
|
||||
disabled = false,
|
||||
showButtonWithNumber = false,
|
||||
showButtonWithNumber,
|
||||
}: {
|
||||
label?: string;
|
||||
valueLabelFormat?: (value: number) => string;
|
||||
valueLabelFormat?: (value: number) => string | ReactNode;
|
||||
control: Control<any>;
|
||||
name: string;
|
||||
min: number;
|
||||
max: number;
|
||||
step: number | ((value: number, add: boolean) => number);
|
||||
doubleStep?: number;
|
||||
step: number | ((value: number, sign: number) => number);
|
||||
doubleStep?: number | ((value: number, sign: number) => number);
|
||||
disabled?: boolean;
|
||||
showButtonWithNumber?: boolean;
|
||||
showButtonWithNumber?: number;
|
||||
}) {
|
||||
const { currentLocales } = useLocaleConfig();
|
||||
|
||||
const stepFn =
|
||||
typeof step === 'function'
|
||||
? step
|
||||
: (value: number, add: boolean) =>
|
||||
+(add ? value + step : value - step).toFixed(2);
|
||||
: (value: number, sign: number) => +(value + step * sign).toFixed(2);
|
||||
|
||||
const doubleStepFn = useCallback(
|
||||
(value: number, add: boolean) =>
|
||||
doubleStep === undefined
|
||||
? 0
|
||||
: +(add ? value + doubleStep : value - doubleStep).toFixed(2),
|
||||
[doubleStep]
|
||||
);
|
||||
const doubleStepFn =
|
||||
typeof doubleStep === 'function'
|
||||
? doubleStep
|
||||
: (value: number, sign: number) =>
|
||||
doubleStep === undefined
|
||||
? 0
|
||||
: +(value + doubleStep * sign).toFixed(2);
|
||||
|
||||
const decimalFormat = useMemo(
|
||||
() =>
|
||||
@@ -66,19 +65,19 @@ export function NumberSelector({
|
||||
<Button
|
||||
variant="tertiary"
|
||||
rounded
|
||||
onClick={() => onChange(doubleStepFn(value, false))}
|
||||
disabled={doubleStepFn(value, false) < min || disabled}
|
||||
onClick={() => onChange(doubleStepFn(value, -1))}
|
||||
disabled={doubleStepFn(value, -1) < min || disabled}
|
||||
>
|
||||
{showButtonWithNumber
|
||||
? decimalFormat.format(-doubleStep)
|
||||
{showButtonWithNumber !== undefined
|
||||
? decimalFormat.format(-showButtonWithNumber)
|
||||
: '--'}
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
variant="tertiary"
|
||||
rounded
|
||||
onClick={() => onChange(stepFn(value, false))}
|
||||
disabled={stepFn(value, false) < min || disabled}
|
||||
onClick={() => onChange(stepFn(value, -1))}
|
||||
disabled={stepFn(value, -1) < min || disabled}
|
||||
>
|
||||
-
|
||||
</Button>
|
||||
@@ -90,8 +89,8 @@ export function NumberSelector({
|
||||
<Button
|
||||
variant="tertiary"
|
||||
rounded
|
||||
onClick={() => onChange(stepFn(value, true))}
|
||||
disabled={stepFn(value, true) > max || disabled}
|
||||
onClick={() => onChange(stepFn(value, 1))}
|
||||
disabled={stepFn(value, 1) > max || disabled}
|
||||
>
|
||||
+
|
||||
</Button>
|
||||
@@ -99,11 +98,11 @@ export function NumberSelector({
|
||||
<Button
|
||||
variant="tertiary"
|
||||
rounded
|
||||
onClick={() => onChange(doubleStepFn(value, true))}
|
||||
disabled={doubleStepFn(value, true) > max || disabled}
|
||||
onClick={() => onChange(doubleStepFn(value, 1))}
|
||||
disabled={doubleStepFn(value, 1) > max || disabled}
|
||||
>
|
||||
{showButtonWithNumber
|
||||
? decimalFormat.format(doubleStep)
|
||||
{showButtonWithNumber !== undefined
|
||||
? decimalFormat.format(showButtonWithNumber)
|
||||
: '++'}
|
||||
</Button>
|
||||
)}
|
||||
|
||||
54
gui/src/components/commons/UnitSelector.tsx
Normal file
54
gui/src/components/commons/UnitSelector.tsx
Normal file
@@ -0,0 +1,54 @@
|
||||
import { defaultConfig, UnitType, useConfig } from '@/hooks/config';
|
||||
import { Dropdown, DropdownDirection } from './Dropdown';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useEffect, useMemo } from 'react';
|
||||
import { useLocalization } from '@fluent/react';
|
||||
|
||||
export function UnitSelector({
|
||||
direction = 'up',
|
||||
alignment = 'right',
|
||||
}: {
|
||||
direction?: DropdownDirection;
|
||||
alignment?: 'right' | 'left';
|
||||
}) {
|
||||
const { l10n } = useLocalization();
|
||||
const { config, setConfig } = useConfig();
|
||||
const { control, watch, handleSubmit } = useForm<{ unitSystem: UnitType }>({
|
||||
defaultValues: {
|
||||
unitSystem: config?.unitSystem ?? defaultConfig.unitSystem,
|
||||
},
|
||||
});
|
||||
|
||||
const unitItems = useMemo(
|
||||
() =>
|
||||
Object.values(UnitType).map((type) => ({
|
||||
label: l10n.getString(
|
||||
`settings-interface-appearance-unit_system-${type}`
|
||||
),
|
||||
value: type,
|
||||
})),
|
||||
[l10n]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const subscription = watch(() => handleSubmit(onSubmit)());
|
||||
return () => subscription.unsubscribe();
|
||||
}, []);
|
||||
|
||||
const onSubmit = (value: { unitSystem: UnitType }) => {
|
||||
setConfig({ unitSystem: value.unitSystem });
|
||||
};
|
||||
|
||||
return (
|
||||
<Dropdown
|
||||
control={control}
|
||||
name="unitSystem"
|
||||
placeholder={l10n.getString(
|
||||
'settings-interface-appearance-unit_system-placeholder'
|
||||
)}
|
||||
items={unitItems}
|
||||
direction={direction}
|
||||
alignment={alignment}
|
||||
></Dropdown>
|
||||
);
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import { Button } from '@/components/commons/Button';
|
||||
import { SlimeVRIcon } from '@/components/commons/icon/SimevrIcon';
|
||||
import { LangSelector } from '@/components/commons/LangSelector';
|
||||
import { Typography } from '@/components/commons/Typography';
|
||||
import { UnitSelector } from '@/components/commons/UnitSelector';
|
||||
|
||||
export function HomePage() {
|
||||
const { l10n } = useLocalization();
|
||||
@@ -23,8 +24,9 @@ export function HomePage() {
|
||||
{l10n.getString('onboarding-home-start')}
|
||||
</Button>
|
||||
</div>
|
||||
<div className="absolute right-4 bottom-4 z-50">
|
||||
<div className="flex flex-col gap-3 absolute right-4 bottom-4 z-50">
|
||||
<LangSelector />
|
||||
<UnitSelector />
|
||||
</div>
|
||||
<div
|
||||
className="absolute bg-accent-background-50 w-full rounded-full"
|
||||
|
||||
@@ -32,9 +32,9 @@ import { FullResetIcon } from '@/components/commons/icon/ResetIcon';
|
||||
import { ImportIcon } from '@/components/commons/icon/ImportIcon';
|
||||
import { HumanIcon } from '@/components/commons/icon/HumanIcon';
|
||||
import { Typography } from '@/components/commons/Typography';
|
||||
import { useLocaleConfig } from '@/i18n/config';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { ResetButton } from '@/components/home/ResetButton';
|
||||
import { HeightDisplay } from '@/components/commons/HeightDisplay';
|
||||
import { Vector3 } from 'three';
|
||||
|
||||
function IconButton({
|
||||
@@ -406,7 +406,6 @@ function ButtonsControl({ control }: { control: ManualProportionControls }) {
|
||||
export function ManualProportionsPage() {
|
||||
const { applyProgress, state } = useOnboarding();
|
||||
const { useRPCPacket } = useWebsocketAPI();
|
||||
const { currentLocales } = useLocaleConfig();
|
||||
|
||||
const [userHeight, setUserHeight] = useState(0);
|
||||
|
||||
@@ -421,15 +420,6 @@ export function ManualProportionsPage() {
|
||||
});
|
||||
const { precise, ratio } = watch();
|
||||
|
||||
const { cmFormat } = useMemo(() => {
|
||||
const cmFormat = Intl.NumberFormat(currentLocales, {
|
||||
style: 'unit',
|
||||
unit: 'centimeter',
|
||||
maximumFractionDigits: 1,
|
||||
});
|
||||
return { cmFormat };
|
||||
}, [currentLocales]);
|
||||
|
||||
useEffect(() => {
|
||||
localStorage.setItem('ratioMode', ratio?.toString() ?? 'true');
|
||||
}, [ratio]);
|
||||
@@ -491,7 +481,7 @@ export function ManualProportionsPage() {
|
||||
>
|
||||
<div className="h-14 bg-background-50 p-4 flex items-center rounded-lg min-w-36 justify-center">
|
||||
<Typography variant="main-title">
|
||||
{cmFormat.format((userHeight * 100) / 0.936)}
|
||||
<HeightDisplay height={userHeight / 0.936} />
|
||||
</Typography>
|
||||
</div>
|
||||
</Tooltip>
|
||||
|
||||
@@ -2,8 +2,7 @@ import { useWebsocketAPI } from '@/hooks/websocket-api';
|
||||
import { Button } from '@/components/commons/Button';
|
||||
import { Typography } from '@/components/commons/Typography';
|
||||
import { useLocalization } from '@fluent/react';
|
||||
import { useEffect, useMemo } from 'react';
|
||||
import { useLocaleConfig } from '@/i18n/config';
|
||||
import { useEffect } from 'react';
|
||||
import {
|
||||
DEFAULT_FULL_HEIGHT,
|
||||
EYE_HEIGHT_TO_HEIGHT_RATIO,
|
||||
@@ -19,11 +18,16 @@ import {
|
||||
import { NumberSelector } from '@/components/commons/NumberSelector';
|
||||
import { useOnboarding } from '@/hooks/onboarding';
|
||||
import { MIN_HEIGHT } from '@/hooks/manual-proportions';
|
||||
import { HeightDisplay } from '@/components/commons/HeightDisplay';
|
||||
import { useUnit } from '@/hooks/config';
|
||||
import convert from 'convert';
|
||||
|
||||
interface HeightForm {
|
||||
height: number;
|
||||
}
|
||||
|
||||
const INCH_IN_METER = convert(1, 'inch').to('m');
|
||||
|
||||
export function ManualHeightStep({
|
||||
nextStep,
|
||||
prevStep,
|
||||
@@ -41,8 +45,8 @@ export function ManualHeightStep({
|
||||
defaultValues: { height: DEFAULT_FULL_HEIGHT },
|
||||
});
|
||||
const { sendRPCPacket } = useWebsocketAPI();
|
||||
const { currentLocales } = useLocaleConfig();
|
||||
const height = watch('height');
|
||||
const currentUnit = useUnit();
|
||||
|
||||
// Load the last configured height
|
||||
useEffect(() => {
|
||||
@@ -53,16 +57,6 @@ export function ManualHeightStep({
|
||||
});
|
||||
}, [currentHeight]);
|
||||
|
||||
const mFormat = useMemo(
|
||||
() =>
|
||||
new Intl.NumberFormat(currentLocales, {
|
||||
style: 'unit',
|
||||
unit: 'meter',
|
||||
maximumFractionDigits: 2,
|
||||
}),
|
||||
[currentLocales]
|
||||
);
|
||||
|
||||
const submitFullHeight = (values: HeightForm) => {
|
||||
const newHeight = values.height * EYE_HEIGHT_TO_HEIGHT_RATIO;
|
||||
setHmdHeight(newHeight);
|
||||
@@ -104,17 +98,36 @@ export function ManualHeightStep({
|
||||
'onboarding-scaled_proportions-manual_height-height-v2'
|
||||
)}
|
||||
valueLabelFormat={(value) =>
|
||||
isNaN(value)
|
||||
? l10n.getString(
|
||||
'onboarding-scaled_proportions-manual_height-unknown'
|
||||
)
|
||||
: mFormat.format(value)
|
||||
isNaN(value) ? (
|
||||
l10n.getString(
|
||||
'onboarding-scaled_proportions-manual_height-unknown'
|
||||
)
|
||||
) : (
|
||||
<HeightDisplay
|
||||
height={value}
|
||||
unitDisplay="narrow"
|
||||
roundInches
|
||||
/>
|
||||
)
|
||||
}
|
||||
min={MIN_HEIGHT}
|
||||
max={4}
|
||||
step={0.01}
|
||||
showButtonWithNumber
|
||||
doubleStep={0.1}
|
||||
step={
|
||||
currentUnit === 'm'
|
||||
? 0.01
|
||||
: (v, s) =>
|
||||
Math.round((v + INCH_IN_METER * s) / INCH_IN_METER) *
|
||||
INCH_IN_METER
|
||||
}
|
||||
showButtonWithNumber={currentUnit === 'm' ? 10 : 12}
|
||||
doubleStep={
|
||||
currentUnit === 'm'
|
||||
? 0.1
|
||||
: (v, s) =>
|
||||
Math.round(
|
||||
(v + convert(1, 'ft').to('m') * s) / INCH_IN_METER
|
||||
) * INCH_IN_METER
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col self-center items-center justify-center">
|
||||
@@ -124,7 +137,7 @@ export function ManualHeightStep({
|
||||
)}
|
||||
</Typography>
|
||||
<Typography>
|
||||
{mFormat.format(height * EYE_HEIGHT_TO_HEIGHT_RATIO)}
|
||||
<HeightDisplay height={height * EYE_HEIGHT_TO_HEIGHT_RATIO} />
|
||||
</Typography>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -18,6 +18,7 @@ import { Range } from '@/components/commons/Range';
|
||||
import { Dropdown } from '@/components/commons/Dropdown';
|
||||
import { ArrowRightLeftIcon } from '@/components/commons/icon/ArrowIcons';
|
||||
import { isTrayAvailable } from '@/utils/tauri';
|
||||
import { UnitSelector } from '@/components/commons/UnitSelector';
|
||||
import { isTauri } from '@tauri-apps/api/core';
|
||||
import { TauriFileInput } from '@/components/commons/TauriFileInput';
|
||||
|
||||
@@ -552,6 +553,20 @@ export function InterfaceSettings() {
|
||||
<div className="grid sm:grid-cols-2 pb-4">
|
||||
<LangSelector alignment="left" />
|
||||
</div>
|
||||
|
||||
<Typography bold>
|
||||
{l10n.getString('settings-interface-appearance-unit_system')}
|
||||
</Typography>
|
||||
<div className="flex flex-col pt-1 pb-2">
|
||||
<Typography color="secondary">
|
||||
{l10n.getString(
|
||||
'settings-interface-appearance-unit_system-description'
|
||||
)}
|
||||
</Typography>
|
||||
</div>
|
||||
<div className="grid sm:grid-cols-2 pb-4">
|
||||
<UnitSelector alignment="left" />
|
||||
</div>
|
||||
</>
|
||||
</SettingsPagePaneLayout>
|
||||
</form>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { createContext, useContext, useState } from 'react';
|
||||
import { createContext, useContext, useMemo, useState } from 'react';
|
||||
import {
|
||||
defaultValues as defaultDevSettings,
|
||||
DeveloperModeWidgetForm,
|
||||
@@ -25,6 +25,11 @@ export enum AssignMode {
|
||||
All = 'all',
|
||||
}
|
||||
|
||||
export enum UnitType {
|
||||
Metric = 'metric',
|
||||
Imperial = 'imperial',
|
||||
}
|
||||
|
||||
export interface Config {
|
||||
debug: boolean;
|
||||
lang: string;
|
||||
@@ -46,6 +51,7 @@ export interface Config {
|
||||
showNavbarOnboarding: boolean;
|
||||
vrcMutedWarnings: string[];
|
||||
bvhDirectory: string | null;
|
||||
unitSystem: UnitType;
|
||||
}
|
||||
|
||||
export interface ConfigContext {
|
||||
@@ -73,6 +79,7 @@ export const defaultConfig: Config = {
|
||||
decorations: false,
|
||||
showNavbarOnboarding: true,
|
||||
vrcMutedWarnings: [],
|
||||
unitSystem: UnitType.Metric,
|
||||
devSettings: defaultDevSettings,
|
||||
bvhDirectory: null,
|
||||
};
|
||||
@@ -201,3 +208,11 @@ export function useConfig() {
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
export function useUnit() {
|
||||
const { config } = useConfig();
|
||||
return useMemo(() => {
|
||||
const unitSystem = config?.unitSystem ?? defaultConfig.unitSystem;
|
||||
return unitSystem === UnitType.Metric ? 'm' : 'in';
|
||||
}, [config?.unitSystem]);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import { createContext, useContext, useEffect, useMemo, useState } from 'react';
|
||||
import { useWebsocketAPI } from './websocket-api';
|
||||
import { RpcMessage, SettingsRequestT, SettingsResponseT } from 'solarxr-protocol';
|
||||
import { MIN_HEIGHT } from './manual-proportions';
|
||||
import convert from 'convert';
|
||||
|
||||
export interface HeightContext {
|
||||
hmdHeight: number | null;
|
||||
@@ -73,4 +74,7 @@ export const EYE_HEIGHT_TO_HEIGHT_RATIO = 0.936;
|
||||
// Based on average human height (1.65m)
|
||||
// From https://ourworldindata.org/human-height (January 2024)
|
||||
export const DEFAULT_FULL_HEIGHT = 1.65;
|
||||
export const DEFAULT_FULL_HEIGHT_INCHES = Math.round(
|
||||
convert(DEFAULT_FULL_HEIGHT, 'm').to('in')
|
||||
);
|
||||
export const DEFAULT_EYE_HEIGHT = DEFAULT_FULL_HEIGHT * EYE_HEIGHT_TO_HEIGHT_RATIO;
|
||||
|
||||
@@ -258,10 +258,12 @@ const config = {
|
||||
addUtilities({
|
||||
'.text-main-title': textConfig('calc(var(--font-size-title) / 16)', 700),
|
||||
'.text-section-title': textConfig('calc(var(--font-size-vr) / 16)', 700),
|
||||
|
||||
'.text-standard': textConfig('calc(var(--font-size-standard) / 16)', 500),
|
||||
'.text-standard-bold': textConfig('calc(var(--font-size-standard) / 16)', 700),
|
||||
|
||||
'.text-vr-accesible': textConfig('calc(var(--font-size-vr) / 16)', 500),
|
||||
'.text-vr-accesible-bold': textConfig('calc(var(--font-size-vr) / 16)', 700),
|
||||
'.text-standard-bold': textConfig('calc(var(--font-size-standard) / 16)', 700),
|
||||
});
|
||||
}),
|
||||
plugin(function ({ addVariant }) {
|
||||
|
||||
8
pnpm-lock.yaml
generated
8
pnpm-lock.yaml
generated
@@ -83,6 +83,9 @@ importers:
|
||||
classnames:
|
||||
specifier: ^2.5.1
|
||||
version: 2.5.1
|
||||
convert:
|
||||
specifier: ^5.10.0
|
||||
version: 5.10.0
|
||||
flatbuffers:
|
||||
specifier: 22.10.26
|
||||
version: 22.10.26
|
||||
@@ -1868,6 +1871,9 @@ packages:
|
||||
resolution: {integrity: sha512-cj09EBuObp9gZNQCzc7hByQyrs6jVGE+o9kSJmeUoj+GiPiJvi5LYqEH/Hmme4+MTLHM+Ejtq+FChpjjEnsPdQ==}
|
||||
engines: {node: '>= 4'}
|
||||
|
||||
convert@5.10.0:
|
||||
resolution: {integrity: sha512-1agZ7UULsa0AVUZefVDmfy7hdHDIm7hr/tB8scKwSy8HE8a+5c3EQsCTEQfx6/+8LGNa7Re2+4fir9mKxFsBuw==}
|
||||
|
||||
create-require@1.1.1:
|
||||
resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==}
|
||||
|
||||
@@ -6156,6 +6162,8 @@ snapshots:
|
||||
|
||||
convert-to-spaces@1.0.2: {}
|
||||
|
||||
convert@5.10.0: {}
|
||||
|
||||
create-require@1.1.1: {}
|
||||
|
||||
cross-env@7.0.3:
|
||||
|
||||
Reference in New Issue
Block a user