Compare commits

...

23 Commits

Author SHA1 Message Date
Uriel
dc21b3a02d Merge branch 'main' into update-eslint 2025-09-09 19:48:55 -03:00
Uriel
0f06ac0253 update linux metadata (#1524) 2025-09-04 13:44:17 -03:00
Butterscotch!
4568ebb41a Foot snapping fix (#1519) 2025-09-03 21:11:24 -03:00
Ilia Ki
2777d8af89 Fix endian docs for smol HID (#1520) 2025-09-03 20:03:39 -03:00
Uriel
ac2472d62f format 2025-09-01 12:20:35 -04:00
Uriel
820d99d76d Merge branch 'main' into update-eslint 2025-09-01 12:17:17 -04:00
Eiren Rain
3f9b997ffa Bump awalsh128/cache-apt-pkgs-action from 1.5.1 to 1.5.3 (#1515) 2025-08-19 21:45:46 +02:00
dependabot[bot]
df379ee234 Bump awalsh128/cache-apt-pkgs-action from 1.5.1 to 1.5.3
Bumps [awalsh128/cache-apt-pkgs-action](https://github.com/awalsh128/cache-apt-pkgs-action) from 1.5.1 to 1.5.3.
- [Release notes](https://github.com/awalsh128/cache-apt-pkgs-action/releases)
- [Commits](https://github.com/awalsh128/cache-apt-pkgs-action/compare/v1.5.1...v1.5.3)

---
updated-dependencies:
- dependency-name: awalsh128/cache-apt-pkgs-action
  dependency-version: 1.5.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-19 12:17:12 +00:00
Eiren Rain
acd628637e Bump actions/download-artifact from 4 to 5 (#1516) 2025-08-19 14:14:48 +02:00
dependabot[bot]
155dbfbff1 Bump actions/download-artifact from 4 to 5
Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 4 to 5.
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/download-artifact
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-19 12:13:29 +00:00
Eiren Rain
eda3d74c54 Bump actions/checkout from 4 to 5 (#1517) 2025-08-19 14:12:25 +02:00
dependabot[bot]
4b9f393cee Bump actions/checkout from 4 to 5
Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-18 22:56:14 +00:00
Uriel
68e3100a04 add a fixme 2025-07-09 18:09:47 -04:00
Uriel
59e13eba43 fix after merge 2025-07-09 18:09:20 -04:00
Uriel
858191729c Merge branch 'main' into update-eslint 2025-07-09 18:02:30 -04:00
Uriel
36a3303b2b eslint fix latest 2025-06-20 18:07:43 -04:00
Uriel
0792c06136 Merge branch 'main' into update-eslint 2025-06-20 18:05:33 -04:00
Uriel
657ee489a1 update eslint again 2025-06-20 17:56:33 -04:00
Uriel
c115d4a8fb dont show prettier errors 2025-06-15 19:08:58 -04:00
Uriel
7df4d5643b Merge branch 'main' into update-eslint 2025-06-15 18:38:10 -04:00
Uriel
83583d42ad remove unused imports 2025-06-15 18:36:47 -04:00
Uriel
62c8567052 eslint fix everything 2025-06-15 04:12:04 -04:00
Uriel
290b9869b8 Update eslint packages 2025-06-15 04:11:38 -04:00
194 changed files with 5030 additions and 4257 deletions

View File

@@ -21,7 +21,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
with:
submodules: recursive
@@ -59,13 +59,13 @@ jobs:
BUILD_ARCH: ${{ endsWith(matrix.os, 'arm') && 'aarch64' || 'amd64' }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
with:
submodules: recursive
- if: startsWith(matrix.os, 'ubuntu')
name: Set up Linux dependencies
uses: awalsh128/cache-apt-pkgs-action@v1.5.1
uses: awalsh128/cache-apt-pkgs-action@v1.5.3
with:
packages: libgtk-3-dev webkit2gtk-4.1 libappindicator3-dev librsvg2-dev patchelf
# Increment to invalidate the cache

View File

@@ -19,7 +19,7 @@ jobs:
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
with:
submodules: recursive
@@ -48,7 +48,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
with:
submodules: recursive
@@ -87,7 +87,7 @@ jobs:
bundle-android:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
with:
submodules: recursive
@@ -154,17 +154,17 @@ jobs:
env:
BUILD_ARCH: ${{ endsWith(matrix.os, 'arm') && 'aarch64' || 'amd64' }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
with:
submodules: recursive
- uses: actions/download-artifact@v4
- uses: actions/download-artifact@v5
with:
name: 'SlimeVR-Server'
path: server/desktop/build/libs/
- name: Set up Linux dependencies
uses: awalsh128/cache-apt-pkgs-action@v1.5.1
uses: awalsh128/cache-apt-pkgs-action@v1.5.3
with:
packages: |
build-essential curl wget file libssl-dev libgtk-3-dev libappindicator3-dev librsvg2-dev
@@ -252,11 +252,11 @@ jobs:
needs: [build, test]
if: contains(fromJSON('["workflow_dispatch", "create"]'), github.event_name)
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
with:
submodules: recursive
- uses: actions/download-artifact@v4
- uses: actions/download-artifact@v5
with:
name: 'SlimeVR-Server'
path: server/desktop/build/libs/
@@ -332,11 +332,11 @@ jobs:
env:
BUILD_ARCH: ${{ endsWith(matrix.os, 'arm') && 'win-aarch64' || 'win64' }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
with:
submodules: recursive
- uses: actions/download-artifact@v4
- uses: actions/download-artifact@v5
with:
name: 'SlimeVR-Server'
path: server/desktop/build/libs/

View File

@@ -12,7 +12,7 @@ jobs:
pull-requests: write
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
with:
submodules: recursive
- uses: repo-sync/pull-request@v2

View File

@@ -15,7 +15,7 @@ jobs:
contents: write
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
with:
ref: pontoon
submodules: recursive

View File

@@ -1,28 +1,74 @@
import { FlatCompat } from '@eslint/eslintrc';
import eslint from '@eslint/js';
import globals from 'globals';
import tseslint from 'typescript-eslint';
import js from '@eslint/js';
import importAlias from '@dword-design/eslint-plugin-import-alias';
import { configs, plugins } from 'eslint-config-airbnb-extended';
import { rules as prettierConfigRules } from 'eslint-config-prettier';
import prettierPlugin from 'eslint-plugin-prettier';
const compat = new FlatCompat();
const jsConfig = [
// ESLint Recommended Rules
{
name: 'js/config',
...js.configs.recommended,
},
// Stylistic Plugin
plugins.stylistic,
// Import X Plugin
plugins.importX,
// Airbnb Base Recommended Config
...configs.base.recommended,
];
const reactConfig = [
// React Plugin
plugins.react,
// React Hooks Plugin
plugins.reactHooks,
// React JSX A11y Plugin
plugins.reactA11y,
// Airbnb React Recommended Config
...configs.react.recommended,
];
const typescriptConfig = [
// TypeScript ESLint Plugin
plugins.typescriptEslint,
// Airbnb Base TypeScript Config
...configs.base.typescript,
// Airbnb React TypeScript Config
...configs.react.typescript,
];
const prettierConfig = [
// Prettier Plugin
{
name: 'prettier/plugin/config',
plugins: {
prettier: prettierPlugin,
},
},
// Prettier Config
{
name: 'prettier/config',
rules: {
...prettierConfigRules,
// remove errors with prettier, use prettier for that pls
// 'prettier/prettier': 'error',
},
},
];
export const gui = [
eslint.configs.recommended,
...tseslint.configs.recommended,
...compat.extends('plugin:@dword-design/import-alias/recommended'),
...compat.plugins('eslint-plugin-react-hooks'),
// Add import-alias rule inside compat because plugin doesn't like flat configs
...compat.config({
rules: {
'@dword-design/import-alias/prefer-alias': [
'error',
{
alias: {
'@': './src/',
},
},
],
},
}),
// Javascript Config
...jsConfig,
// React Config
...reactConfig,
// TypeScript Config
...typescriptConfig,
// Prettier Config
...prettierConfig,
importAlias.configs.recommended,
{
languageOptions: {
ecmaVersion: 'latest',
@@ -39,10 +85,52 @@ export const gui = [
},
},
files: ['src/**/*.{js,jsx,ts,tsx,json}'],
plugins: {
'@typescript-eslint': tseslint.plugin,
},
rules: {
// fixes for airbnb
'max-classes-per-file': 'off',
'no-underscore-dangle': 'off',
'no-plusplus': 'off',
'consistent-return': 'off',
'no-bitwise': 'off',
eqeqeq: ['error', 'smart'],
'no-restricted-syntax': 'off',
'no-param-reassign': 'off',
'no-return-assign': ['error', 'except-parens'],
'default-case': 'off',
'no-continue': 'off',
'react/forbid-prop-types': 'off',
'no-script-url': 'off',
// stuff that should be enabled again later
eqeqeq: 'off',
'no-nested-ternary': 'off',
// FIXME: Remove after merge of https://github.com/SlimeVR/SlimeVR-Server/pull/1494
'no-await-in-loop': 'off',
'@typescript-eslint/no-use-before-define': 'off',
'@typescript-eslint/no-shadow': 'off',
'import-x/prefer-default-export': 'off',
'import-x/no-cycle': 'off',
'import-x/no-rename-default': 'off',
'jsx-a11y/heading-has-content': 'off',
'jsx-a11y/alt-text': 'off',
'jsx-a11y/media-has-caption': 'off',
'jsx-a11y/no-static-element-interactions': 'off',
'jsx-a11y/click-events-have-key-events': 'off',
'jsx-a11y/label-has-associated-control': 'off',
'jsx-a11y/no-noninteractive-element-interactions': 'off',
'jsx-a11y/no-noninteractive-element-to-interactive-role': 'off',
'jsx-a11y/anchor-is-valid': 'off',
'react/require-default-props': 'off',
'react/no-unstable-nested-components': 'off',
'react/no-unused-prop-types': 'off',
'react-hooks/rules-of-hooks': 'off',
'react-hooks/exhaustive-deps': 'off',
// end
'@typescript-eslint/switch-exhaustiveness-check': [
'error',
{
considerDefaultExhaustiveForUnions: true,
},
],
'react/react-in-jsx-scope': 'off',
'react/prop-types': 'off',
'spaced-comment': 'error',
@@ -60,14 +148,14 @@ export const gui = [
ignoreRestSiblings: true,
},
],
},
settings: {
'import/resolver': {
typescript: {},
},
react: {
version: 'detect',
},
'@dword-design/import-alias/prefer-alias': [
'error',
{
alias: {
'@': './src/',
},
},
],
},
},
// Global ignore

View File

@@ -66,9 +66,12 @@
"gen:icons": "tauri icon --ios-color '#663499' src-tauri/icons/icon.svg"
},
"devDependencies": {
"@dword-design/eslint-plugin-import-alias": "^4.0.9",
"@dword-design/eslint-plugin-import-alias": "^6.0.3",
"@eslint/compat": "^1.3.0",
"@eslint/js": "^9.29.0",
"@openapi-codegen/cli": "^2.0.2",
"@openapi-codegen/typescript": "^8.0.2",
"@stylistic/eslint-plugin": "^3.1.0",
"@tailwindcss/forms": "^0.5.9",
"@tauri-apps/cli": "^2.0.2",
"@types/file-saver": "^2.0.7",
@@ -78,27 +81,29 @@
"@types/react-modal": "3.16.3",
"@types/semver": "^7.5.8",
"@types/three": "^0.163.0",
"@typescript-eslint/eslint-plugin": "^7.18.0",
"@typescript-eslint/parser": "^7.18.0",
"@typescript-eslint/eslint-plugin": "^8.34.0",
"@typescript-eslint/parser": "^8.34.0",
"@vitejs/plugin-react": "^4.3.2",
"autoprefixer": "^10.4.20",
"cross-env": "^7.0.3",
"dotenv": "^16.4.5",
"eslint": "^8.57.1",
"eslint-config-airbnb": "^19.0.4",
"eslint-import-resolver-typescript": "^3.6.3",
"eslint-plugin-import": "^2.31.0",
"eslint-plugin-jsx-a11y": "^6.10.0",
"eslint-plugin-react": "^7.37.1",
"eslint-plugin-react-hooks": "^4.6.2",
"eslint": "^9.29.0",
"eslint-config-airbnb-extended": "^2.1.0",
"eslint-config-prettier": "^10.1.5",
"eslint-import-resolver-typescript": "^4.4.3",
"eslint-plugin-import-x": "^4.15.2",
"eslint-plugin-jsx-a11y": "^6.10.2",
"eslint-plugin-prettier": "^5.4.1",
"eslint-plugin-react": "^7.37.5",
"eslint-plugin-react-hooks": "^5.2.0",
"globals": "^15.10.0",
"prettier": "^3.3.3",
"prettier": "^3.5.3",
"rollup-plugin-visualizer": "^5.12.0",
"sass": "^1.79.4",
"spdx-satisfies": "^5.0.1",
"tailwind-gradient-mask-image": "^1.2.0",
"tailwindcss": "^3.4.13",
"typescript-eslint": "^8.8.0",
"typescript-eslint": "^8.34.0",
"vite": "^5.4.8"
}
}

View File

@@ -65,6 +65,10 @@ work. If not, see <http://creativecommons.org/publicdomain/zero/1.0/>.
</provides>
<releases>
<release version="0.16.2" date="2025-08-01"><url>https://github.com/SlimeVR/SlimeVR-Server/releases/tag/v0.16.2</url></release>
<release version="0.16.1" date="2025-07-27"><url>https://github.com/SlimeVR/SlimeVR-Server/releases/tag/v0.16.1</url></release>
<release version="0.16.1~rc.2" type="development" date="2025-07-17"><url>https://github.com/SlimeVR/SlimeVR-Server/releases/tag/v0.16.1-rc.2</url></release>
<release version="0.16.1~rc.1" type="development" date="2025-07-04"><url>https://github.com/SlimeVR/SlimeVR-Server/releases/tag/v0.16.1-rc.1</url></release>
<release version="0.16.0" date="2025-07-01"><url>https://github.com/SlimeVR/SlimeVR-Server/releases/tag/v0.16.0</url></release>
<release version="0.16.0~rc.2" type="development" date="2025-06-20"><url>https://github.com/SlimeVR/SlimeVR-Server/releases/tag/v0.16.0-rc.2</url></release>
<release version="0.16.0~rc.1" type="development" date="2025-05-27"><url>https://github.com/SlimeVR/SlimeVR-Server/releases/tag/v0.16.0-rc.1</url></release>

View File

@@ -5,6 +5,11 @@ import {
Route,
Routes,
} from 'react-router-dom';
import { Event, listen } from '@tauri-apps/api/event';
import * as os from '@tauri-apps/plugin-os';
import { open } from '@tauri-apps/plugin-shell';
import semver from 'semver';
import { withSentryReactRouterV6Routing } from '@sentry/react';
import { Home } from './components/home/Home';
import { MainLayout } from './components/MainLayout';
import { AppContextProvider } from './components/providers/AppContext';
@@ -16,7 +21,6 @@ import {
WebSocketApiContext,
} from './hooks/websocket-api';
import { Event, listen } from '@tauri-apps/api/event';
import { OnboardingContextProvider } from './components/onboarding/OnboardingContextProvicer';
import { OnboardingLayout } from './components/onboarding/OnboardingLayout';
import { AutomaticProportionsPage } from './components/onboarding/pages/body-proportions/AutomaticProportions';
@@ -36,15 +40,12 @@ import { VRCOSCSettings } from './components/settings/pages/VRCOSCSettings';
import { TopBar } from './components/TopBar';
import { TrackerSettingsPage } from './components/tracker/TrackerSettings';
import { OSCRouterSettings } from './components/settings/pages/OSCRouterSettings';
import * as os from '@tauri-apps/plugin-os';
import { VMCSettings } from './components/settings/pages/VMCSettings';
import { MountingChoose } from './components/onboarding/pages/mounting/MountingChoose';
import { StatusProvider } from './components/providers/StatusSystemContext';
import { VersionUpdateModal } from './components/VersionUpdateModal';
import { CalibrationTutorialPage } from './components/onboarding/pages/CalibrationTutorial';
import { AssignmentTutorialPage } from './components/onboarding/pages/assignment-preparation/AssignmentTutorial';
import { open } from '@tauri-apps/plugin-shell';
import semver from 'semver';
import { useBreakpoint, useIsTauri } from './hooks/breakpoint';
import { VRModePage } from './components/vr-mode/VRModePage';
import { InterfaceSettings } from './components/settings/pages/InterfaceSettings';
@@ -54,7 +55,6 @@ import { AppLayout } from './AppLayout';
import { Preload } from './components/Preload';
import { UnknownDeviceModal } from './components/UnknownDeviceModal';
import { useDiscordPresence } from './hooks/discord-presence';
import { withSentryReactRouterV6Routing } from '@sentry/react';
import { ScaledProportionsPage } from './components/onboarding/pages/body-proportions/ScaledProportions';
import { AdvancedSettings } from './components/settings/pages/AdvancedSettings';
import { FirmwareUpdate } from './components/firmware-update/FirmwareUpdate';
@@ -75,9 +75,9 @@ function Layout() {
return (
<>
<SerialDetectionModal></SerialDetectionModal>
<VersionUpdateModal></VersionUpdateModal>
<UnknownDeviceModal></UnknownDeviceModal>
<SerialDetectionModal />
<VersionUpdateModal />
<UnknownDeviceModal />
<SentryRoutes>
<Route element={<AppLayout />}>
<Route
@@ -158,7 +158,7 @@ function Layout() {
/>
<Route path="trackers-assign" element={<TrackersAssignPage />} />
<Route path="enter-vr" element={<EnterVRPage />} />
<Route path="mounting/choose" element={<MountingChoose />}></Route>
<Route path="mounting/choose" element={<MountingChoose />} />
<Route path="mounting/auto" element={<AutomaticMountingPage />} />
<Route path="mounting/manual" element={<ManualMountingPage />} />
<Route path="reset-tutorial" element={<ResetTutorialPage />} />
@@ -177,7 +177,7 @@ function Layout() {
<Route path="stay-aligned" element={<StayAlignedSetup />} />
<Route path="done" element={<DonePage />} />
</Route>
<Route path="*" element={<TopBar></TopBar>}></Route>
<Route path="*" element={<TopBar />} />
</Route>
</SentryRoutes>
</>
@@ -190,7 +190,7 @@ export default function App() {
const isTauri = useIsTauri();
useEffect(() => {
const onKeydown: (arg0: KeyboardEvent) => void = function (event) {
const onKeydown = (event: KeyboardEvent) => {
// prevent search bar keybind
if (
event.key === 'F3' ||
@@ -239,10 +239,11 @@ export default function App() {
'server-status',
(event: Event<[string, string]>) => {
const [eventType, s] = event.payload;
if ('stderr' === eventType) {
if (eventType === 'stderr') {
// This strange invocation is what lets us lose the line information in the console
// See more here: https://stackoverflow.com/a/48994308
// These two are fine to keep with console.log, they are server logs
/* eslint-disable no-console */
setTimeout(
console.log.bind(
console,
@@ -260,6 +261,7 @@ export default function App() {
'color:green'
)
);
/* eslint-enable no-console */
} else if (eventType === 'error') {
error('Error: %s', s);
} else if (eventType === 'terminated') {
@@ -278,7 +280,7 @@ export default function App() {
useEffect(() => {
function onKeyboard(ev: KeyboardEvent) {
if (ev.key === 'F1') {
return open(DOCS_SITE).catch(() => window.open(DOCS_SITE, '_blank'));
open(DOCS_SITE).catch(() => window.open(DOCS_SITE, '_blank'));
}
}
@@ -296,10 +298,8 @@ export default function App() {
<VersionContext.Provider value={updateFound}>
<div className="h-full w-full text-standard bg-background-80 text-background-10">
<Preload />
{!websocketAPI.isConnected && (
<ConnectionLost></ConnectionLost>
)}
{websocketAPI.isConnected && <Layout></Layout>}
{!websocketAPI.isConnected && <ConnectionLost />}
{websocketAPI.isConnected && <Layout />}
</div>
</VersionContext.Provider>
</StatusProvider>

View File

@@ -1,6 +1,6 @@
import { useLayoutEffect } from 'react';
import { useConfig } from './hooks/config';
import { Outlet, useNavigate } from 'react-router-dom';
import { useConfig } from './hooks/config';
export function AppLayout() {
const { config } = useConfig();
@@ -33,9 +33,5 @@ export function AppLayout() {
}
}, [config?.doneOnboarding]);
return (
<>
<Outlet />
</>
);
return <Outlet />;
}

View File

@@ -6,14 +6,16 @@ import {
RpcMessage,
} from 'solarxr-protocol';
import { useWebsocketAPI } from '@/hooks/websocket-api';
import { BigButton } from './commons/BigButton';
import { RecordIcon } from './commons/icon/RecordIcon';
import classNames from 'classnames';
import { isTauri } from '@tauri-apps/api/core';
import { save } from '@tauri-apps/plugin-dialog';
import { useConfig } from '@/hooks/config';
import { RecordIcon } from './commons/icon/RecordIcon';
import { BigButton } from './commons/BigButton';
export function BVHButton(props: React.HTMLAttributes<HTMLButtonElement>) {
export function BVHButton({
className,
}: React.HTMLAttributes<HTMLButtonElement>) {
const { config } = useConfig();
const { useRPCPacket, sendRPCPacket } = useWebsocketAPI();
const [recording, setRecording] = useState(false);
@@ -60,11 +62,11 @@ export function BVHButton(props: React.HTMLAttributes<HTMLButtonElement>) {
onClick={toggleBVH}
disabled={saving}
className={classNames(
props.className,
className,
'border',
recording ? 'border-status-critical' : 'border-transparent'
)}
></BigButton>
/>
</Localized>
);
}

View File

@@ -1,15 +1,15 @@
import { Localized } from '@fluent/react';
import { ClearMountingResetRequestT, RpcMessage } from 'solarxr-protocol';
import { useWebsocketAPI } from '@/hooks/websocket-api';
import { BigButton } from './commons/BigButton';
import { TrashIcon } from './commons/icon/TrashIcon';
import { Quaternion } from 'three';
import { QuaternionFromQuatT, similarQuaternions } from '@/maths/quaternion';
import { useMemo } from 'react';
import { useAtomValue } from 'jotai';
import { assignedTrackersAtom } from '@/store/app-store';
import { TrashIcon } from './commons/icon/TrashIcon';
import { BigButton } from './commons/BigButton';
const _q = new Quaternion();
const QUAT = new Quaternion();
export function ClearMountingButton() {
const { sendRPCPacket } = useWebsocketAPI();
@@ -21,7 +21,7 @@ export function ClearMountingButton() {
(d) =>
!similarQuaternions(
QuaternionFromQuatT(d?.tracker.info?.mountingResetOrientation),
_q
QUAT
)
),
[assignedTrackers]

View File

@@ -6,7 +6,7 @@ export function EmptyLayout({ children }: { children: ReactNode }) {
return (
<div className="empty-layout h-full">
<div style={{ gridArea: 't' }}>
<TopBar></TopBar>
<TopBar />
</div>
<div style={{ gridArea: 'c' }} className="mt-2 relative">
{children}

View File

@@ -26,34 +26,29 @@ export function ErrorConsentModal({
return (
<BaseModal isOpen={isOpen} onRequestClose={cancel} closeable={false}>
<div className="flex flex-col gap-3">
<>
<div className="flex flex-col items-center gap-3 fill-accent-background-20">
<div className="flex flex-col items-center gap-2 max-w-[512px]">
<Typography variant="main-title">
{l10n.getString('error_collection_modal-title')}
</Typography>
<Localized
id={'error_collection_modal-description_v2'}
elems={{
b: <b></b>,
h1: <span className="text-lg font-bold"></span>,
}}
>
<Typography
variant="standard"
whitespace="whitespace-pre-line"
/>
</Localized>
</div>
<div className="flex flex-col items-center gap-3 fill-accent-background-20">
<div className="flex flex-col items-center gap-2 max-w-[512px]">
<Typography variant="main-title">
{l10n.getString('error_collection_modal-title')}
</Typography>
<Localized
id={'error_collection_modal-description_v2'}
elems={{
b: <b />,
h1: <span className="text-lg font-bold" />,
}}
>
<Typography variant="standard" whitespace="whitespace-pre-line" />
</Localized>
</div>
</div>
<Button variant="primary" onClick={accept}>
{l10n.getString('error_collection_modal-confirm')}
</Button>
<Button variant="tertiary" onClick={cancel}>
{l10n.getString('error_collection_modal-cancel')}
</Button>
</>
<Button variant="primary" onClick={accept}>
{l10n.getString('error_collection_modal-confirm')}
</Button>
<Button variant="tertiary" onClick={cancel}>
{l10n.getString('error_collection_modal-cancel')}
</Button>
</div>
</BaseModal>
);

View File

@@ -9,7 +9,9 @@
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);
/ var(--navbar-w) calc(100% - var(--navbar-w) - var(--widget-w)) var(
--widget-w
);
}
@screen mobile {

View File

@@ -6,9 +6,9 @@ import {
RpcMessage,
SettingsRequestT,
} from 'solarxr-protocol';
import { useWebsocketAPI } from '@/hooks/websocket-api';
import { Navbar } from './Navbar';
import { TopBar } from './TopBar';
import { useWebsocketAPI } from '@/hooks/websocket-api';
import { WidgetsComponent } from './WidgetsComponent';
import './MainLayout.scss';
@@ -33,11 +33,11 @@ export function MainLayout({
function usePageChanged(callback: () => void) {
useEffect(() => {
callback();
}, [location.pathname]);
}, [window.location.pathname]);
}
usePageChanged(() => {
if (location.pathname.includes('body-proportions')) {
if (window.location.pathname.includes('body-proportions')) {
const tempSettings = new LegTweaksTmpChangeT();
tempSettings.skatingCorrection = false;
tempSettings.floorClip = false;
@@ -54,17 +54,19 @@ export function MainLayout({
sendRPCPacket(RpcMessage.LegTweaksTmpClear, resetSettings);
}
setProportionsLastPageOpen(location.pathname.includes('body-proportions'));
setProportionsLastPageOpen(
window.location.pathname.includes('body-proportions')
);
});
return (
<div className="">
<div className="main-layout w-full h-screen">
<div style={{ gridArea: 't' }}>
<TopBar></TopBar>
<TopBar />
</div>
<div style={{ gridArea: 's' }} className="overflow-y-auto">
<Navbar></Navbar>
<Navbar />
</div>
<div
style={{ gridArea: 'c' }}
@@ -81,7 +83,7 @@ export function MainLayout({
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>
<WidgetsComponent />
</div>
)}
</div>

View File

@@ -2,14 +2,14 @@ import { useLocalization } from '@fluent/react';
import classnames from 'classnames';
import { ReactNode } from 'react';
import { NavLink, useMatch } from 'react-router-dom';
import { useBreakpoint } from '@/hooks/breakpoint';
import { useConfig } from '@/hooks/config';
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';
export function NavButton({
to,
@@ -70,13 +70,13 @@ export function MainLinks() {
return (
<>
<NavButton to="/" icon={<CubeIcon></CubeIcon>}>
<NavButton to="/" icon={<CubeIcon />}>
{l10n.getString('navbar-home')}
</NavButton>
<NavButton
to="/onboarding/trackers-assign"
state={{ alonePage: true }}
icon={<HumanIcon></HumanIcon>}
icon={<HumanIcon />}
>
{l10n.getString('navbar-trackers_assign')}
</NavButton>
@@ -84,7 +84,7 @@ export function MainLinks() {
to="/onboarding/mounting/choose"
match="/onboarding/mounting/*"
state={{ alonePage: true }}
icon={<WrenchIcon></WrenchIcon>}
icon={<WrenchIcon />}
>
{l10n.getString('navbar-mounting')}
</NavButton>
@@ -92,12 +92,12 @@ export function MainLinks() {
to="/onboarding/body-proportions/scaled"
match="/onboarding/body-proportions/*"
state={{ alonePage: true }}
icon={<RulerIcon></RulerIcon>}
icon={<RulerIcon />}
>
{l10n.getString('navbar-body_proportions')}
</NavButton>
{config?.showNavbarOnboarding && (
<NavButton to="/onboarding/home" icon={<SparkleIcon></SparkleIcon>}>
<NavButton to="/onboarding/home" icon={<SparkleIcon />}>
{l10n.getString('navbar-onboarding')}
</NavButton>
)}
@@ -111,18 +111,18 @@ export function Navbar() {
return isMobile ? (
<div className="flex flex-row justify-around px-2 pt-2 bg-background-80 gap-2">
<MainLinks></MainLinks>
<MainLinks />
</div>
) : (
<div className="flex flex-col h-full p-2 gap-2">
<div className="flex flex-col flex-grow gap-2">
<MainLinks></MainLinks>
<MainLinks />
</div>
<NavButton
to="/settings/trackers"
match="/settings/*"
state={{ scrollTo: 'steamvr' }}
icon={<GearIcon></GearIcon>}
icon={<GearIcon />}
>
{l10n.getString('navbar-settings')}
</NavButton>

View File

@@ -1,4 +1,5 @@
import { Helmet } from 'react-helmet';
export function Preload() {
return (
<Helmet>

View File

@@ -69,7 +69,7 @@ export function SerialDetectionModal() {
{!showWifiForm && (
<>
<div className="flex flex-col items-center gap-3 fill-accent-background-20">
<USBIcon></USBIcon>
<USBIcon />
<div className="flex flex-col items-center gap-2">
<Typography variant="main-title">
{l10n.getString('serial_detection-new_device-p0')}
@@ -101,7 +101,7 @@ export function SerialDetectionModal() {
>
<div className="flex flex-col items-center gap-3">
<div className="fill-background-10">
<BulbIcon></BulbIcon>
<BulbIcon />
</div>
<Typography variant="main-title">
{l10n.getString('serial_detection-new_device-p0')}

View File

@@ -7,28 +7,16 @@ import {
TrackerStatus,
} from 'solarxr-protocol';
import { useWebsocketAPI } from '@/hooks/websocket-api';
import { CloseIcon } from './commons/icon/CloseIcon';
import { MaximiseIcon } from './commons/icon/MaximiseIcon';
import { MinimiseIcon } from './commons/icon/MinimiseIcon';
import { SlimeVRIcon } from './commons/icon/SimevrIcon';
import { ProgressBar } from './commons/ProgressBar';
import { Typography } from './commons/Typography';
import { DownloadIcon } from './commons/icon/DownloadIcon';
import { open } from '@tauri-apps/plugin-shell';
import { DOCS_SITE, GH_REPO, VersionContext } from '@/App';
import classNames from 'classnames';
import { QuestionIcon } from './commons/icon/QuestionIcon';
import { useBreakpoint, useIsTauri } from '@/hooks/breakpoint';
import { GearIcon } from './commons/icon/GearIcon';
import { invoke } from '@tauri-apps/api/core';
import { TrackersStillOnModal } from './TrackersStillOnModal';
import { useConfig } from '@/hooks/config';
import { listen, TauriEvent } from '@tauri-apps/api/event';
import { TrayOrExitModal } from './TrayOrExitModal';
import { error } from '@/utils/logging';
import { useDoubleTap } from 'use-double-tap';
import { isTrayAvailable } from '@/utils/tauri';
import { ErrorConsentModal } from './ErrorConsentModal';
import {
CloseRequestedEvent,
getCurrentWindow,
@@ -36,6 +24,18 @@ import {
} from '@tauri-apps/api/window';
import { useAtomValue } from 'jotai';
import { connectedIMUTrackersAtom } from '@/store/app-store';
import { ErrorConsentModal } from './ErrorConsentModal';
import { TrayOrExitModal } from './TrayOrExitModal';
import { TrackersStillOnModal } from './TrackersStillOnModal';
import { GearIcon } from './commons/icon/GearIcon';
import { QuestionIcon } from './commons/icon/QuestionIcon';
import { DownloadIcon } from './commons/icon/DownloadIcon';
import { Typography } from './commons/Typography';
import { ProgressBar } from './commons/ProgressBar';
import { SlimeVRIcon } from './commons/icon/SimevrIcon';
import { MinimiseIcon } from './commons/icon/MinimiseIcon';
import { MaximiseIcon } from './commons/icon/MaximiseIcon';
import { CloseIcon } from './commons/icon/CloseIcon';
export function VersionTag() {
return (
@@ -155,7 +155,7 @@ export function TopBar({
return (
<>
<div className="flex gap-0 flex-col">
<div className="h-[3px]"></div>
<div className="h-[3px]" />
<div data-tauri-drag-region className="flex gap-2 h-[38px] z-50">
<div
className="flex px-2 py-2 justify-around z-50"
@@ -168,7 +168,7 @@ export function TopBar({
className="flex justify-around flex-col select-all"
data-tauri-drag-region
>
<SlimeVRIcon></SlimeVRIcon>
<SlimeVRIcon />
</NavLink>
)}
{(isTauri || !isMobile) && !config?.decorations && (
@@ -181,7 +181,7 @@ export function TopBar({
)}
{(!(isMobile && !config?.decorations) || showVersionMobile) && (
<>
<VersionTag></VersionTag>
<VersionTag />
{doesMatchSettings && (
<div
className={classNames(
@@ -206,7 +206,7 @@ export function TopBar({
open(url).catch(() => window.open(url, '_blank'));
}}
>
<DownloadIcon></DownloadIcon>
<DownloadIcon />
</div>
)}
</div>
@@ -216,20 +216,14 @@ export function TopBar({
data-tauri-drag-region
>
{!isMobile && (
<>
<div
className="flex max-w-xl h-full items-center w-full"
data-tauri-drag-region
>
{progress !== undefined && (
<ProgressBar
progress={progress}
height={3}
parts={3}
></ProgressBar>
)}
</div>
</>
<div
className="flex max-w-xl h-full items-center w-full"
data-tauri-drag-region
>
{progress !== undefined && (
<ProgressBar progress={progress} height={3} parts={3} />
)}
</div>
)}
{!isTauri && !showVersionMobile && !config?.decorations && (
@@ -256,7 +250,7 @@ export function TopBar({
data-tauri-drag-region
state={{ scrollTo: 'steamvr' }}
>
<GearIcon></GearIcon>
<GearIcon />
</NavLink>
{!isMobile && (
@@ -269,7 +263,7 @@ export function TopBar({
open(DOCS_SITE).catch(() => window.open(DOCS_SITE, '_blank'))
}
>
<QuestionIcon></QuestionIcon>
<QuestionIcon />
</div>
)}
@@ -279,19 +273,19 @@ export function TopBar({
className="flex items-center justify-center hover:bg-background-60 rounded-full w-7 h-7"
onClick={() => getCurrentWindow().minimize()}
>
<MinimiseIcon></MinimiseIcon>
<MinimiseIcon />
</div>
<div
className="flex items-center justify-center hover:bg-background-60 rounded-full w-7 h-7"
onClick={() => getCurrentWindow().toggleMaximize()}
>
<MaximiseIcon></MaximiseIcon>
<MaximiseIcon />
</div>
<div
className="flex items-center justify-center hover:bg-background-60 rounded-full w-7 h-7"
onClick={() => tryCloseApp()}
>
<CloseIcon></CloseIcon>
<CloseIcon />
</div>
</>
)}
@@ -299,7 +293,7 @@ export function TopBar({
</div>
{isMobile && progress !== undefined && (
<div className="flex gap-2 px-2 h-6 mb-2 justify-center flex-col border-b border-accent-background-30">
<ProgressBar progress={progress} height={3} parts={3}></ProgressBar>
<ProgressBar progress={progress} height={3} parts={3} />
</div>
)}
</div>
@@ -333,7 +327,7 @@ export function TopBar({
setConnectedTrackerWarning(false);
getCurrentWindow().requestUserAttention(null);
}}
></TrackersStillOnModal>
/>
<ErrorConsentModal
isOpen={config?.errorTracking === null}
accept={() => setConfig({ errorTracking: true })}

View File

@@ -26,25 +26,23 @@ export function TrackersStillOnModal({
return (
<BaseModal isOpen={isOpen} onRequestClose={cancel} important>
<div className="flex flex-col gap-3">
<>
<div className="flex flex-col items-center gap-3 fill-accent-background-20">
<div className="flex flex-col items-center gap-2">
<Typography variant="main-title">
{l10n.getString('trackers_still_on-modal-title')}
</Typography>
<Typography variant="standard">
{l10n.getString('trackers_still_on-modal-description')}
</Typography>
</div>
<div className="flex flex-col items-center gap-3 fill-accent-background-20">
<div className="flex flex-col items-center gap-2">
<Typography variant="main-title">
{l10n.getString('trackers_still_on-modal-title')}
</Typography>
<Typography variant="standard">
{l10n.getString('trackers_still_on-modal-description')}
</Typography>
</div>
</div>
<Button variant="primary" onClick={accept}>
{l10n.getString('trackers_still_on-modal-confirm')}
</Button>
<Button variant="tertiary" onClick={cancel}>
{l10n.getString('trackers_still_on-modal-cancel')}
</Button>
</>
<Button variant="primary" onClick={accept}>
{l10n.getString('trackers_still_on-modal-confirm')}
</Button>
<Button variant="tertiary" onClick={cancel}>
{l10n.getString('trackers_still_on-modal-cancel')}
</Button>
</div>
</BaseModal>
);

View File

@@ -7,14 +7,14 @@ import {
TrackingPauseStateRequestT,
} from 'solarxr-protocol';
import { useWebsocketAPI } from '@/hooks/websocket-api';
import classNames from 'classnames';
import { BigButton } from './commons/BigButton';
import { PlayIcon } from './commons/icon/PlayIcon';
import { PauseIcon } from './commons/icon/PauseIcon';
import classNames from 'classnames';
export function TrackingPauseButton(
props: React.HTMLAttributes<HTMLButtonElement>
) {
export function TrackingPauseButton({
className,
}: React.HTMLAttributes<HTMLButtonElement>) {
const { useRPCPacket, sendRPCPacket } = useWebsocketAPI();
const [trackingPause, setTrackingPause] = useState(false);
@@ -44,8 +44,8 @@ export function TrackingPauseButton(
trackingPause ? <PlayIcon width={20} /> : <PauseIcon width={20} />
}
onClick={toggleTracking}
className={classNames(props.className, 'min-h-24')}
></BigButton>
className={classNames(className, 'min-h-24')}
/>
</Localized>
);
}

View File

@@ -1,8 +1,8 @@
import { useLocalization } from '@fluent/react';
import { useForm } from 'react-hook-form';
import { BaseModal } from './commons/BaseModal';
import { Button } from './commons/Button';
import { Typography } from './commons/Typography';
import { useForm } from 'react-hook-form';
import { Radio } from './commons/Radio';
interface TrayOrExitForm {
@@ -57,13 +57,13 @@ export function TrayOrExitModal({
name="exitType"
label={l10n.getString('tray_or_exit_modal-radio-exit')}
value="0"
></Radio>
/>
<Radio
control={control}
name="exitType"
label={l10n.getString('tray_or_exit_modal-radio-tray')}
value="1"
></Radio>
/>
</div>
<Button type="submit" variant="primary">

View File

@@ -1,7 +1,4 @@
import { useState } from 'react';
import { BaseModal } from './commons/BaseModal';
import { Typography } from './commons/Typography';
import { Button } from './commons/Button';
import { Localized, useLocalization } from '@fluent/react';
import { useWebsocketAPI } from '@/hooks/websocket-api';
import { useLocation } from 'react-router-dom';
@@ -13,6 +10,9 @@ import {
import { useDebouncedEffect } from '@/hooks/timeout';
import { useAtom } from 'jotai';
import { ignoredTrackersAtom } from '@/store/app-store';
import { Button } from './commons/Button';
import { Typography } from './commons/Typography';
import { BaseModal } from './commons/BaseModal';
export function UnknownDeviceModal() {
const { l10n } = useLocalization();
@@ -63,7 +63,7 @@ export function UnknownDeviceModal() {
</Typography>
<Localized
id="unknown_device-modal-description"
elems={{ b: <b></b> }}
elems={{ b: <b /> }}
vars={{ deviceId: currentTracker ?? 'ERROR' }}
>
<Typography
@@ -93,7 +93,7 @@ export function UnknownDeviceModal() {
variant="tertiary"
onClick={() => {
setIgnoredTracker((state) => {
if (!currentTracker) throw 'should have a tracker';
if (!currentTracker) throw Error('should have a tracker');
state.add(currentTracker);
return state;
});

View File

@@ -1,12 +1,12 @@
import { useLocalization } from '@fluent/react';
import { useContext, useState } from 'react';
import { BaseModal } from './commons/BaseModal';
import { Button } from './commons/Button';
import { Typography } from './commons/Typography';
import { open } from '@tauri-apps/plugin-shell';
import semver from 'semver';
import { GH_REPO, VersionContext } from '@/App';
import { error } from '@/utils/logging';
import { Typography } from './commons/Typography';
import { Button } from './commons/Button';
import { BaseModal } from './commons/BaseModal';
export function VersionUpdateModal() {
const { l10n } = useLocalization();
@@ -34,36 +34,34 @@ export function VersionUpdateModal() {
onRequestClose={closeModal}
>
<div className="flex flex-col gap-3">
<>
<div className="flex flex-col items-center gap-3 fill-accent-background-20">
<div className="flex flex-col items-center gap-2">
<Typography variant="main-title">
{l10n.getString('version_update-title', {
version: newVersion,
})}
</Typography>
<Typography variant="standard">
{l10n.getString('version_update-description')}
</Typography>
</div>
<div className="flex flex-col items-center gap-3 fill-accent-background-20">
<div className="flex flex-col items-center gap-2">
<Typography variant="main-title">
{l10n.getString('version_update-title', {
version: newVersion,
})}
</Typography>
<Typography variant="standard">
{l10n.getString('version_update-description')}
</Typography>
</div>
</div>
<Button
variant="primary"
onClick={async () => {
const url = document.body.classList.contains('windows')
? 'https://slimevr.dev/download'
: `https://github.com/${GH_REPO}/releases/latest`;
await open(url).catch(() => window.open(url, '_blank'));
closeModal();
}}
>
{l10n.getString('version_update-update')}
</Button>
<Button variant="tertiary" onClick={closeModal}>
{l10n.getString('version_update-close')}
</Button>
</>
<Button
variant="primary"
onClick={async () => {
const url = document.body.classList.contains('windows')
? 'https://slimevr.dev/download'
: `https://github.com/${GH_REPO}/releases/latest`;
await open(url).catch(() => window.open(url, '_blank'));
closeModal();
}}
>
{l10n.getString('version_update-update')}
</Button>
<Button variant="tertiary" onClick={closeModal}>
{l10n.getString('version_update-close')}
</Button>
</div>
</BaseModal>
);

View File

@@ -1,18 +1,18 @@
import { Localized, useLocalization } from '@fluent/react';
import { useConfig } from '@/hooks/config';
import { ResetType, StatusData } from 'solarxr-protocol';
import { useMemo } from 'react';
import { parseStatusToLocale, useStatusContext } from '@/hooks/status-system';
import { useAtomValue } from 'jotai';
import { flatTrackersAtom } from '@/store/app-store';
import { BVHButton } from './BVHButton';
import { TrackingPauseButton } from './TrackingPauseButton';
import { ResetButton } from './home/ResetButton';
import { OverlayWidget } from './widgets/OverlayWidget';
import { TipBox } from './commons/TipBox';
import { DeveloperModeWidget } from './widgets/DeveloperModeWidget';
import { useConfig } from '@/hooks/config';
import { ResetType, StatusData } from 'solarxr-protocol';
import { useMemo } from 'react';
import { parseStatusToLocale, useStatusContext } from '@/hooks/status-system';
import { ClearMountingButton } from './ClearMountingButton';
import { ToggleableSkeletonVisualizerWidget } from './widgets/SkeletonVisualizerWidget';
import { useAtomValue } from 'jotai';
import { flatTrackersAtom } from '@/store/app-store';
import { A } from './commons/A';
function UnprioritizedStatuses() {
@@ -33,7 +33,7 @@ function UnprioritizedStatuses() {
key={status.id}
elems={{
PublicFixLink: (
<A href="https://docs.slimevr.dev/common-issues.html#network-profile-is-currently-set-to-public"></A>
<A href="https://docs.slimevr.dev/common-issues.html#network-profile-is-currently-set-to-public" />
),
}}
>
@@ -52,35 +52,35 @@ export function WidgetsComponent() {
return (
<>
<div className="grid grid-cols-2 gap-2 w-full [&>*:nth-child(odd):last-of-type]:col-span-full">
<ResetButton type={ResetType.Yaw} size="big"></ResetButton>
<ResetButton type={ResetType.Full} size="big"></ResetButton>
<ResetButton type={ResetType.Mounting} size="big"></ResetButton>
<ResetButton type={ResetType.Yaw} size="big" />
<ResetButton type={ResetType.Full} size="big" />
<ResetButton type={ResetType.Mounting} size="big" />
<ResetButton
type={ResetType.Mounting}
size="big"
bodyPartsToReset="feet"
></ResetButton>
/>
<ResetButton
type={ResetType.Mounting}
size="big"
bodyPartsToReset="fingers"
></ResetButton>
<ClearMountingButton></ClearMountingButton>
/>
<ClearMountingButton />
{(typeof __ANDROID__ === 'undefined' || !__ANDROID__?.isThere()) && (
<BVHButton></BVHButton>
<BVHButton />
)}
<TrackingPauseButton></TrackingPauseButton>
<TrackingPauseButton />
</div>
<div className="w-full">
<OverlayWidget></OverlayWidget>
<OverlayWidget />
</div>
<div className="mb-2">
<ToggleableSkeletonVisualizerWidget height={400} />
</div>
<UnprioritizedStatuses></UnprioritizedStatuses>
<UnprioritizedStatuses />
{config?.debug && (
<div className="w-full">
<DeveloperModeWidget></DeveloperModeWidget>
<DeveloperModeWidget />
</div>
)}
</>

View File

@@ -4,7 +4,7 @@ import { ReactNode } from 'react';
export function A({ href, children }: { href?: string; children?: ReactNode }) {
return (
<a
href="javascript:void(0)"
href={'javascript:'}
onClick={() =>
href && open(href).catch(() => window.open(href, '_blank'))
}

View File

@@ -36,13 +36,13 @@ export function ArrowLink({
<NavLink to={to} state={state} className={classes}>
{direction === 'left' && (
<div className="flex flex-col justify-center">
<ArrowLeftIcon></ArrowLeftIcon>
<ArrowLeftIcon />
</div>
)}
{children}
{direction === 'right' && (
<div className="flex flex-col justify-center">
<ArrowRightIcon></ArrowRightIcon>
<ArrowRightIcon />
</div>
)}
</NavLink>

View File

@@ -8,9 +8,9 @@ import {
useState,
} from 'react';
import { BodyPart, TrackerDataT } from 'solarxr-protocol';
import { useTracker } from '@/hooks/tracker';
import { PersonFrontIcon } from './PersonFrontIcon';
import { uniqueNumberFromTracker, useTracker } from '@/hooks/tracker';
import { FlatDeviceTracker } from '@/store/app-store';
import { PersonFrontIcon } from './PersonFrontIcon';
interface SlotDot {
id: string;
@@ -45,7 +45,7 @@ function Tracker({
updateVelocity(velocity);
}, [velocity]);
return <></>;
return null;
}
function Dot({
@@ -94,11 +94,11 @@ function Dot({
height: dotSize,
outlineWidth: globalVelocity * 2 + 2,
}}
></div>
{trackers?.map(({ tracker }, index) => (
/>
{trackers?.map(({ tracker }) => (
<Tracker
tracker={tracker}
key={index}
key={uniqueNumberFromTracker(tracker)}
updateVelocity={(vel) => updateVelocity(vel)}
/>
))}
@@ -126,14 +126,11 @@ export function BodyDisplay({
const personRef = useRef<HTMLDivElement | null>(null);
const [slotsButtonsPos, setSlotsButtonPos] = useState<SlotDot[]>([]);
const getSlotsPos = () => {
return (
(personRef.current && [
...(personRef.current.querySelectorAll('.body-part-circle') as any),
]) ||
[]
);
};
const getSlotsPos = () =>
(personRef.current && [
...(personRef.current.querySelectorAll('.body-part-circle') as any),
]) ||
[];
const getOffset = (el: HTMLDivElement, offset = { left: 0, top: 0 }) => {
const rect = el.getBoundingClientRect();
@@ -166,7 +163,7 @@ export function BodyDisplay({
const trackerPartGrouped = useMemo(
() =>
trackers.reduce<{ [key: number]: FlatDeviceTracker[] }>((curr, td) => {
trackers.reduce<Record<number, FlatDeviceTracker[]>>((curr, td) => {
if (!td) return curr;
const key = td.tracker.info?.bodyPart || BodyPart.NONE;
@@ -187,7 +184,7 @@ export function BodyDisplay({
variant === 'tracker-select' && 'mx-10'
)}
>
<PersonFrontIcon width={width}></PersonFrontIcon>
<PersonFrontIcon width={width} />
{slotsButtonsPos.map((dotData) => (
<Dot
{...dotData}

View File

@@ -1,8 +1,8 @@
import classNames from 'classnames';
import { ReactNode, useEffect, useRef, useState } from 'react';
import { BodyPart } from 'solarxr-protocol';
import { PersonFrontIcon } from './PersonFrontIcon';
import { useBreakpoint } from '@/hooks/breakpoint';
import { PersonFrontIcon } from './PersonFrontIcon';
export function BodyInteractions({
leftControls,
@@ -52,14 +52,11 @@ export function BodyInteractions({
}[]
>([]);
const getSlotsPos = () => {
return (
(personRef.current && [
...(personRef.current.querySelectorAll('.body-part-circle') as any),
]) ||
[]
);
};
const getSlotsPos = () =>
(personRef.current && [
...(personRef.current.querySelectorAll('.body-part-circle') as any),
]) ||
[];
const getControlsPos = () => {
const pos = (container: HTMLDivElement) =>
@@ -198,7 +195,7 @@ export function BodyInteractions({
className="absolute w-full h-full top-0 z-10"
width="100%"
height="100%"
></canvas>
/>
<div className="flex">
<div ref={leftContainerRef} className="z-10">
{leftControls}
@@ -210,7 +207,7 @@ export function BodyInteractions({
variant === 'tracker-select' && 'mobile:mx-0 xs:mx-10'
)}
>
<PersonFrontIcon width={width} mirror={mirror}></PersonFrontIcon>
<PersonFrontIcon width={width} mirror={mirror} />
{slotsButtonsPos.map(
({ top, left, height, width, id, hidden, buttonOffset }) => (
<div
@@ -234,7 +231,7 @@ export function BodyInteractions({
height: dotsSize,
animationDuration: '1.5s',
}}
></div>
/>
)}
<div
className={classNames(
@@ -249,7 +246,7 @@ export function BodyInteractions({
width: dotsSize,
height: dotsSize,
}}
></div>
/>
</div>
</div>
)

View File

@@ -28,155 +28,103 @@ export const mapPart: Record<
currentLocales: string[];
}) => JSX.Element
> = {
[BodyPart.UPPER_CHEST]: ({ width }) => (
<UpperChestIcon width={width}></UpperChestIcon>
),
[BodyPart.CHEST]: ({ width }) => <ChestIcon width={width}></ChestIcon>,
[BodyPart.HEAD]: ({ width }) => <HeadsetIcon width={width}></HeadsetIcon>,
[BodyPart.HIP]: ({ width }) => <HipIcon width={width}></HipIcon>,
[BodyPart.LEFT_HIP]: ({ width }) => <HipIcon width={width}></HipIcon>, // Unused
[BodyPart.RIGHT_HIP]: ({ width }) => <HipIcon width={width}></HipIcon>, // Unused
[BodyPart.UPPER_CHEST]: ({ width }) => <UpperChestIcon width={width} />,
[BodyPart.CHEST]: ({ width }) => <ChestIcon width={width} />,
[BodyPart.HEAD]: ({ width }) => <HeadsetIcon width={width} />,
[BodyPart.HIP]: ({ width }) => <HipIcon width={width} />,
[BodyPart.LEFT_HIP]: ({ width }) => <HipIcon width={width} />, // Unused
[BodyPart.RIGHT_HIP]: ({ width }) => <HipIcon width={width} />, // Unused
[BodyPart.LEFT_FOOT]: ({ width, currentLocales }) =>
currentLocales.includes('en-x-owo') ? (
<PawIcon></PawIcon>
<PawIcon />
) : (
<FootIcon width={width}></FootIcon>
<FootIcon width={width} />
),
[BodyPart.LEFT_HAND]: ({ width }) => (
<ControllerIcon width={width}></ControllerIcon>
),
[BodyPart.LEFT_LOWER_ARM]: ({ width }) => (
<LowerArmIcon width={width}></LowerArmIcon>
),
[BodyPart.LEFT_LOWER_LEG]: ({ width }) => (
<AnkleIcon width={width}></AnkleIcon>
),
[BodyPart.LEFT_SHOULDER]: ({ width }) => (
<ShoulderIcon width={width}></ShoulderIcon>
),
[BodyPart.LEFT_UPPER_ARM]: ({ width }) => (
<UpperArmIcon width={width}></UpperArmIcon>
),
[BodyPart.LEFT_UPPER_LEG]: ({ width }) => (
<UpperLegIcon width={width}></UpperLegIcon>
),
[BodyPart.NECK]: ({ width }) => <NeckIcon width={width}></NeckIcon>,
[BodyPart.NONE]: ({ width }) => <SlimeVRIcon width={width}></SlimeVRIcon>,
[BodyPart.LEFT_HAND]: ({ width }) => <ControllerIcon width={width} />,
[BodyPart.LEFT_LOWER_ARM]: ({ width }) => <LowerArmIcon width={width} />,
[BodyPart.LEFT_LOWER_LEG]: ({ width }) => <AnkleIcon width={width} />,
[BodyPart.LEFT_SHOULDER]: ({ width }) => <ShoulderIcon width={width} />,
[BodyPart.LEFT_UPPER_ARM]: ({ width }) => <UpperArmIcon width={width} />,
[BodyPart.LEFT_UPPER_LEG]: ({ width }) => <UpperLegIcon width={width} />,
[BodyPart.NECK]: ({ width }) => <NeckIcon width={width} />,
[BodyPart.NONE]: ({ width }) => <SlimeVRIcon width={width} />,
[BodyPart.RIGHT_FOOT]: ({ width, currentLocales }) =>
currentLocales.includes('en-x-owo') ? (
<PawIcon></PawIcon>
<PawIcon />
) : (
<FootIcon width={width} flipped></FootIcon>
<FootIcon width={width} flipped />
),
[BodyPart.RIGHT_HAND]: ({ width }) => (
<ControllerIcon width={width} flipped></ControllerIcon>
<ControllerIcon width={width} flipped />
),
[BodyPart.RIGHT_LOWER_ARM]: ({ width }) => (
<LowerArmIcon width={width} flipped></LowerArmIcon>
<LowerArmIcon width={width} flipped />
),
[BodyPart.RIGHT_LOWER_LEG]: ({ width }) => (
<AnkleIcon width={width} flipped></AnkleIcon>
),
[BodyPart.RIGHT_SHOULDER]: ({ width }) => (
<ShoulderIcon width={width}></ShoulderIcon>
<AnkleIcon width={width} flipped />
),
[BodyPart.RIGHT_SHOULDER]: ({ width }) => <ShoulderIcon width={width} />,
[BodyPart.RIGHT_UPPER_ARM]: ({ width }) => (
<UpperArmIcon width={width} flipped></UpperArmIcon>
<UpperArmIcon width={width} flipped />
),
[BodyPart.RIGHT_UPPER_LEG]: ({ width }) => (
<UpperLegIcon width={width} flipped></UpperLegIcon>
<UpperLegIcon width={width} flipped />
),
[BodyPart.WAIST]: ({ width }) => <WaistIcon width={width}></WaistIcon>,
[BodyPart.WAIST]: ({ width }) => <WaistIcon width={width} />,
[BodyPart.LEFT_THUMB_METACARPAL]: ({ width }) => (
<FingersIcon width={width}></FingersIcon>
),
[BodyPart.LEFT_THUMB_PROXIMAL]: ({ width }) => (
<FingersIcon width={width}></FingersIcon>
),
[BodyPart.LEFT_THUMB_DISTAL]: ({ width }) => (
<FingersIcon width={width}></FingersIcon>
),
[BodyPart.LEFT_INDEX_PROXIMAL]: ({ width }) => (
<FingersIcon width={width}></FingersIcon>
<FingersIcon width={width} />
),
[BodyPart.LEFT_THUMB_PROXIMAL]: ({ width }) => <FingersIcon width={width} />,
[BodyPart.LEFT_THUMB_DISTAL]: ({ width }) => <FingersIcon width={width} />,
[BodyPart.LEFT_INDEX_PROXIMAL]: ({ width }) => <FingersIcon width={width} />,
[BodyPart.LEFT_INDEX_INTERMEDIATE]: ({ width }) => (
<FingersIcon width={width}></FingersIcon>
),
[BodyPart.LEFT_INDEX_DISTAL]: ({ width }) => (
<FingersIcon width={width}></FingersIcon>
),
[BodyPart.LEFT_MIDDLE_PROXIMAL]: ({ width }) => (
<FingersIcon width={width}></FingersIcon>
<FingersIcon width={width} />
),
[BodyPart.LEFT_INDEX_DISTAL]: ({ width }) => <FingersIcon width={width} />,
[BodyPart.LEFT_MIDDLE_PROXIMAL]: ({ width }) => <FingersIcon width={width} />,
[BodyPart.LEFT_MIDDLE_INTERMEDIATE]: ({ width }) => (
<FingersIcon width={width}></FingersIcon>
),
[BodyPart.LEFT_MIDDLE_DISTAL]: ({ width }) => (
<FingersIcon width={width}></FingersIcon>
),
[BodyPart.LEFT_RING_PROXIMAL]: ({ width }) => (
<FingersIcon width={width}></FingersIcon>
<FingersIcon width={width} />
),
[BodyPart.LEFT_MIDDLE_DISTAL]: ({ width }) => <FingersIcon width={width} />,
[BodyPart.LEFT_RING_PROXIMAL]: ({ width }) => <FingersIcon width={width} />,
[BodyPart.LEFT_RING_INTERMEDIATE]: ({ width }) => (
<FingersIcon width={width}></FingersIcon>
),
[BodyPart.LEFT_RING_DISTAL]: ({ width }) => (
<FingersIcon width={width}></FingersIcon>
),
[BodyPart.LEFT_LITTLE_PROXIMAL]: ({ width }) => (
<FingersIcon width={width}></FingersIcon>
<FingersIcon width={width} />
),
[BodyPart.LEFT_RING_DISTAL]: ({ width }) => <FingersIcon width={width} />,
[BodyPart.LEFT_LITTLE_PROXIMAL]: ({ width }) => <FingersIcon width={width} />,
[BodyPart.LEFT_LITTLE_INTERMEDIATE]: ({ width }) => (
<FingersIcon width={width}></FingersIcon>
),
[BodyPart.LEFT_LITTLE_DISTAL]: ({ width }) => (
<FingersIcon width={width}></FingersIcon>
<FingersIcon width={width} />
),
[BodyPart.LEFT_LITTLE_DISTAL]: ({ width }) => <FingersIcon width={width} />,
[BodyPart.RIGHT_THUMB_METACARPAL]: ({ width }) => (
<FingersIcon width={width}></FingersIcon>
),
[BodyPart.RIGHT_THUMB_PROXIMAL]: ({ width }) => (
<FingersIcon width={width}></FingersIcon>
),
[BodyPart.RIGHT_THUMB_DISTAL]: ({ width }) => (
<FingersIcon width={width}></FingersIcon>
),
[BodyPart.RIGHT_INDEX_PROXIMAL]: ({ width }) => (
<FingersIcon width={width}></FingersIcon>
<FingersIcon width={width} />
),
[BodyPart.RIGHT_THUMB_PROXIMAL]: ({ width }) => <FingersIcon width={width} />,
[BodyPart.RIGHT_THUMB_DISTAL]: ({ width }) => <FingersIcon width={width} />,
[BodyPart.RIGHT_INDEX_PROXIMAL]: ({ width }) => <FingersIcon width={width} />,
[BodyPart.RIGHT_INDEX_INTERMEDIATE]: ({ width }) => (
<FingersIcon width={width}></FingersIcon>
),
[BodyPart.RIGHT_INDEX_DISTAL]: ({ width }) => (
<FingersIcon width={width}></FingersIcon>
<FingersIcon width={width} />
),
[BodyPart.RIGHT_INDEX_DISTAL]: ({ width }) => <FingersIcon width={width} />,
[BodyPart.RIGHT_MIDDLE_PROXIMAL]: ({ width }) => (
<FingersIcon width={width}></FingersIcon>
<FingersIcon width={width} />
),
[BodyPart.RIGHT_MIDDLE_INTERMEDIATE]: ({ width }) => (
<FingersIcon width={width}></FingersIcon>
),
[BodyPart.RIGHT_MIDDLE_DISTAL]: ({ width }) => (
<FingersIcon width={width}></FingersIcon>
),
[BodyPart.RIGHT_RING_PROXIMAL]: ({ width }) => (
<FingersIcon width={width}></FingersIcon>
<FingersIcon width={width} />
),
[BodyPart.RIGHT_MIDDLE_DISTAL]: ({ width }) => <FingersIcon width={width} />,
[BodyPart.RIGHT_RING_PROXIMAL]: ({ width }) => <FingersIcon width={width} />,
[BodyPart.RIGHT_RING_INTERMEDIATE]: ({ width }) => (
<FingersIcon width={width}></FingersIcon>
),
[BodyPart.RIGHT_RING_DISTAL]: ({ width }) => (
<FingersIcon width={width}></FingersIcon>
<FingersIcon width={width} />
),
[BodyPart.RIGHT_RING_DISTAL]: ({ width }) => <FingersIcon width={width} />,
[BodyPart.RIGHT_LITTLE_PROXIMAL]: ({ width }) => (
<FingersIcon width={width}></FingersIcon>
<FingersIcon width={width} />
),
[BodyPart.RIGHT_LITTLE_INTERMEDIATE]: ({ width }) => (
<FingersIcon width={width}></FingersIcon>
),
[BodyPart.RIGHT_LITTLE_DISTAL]: ({ width }) => (
<FingersIcon width={width}></FingersIcon>
<FingersIcon width={width} />
),
[BodyPart.RIGHT_LITTLE_DISTAL]: ({ width }) => <FingersIcon width={width} />,
};
export function BodyPartIcon({

View File

@@ -29,7 +29,7 @@ function ButtonContent({
</div>
{loading && (
<div className="absolute top-0 left-0 w-full h-full flex justify-center items-center fill-background-10">
<LoaderIcon slimeState={SlimeState.JUMPY}></LoaderIcon>
<LoaderIcon slimeState={SlimeState.JUMPY} />
</div>
)}
</>

View File

@@ -100,7 +100,7 @@ export function CheckBox({
'right-0': value && !loading,
'bg-background-30': disabled,
})}
></div>
/>
</div>
)}
{label}

View File

@@ -68,7 +68,7 @@ export function DropdownItems({
<div
className="z-[999] fixed top-0 w-full h-full"
onClick={onBackdropClick}
></div>
/>
<div
ref={ref}
className={classNames(
@@ -80,7 +80,7 @@ export function DropdownItems({
itemBounds?.height == 0 && 'opacity-0' // Avoid flicker while the component find its position
)}
style={{
maxHeight: maxHeight,
maxHeight,
left:
alignment === 'left'
? dropdownBounds.left
@@ -117,6 +117,7 @@ export function DropdownItems({
onSelectItem(item);
}}
key={item.value}
role="button"
tabIndex={0}
data-checked={item.value === value}
>
@@ -194,6 +195,7 @@ export function Dropdown({
)}
onClick={() => setOpen((open) => !open)}
onKeyDown={(ev) => a11yClick(ev) && setOpen((open) => !open)}
role="button"
tabIndex={0}
>
<div className="flex-grow text-standard first:pointer-events-none">
@@ -208,7 +210,7 @@ export function Dropdown({
direction == 'down' && 'rotate-0'
)}
>
<ArrowDownIcon size={16}></ArrowDownIcon>
<ArrowDownIcon size={16} />
</div>
</div>
{isOpen &&
@@ -230,7 +232,7 @@ export function Dropdown({
onBackdropClick={() => {
setOpen(false);
}}
></DropdownItems>,
/>,
document.body
)}
</>

View File

@@ -19,13 +19,13 @@ interface InputProps {
name: string;
}
export const FileInputContentBlank = ({
export function FileInputContentBlank({
isDragging,
label,
}: {
isDragging: boolean;
label: string;
}) => {
}) {
return (
<div
className={classNames(
@@ -41,7 +41,7 @@ export const FileInputContentBlank = ({
<Localized
id={label}
elems={{
u: <span className="underline text-background-20"></span>,
u: <span className="underline text-background-20" />,
}}
>
<Typography>
@@ -53,15 +53,15 @@ export const FileInputContentBlank = ({
</span>
</div>
);
};
}
export const FileInputContentFile = ({
export function FileInputContentFile({
importedFileName,
onClearPicker,
}: {
importedFileName: string;
onClearPicker: () => any;
}) => {
}) {
return (
<div
className={classNames(
@@ -75,7 +75,7 @@ export const FileInputContentFile = ({
<FileIcon />
<span>{importedFileName}</span>
</div>
<span className="flex-grow"></span>
<span className="flex-grow" />
<a
href="#"
className="h-12 w-12 hover:bg-accent-background-20 cursor-pointer"
@@ -92,7 +92,7 @@ export const FileInputContentFile = ({
</div>
</div>
);
};
}
export const FileInputInside = forwardRef<
HTMLInputElement,
@@ -107,83 +107,85 @@ export const FileInputInside = forwardRef<
name: string;
importedFileName: string | null;
}
>(function AppInput(
{
label = 'tips-file_select',
name,
onChange,
accept,
capture,
multiple = false,
importedFileName,
},
ref
) {
const innerRef = useRef<HTMLInputElement>(null);
>(
(
{
label = 'tips-file_select',
name,
onChange,
accept,
capture,
multiple = false,
importedFileName,
},
ref
) => {
const innerRef = useRef<HTMLInputElement>(null);
useImperativeHandle(ref, () => innerRef.current!);
useImperativeHandle(ref, () => innerRef.current!);
const acceptList = useMemo(() => accept.split(/, ?/), [accept]);
const [isDragging, setDragging] = useState(false);
const acceptList = useMemo(() => accept.split(/, ?/), [accept]);
const [isDragging, setDragging] = useState(false);
const isFileImported = importedFileName !== null && !isDragging;
const isFileImported = importedFileName !== null && !isDragging;
const onClearPicker = () => {
onChange([]);
innerRef.current!.value = '';
};
const onClearPicker = () => {
onChange([]);
innerRef.current!.value = '';
};
return (
<label
onDragOver={(ev) => ev.preventDefault()}
onDrop={(ev) => {
ev.preventDefault();
setDragging(false);
return (
<label
onDragOver={(ev) => ev.preventDefault()}
onDrop={(ev) => {
ev.preventDefault();
setDragging(false);
if (
ev.dataTransfer.files.length &&
// If MIME type is any of the accept list,
// or if file extension is anything on the acceptList
(acceptList.includes(ev.dataTransfer.files[0].type) ||
acceptList.some((ext) =>
ev.dataTransfer.files[0].name.endsWith(ext)
))
) {
onChange(ev.dataTransfer.files);
}
}}
onDragEnter={(ev) => {
ev.preventDefault();
setDragging(true);
}}
onDragLeave={(ev) => {
ev.preventDefault();
setDragging(false);
}}
>
{isFileImported
? FileInputContentFile({ importedFileName, onClearPicker })
: FileInputContentBlank({ isDragging, label })}
<input
type="file"
className="hidden"
onChange={(ev) => {
if (ev.target.files?.length) {
onChange(ev.target.files);
if (
ev.dataTransfer.files.length &&
// If MIME type is any of the accept list,
// or if file extension is anything on the acceptList
(acceptList.includes(ev.dataTransfer.files[0].type) ||
acceptList.some((ext) =>
ev.dataTransfer.files[0].name.endsWith(ext)
))
) {
onChange(ev.dataTransfer.files);
}
}}
name={name}
ref={innerRef}
accept={accept}
multiple={multiple}
capture={capture}
></input>
</label>
);
});
onDragEnter={(ev) => {
ev.preventDefault();
setDragging(true);
}}
onDragLeave={(ev) => {
ev.preventDefault();
setDragging(false);
}}
>
{isFileImported
? FileInputContentFile({ importedFileName, onClearPicker })
: FileInputContentBlank({ isDragging, label })}
export const FileInput = ({
<input
type="file"
className="hidden"
onChange={(ev) => {
if (ev.target.files?.length) {
onChange(ev.target.files);
}
}}
name={name}
ref={innerRef}
accept={accept}
multiple={multiple}
capture={capture}
/>
</label>
);
}
);
export function FileInput({
control,
name,
label,
@@ -201,11 +203,11 @@ export const FileInput = ({
capture?: boolean | 'user' | 'environment';
/**
* Use a translation key!
**/
* */
label?: string;
importedFileName: string | null;
} & InputProps &
Partial<HTMLInputElement>) => {
Partial<HTMLInputElement>) {
return (
<Controller
control={control}
@@ -223,8 +225,8 @@ export const FileInput = ({
capture={capture}
multiple={multiple}
importedFileName={importedFileName}
></FileInputInside>
/>
)}
/>
);
};
}

View File

@@ -22,101 +22,103 @@ export const InputInside = forwardRef<
error?: FieldError;
onChange: () => void;
} & Partial<HTMLInputElement>
>(function AppInput(
{
type,
placeholder,
label,
disabled,
autocomplete,
name,
onChange,
value,
error,
variant = 'primary',
},
ref
) {
const [forceText, setForceText] = useState(false);
>(
(
{
type,
placeholder,
label,
disabled,
autocomplete,
name,
onChange,
value,
error,
variant = 'primary',
},
ref
) => {
const [forceText, setForceText] = useState(false);
const togglePassword = (e: MouseEvent<HTMLDivElement>) => {
e.preventDefault();
setForceText(!forceText);
};
const classes = useMemo(() => {
const variantsMap = {
primary: classNames({
'placeholder:text-background-30 bg-background-60 border-background-60':
!disabled,
'text-background-30 placeholder:text-background-30 border-background-70 bg-background-70':
disabled,
}),
secondary: classNames({
'placeholder:text-background-30 bg-background-50 border-background-50':
!disabled,
'text-background-40 placeholder:text-background-40 border-background-70 bg-background-70':
disabled,
}),
tertiary: classNames({
'placeholder:text-background-30 bg-background-40 border-background-40':
!disabled,
'text-background-30 placeholder:text-background-30 border-background-70 bg-background-70':
disabled,
}),
const togglePassword = (e: MouseEvent<HTMLDivElement>) => {
e.preventDefault();
setForceText(!forceText);
};
return classNames(
variantsMap[variant],
'w-full focus:ring-transparent focus:ring-offset-transparent min-h-[42px] z-10',
'focus:outline-transparent rounded-md focus:border-accent-background-40',
'text-standard relative transition-colors',
error && 'border-status-critical border-1'
const classes = useMemo(() => {
const variantsMap = {
primary: classNames({
'placeholder:text-background-30 bg-background-60 border-background-60':
!disabled,
'text-background-30 placeholder:text-background-30 border-background-70 bg-background-70':
disabled,
}),
secondary: classNames({
'placeholder:text-background-30 bg-background-50 border-background-50':
!disabled,
'text-background-40 placeholder:text-background-40 border-background-70 bg-background-70':
disabled,
}),
tertiary: classNames({
'placeholder:text-background-30 bg-background-40 border-background-40':
!disabled,
'text-background-30 placeholder:text-background-30 border-background-70 bg-background-70':
disabled,
}),
};
return classNames(
variantsMap[variant],
'w-full focus:ring-transparent focus:ring-offset-transparent min-h-[42px] z-10',
'focus:outline-transparent rounded-md focus:border-accent-background-40',
'text-standard relative transition-colors',
error && 'border-status-critical border-1'
);
}, [variant, disabled, error]);
const computedValue = disabled
? placeholder
: value !== undefined
? value
: '';
return (
<label className="flex flex-col gap-1">
{label}
<div className="relative w-full">
<input
type={forceText ? 'text' : type}
className={classNames(classes, {
'pr-10 sentry-mask': type === 'password',
})}
placeholder={placeholder || undefined}
autoComplete={autocomplete ? 'off' : 'on'}
onChange={onChange}
name={name}
value={computedValue} // Do we want that behaviour ?
disabled={disabled}
ref={ref}
/>
{type === 'password' && (
<div
className="fill-background-10 absolute inset-y-0 right-0 pr-6 z-10 my-auto w-[16px] h-[16px] cursor-pointer"
onClick={togglePassword}
>
<EyeIcon width={16} closed={forceText} />
</div>
)}
{error?.message && (
<div className="absolute top-[38px] z-0 pt-1.5 bg-background-70 px-1 w-full rounded-b-md text-status-critical">
{error.message}
</div>
)}
</div>
</label>
);
}, [variant, disabled, error]);
}
);
const computedValue = disabled
? placeholder
: value !== undefined
? value
: '';
return (
<label className="flex flex-col gap-1">
{label}
<div className="relative w-full">
<input
type={forceText ? 'text' : type}
className={classNames(classes, {
'pr-10 sentry-mask': type === 'password',
})}
placeholder={placeholder || undefined}
autoComplete={autocomplete ? 'off' : 'on'}
onChange={onChange}
name={name}
value={computedValue} // Do we want that behaviour ?
disabled={disabled}
ref={ref}
></input>
{type === 'password' && (
<div
className="fill-background-10 absolute inset-y-0 right-0 pr-6 z-10 my-auto w-[16px] h-[16px] cursor-pointer"
onClick={togglePassword}
>
<EyeIcon width={16} closed={forceText}></EyeIcon>
</div>
)}
{error?.message && (
<div className="absolute top-[38px] z-0 pt-1.5 bg-background-70 px-1 w-full rounded-b-md text-status-critical">
{error.message}
</div>
)}
</div>
</label>
);
});
export const Input = ({
export function Input({
type = 'text',
control,
name,
@@ -130,7 +132,7 @@ export const Input = ({
rules?: UseControllerProps<any>['rules'];
control: Control<any>;
} & InputProps &
Partial<HTMLInputElement>) => {
Partial<HTMLInputElement>) {
return (
<Controller
control={control}
@@ -152,8 +154,8 @@ export const Input = ({
onChange={onChange}
ref={ref}
name={name}
></InputInside>
/>
)}
/>
);
};
}

View File

@@ -30,7 +30,7 @@ export function LangSelector({
className="inline-block w-auto h-[1em] -translate-y-[0.05em]"
src={emoji}
/>
{' ' + name}
{` ${name}`}
</div>
),
label: name,
@@ -59,6 +59,6 @@ export function LangSelector({
items={languagesItems}
direction={direction}
alignment={alignment}
></Dropdown>
/>
);
}

View File

@@ -1,8 +1,8 @@
import { Control, Controller } from 'react-hook-form';
import { Button } from './Button';
import { Typography } from './Typography';
import { useCallback, useMemo } from 'react';
import { useLocaleConfig } from '@/i18n/config';
import { Button } from './Button';
import { Typography } from './Typography';
export function NumberSelector({
label,

View File

@@ -38,11 +38,7 @@ export function PersonFrontIcon({
viewBox="0 0 165 392"
xmlns="http://www.w3.org/2000/svg"
>
<image
height={'105%'}
x="8.5%"
href="/images/front-standing-pose.webp"
></image>
<image height={'105%'} x="8.5%" href="/images/front-standing-pose.webp" />
{/* <path d="M84.53 224.074C83.953 230.874 88.569 266.874 90.951 280.984C92.085 287.671 95.195 298.565 94.076 304.349C92.476 312.411 92.017 322.843 92.896 328.918C93.451 332.607 95.196 349.618 92.696 355.845C91.389 359.108 88.996 375.832 88.996 375.832C82.756 391.587 86.278 390.812 86.278 390.812C88.21 393.183 91.519 390.998 91.519 390.998C92.1549 391.464 92.9388 391.682 93.7241 391.612C94.5094 391.542 95.2421 391.188 95.785 390.616C97.949 392.407 100.471 390.396 100.471 390.396C103.189 391.807 105.71 389.205 105.71 389.205C107.271 389.991 107.653 388.998 107.653 388.998C112.337 388.698 105.039 373.706 105.039 373.706C103.291 360.242 106.773 352.748 106.773 352.748C118.178 318.926 118.758 309.948 114.199 297.204C112.915 293.524 112.59 292.067 113.181 290.47C114.547 286.783 113.551 271.953 115.217 266.064C118.431 254.706 121.602 225.903 123.254 212.464C125.475 194.364 115.388 170.088 115.388 170.088C113.179 160.21 116.418 125.016 116.418 125.016C120.941 132.054 120.768 144.477 120.768 144.477C120.05 157.506 131.294 177.42 131.294 177.42C136.694 185.649 138.742 193.456 138.742 194.036C138.742 196.407 138.223 202.145 138.223 202.145L138.43 207.145C138.803 209.721 139.034 212.316 139.123 214.918C138.28 227.953 140.35 225.501 140.35 225.501C142.098 225.501 144.018 215.011 144.018 215.011C144.018 217.711 143.357 225.811 144.818 228.869C146.564 232.512 147.848 228.244 147.871 227.387C148.333 210.787 149.33 215.138 149.33 215.138C150.301 228.602 151.494 231.644 153.63 230.591C155.25 229.818 153.769 214.433 153.769 214.433C156.544 223.572 158.649 225.027 158.649 225.027C163.229 228.243 160.397 219.361 159.76 217.602C156.371 208.256 156.267 205.017 156.267 205.017C160.501 213.417 163.692 213.104 163.692 213.104C167.822 211.786 160.083 199.894 155.548 194.197C153.234 191.297 150.248 187.408 149.384 185.097C147.973 181.188 146.907 168.62 146.907 168.62C146.48 153.79 142.813 147.348 142.813 147.348C136.544 137.314 135.365 118.598 135.365 118.598L135.09 87C132.89 65.445 117.01 65.29 117.01 65.29C100.957 62.9 98.723 57.714 98.723 57.714C95.323 52.821 97.266 43.44 97.266 43.44C100.087 41.145 101.175 35.053 101.175 35.053C105.859 31.461 105.63 26.205 103.466 26.262C101.73 26.308 102.123 24.87 102.123 24.87C105.052 1.208 84.046 0 84.046 0H80.836C80.836 0 59.821 1.208 62.746 24.864C62.746 24.864 63.139 26.304 61.388 26.256C59.23 26.199 59.029 31.456 63.696 35.047C63.696 35.047 64.783 41.137 67.605 43.434C67.605 43.434 69.548 52.814 66.148 57.708C66.148 57.708 63.922 62.894 47.861 65.284C47.861 65.284 31.952 65.44 29.788 86.994L29.488 118.594C29.488 118.594 28.331 137.311 22.038 147.344C22.038 147.344 18.389 153.787 17.967 168.616C17.967 168.616 16.898 181.184 15.492 185.093C14.635 187.393 11.653 191.276 9.32001 194.193C4.74601 199.878 -2.94199 211.745 1.17101 213.1C1.17101 213.1 4.37901 213.412 8.59601 205.013C8.59601 205.013 8.50901 208.229 5.12501 217.598C4.46001 219.334 1.63201 228.217 6.21301 225.024C6.21301 225.024 8.33501 223.567 11.093 214.43C11.093 214.43 9.61301 229.815 11.26 230.588C13.412 231.642 14.586 228.599 15.56 215.135C15.56 215.135 16.56 210.787 17.017 227.384C17.04 228.241 18.295 232.509 20.049 228.866C21.529 225.811 20.864 217.727 20.864 215.008C20.864 215.008 22.764 225.498 24.536 225.498C24.536 225.498 26.624 227.95 25.767 214.915C25.628 212.786 26.375 208.415 26.467 207.142L26.667 202.142C26.667 202.142 26.146 196.417 26.146 194.033C26.146 193.442 28.194 185.646 33.594 177.417C33.594 177.417 44.826 157.494 44.103 144.474C44.103 144.474 43.947 132.051 48.47 125.013C48.47 125.013 51.68 160.205 49.505 170.085C49.505 170.085 39.405 194.358 41.629 212.461C43.27 225.937 46.435 254.702 49.657 266.061C51.34 271.938 50.345 286.761 51.693 290.467C52.301 292.076 51.982 293.558 50.675 297.201C46.141 309.947 46.718 318.925 58.123 352.745C58.123 352.745 61.633 360.239 59.859 373.703C59.859 373.703 52.572 388.695 57.239 388.995C57.239 388.995 57.604 389.988 59.182 389.202C59.182 389.202 61.703 391.802 64.427 390.393C64.427 390.393 66.95 392.407 69.106 390.613C69.6451 391.185 70.3751 391.54 71.158 391.61C71.9409 391.681 72.7225 391.462 73.355 390.995C73.355 390.995 76.664 393.227 78.63 390.809C78.63 390.809 82.123 391.584 75.904 375.829C75.904 375.829 73.522 359.129 72.209 355.842C69.709 349.621 71.474 332.57 72.009 328.915C72.87 322.806 72.409 312.398 70.835 304.346C69.684 298.575 72.801 287.679 73.952 280.981C76.317 266.881 80.952 230.881 80.373 224.071L82.288 224.743C83.0863 224.756 83.8692 224.522 84.53 224.074Z" /> */}
<circle
className="body-part-circle"

View File

@@ -21,6 +21,7 @@ export function ProgressBar({
{Array.from({ length: parts }).map((_, key) => (
<Bar
index={key}
// eslint-disable-next-line react/no-array-index-key
key={key}
progress={progress}
height={height}
@@ -28,7 +29,7 @@ export function ProgressBar({
animated={animated}
parts={parts}
bottom={bottom}
></Bar>
/>
))}
</div>
);
@@ -74,7 +75,7 @@ export function Bar({
width: `${value * 100}%`,
height: `${height}px`,
}}
></div>
/>
</div>
);
}

View File

@@ -1,7 +1,7 @@
import classNames from 'classnames';
import { Control, Controller } from 'react-hook-form';
import { Typography } from './Typography';
import { ReactNode } from 'react';
import { Typography } from './Typography';
export function Radio({
control,
@@ -49,7 +49,7 @@ export function Radio({
{...props}
/>
<div className="flex flex-col gap-2 pointer-events-none">
{children ? children : <Typography bold>{label}</Typography>}
{children || <Typography bold>{label}</Typography>}
{description && (
<Typography variant="standard" color="secondary">
{description}

View File

@@ -42,6 +42,7 @@ export function Range({
/>
<datalist id={`${name}-datalist`} className="">
{values.map(({ value }, i) => (
// eslint-disable-next-line react/no-array-index-key
<option key={i}>{value}</option>
))}
</datalist>
@@ -54,6 +55,7 @@ export function Range({
);
return (
<span
// eslint-disable-next-line react/no-array-index-key
key={i}
className={classNames(
'flex-1',

View File

@@ -4,8 +4,8 @@ import {
RefCallBack,
UseControllerProps,
} from 'react-hook-form';
import { FileInputContentBlank, FileInputContentFile } from './FileInput';
import { open } from '@tauri-apps/plugin-dialog';
import { FileInputContentBlank, FileInputContentFile } from './FileInput';
export function InnerTauriFileInput({
label,
@@ -43,7 +43,7 @@ export function TauriFileInput({
control: Control<any>;
/**
* Use a translation key!
**/
* */
label: string;
name: string;
directory?: boolean;

View File

@@ -1,8 +1,8 @@
import { ReactNode } from 'react';
import classNames from 'classnames';
import { BulbIcon } from './icon/BulbIcon';
import { WarningIcon } from './icon/WarningIcon';
import { Typography } from './Typography';
import classNames from 'classnames';
export function TipBox({
children,
@@ -28,7 +28,7 @@ export function TipBox({
hideIcon && 'hidden'
)}
>
<BulbIcon></BulbIcon>
<BulbIcon />
</div>
<div className="flex flex-col">
<Typography
@@ -62,7 +62,7 @@ export function WarningBox({
hideIcon && 'hidden'
)}
>
<WarningIcon></WarningIcon>
<WarningIcon />
</div>
<div className="flex flex-col justify-center w-full">
<Typography

View File

@@ -28,12 +28,12 @@ interface TooltipPos {
height?: number;
}
type Rect = {
interface Rect {
left: number;
top: number;
width: number;
height: number;
};
}
function overlapArea(rect1: Rect, rect2: Rect) {
// Find the overlap in the x direction (width)
@@ -273,6 +273,8 @@ export function FloatingTooltip({
elem.removeEventListener('mouseleave', onMouseLeave);
};
}
return undefined;
}, []);
useLayoutEffect(() => {
@@ -395,6 +397,8 @@ export function DrawerTooltip({
clearTimeout(touchTimeout.current);
};
}
return undefined;
}, []);
return (
@@ -406,7 +410,7 @@ export function DrawerTooltip({
opacity: drawerStyle ? 0.5 : 0,
pointerEvents: drawerStyle ? 'all' : 'none',
}}
></div>
/>
<div
className={classNames(
'fixed z-50 w-full text-background-10 max-h-full -bottom-full transition-all overflow-clip'
@@ -422,10 +426,11 @@ export function DrawerTooltip({
Pro tip
</Typography>
<button
type="button"
className="absolute right-4 top-3 h-6 w-6 bg-background-70 rounded-full flex justify-center items-center"
onClick={() => close()}
>
<CloseIcon size={20} className="stroke-white"></CloseIcon>
<CloseIcon size={20} className="stroke-white" />
</button>
</div>
<div

View File

@@ -1,6 +1,4 @@
import classNames from 'classnames';
import { CheckIcon } from './icon/CheckIcon';
import { Typography } from './Typography';
import {
FC,
ReactNode,
@@ -11,6 +9,8 @@ import {
} from 'react';
import { useElemSize } from '@/hooks/layout';
import { useDebouncedEffect } from '@/hooks/timeout';
import { Typography } from './Typography';
import { CheckIcon } from './icon/CheckIcon';
export function VerticalStep({
active,
@@ -66,7 +66,7 @@ export function VerticalStep({
)}
>
{isPrevious ? (
<CheckIcon></CheckIcon>
<CheckIcon />
) : (
<Typography variant="section-title">{index + 1}</Typography>
)}
@@ -88,19 +88,19 @@ export function VerticalStep({
);
}
export type VerticalStepComponentProps = {
export interface VerticalStepComponentProps {
nextStep: () => void;
prevStep: () => void;
goTo: (id: string) => void;
isActive: boolean;
};
}
type VerticalStepComponentType = FC<VerticalStepComponentProps>;
export type VerticalStep = {
export interface VerticalStep {
title: string;
id?: string;
component: VerticalStepComponentType;
};
}
export default function VerticalStepper({
steps,
@@ -135,13 +135,14 @@ export default function VerticalStepper({
return (
<ol className="relative border-l border-gray-700 text-gray-400">
{steps.map(({ title, component: StepComponent }, index) => (
// eslint-disable-next-line react/no-array-index-key
<VerticalStep active={currStep} index={index} title={title} key={index}>
<StepComponent
nextStep={nextStep}
prevStep={prevStep}
goTo={goTo}
isActive={currStep === index}
></StepComponent>
/>
</VerticalStep>
))}
</ol>

View File

@@ -11,7 +11,7 @@ export function BatteryIcon({
charging: boolean;
}) {
const col = useMemo(() => {
const colorsMap: { [key: number]: string } = {
const colorsMap: Record<number, string> = {
0.4: 'fill-status-success',
0.2: 'fill-status-warning',
0: 'fill-status-critical',

View File

@@ -7,7 +7,7 @@ export function ChestIcon({ width = 24 }: { width?: number }) {
width={width}
viewBox="0 0 50 50"
>
<path d="M 20 2 C 18.156 2 16.700141 3.1345312 15.619141 4.0195312 C 12.698141 4.2755313 4 7.245 4 15 C 4 17.305 4.551 19.068203 4.875 19.908203 C 4.262 20.969203 3 23.679 3 28 C 3 28.352 3.184375 28.677422 3.484375 28.857422 L 8.484375 31.857422 C 8.835375 32.068422 9.2804219 32.044922 9.6074219 31.794922 C 10.290422 31.270922 10.833578 30.512578 11.267578 29.642578 C 11.118578 28.880578 11 28.05 11 27 L 11 21 C 11 20.448 11.448 20 12 20 C 12.43 20 12.790641 20.27425 12.931641 20.65625 C 12.944641 20.68425 12.982 20.797656 13 20.847656 C 13.197 21.404656 14.365 24 19 24 C 23.133 24 23.984531 19.976687 24.019531 19.804688 C 24.113531 19.329687 24.535 19.018531 25 19.019531 C 25.465 19.019531 25.886469 19.330687 25.980469 19.804688 C 26.015469 19.976687 26.867 24 31 24 C 35.635 24 36.803 21.405656 37 20.847656 C 37.018 20.797656 37.055359 20.68425 37.068359 20.65625 C 37.209359 20.27525 37.57 20 38 20 C 38.552 20 39 20.448 39 21 L 39 27 C 39 28.05 38.881422 28.881578 38.732422 29.642578 C 39.166422 30.513578 39.709578 31.269969 40.392578 31.792969 C 40.719578 32.042969 41.164625 32.066469 41.515625 31.855469 L 46.515625 28.855469 C 46.815625 28.676469 47 28.352 47 28 C 47 23.679 45.738 20.969203 45.125 19.908203 C 45.449 19.068203 46 17.305 46 15 C 46 7.245 37.301859 4.2755312 34.380859 4.0195312 C 33.299859 3.1345312 31.844 2 30 2 L 25 2 L 20 2 z M 25 22.609375 C 23.953 24.323375 22.059 26 19 26 C 16.075 26 14.195 25.130766 13 24.134766 L 13 27 C 13 28.281 13.194875 29.155016 13.421875 30.166016 C 13.692875 31.375016 14 32.746 14 35 C 14 35.027 13.987375 35.049172 13.984375 35.076172 C 13.994375 35.366172 14 35.669 14 36 L 14 42 C 14 44.997 18.628641 47.438484 19.556641 47.896484 C 19.693641 47.965484 19.847 48 20 48 L 25 48 L 30 48 C 30.153 48 30.306359 47.965484 30.443359 47.896484 C 31.371359 47.438484 36 44.997 36 42 L 36 36 C 36 35.669 36.005625 35.366172 36.015625 35.076172 C 36.012625 35.049172 36 35.027 36 35 C 36 32.746 36.307125 31.375016 36.578125 30.166016 C 36.805125 29.155016 37 28.281 37 27 L 37 24.134766 C 35.805 25.130766 33.925 26 31 26 C 27.941 26 26.047 24.323375 25 22.609375 z"></path>
<path d="M 20 2 C 18.156 2 16.700141 3.1345312 15.619141 4.0195312 C 12.698141 4.2755313 4 7.245 4 15 C 4 17.305 4.551 19.068203 4.875 19.908203 C 4.262 20.969203 3 23.679 3 28 C 3 28.352 3.184375 28.677422 3.484375 28.857422 L 8.484375 31.857422 C 8.835375 32.068422 9.2804219 32.044922 9.6074219 31.794922 C 10.290422 31.270922 10.833578 30.512578 11.267578 29.642578 C 11.118578 28.880578 11 28.05 11 27 L 11 21 C 11 20.448 11.448 20 12 20 C 12.43 20 12.790641 20.27425 12.931641 20.65625 C 12.944641 20.68425 12.982 20.797656 13 20.847656 C 13.197 21.404656 14.365 24 19 24 C 23.133 24 23.984531 19.976687 24.019531 19.804688 C 24.113531 19.329687 24.535 19.018531 25 19.019531 C 25.465 19.019531 25.886469 19.330687 25.980469 19.804688 C 26.015469 19.976687 26.867 24 31 24 C 35.635 24 36.803 21.405656 37 20.847656 C 37.018 20.797656 37.055359 20.68425 37.068359 20.65625 C 37.209359 20.27525 37.57 20 38 20 C 38.552 20 39 20.448 39 21 L 39 27 C 39 28.05 38.881422 28.881578 38.732422 29.642578 C 39.166422 30.513578 39.709578 31.269969 40.392578 31.792969 C 40.719578 32.042969 41.164625 32.066469 41.515625 31.855469 L 46.515625 28.855469 C 46.815625 28.676469 47 28.352 47 28 C 47 23.679 45.738 20.969203 45.125 19.908203 C 45.449 19.068203 46 17.305 46 15 C 46 7.245 37.301859 4.2755312 34.380859 4.0195312 C 33.299859 3.1345312 31.844 2 30 2 L 25 2 L 20 2 z M 25 22.609375 C 23.953 24.323375 22.059 26 19 26 C 16.075 26 14.195 25.130766 13 24.134766 L 13 27 C 13 28.281 13.194875 29.155016 13.421875 30.166016 C 13.692875 31.375016 14 32.746 14 35 C 14 35.027 13.987375 35.049172 13.984375 35.076172 C 13.994375 35.366172 14 35.669 14 36 L 14 42 C 14 44.997 18.628641 47.438484 19.556641 47.896484 C 19.693641 47.965484 19.847 48 20 48 L 25 48 L 30 48 C 30.153 48 30.306359 47.965484 30.443359 47.896484 C 31.371359 47.438484 36 44.997 36 42 L 36 36 C 36 35.669 36.005625 35.366172 36.015625 35.076172 C 36.012625 35.049172 36 35.027 36 35 C 36 32.746 36.307125 31.375016 36.578125 30.166016 C 36.805125 29.155016 37 28.281 37 27 L 37 24.134766 C 35.805 25.130766 33.925 26 31 26 C 27.941 26 26.047 24.323375 25 22.609375 z" />
</svg>
);
}

View File

@@ -23,23 +23,23 @@ export function ControllerIcon({
<path
d="M118.822 208.203c-2.618-.617-4.697-3.542-5.882-8.273-.291-1.162-.298-1.318-.28-6.087.025-6.892.614-23.54.873-24.712.407-1.836.45-2.166.39-2.962-.101-1.363-.496-2.192-1.523-3.2-1.327-1.302-3.303-2.24-6.48-3.078-1.73-.456-3.586-1.518-6.604-3.78-3.429-2.571-4.412-3.847-7.412-9.617-.584-1.123-1.155-2.09-1.27-2.152-.259-.138-1.272-.289-5.165-.769-3.5-.43-6.014-.807-6.642-.992-.827-.245-1.493-1.177-2.218-3.103-.921-2.446-2.059-8.04-2.059-10.122 0-2.587 1.23-6.419 2.836-8.832.83-1.247 1.148-1.489 2.265-1.723 7.263-1.526 53.58-5.18 66.688-5.262 3.11-.019 3.213-.012 3.65.26 1.497.931 3.276 3.686 4.198 6.502.86 2.622 1.011 4.66.57 7.652-.873 5.938-2.877 9.425-6.303 10.969-.767.345-1.928.65-3.119.822-.955.137-1.121.36-1.443 1.948-.523 2.573-1.864 5.5-4.178 9.114-1.084 1.694-1.407 2.78-2.776 9.339-1.166 5.587-1.297 6.347-1.893 11.008-.914 7.15-2.987 22.105-3.276 23.631-.85 4.5-4.377 9.213-9.243 12.352-1.778 1.146-2.492 1.352-3.704 1.067zm-1.16-48.81c4.208-.596 8.777-2.031 12.644-3.97 5.698-2.858 9.94-7.053 11.599-11.471.494-1.315.643-2.34.42-2.88-.147-.354-.203-.38-.802-.38-.78 0-5.773.591-14.198 1.683-6.609.855-9.629 1.2-13.24 1.511-4.913.423-10.917.694-15.438.696-1.622 0-3.59.038-4.373.083-1.345.078-1.431.099-1.561.385-.206.452.06 1.523.715 2.881 1.05 2.177 2.932 4.619 4.83 6.27 5.185 4.512 11.77 6.274 19.405 5.192z"
transform="scale(.26458)"
></path>
/>
<path
d="M111.38 158.518c-4.784-.43-8.953-2.192-12.419-5.25-1.997-1.763-4.05-4.546-4.817-6.531l-.371-.963 5.355-.078c5.019-.074 10.45-.275 11.662-.432.514-.067.533-.057.533.272 0 .498.536 1.333 1.111 1.732 1.473 1.02 3.312 1.046 5.07.434 1.981-.69 2.783-1.579 2.79-2.727l.002-.577 2.26-.276c1.242-.151 4.913-.616 8.157-1.033 7.376-.947 10.484-1.306 10.568-1.222.135.135-.508 1.937-1.101 3.092-2.017 3.922-6.233 7.574-11.529 9.985-5.871 2.674-12.356 4.016-17.272 3.574zm20.458-8.081c.958-.439 1.503-.858 1.904-1.464 1.196-1.808-.01-3.98-2.721-4.896-.637-.216-1.009-.263-1.75-.22-1.096.064-1.542.264-2.423 1.084-1.089 1.013-1.487 2.354-1.026 3.457.397.949 1.521 1.89 2.731 2.285.858.28 2.388.165 3.285-.246z"
transform="scale(.26458)"
></path>
/>
<path
d="M109.855 171.583c-.839-.29-1.655-.856-1.744-1.21-.04-.163.093-.688.324-1.268.588-1.483.61-2.105.143-3.918-.463-1.793-.495-2.343-.15-2.528 1.115-.596 3.07 1.299 3.795 3.679.243.797.25.956.114 2.196-.193 1.752-.4 2.931-.548 3.126-.173.226-1.173.186-1.934-.077z"
transform="scale(.26458)"
></path>
/>
<path
d="M128.454 149.37c-.797-.376-1.472-1.276-1.603-1.798-.207-.822.52-1.842 1.577-2.38 1.397-.714 3.825.066 4.176 1.128.35 1.062.33 2.041-.436 2.602-1.214.889-2.465 1.04-3.714.449z"
transform="scale(.26458)"
></path>
/>
<path
d="M113.895 146.8c-.662-.31-1.51-1.648-1.238-1.67l6.153-.485c.284-.023-.434 1.493-1.413 1.947-1.272.591-2.557.65-3.502.207z"
transform="scale(.26458)"
></path>
/>
</g>
</g>
</svg>

View File

@@ -17,7 +17,7 @@ export function CrossIcon({
fillRule="evenodd"
d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z"
clipRule="evenodd"
></path>
/>
</svg>
);
}

View File

@@ -16,7 +16,7 @@ export function FootIcon({
<path
transform={flipped ? undefined : 'scale(-1,1) translate(-28,0)'}
d="M11.5 3A1.5 2 0 0 0 10 5a1.5 2 0 0 0 1.5 2A1.5 2 0 0 0 13 5a1.5 2 0 0 0-1.5-2zm3.244.678a1 1.333 0 0 0-1 1.334 1 1.333 0 0 0 1 1.332 1 1.333 0 0 0 1-1.332 1 1.333 0 0 0-1-1.334zm2.33 1.039a.863 1.151 0 0 0-.861 1.15.863 1.151 0 0 0 .861 1.15.863 1.151 0 0 0 .863-1.15.863 1.151 0 0 0-.863-1.15zm1.582 1.863a.611.815 0 0 0-.61.815.611.815 0 0 0 .61.816.611.815 0 0 0 .612-.816.611.815 0 0 0-.612-.815zM13.918 8C11.629 8 10 9.731 10 12c0 2.887 3 2.82 3 5 0 1.604-1.272 2.766-1.781 4.295a3.5 3.5 0 0 0-.096.305c-.014.054-.031.108-.043.164a3.5 3.5 0 0 0-.08.736 3.5 3.5 0 0 0 3.5 3.5 3.5 3.5 0 0 0 3.5-3.5c0-3.91 1.998-5.5 1.998-8.5 0-3.612-3.317-6-6.08-6zm5.582.666a.5.667 0 0 0-.5.666.5.667 0 0 0 .5.668.5.667 0 0 0 .5-.668.5.667 0 0 0-.5-.666z"
></path>
/>
</svg>
);
}

View File

@@ -4,7 +4,7 @@ export function HeadsetIcon({ width = 24 }: { width?: number }) {
<path
transform="scale(0.75, 0.75) translate(120, 96)"
d="M576 64H64C28.7 64 0 92.7 0 128v256c0 35.3 28.7 64 64 64h120.4c24.2 0 46.4-13.7 57.2-35.4l32-64c8.8-17.5 26.7-28.6 46.3-28.6s37.5 11.1 46.3 28.6l32 64c10.8 21.7 33 35.4 57.2 35.4H576c35.3 0 64-28.7 64-64V128c0-35.3-28.7-64-64-64zM96 240a64 64 0 11128 0 64 64 0 11-128 0zm384-64a64 64 0 110 128 64 64 0 110-128z"
></path>
/>
</svg>
);
}

View File

@@ -1,8 +1,8 @@
export function HumanIcon({ width = 20 }: { width?: number }) {
return (
<svg xmlns="http://www.w3.org/2000/svg" width={width} viewBox="0 0 512 512">
<path d="M256 112a56 56 0 1156-56 56.06 56.06 0 01-56 56z"></path>
<path d="M432 112.8l-.45.12-.42.13c-1 .28-2 .58-3 .89-18.61 5.46-108.93 30.92-172.56 30.92-59.13 0-141.28-22-167.56-29.47a73.79 73.79 0 00-8-2.58c-19-5-32 14.3-32 31.94 0 17.47 15.7 25.79 31.55 31.76v.28l95.22 29.74c9.73 3.73 12.33 7.54 13.6 10.84 4.13 10.59.83 31.56-.34 38.88l-5.8 45-32.19 176.19q-.15.72-.27 1.47l-.23 1.27c-2.32 16.15 9.54 31.82 32 31.82 19.6 0 28.25-13.53 32-31.94s28-157.57 42-157.57 42.84 157.57 42.84 157.57c3.75 18.41 12.4 31.94 32 31.94 22.52 0 34.38-15.74 32-31.94a57.17 57.17 0 00-.76-4.06L329 301.27l-5.79-45c-4.19-26.21-.82-34.87.32-36.9a1.09 1.09 0 00.08-.15c1.08-2 6-6.48 17.48-10.79l89.28-31.21a16.9 16.9 0 001.62-.52c16-6 32-14.3 32-31.93S451 107.81 432 112.8z"></path>
<path d="M256 112a56 56 0 1156-56 56.06 56.06 0 01-56 56z" />
<path d="M432 112.8l-.45.12-.42.13c-1 .28-2 .58-3 .89-18.61 5.46-108.93 30.92-172.56 30.92-59.13 0-141.28-22-167.56-29.47a73.79 73.79 0 00-8-2.58c-19-5-32 14.3-32 31.94 0 17.47 15.7 25.79 31.55 31.76v.28l95.22 29.74c9.73 3.73 12.33 7.54 13.6 10.84 4.13 10.59.83 31.56-.34 38.88l-5.8 45-32.19 176.19q-.15.72-.27 1.47l-.23 1.27c-2.32 16.15 9.54 31.82 32 31.82 19.6 0 28.25-13.53 32-31.94s28-157.57 42-157.57 42.84 157.57 42.84 157.57c3.75 18.41 12.4 31.94 32 31.94 22.52 0 34.38-15.74 32-31.94a57.17 57.17 0 00-.76-4.06L329 301.27l-5.79-45c-4.19-26.21-.82-34.87.32-36.9a1.09 1.09 0 00.08-.15c1.08-2 6-6.48 17.48-10.79l89.28-31.21a16.9 16.9 0 001.62-.52c16-6 32-14.3 32-31.93S451 107.81 432 112.8z" />
</svg>
);
}

View File

@@ -20,28 +20,28 @@ export function LoaderIcon({
alt="Slime jumping"
width={size}
// className="crisp-edges"
></img>
/>
<img
hidden={slimeState !== SlimeState.HAPPY}
src="/images/happy-slime.gif"
alt="Happy slime"
width={size}
// className="crisp-edges"
></img>
/>
<img
hidden={slimeState !== SlimeState.SAD}
src="/images/sad-slime.gif"
alt="Sad slime"
width={size}
// className="crisp-edges"
></img>
/>
<img
hidden={slimeState !== SlimeState.CURIOUS}
src="/images/curious-slime.gif"
alt="Slime looking for something"
width={size}
// className="crisp-edges"
></img>
/>
</>
);
}

View File

@@ -16,7 +16,7 @@ export function LowerArmIcon({
<path
transform={flipped ? 'scale(-1,1) translate(-50,0)' : undefined}
d="M27.203 1.98c-1.019 0-1.954.56-2.437 1.458L17.26 15.858c-.157.291-.24.619-.24.95v16.22l-4.74 5.49a1.001 1.001 0 0 0 .156 1.407l9.386 7.818a.998.998 0 0 0 1.416-.17l8.354-10.443a13.001 13.001 0 0 0 2.668-7.072L35 20.83A2.83 2.83 0 0 0 32.17 18h-.008a2.83 2.83 0 0 0-2.73 2.086l-2.41 5.935-2.026-3.019L25 16l4.734-10.09c.847-1.836-.491-3.93-2.513-3.93h-.018z"
></path>
/>
</svg>
);
}

View File

@@ -15,7 +15,7 @@ export function PawIcon({
<path
transform={`${transform} scale(0.75, 0.75) translate(96, 96)`}
d="M490.39 182.75c-5.55-13.19-14.77-22.7-26.67-27.49l-.16-.06a46.46 46.46 0 00-17-3.2h-.64c-27.24.41-55.05 23.56-69.19 57.61-10.37 24.9-11.56 51.68-3.18 71.64 5.54 13.2 14.78 22.71 26.73 27.5l.13.05a46.53 46.53 0 0017 3.2c27.5 0 55.6-23.15 70-57.65 10.24-24.87 11.37-51.63 2.98-71.6zM381.55 329.61c-15.71-9.44-30.56-18.37-40.26-34.41C314.53 250.8 298.37 224 256 224s-58.57 26.8-85.39 71.2c-9.72 16.06-24.6 25-40.36 34.48-18.07 10.86-36.74 22.08-44.8 44.16a66.93 66.93 0 00-4.65 25c0 35.95 28 65.2 62.4 65.2 17.75 0 36.64-6.15 56.63-12.66 19.22-6.26 39.09-12.73 56.27-12.73s37 6.47 56.15 12.73C332.2 457.85 351 464 368.8 464c34.35 0 62.3-29.25 62.3-65.2a67 67 0 00-4.75-25c-8.06-22.1-26.74-33.33-44.8-44.19zM150 188.85c11.9 14.93 27 23.15 42.52 23.15a42.88 42.88 0 006.33-.47c32.37-4.76 52.54-44.26 45.92-90C242 102.3 234.6 84.39 224 71.11 212.12 56.21 197 48 181.49 48a42.88 42.88 0 00-6.33.47c-32.37 4.76-52.54 44.26-45.92 90 2.76 19.2 10.16 37.09 20.76 50.38zm163.16 22.68a42.88 42.88 0 006.33.47c15.53 0 30.62-8.22 42.52-23.15 10.59-13.29 17.95-31.18 20.75-50.4 6.62-45.72-13.55-85.22-45.92-90a42.88 42.88 0 00-6.33-.47C315 48 299.88 56.21 288 71.11c-10.6 13.28-18 31.19-20.76 50.44-6.62 45.72 13.55 85.22 45.92 89.98zM111.59 308.8l.14-.05c11.93-4.79 21.16-14.29 26.69-27.48 8.38-20 7.2-46.75-3.15-71.65C120.94 175.16 92.85 152 65.38 152a46.4 46.4 0 00-17 3.2l-.14.05c-11.9 4.75-21.13 14.29-26.66 27.48-8.38 20-7.2 46.75 3.15 71.65C39.06 288.84 67.15 312 94.62 312a46.4 46.4 0 0016.97-3.2z"
></path>
/>
</svg>
);
}

View File

@@ -15,11 +15,11 @@ export function SlimeUpIcon({ width = 60 }: { width?: number }) {
strokeWidth="0.265"
rx="6.963"
ry="3.423"
></rect>
/>
<path
strokeWidth="0.195"
d="M59.587 23.923c.514 0 .978.382 1.313 1.001.336.62.402 1.512.543 2.425l1.376 8.937v40.902l-1.38 8.95c-.14.917-.206 1.805-.542 2.425-.335.619-.8 1.001-1.314 1.001-1.028 0-2.14-1.59-1.856-3.427l1.38-8.949V36.286l-1.377-8.937c-.281-1.826.828-3.426 1.857-3.426z"
></path>
/>
</g>
</svg>
);

View File

@@ -9,13 +9,13 @@ export function SlimeVRIcon({ width = 28 }: { width?: number }) {
viewBox="0 0 380 380"
>
<g fill="none" stroke="#fff">
<path strokeWidth="13.62" d="M72.867 191.74l37-39 39 36"></path>
<path strokeWidth="13.62" d="M208.87 187.74l38-35 36 38"></path>
<path strokeWidth="13.62" d="M72.867 191.74l37-39 39 36" />
<path strokeWidth="13.62" d="M208.87 187.74l38-35 36 38" />
<path
strokeLinecap="square"
strokeWidth="17"
d="M56.867 253.74s130.61-31.182 248 5c13.45 4.146 20.244 2.975 20-8s1.909-126.06-46-131"
></path>
/>
</g>
</svg>
);

File diff suppressed because one or more lines are too long

View File

@@ -16,7 +16,7 @@ export function UpperArmIcon({
<path
transform={flipped ? undefined : 'scale(-1,1) translate(-50,0)'}
d="M47.625 12.219c-.171-.137-8.614-8.622-8.918-8.926a.999.999 0 0 0-1.414 0c-1.996 1.996-4.216 8.745-4.291 15.364-2.309 3.104-2.842 7.479-2.965 9.393-3.187.334-5.254 2.287-6.943 3.884-.81.765-1.573 1.486-2.359 1.96-1.967 1.183-4.493 2.413-5.596 2.937a14.019 14.019 0 0 0-1.657-1.082c-.947-.52-2.496-.683-3.151-.73l-3.092-1.816A1.478 1.478 0 0 0 6.5 33 1.5 1.5 0 0 0 5 34.5c0 .35.082.689.629 1.236.597.597 1.101 1.14 2.976 3.014l-2.907 2.258H2a.998.998 0 0 0-.97 1.242l1 2.992a1 1 0 0 0 .651.706l2.973 1a1.015 1.015 0 0 0 .483.038l6.027-1a.988.988 0 0 0 .436-.187l3.946-2.959c.428-.233 3.692-1.952 8.755-2.856 5.89-1.052 9.31-2.796 10.608-3.565.058-.018.116-.033.176-.051.684-.192 1.716-.483 2.495-2.046.334-.669.54-1.318.74-1.947.325-1.022.605-1.906 1.378-2.659C46.089 24.46 48 20.085 48 13a.997.997 0 0 0-.375-.781z"
></path>
/>
</svg>
);
}

View File

@@ -7,7 +7,7 @@ export function UpperChestIcon({ width = 24 }: { width?: number }) {
width={width}
viewBox="0 0 50 50"
>
<path d="M 20 2 C 18.156 2 16.700141 3.1345312 15.619141 4.0195312 C 12.698141 4.2755313 4 7.245 4 15 C 4 17.305 4.551 19.068203 4.875 19.908203 C 4.262 20.969203 3 23.679 3 28 C 3 28.352 3.184375 28.677422 3.484375 28.857422 L 8.484375 31.857422 C 8.835375 32.068422 9.2804219 32.044922 9.6074219 31.794922 C 10.290422 31.270922 10.833578 30.512578 11.267578 29.642578 C 11.118578 28.880578 11 28.05 11 27 L 11 21 C 11 20.448 11.448 20 12 20 C 12.43 20 12.790641 20.27425 12.931641 20.65625 C 12.944641 20.68425 12.982 20.797656 13 20.847656 C 13.197 21.404656 14.365 24 19 24 C 23.133 24 23.984531 19.976687 24.019531 19.804688 C 24.113531 19.329687 24.535 19.018531 25 19.019531 C 25.465 19.019531 25.886469 19.330687 25.980469 19.804688 C 26.015469 19.976687 26.867 24 31 24 C 35.635 24 36.803 21.405656 37 20.847656 C 37.018 20.797656 37.055359 20.68425 37.068359 20.65625 C 37.209359 20.27525 37.57 20 38 20 C 38.552 20 39 20.448 39 21 L 39 27 C 39 28.05 38.881422 28.881578 38.732422 29.642578 C 39.166422 30.513578 39.709578 31.269969 40.392578 31.792969 C 40.719578 32.042969 41.164625 32.066469 41.515625 31.855469 L 46.515625 28.855469 C 46.815625 28.676469 47 28.352 47 28 C 47 23.679 45.738 20.969203 45.125 19.908203 C 45.449 19.068203 46 17.305 46 15 C 46 7.245 37.301859 4.2755312 34.380859 4.0195312 C 33.299859 3.1345312 31.844 2 30 2 L 25 2 L 20 2 z M 25 22.609375 C 23.953 24.323375 22.059 26 19 26 C 16.075 26 14.195 25.130766 13 24.134766 L 13 27 C 13 28.281 13.194875 29.155016 13.421875 30.166016 C 13.692875 31.375016 14 32.746 14 35 C 14 35.027 13.987375 35.049172 13.984375 35.076172 C 13.994375 35.366172 14 35.669 14 36 L 14 42 C 14 44.997 18.628641 47.438484 19.556641 47.896484 C 19.693641 47.965484 19.847 48 20 48 L 25 48 L 30 48 C 30.153 48 30.306359 47.965484 30.443359 47.896484 C 31.371359 47.438484 36 44.997 36 42 L 36 36 C 36 35.669 36.005625 35.366172 36.015625 35.076172 C 36.012625 35.049172 36 35.027 36 35 C 36 32.746 36.307125 31.375016 36.578125 30.166016 C 36.805125 29.155016 37 28.281 37 27 L 37 24.134766 C 35.805 25.130766 33.925 26 31 26 C 27.941 26 26.047 24.323375 25 22.609375 z"></path>
<path d="M 20 2 C 18.156 2 16.700141 3.1345312 15.619141 4.0195312 C 12.698141 4.2755313 4 7.245 4 15 C 4 17.305 4.551 19.068203 4.875 19.908203 C 4.262 20.969203 3 23.679 3 28 C 3 28.352 3.184375 28.677422 3.484375 28.857422 L 8.484375 31.857422 C 8.835375 32.068422 9.2804219 32.044922 9.6074219 31.794922 C 10.290422 31.270922 10.833578 30.512578 11.267578 29.642578 C 11.118578 28.880578 11 28.05 11 27 L 11 21 C 11 20.448 11.448 20 12 20 C 12.43 20 12.790641 20.27425 12.931641 20.65625 C 12.944641 20.68425 12.982 20.797656 13 20.847656 C 13.197 21.404656 14.365 24 19 24 C 23.133 24 23.984531 19.976687 24.019531 19.804688 C 24.113531 19.329687 24.535 19.018531 25 19.019531 C 25.465 19.019531 25.886469 19.330687 25.980469 19.804688 C 26.015469 19.976687 26.867 24 31 24 C 35.635 24 36.803 21.405656 37 20.847656 C 37.018 20.797656 37.055359 20.68425 37.068359 20.65625 C 37.209359 20.27525 37.57 20 38 20 C 38.552 20 39 20.448 39 21 L 39 27 C 39 28.05 38.881422 28.881578 38.732422 29.642578 C 39.166422 30.513578 39.709578 31.269969 40.392578 31.792969 C 40.719578 32.042969 41.164625 32.066469 41.515625 31.855469 L 46.515625 28.855469 C 46.815625 28.676469 47 28.352 47 28 C 47 23.679 45.738 20.969203 45.125 19.908203 C 45.449 19.068203 46 17.305 46 15 C 46 7.245 37.301859 4.2755312 34.380859 4.0195312 C 33.299859 3.1345312 31.844 2 30 2 L 25 2 L 20 2 z M 25 22.609375 C 23.953 24.323375 22.059 26 19 26 C 16.075 26 14.195 25.130766 13 24.134766 L 13 27 C 13 28.281 13.194875 29.155016 13.421875 30.166016 C 13.692875 31.375016 14 32.746 14 35 C 14 35.027 13.987375 35.049172 13.984375 35.076172 C 13.994375 35.366172 14 35.669 14 36 L 14 42 C 14 44.997 18.628641 47.438484 19.556641 47.896484 C 19.693641 47.965484 19.847 48 20 48 L 25 48 L 30 48 C 30.153 48 30.306359 47.965484 30.443359 47.896484 C 31.371359 47.438484 36 44.997 36 42 L 36 36 C 36 35.669 36.005625 35.366172 36.015625 35.076172 C 36.012625 35.049172 36 35.027 36 35 C 36 32.746 36.307125 31.375016 36.578125 30.166016 C 36.805125 29.155016 37 28.281 37 27 L 37 24.134766 C 35.805 25.130766 33.925 26 31 26 C 27.941 26 26.047 24.323375 25 22.609375 z" />
</svg>
);
}

View File

@@ -16,7 +16,7 @@ export function UpperLegIcon({
<path
transform={flipped ? 'scale(-1,1) translate(-50,0)' : undefined}
d="M42.727.059a.998.998 0 0 0-.774.043A1.042 1.042 0 0 0 41.516 0H24.313c-.04 0-.079.016-.118.02a.972.972 0 0 0-.8.238c-2.407 2.172-5.836 7.715-6.793 11.773-2.012 2.281-2.11 4.774-.313 7.828.617 1.043.746 2.262.895 3.676.086.8.171 1.625.355 2.492.29 1.325.883 2.637 1.453 3.907.32.707.621 1.37.863 2.02.29.78.618 1.605.95 2.437.797 2.004 1.789 4.5 2.144 6.28.082 1.087-.012 2-.367 2.638-1.227 2.18-2.941 1.793-4.988 2.664-2.153.922-3.508 1.32-4.32 1.472-2.07.387-1.864.57-1.864.934 0 .113.016.219.035.324.168.766.922 1.3 1.77 1.3.156 0 .32-.015.48-.038a3.2 3.2 0 0 0 1.086-.352c.09.063.188.102.285.149.493.234 1.028.27 1.618.27 2.011 0 3.293-.063 4.207-.15 1.511-.14 2.02-.331 3.074-.378.152-.008.316-.012.504-.012.898 0 2.734.54 4.84.54.562 0 1.02-.114 1.394-.298 1.024-.496 1.399-1.543 1.399-2.48 0-1.566-1.063-2.547-2.04-5.121-.44-1.328-.91-3.035-1.253-5.418-.047-.344-.075-.668-.106-.996l-.047-.602c-.164-2.992.45-5.394.965-7.37.43-1.653.8-3.083.594-4.4-.473-3.015-1.781-4.863-2.563-5.964-.148-.207-.32-.453-.414-.61.223-.484 1.043-1.53 2.39-2.168 2.15-1.015 11.349-6.57 13.712-13.27A1.007 1.007 0 0 0 42.727.06Z"
></path>
/>
</svg>
);
}

View File

@@ -7,7 +7,7 @@ export function WaistIcon({ width = 24 }: { width?: number }) {
width={width}
viewBox="0 0 72 72"
>
<path d="M17 2c-.3 0-.6.2-.8.4-.2.3-.3.7-.2 1 2.9 7.1 3.1 15.3.6 22.4-.6 2-1.5 3.9-2.4 5.7-.1.2-.2.5-.3.7 13.2 4.2 26.7 4.1 40.2-.1-.6-1.4-1.9-3.9-2.7-6.3-2.5-7.1-2.3-15.3.6-22.4.1-.3.1-.7-.1-.9-.2-.3-.5-.5-.9-.5H17zM9.863 14.75a.944.944 0 0 0-.664.35c-.4.4-.298 1.1.102 1.4l2.1 1.8H3.9c-.5.1-1 .6-1 1.1 0 .6.4 1 1 1h7.5l-2.1 1.8c-.4.4-.502 1-.102 1.4.4.4 1 .5 1.4.1 1.6-1.3 4.002-3.4 4.202-3.6.4-.4.5-1 0-1.5-.2-.2-3.201-2.7-4.201-3.6a.908.908 0 0 0-.737-.25zm48.237.1a.846.846 0 0 0-.7.25c-1 .9-3.9 3.3-4.2 3.6-.4.4-.4 1.1 0 1.5.2.2 3.2 2.7 4.2 3.6.4.4 1 .3 1.4-.1.4-.4.3-1.1-.1-1.4l-2.1-1.8h7.5c.6 0 1-.4 1-1s-.4-1-1-1h-7.5l2.1-1.8c.4-.4.4-1.1.1-1.5-.2-.2-.45-.325-.7-.35zm-3 19.45c-14.2 4.6-28.4 4.6-42.2.1-.5 1.1-1 2.3-1.4 3.5-.1.4-.3.8-.4 1.2 8.4 1.7 15.9 7 22.1 15.8 6.4-9.3 14.5-14.8 23.6-16.1l-.3-.9c-.4-1.2-.9-2.4-1.4-3.6zm2.3 6.8c-8.9 1.2-16.8 6.8-23 16.3V66h17.5c.3 0 .6-.2.8-.4 4.8-7.1 6.5-16.2 4.7-24.5zm-46.8.2c-1.7 8.3 0 17.3 4.7 24.3.2.3.5.4.8.4h16.2v-8.4s0-.1-.1-.1c-6-9.2-13.3-14.6-21.6-16.2z"></path>
<path d="M17 2c-.3 0-.6.2-.8.4-.2.3-.3.7-.2 1 2.9 7.1 3.1 15.3.6 22.4-.6 2-1.5 3.9-2.4 5.7-.1.2-.2.5-.3.7 13.2 4.2 26.7 4.1 40.2-.1-.6-1.4-1.9-3.9-2.7-6.3-2.5-7.1-2.3-15.3.6-22.4.1-.3.1-.7-.1-.9-.2-.3-.5-.5-.9-.5H17zM9.863 14.75a.944.944 0 0 0-.664.35c-.4.4-.298 1.1.102 1.4l2.1 1.8H3.9c-.5.1-1 .6-1 1.1 0 .6.4 1 1 1h7.5l-2.1 1.8c-.4.4-.502 1-.102 1.4.4.4 1 .5 1.4.1 1.6-1.3 4.002-3.4 4.202-3.6.4-.4.5-1 0-1.5-.2-.2-3.201-2.7-4.201-3.6a.908.908 0 0 0-.737-.25zm48.237.1a.846.846 0 0 0-.7.25c-1 .9-3.9 3.3-4.2 3.6-.4.4-.4 1.1 0 1.5.2.2 3.2 2.7 4.2 3.6.4.4 1 .3 1.4-.1.4-.4.3-1.1-.1-1.4l-2.1-1.8h7.5c.6 0 1-.4 1-1s-.4-1-1-1h-7.5l2.1-1.8c.4-.4.4-1.1.1-1.5-.2-.2-.45-.325-.7-.35zm-3 19.45c-14.2 4.6-28.4 4.6-42.2.1-.5 1.1-1 2.3-1.4 3.5-.1.4-.3.8-.4 1.2 8.4 1.7 15.9 7 22.1 15.8 6.4-9.3 14.5-14.8 23.6-16.1l-.3-.9c-.4-1.2-.9-2.4-1.4-3.6zm2.3 6.8c-8.9 1.2-16.8 6.8-23 16.3V66h17.5c.3 0 .6-.2.8-.4 4.8-7.1 6.5-16.2 4.7-24.5zm-46.8.2c-1.7 8.3 0 17.3 4.7 24.3.2.3.5.4.8.4h16.2v-8.4s0-.1-.1-.1c-6-9.2-13.3-14.6-21.6-16.2z" />
</svg>
);
}

View File

@@ -1,12 +1,12 @@
export function WarningIcon(props: any) {
export function WarningIcon({ width, className, ...props }: any) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
{...props}
width={props.width || 30}
className={`${props.className || ''}`}
width={width || 30}
className={`${className || ''}`}
>
<path
fillRule="evenodd"

View File

@@ -21,7 +21,7 @@ export function WifiIcon({
const y = useMemo(() => (1 - percent) * 13, [percent]);
const col = useMemo(() => {
const colorsMap: { [key: number]: string } = {
const colorsMap: Record<number, string> = {
0.4: 'fill-status-success',
0.2: 'fill-status-warning',
0: 'fill-status-critical',

View File

@@ -30,7 +30,7 @@ function IMUCard({
}: {
imuTypes: Imudto[];
hasIntPin: boolean;
control: Control<{ imus: CreateImuConfigDTO[] }, any>;
control: Control<{ imus: CreateImuConfigDTO[] }>;
index: number;
onDelete: () => void;
}) {
@@ -50,7 +50,7 @@ function IMUCard({
<div className={'w-full flex flex-col gap-2'}>
<div className="grid xs-settings:grid-cols-2 mobile-settings:grid-cols-1 gap-3 fill-background-10">
<label className="flex flex-col justify-end gap-1">
<Localized id="firmware_tool-add_imus_step-imu_type-label"></Localized>
<Localized id="firmware_tool-add_imus_step-imu_type-label" />
<Dropdown
control={control}
name={`imus[${index}].type`}
@@ -65,7 +65,7 @@ function IMUCard({
)}
direction="down"
display="block"
></Dropdown>
/>
</label>
<Localized
id="firmware_tool-add_imus_step-imu_rotation"
@@ -82,7 +82,7 @@ function IMUCard({
label="Rotation Degree"
placeholder="Rotation Degree"
autocomplete="off"
></Input>
/>
</Localized>
</div>
<div
@@ -104,7 +104,7 @@ function IMUCard({
name={`imus[${index}].sclPin`}
variant="primary"
autocomplete="off"
></Input>
/>
</Localized>
<Localized
id="firmware_tool-add_imus_step-sda_pin"
@@ -119,7 +119,7 @@ function IMUCard({
label="SDA Pin"
placeholder="SDA Pin"
autocomplete="off"
></Input>
/>
</Localized>
{hasIntPin && (
@@ -134,7 +134,7 @@ function IMUCard({
name={`imus[${index}].intPin`}
variant="primary"
autocomplete="off"
></Input>
/>
</Localized>
)}
<label className="flex flex-col justify-end gap-1 md:pt-3 sm:pt-3">
@@ -148,7 +148,7 @@ function IMUCard({
variant="toggle"
color="tertiary"
label=""
></CheckBox>
/>
</Localized>
</label>
</div>
@@ -156,7 +156,7 @@ function IMUCard({
</div>
<div className="flex flex-col items-center mt-[25px] fill-background-10">
<Button variant="quaternary" rounded onClick={onDelete}>
<TrashIcon size={15}></TrashIcon>
<TrashIcon size={15} />
</Button>
</div>
</div>
@@ -171,8 +171,8 @@ function IMUCard({
: 'firmware_tool-add_imus_step-show_more'
)}
</Typography>
{!open && <ArrowDownIcon></ArrowDownIcon>}
{open && <ArrowUpIcon></ArrowUpIcon>}
{!open && <ArrowDownIcon />}
{open && <ArrowUpIcon />}
</div>
</div>
);
@@ -237,72 +237,71 @@ export function AddImusStep({
};
return (
<>
<div className="flex flex-col w-full">
<div className="flex flex-col gap-4">
<Typography color="secondary">
{l10n.getString('firmware_tool-add_imus_step-description')}
</Typography>
</div>
<div className="my-4 flex flex-col gap-4">
{!isAckchuallyLoading && imuTypes && newConfig && (
<>
<div className="flex flex-col gap-3">
<div
className={classNames(
'grid gap-2 px-2',
form.imus.length > 1
? 'md:grid-cols-2 mobile-settings:grid-cols-1'
: 'grid-cols-1'
)}
>
{form.imus.map((imu, index) => (
<IMUCard
control={control}
imuTypes={imuTypes}
key={`${index}:${imu.type}`}
hasIntPin={
imuTypes?.find(({ type: t }) => t == imu.type)
?.hasIntPin ?? false
}
index={index}
onDelete={() => deleteImu(index)}
></IMUCard>
))}
</div>
<div className="flex justify-center">
<Localized id="firmware_tool-add_imus_step-add_more">
<Button variant="primary" onClick={addImu}></Button>
</Localized>
</div>
<div className="flex flex-col w-full">
<div className="flex flex-col gap-4">
<Typography color="secondary">
{l10n.getString('firmware_tool-add_imus_step-description')}
</Typography>
</div>
<div className="my-4 flex flex-col gap-4">
{!isAckchuallyLoading && imuTypes && newConfig && (
<>
<div className="flex flex-col gap-3">
<div
className={classNames(
'grid gap-2 px-2',
form.imus.length > 1
? 'md:grid-cols-2 mobile-settings:grid-cols-1'
: 'grid-cols-1'
)}
>
{form.imus.map((imu, index) => (
<IMUCard
control={control}
imuTypes={imuTypes}
// eslint-disable-next-line react/no-array-index-key
key={`${index}:${imu.type}`}
hasIntPin={
imuTypes?.find(({ type: t }) => t == imu.type)
?.hasIntPin ?? false
}
index={index}
onDelete={() => deleteImu(index)}
/>
))}
</div>
<div className="flex justify-between">
<Localized id="firmware_tool-previous_step">
<Button variant="tertiary" onClick={prevStep}></Button>
</Localized>
<Localized id="firmware_tool-next_step">
<Button
variant="primary"
disabled={!isValidState || form.imus.length === 0}
onClick={() => {
updateImus(form.imus);
nextStep();
}}
></Button>
<div className="flex justify-center">
<Localized id="firmware_tool-add_imus_step-add_more">
<Button variant="primary" onClick={addImu} />
</Localized>
</div>
</>
)}
{isAckchuallyLoading && (
<div className="flex justify-center flex-col items-center gap-3 h-44">
<LoaderIcon slimeState={SlimeState.JUMPY}></LoaderIcon>
<Localized id="firmware_tool-loading">
<Typography color="secondary"></Typography>
</div>
<div className="flex justify-between">
<Localized id="firmware_tool-previous_step">
<Button variant="tertiary" onClick={prevStep} />
</Localized>
<Localized id="firmware_tool-next_step">
<Button
variant="primary"
disabled={!isValidState || form.imus.length === 0}
onClick={() => {
updateImus(form.imus);
nextStep();
}}
/>
</Localized>
</div>
)}
</div>
</>
)}
{isAckchuallyLoading && (
<div className="flex justify-center flex-col items-center gap-3 h-44">
<LoaderIcon slimeState={SlimeState.JUMPY} />
<Localized id="firmware_tool-loading">
<Typography color="secondary" />
</Localized>
</div>
)}
</div>
</>
</div>
);
}

View File

@@ -50,149 +50,147 @@ export function BoardPinsStep({
}, [defaultConfig]);
return (
<>
<div className="flex flex-col w-full justify-between text-background-10">
<div className="flex flex-col gap-4">
<Typography color="secondary">
{l10n.getString('firmware_tool-board_pins_step-description')}
</Typography>
</div>
<div className="my-4 p-2">
{!isLoading && !isFetching && batteryTypes && (
<form className="flex flex-col gap-2">
<div className="grid xs-settings:grid-cols-2 mobile-settings:grid-cols-1 gap-2">
<label className="flex flex-col justify-end">
{/* Allows to have the right spacing at the top of the checkbox */}
<CheckBox
control={control}
color="tertiary"
name="enableLed"
variant="toggle"
outlined
label={l10n.getString(
'firmware_tool-board_pins_step-enable_led'
)}
></CheckBox>
</label>
<Localized
id="firmware_tool-board_pins_step-led_pin"
attrs={{ placeholder: true, label: true }}
>
<Input
control={control}
rules={{ required: true }}
type="text"
name="ledPin"
variant="secondary"
disabled={!ledEnabled}
></Input>
</Localized>
</div>
<div
className={classNames(
batteryType === 'BAT_EXTERNAL' &&
'bg-background-80 p-2 rounded-md',
'transition-all duration-500 flex-col flex gap-2'
)}
>
<Dropdown
<div className="flex flex-col w-full justify-between text-background-10">
<div className="flex flex-col gap-4">
<Typography color="secondary">
{l10n.getString('firmware_tool-board_pins_step-description')}
</Typography>
</div>
<div className="my-4 p-2">
{!isLoading && !isFetching && batteryTypes && (
<form className="flex flex-col gap-2">
<div className="grid xs-settings:grid-cols-2 mobile-settings:grid-cols-1 gap-2">
<label className="flex flex-col justify-end">
{/* Allows to have the right spacing at the top of the checkbox */}
<CheckBox
control={control}
name="batteryType"
variant="primary"
placeholder={l10n.getString(
'firmware_tool-board_pins_step-battery_type'
color="tertiary"
name="enableLed"
variant="toggle"
outlined
label={l10n.getString(
'firmware_tool-board_pins_step-enable_led'
)}
direction="up"
display="block"
items={batteryTypes.map((battery) => ({
label: l10n.getString(
'firmware_tool-board_pins_step-battery_type-' + battery
),
value: battery,
}))}
></Dropdown>
{batteryType === 'BAT_EXTERNAL' && (
<div className="grid grid-cols-2 gap-2">
<Localized
id="firmware_tool-board_pins_step-battery_sensor_pin"
attrs={{ placeholder: true, label: true }}
>
<Input
control={control}
rules={{ required: true }}
type="text"
name="batteryPin"
variant="secondary"
></Input>
</Localized>
<Localized
id="firmware_tool-board_pins_step-battery_resistor"
attrs={{ placeholder: true, label: true }}
>
<Input
control={control}
rules={{ required: true, min: 0 }}
type="number"
name="batteryResistances[0]"
variant="secondary"
label="Battery Resistor"
placeholder="Battery Resistor"
></Input>
</Localized>
<Localized
id="firmware_tool-board_pins_step-battery_shield_resistor-0"
attrs={{ placeholder: true, label: true }}
>
<Input
control={control}
rules={{ required: true, min: 0 }}
type="number"
name="batteryResistances[1]"
variant="secondary"
></Input>
</Localized>
<Localized
id="firmware_tool-board_pins_step-battery_shield_resistor-1"
attrs={{ placeholder: true, label: true }}
>
<Input
control={control}
rules={{ required: true, min: 0 }}
type="number"
name="batteryResistances[2]"
variant="secondary"
></Input>
</Localized>
</div>
)}
</div>
</form>
)}
{(isLoading || isFetching) && (
<div className="flex justify-center flex-col items-center gap-3 h-44">
<LoaderIcon slimeState={SlimeState.JUMPY}></LoaderIcon>
<Localized id="firmware_tool-loading">
<Typography color="secondary"></Typography>
/>
</label>
<Localized
id="firmware_tool-board_pins_step-led_pin"
attrs={{ placeholder: true, label: true }}
>
<Input
control={control}
rules={{ required: true }}
type="text"
name="ledPin"
variant="secondary"
disabled={!ledEnabled}
/>
</Localized>
</div>
)}
</div>
<div className="flex justify-between">
<Localized id="firmware_tool-previous_step">
<Button variant="tertiary" onClick={prevStep}></Button>
</Localized>
<Localized id="firmware_tool-ok">
<Button
variant="primary"
disabled={Object.keys(formState.errors).length !== 0}
onClick={() => {
updatePins(formValue);
nextStep();
}}
></Button>
</Localized>
</div>
<div
className={classNames(
batteryType === 'BAT_EXTERNAL' &&
'bg-background-80 p-2 rounded-md',
'transition-all duration-500 flex-col flex gap-2'
)}
>
<Dropdown
control={control}
name="batteryType"
variant="primary"
placeholder={l10n.getString(
'firmware_tool-board_pins_step-battery_type'
)}
direction="up"
display="block"
items={batteryTypes.map((battery) => ({
label: l10n.getString(
`firmware_tool-board_pins_step-battery_type-${battery}`
),
value: battery,
}))}
/>
{batteryType === 'BAT_EXTERNAL' && (
<div className="grid grid-cols-2 gap-2">
<Localized
id="firmware_tool-board_pins_step-battery_sensor_pin"
attrs={{ placeholder: true, label: true }}
>
<Input
control={control}
rules={{ required: true }}
type="text"
name="batteryPin"
variant="secondary"
/>
</Localized>
<Localized
id="firmware_tool-board_pins_step-battery_resistor"
attrs={{ placeholder: true, label: true }}
>
<Input
control={control}
rules={{ required: true, min: 0 }}
type="number"
name="batteryResistances[0]"
variant="secondary"
label="Battery Resistor"
placeholder="Battery Resistor"
/>
</Localized>
<Localized
id="firmware_tool-board_pins_step-battery_shield_resistor-0"
attrs={{ placeholder: true, label: true }}
>
<Input
control={control}
rules={{ required: true, min: 0 }}
type="number"
name="batteryResistances[1]"
variant="secondary"
/>
</Localized>
<Localized
id="firmware_tool-board_pins_step-battery_shield_resistor-1"
attrs={{ placeholder: true, label: true }}
>
<Input
control={control}
rules={{ required: true, min: 0 }}
type="number"
name="batteryResistances[2]"
variant="secondary"
/>
</Localized>
</div>
)}
</div>
</form>
)}
{(isLoading || isFetching) && (
<div className="flex justify-center flex-col items-center gap-3 h-44">
<LoaderIcon slimeState={SlimeState.JUMPY} />
<Localized id="firmware_tool-loading">
<Typography color="secondary" />
</Localized>
</div>
)}
</div>
</>
<div className="flex justify-between">
<Localized id="firmware_tool-previous_step">
<Button variant="tertiary" onClick={prevStep} />
</Localized>
<Localized id="firmware_tool-ok">
<Button
variant="primary"
disabled={Object.keys(formState.errors).length !== 0}
onClick={() => {
updatePins(formValue);
nextStep();
}}
/>
</Localized>
</div>
</div>
);
}

View File

@@ -10,6 +10,7 @@ import {
import { useEffect, useMemo } from 'react';
import { firmwareToolBaseUrl } from '@/firmware-tool-api/firmwareToolFetcher';
import { Button } from '@/components/commons/Button';
import { error } from '@/utils/logging';
export function BuildStep({
isActive,
@@ -42,7 +43,7 @@ export function BuildStep({
};
}
} catch (e) {
console.error(e);
error(e);
setBuildStatus({ id: '', status: 'ERROR' });
}
};
@@ -65,47 +66,45 @@ export function BuildStep({
);
return (
<>
<div className="flex flex-col w-full">
<div className="flex flex-grow flex-col gap-4">
<Typography color="secondary">
{l10n.getString('firmware_tool-build_step-description')}
</Typography>
</div>
<div className="my-4">
{!isGlobalLoading && (
<div className="flex justify-center flex-col items-center gap-3 h-44">
<LoaderIcon
slimeState={
buildStatus.status !== 'ERROR'
? SlimeState.JUMPY
: SlimeState.SAD
}
></LoaderIcon>
<Typography variant="section-title" color="secondary">
{l10n.getString('firmware_tool-build-' + buildStatus.status)}
</Typography>
</div>
)}
{isGlobalLoading && (
<div className="flex justify-center flex-col items-center gap-3 h-44">
<LoaderIcon slimeState={SlimeState.JUMPY}></LoaderIcon>
<Localized id="firmware_tool-loading">
<Typography color="secondary"></Typography>
</Localized>
</div>
)}
</div>
<div className="flex justify-end">
<Localized id="firmware_tool-retry">
<Button
variant="secondary"
disabled={hasPendingBuild}
onClick={() => goTo('FlashingMethod')}
></Button>
</Localized>
</div>
<div className="flex flex-col w-full">
<div className="flex flex-grow flex-col gap-4">
<Typography color="secondary">
{l10n.getString('firmware_tool-build_step-description')}
</Typography>
</div>
</>
<div className="my-4">
{!isGlobalLoading && (
<div className="flex justify-center flex-col items-center gap-3 h-44">
<LoaderIcon
slimeState={
buildStatus.status !== 'ERROR'
? SlimeState.JUMPY
: SlimeState.SAD
}
/>
<Typography variant="section-title" color="secondary">
{l10n.getString(`firmware_tool-build-${buildStatus.status}`)}
</Typography>
</div>
)}
{isGlobalLoading && (
<div className="flex justify-center flex-col items-center gap-3 h-44">
<LoaderIcon slimeState={SlimeState.JUMPY} />
<Localized id="firmware_tool-loading">
<Typography color="secondary" />
</Localized>
</div>
)}
</div>
<div className="flex justify-end">
<Localized id="firmware_tool-retry">
<Button
variant="secondary"
disabled={hasPendingBuild}
onClick={() => goTo('FlashingMethod')}
/>
</Localized>
</div>
</div>
);
}

View File

@@ -98,18 +98,18 @@ export function DeviceCardControl({
checked={value || false}
type="checkbox"
disabled={disabled}
></input>
/>
</div>
<div className="w-full">
<DeviceCardContent {...props}></DeviceCardContent>
<DeviceCardContent {...props} />
</div>
</label>
)}
></Controller>
/>
) : (
<div className="px-2 h-full">
<DeviceCardContent {...props}></DeviceCardContent>
<DeviceCardContent {...props} />
</div>
)}
<div
@@ -126,7 +126,7 @@ export function DeviceCardControl({
bottom
height={6}
colorClass="bg-accent-background-20"
></ProgressBar>
/>
</div>
{online !== null && (
<div className="absolute top-2 right-2">
@@ -134,7 +134,7 @@ export function DeviceCardControl({
status={
online ? TrackerStatusEnum.OK : TrackerStatusEnum.DISCONNECTED
}
></TrackerStatus>
/>
</div>
)}
</div>

View File

@@ -5,19 +5,19 @@ import {
FirmwareToolContextC,
useFirmwareToolContext,
} from '@/hooks/firmware-tool';
import { AddImusStep } from './AddImusStep';
import { SelectBoardStep } from './SelectBoardStep';
import { BoardPinsStep } from './BoardPinsStep';
import VerticalStepper from '@/components/commons/VerticalStepper';
import { LoaderIcon, SlimeState } from '@/components/commons/icon/LoaderIcon';
import { Button } from '@/components/commons/Button';
import { FirmwareUpdateMethod } from 'solarxr-protocol';
import { useMemo } from 'react';
import { AddImusStep } from './AddImusStep';
import { SelectBoardStep } from './SelectBoardStep';
import { BoardPinsStep } from './BoardPinsStep';
import { SelectFirmwareStep } from './SelectFirmwareStep';
import { BuildStep } from './BuildStep';
import { FlashingMethodStep } from './FlashingMethodStep';
import { FlashingStep } from './FlashingStep';
import { FlashBtnStep } from './FlashBtnStep';
import { FirmwareUpdateMethod } from 'solarxr-protocol';
import { useMemo } from 'react';
function FirmwareToolContent() {
const { l10n } = useLocalization();
@@ -80,40 +80,39 @@ function FirmwareToolContent() {
{l10n.getString('firmware_tool')}
</Typography>
<div className="flex flex-col pt-2 pb-4">
<>
{l10n
.getString('firmware_tool-description')
.split('\n')
.map((line, i) => (
<Typography color="secondary" key={i}>
{line}
</Typography>
))}
</>
{l10n
.getString('firmware_tool-description')
.split('\n')
.map((line, i) => (
// eslint-disable-next-line react/no-array-index-key
<Typography color="secondary" key={i}>
{line}
</Typography>
))}
</div>
<div className="m-4 h-full">
{isError && (
<div className="w-full flex flex-col justify-center items-center gap-3 h-full">
<LoaderIcon slimeState={SlimeState.SAD}></LoaderIcon>
<LoaderIcon slimeState={SlimeState.SAD} />
{!isCompatible ? (
<Localized id="firmware_tool-not_compatible">
<Typography variant="section-title"></Typography>
<Typography variant="section-title" />
</Localized>
) : (
<Localized id="firmware_tool-not_available">
<Typography variant="section-title"></Typography>
<Typography variant="section-title" />
</Localized>
)}
<Localized id="firmware_tool-retry">
<Button variant="primary" onClick={retry}></Button>
<Button variant="primary" onClick={retry} />
</Localized>
</div>
)}
{isLoading && (
<div className="w-full flex flex-col justify-center items-center gap-3 h-full">
<LoaderIcon slimeState={SlimeState.JUMPY}></LoaderIcon>
<LoaderIcon slimeState={SlimeState.JUMPY} />
<Localized id="firmware_tool-loading">
<Typography variant="section-title"></Typography>
<Typography variant="section-title" />
</Localized>
</div>
)}

View File

@@ -19,68 +19,64 @@ export function FlashBtnStep({
const { defaultConfig } = useFirmwareTool();
return (
<>
<div className="flex flex-col w-full">
<div className="flex flex-grow flex-col gap-4">
<Typography color="secondary">
{l10n.getString('firmware_tool-flashbtn_step-description')}
</Typography>
{defaultConfig?.boardConfig.type ===
boardTypeToFirmwareToolBoardType[BoardType.SLIMEVR] ? (
<>
<Typography variant="standard" whitespace="whitespace-pre">
{l10n.getString('firmware_tool-flashbtn_step-board_SLIMEVR')}
</Typography>
<div className="gap-2 grid lg:grid-cols-3 md:grid-cols-2 mobile:grid-cols-1">
<div className="bg-background-80 p-2 rounded-lg gap-2 flex flex-col justify-between">
<Typography variant="main-title">R11</Typography>
<Typography variant="standard">
{l10n.getString(
'firmware_tool-flashbtn_step-board_SLIMEVR-r11'
)}
</Typography>
<img src="/images/R11_board_reset.webp"></img>
</div>
<div className="bg-background-80 p-2 rounded-lg gap-2 flex flex-col justify-between">
<Typography variant="main-title">R12</Typography>
<Typography variant="standard">
{l10n.getString(
'firmware_tool-flashbtn_step-board_SLIMEVR-r12'
)}
</Typography>
<img src="/images/R12_board_reset.webp"></img>
</div>
<div className="bg-background-80 p-2 rounded-lg gap-2 flex flex-col justify-between">
<Typography variant="main-title">R14</Typography>
<Typography variant="standard">
{l10n.getString(
'firmware_tool-flashbtn_step-board_SLIMEVR-r14'
)}
</Typography>
<img src="/images/R14_board_reset_sw.webp"></img>
</div>
<div className="flex flex-col w-full">
<div className="flex flex-grow flex-col gap-4">
<Typography color="secondary">
{l10n.getString('firmware_tool-flashbtn_step-description')}
</Typography>
{defaultConfig?.boardConfig.type ===
boardTypeToFirmwareToolBoardType[BoardType.SLIMEVR] ? (
<>
<Typography variant="standard" whitespace="whitespace-pre">
{l10n.getString('firmware_tool-flashbtn_step-board_SLIMEVR')}
</Typography>
<div className="gap-2 grid lg:grid-cols-3 md:grid-cols-2 mobile:grid-cols-1">
<div className="bg-background-80 p-2 rounded-lg gap-2 flex flex-col justify-between">
<Typography variant="main-title">R11</Typography>
<Typography variant="standard">
{l10n.getString(
'firmware_tool-flashbtn_step-board_SLIMEVR-r11'
)}
</Typography>
<img src="/images/R11_board_reset.webp" />
</div>
</>
) : (
<>
<Typography variant="standard" whitespace="whitespace-pre">
{l10n.getString('firmware_tool-flashbtn_step-board_OTHER')}
</Typography>
</>
)}
<div className="flex justify-end">
<Localized id="firmware_tool-next_step">
<Button
variant="primary"
onClick={() => {
nextStep();
}}
></Button>
</Localized>
</div>
<div className="bg-background-80 p-2 rounded-lg gap-2 flex flex-col justify-between">
<Typography variant="main-title">R12</Typography>
<Typography variant="standard">
{l10n.getString(
'firmware_tool-flashbtn_step-board_SLIMEVR-r12'
)}
</Typography>
<img src="/images/R12_board_reset.webp" />
</div>
<div className="bg-background-80 p-2 rounded-lg gap-2 flex flex-col justify-between">
<Typography variant="main-title">R14</Typography>
<Typography variant="standard">
{l10n.getString(
'firmware_tool-flashbtn_step-board_SLIMEVR-r14'
)}
</Typography>
<img src="/images/R14_board_reset_sw.webp" />
</div>
</div>
</>
) : (
<Typography variant="standard" whitespace="whitespace-pre">
{l10n.getString('firmware_tool-flashbtn_step-board_OTHER')}
</Typography>
)}
<div className="flex justify-end">
<Localized id="firmware_tool-next_step">
<Button
variant="primary"
onClick={() => {
nextStep();
}}
/>
</Localized>
</div>
</div>
</>
</div>
);
}

View File

@@ -26,11 +26,11 @@ import { Button } from '@/components/commons/Button';
import { Input } from '@/components/commons/Input';
import { Dropdown } from '@/components/commons/Dropdown';
import { useOnboarding } from '@/hooks/onboarding';
import { DeviceCardControl } from './DeviceCard';
import { getTrackerName } from '@/hooks/tracker';
import { ObjectSchema, object, string } from 'yup';
import { useAtomValue } from 'jotai';
import { devicesAtom } from '@/store/app-store';
import { DeviceCardControl } from './DeviceCard';
interface FlashingMethodForm {
flashingMethod?: string;
@@ -40,7 +40,7 @@ interface FlashingMethodForm {
password?: string;
};
ota?: {
selectedDevices: { [key: string]: boolean };
selectedDevices: Record<string, boolean>;
};
}
@@ -129,7 +129,7 @@ function SerialDevicesList({
return (
<>
<Localized id="firmware_tool-flash_method_serial-wifi">
<Typography variant="section-title"></Typography>
<Typography variant="section-title" />
</Localized>
<div className="grid xs-settings:grid-cols-2 mobile-settings:grid-cols-1 gap-3 text-background-10">
<Localized
@@ -156,11 +156,11 @@ function SerialDevicesList({
</Localized>
</div>
<Localized id="firmware_tool-flash_method_serial-devices-label">
<Typography variant="section-title"></Typography>
<Typography variant="section-title" />
</Localized>
{Object.keys(devices).length === 0 ? (
<Localized id="firmware_tool-flash_method_serial-no_devices">
<Typography variant="standard" color="secondary"></Typography>
<Typography variant="standard" color="secondary" />
</Localized>
) : (
<Dropdown
@@ -175,7 +175,7 @@ function SerialDevicesList({
)}
display="block"
direction="down"
></Dropdown>
/>
)}
</>
);
@@ -270,11 +270,11 @@ function OTADevicesList({
return (
<>
<Localized id="firmware_tool-flash_method_ota-devices">
<Typography variant="section-title"></Typography>
<Typography variant="section-title" />
</Localized>
{devices.length === 0 && (
<Localized id="firmware_tool-flash_method_ota-no_devices">
<Typography color="secondary"></Typography>
<Typography color="secondary" />
</Localized>
)}
<div className="grid xs-settings:grid-cols-2 mobile-settings:grid-cols-1 gap-2">
@@ -284,7 +284,7 @@ function OTADevicesList({
key={device.id?.id ?? 0}
name={`ota.selectedDevices.id-${device.id?.id ?? 0}`}
deviceNames={deviceNames(device)}
></DeviceCardControl>
/>
))}
</div>
</>
@@ -344,84 +344,78 @@ export function FlashingMethodStep({
const flashingMethod = watch('flashingMethod');
return (
<>
<div className="flex flex-col w-full">
<div className="flex flex-grow flex-col gap-4">
<Typography color="secondary">
{l10n.getString('firmware_tool-flash_method_step-description')}
</Typography>
</div>
<div className="my-4">
{!isGlobalLoading && (
<div className="flex flex-col gap-3">
<div className="grid xs-settings:grid-cols-2 mobile-settings:grid-cols-1 gap-3">
<Localized
id="firmware_tool-flash_method_step-ota"
attrs={{ label: true, description: true }}
>
<Radio
control={control}
name="flashingMethod"
value={FirmwareUpdateMethod.OTAFirmwareUpdate.toString()}
label=""
></Radio>
</Localized>
<Localized
id="firmware_tool-flash_method_step-serial"
attrs={{ label: true, description: true }}
>
<Radio
control={control}
name="flashingMethod"
value={FirmwareUpdateMethod.SerialFirmwareUpdate.toString()}
label=""
></Radio>
</Localized>
</div>
{flashingMethod ===
FirmwareUpdateMethod.SerialFirmwareUpdate.toString() && (
<SerialDevicesList
<div className="flex flex-col w-full">
<div className="flex flex-grow flex-col gap-4">
<Typography color="secondary">
{l10n.getString('firmware_tool-flash_method_step-description')}
</Typography>
</div>
<div className="my-4">
{!isGlobalLoading && (
<div className="flex flex-col gap-3">
<div className="grid xs-settings:grid-cols-2 mobile-settings:grid-cols-1 gap-3">
<Localized
id="firmware_tool-flash_method_step-ota"
attrs={{ label: true, description: true }}
>
<Radio
control={control}
watch={watch}
reset={reset}
></SerialDevicesList>
)}
{flashingMethod ===
FirmwareUpdateMethod.OTAFirmwareUpdate.toString() && (
<OTADevicesList
name="flashingMethod"
value={FirmwareUpdateMethod.OTAFirmwareUpdate.toString()}
label=""
/>
</Localized>
<Localized
id="firmware_tool-flash_method_step-serial"
attrs={{ label: true, description: true }}
>
<Radio
control={control}
watch={watch}
reset={reset}
></OTADevicesList>
)}
<div className="flex justify-between">
<Localized id="firmware_tool-previous_step">
<Button variant="secondary" onClick={prevStep}></Button>
</Localized>
<Localized id="firmware_tool-next_step">
<Button
variant="primary"
disabled={
!isValid ||
selectedDevices === null ||
selectedDevices.length === 0
}
onClick={nextStep}
></Button>
</Localized>
</div>
</div>
)}
{isGlobalLoading && (
<div className="flex justify-center flex-col items-center gap-3 h-44">
<LoaderIcon slimeState={SlimeState.JUMPY}></LoaderIcon>
<Localized id="firmware_tool-loading">
<Typography color="secondary"></Typography>
name="flashingMethod"
value={FirmwareUpdateMethod.SerialFirmwareUpdate.toString()}
label=""
/>
</Localized>
</div>
)}
</div>
{flashingMethod ===
FirmwareUpdateMethod.SerialFirmwareUpdate.toString() && (
<SerialDevicesList
control={control}
watch={watch}
reset={reset}
/>
)}
{flashingMethod ===
FirmwareUpdateMethod.OTAFirmwareUpdate.toString() && (
<OTADevicesList control={control} watch={watch} reset={reset} />
)}
<div className="flex justify-between">
<Localized id="firmware_tool-previous_step">
<Button variant="secondary" onClick={prevStep} />
</Localized>
<Localized id="firmware_tool-next_step">
<Button
variant="primary"
disabled={
!isValid ||
selectedDevices === null ||
selectedDevices.length === 0
}
onClick={nextStep}
/>
</Localized>
</div>
</div>
)}
{isGlobalLoading && (
<div className="flex justify-center flex-col items-center gap-3 h-44">
<LoaderIcon slimeState={SlimeState.JUMPY} />
<Localized id="firmware_tool-loading">
<Typography color="secondary" />
</Localized>
</div>
)}
</div>
</>
</div>
);
}

View File

@@ -17,11 +17,11 @@ import {
RpcMessage,
} from 'solarxr-protocol';
import { useOnboarding } from '@/hooks/onboarding';
import { DeviceCardControl } from './DeviceCard';
import { WarningBox } from '@/components/commons/TipBox';
import { Button } from '@/components/commons/Button';
import { useNavigate } from 'react-router-dom';
import { firmwareToolS3BaseUrl } from '@/firmware-tool-api/firmwareToolFetcher';
import { DeviceCardControl } from './DeviceCard';
export function FlashingStep({
goTo,
@@ -38,14 +38,17 @@ export function FlashingStep({
useFirmwareTool();
const { state: onboardingState } = useOnboarding();
const { sendRPCPacket, useRPCPacket } = useWebsocketAPI();
const [status, setStatus] = useState<{
[key: string]: {
status: FirmwareUpdateStatus;
type: FirmwareUpdateMethod;
progress: number;
deviceNames: string[];
};
}>({});
const [status, setStatus] = useState<
Record<
string,
{
status: FirmwareUpdateStatus;
type: FirmwareUpdateMethod;
progress: number;
deviceNames: string[];
}
>
>({});
const clear = () => {
setStatus({});
@@ -161,60 +164,58 @@ export function FlashingStep({
);
return (
<>
<div className="flex flex-col w-full">
<div className="flex flex-grow flex-col gap-4">
<Typography color="secondary">
{l10n.getString('firmware_tool-flashing_step-description')}
</Typography>
</div>
<div className="flex flex-col w-full">
<div className="flex flex-grow flex-col gap-4">
<Typography color="secondary">
{l10n.getString('firmware_tool-flashing_step-description')}
</Typography>
</div>
<div className="my-4 flex gap-2 flex-col">
{shouldShowRebootWarning && (
<Localized id="firmware_tool-flashing_step-warning-v2">
<WarningBox>Warning</WarningBox>
</Localized>
)}
<div className="my-4 flex gap-2 flex-col">
{shouldShowRebootWarning && (
<Localized id="firmware_tool-flashing_step-warning-v2">
<WarningBox>Warning</WarningBox>
</Localized>
)}
{Object.keys(status).map((id) => {
const val = status[id];
{Object.keys(status).map((id) => {
const val = status[id];
return (
<DeviceCardControl
status={val.status}
progress={val.progress}
key={id}
deviceNames={val.deviceNames}
></DeviceCardControl>
);
})}
<div className="flex gap-2 self-end">
<Localized id="firmware_tool-retry">
<Button
variant="secondary"
disabled={trackerWithErrors.length === 0}
onClick={retryError}
></Button>
</Localized>
<Localized id="firmware_tool-flashing_step-flash_more">
<Button
variant="secondary"
disabled={hasPendingTrackers}
onClick={() => goTo('FlashingMethod')}
></Button>
</Localized>
<Localized id="firmware_tool-flashing_step-exit">
<Button
variant="primary"
onClick={() => {
clear();
nav('/');
}}
></Button>
</Localized>
</div>
return (
<DeviceCardControl
status={val.status}
progress={val.progress}
key={id}
deviceNames={val.deviceNames}
/>
);
})}
<div className="flex gap-2 self-end">
<Localized id="firmware_tool-retry">
<Button
variant="secondary"
disabled={trackerWithErrors.length === 0}
onClick={retryError}
/>
</Localized>
<Localized id="firmware_tool-flashing_step-flash_more">
<Button
variant="secondary"
disabled={hasPendingTrackers}
onClick={() => goTo('FlashingMethod')}
/>
</Localized>
<Localized id="firmware_tool-flashing_step-exit">
<Button
variant="primary"
onClick={() => {
clear();
nav('/');
}}
/>
</Localized>
</div>
</div>
</>
</div>
);
}

View File

@@ -24,79 +24,77 @@ export function SelectBoardStep({
const { isFetching, data: boards } = useGetFirmwaresBoards({});
return (
<>
<div className="flex flex-col w-full">
<div className="flex flex-grow flex-col gap-4">
<Typography color="secondary">
{l10n.getString('firmware_tool-board_step-description')}
</Typography>
</div>
<div className="my-4">
{!isFetching && (
<div className="gap-2 flex flex-col">
<div className="grid sm:grid-cols-2 mobile-settings:grid-cols-1 gap-2">
{boards
?.filter(
(board) =>
!!firmwareToolToBoardType[
board as CreateBoardConfigDTO['type']
]
)
.map((board) => (
<div
key={board}
className={classNames(
'p-3 rounded-md hover:bg-background-50',
{
'bg-background-50 text-background-10':
newConfig?.boardConfig?.type === board,
'bg-background-60':
newConfig?.boardConfig?.type !== board,
}
)}
onClick={() => {
selectBoard(board as CreateBoardConfigDTO['type']);
}}
>
{l10n.getString(
`board_type-${
BoardType[
firmwareToolToBoardType[
board as CreateBoardConfigDTO['type']
] ?? BoardType.UNKNOWN
]
}`
)}
</div>
))}
</div>
<div className="flex justify-end">
<Localized id="firmware_tool-next_step">
<Button
variant="primary"
disabled={!newConfig?.boardConfig?.type}
onClick={() => {
if (defaultConfig?.shouldOnlyUseDefaults) {
goTo('SelectFirmware');
} else {
nextStep();
<div className="flex flex-col w-full">
<div className="flex flex-grow flex-col gap-4">
<Typography color="secondary">
{l10n.getString('firmware_tool-board_step-description')}
</Typography>
</div>
<div className="my-4">
{!isFetching && (
<div className="gap-2 flex flex-col">
<div className="grid sm:grid-cols-2 mobile-settings:grid-cols-1 gap-2">
{boards
?.filter(
(board) =>
!!firmwareToolToBoardType[
board as CreateBoardConfigDTO['type']
]
)
.map((board) => (
<div
key={board}
className={classNames(
'p-3 rounded-md hover:bg-background-50',
{
'bg-background-50 text-background-10':
newConfig?.boardConfig?.type === board,
'bg-background-60':
newConfig?.boardConfig?.type !== board,
}
)}
onClick={() => {
selectBoard(board as CreateBoardConfigDTO['type']);
}}
></Button>
</Localized>
</div>
>
{l10n.getString(
`board_type-${
BoardType[
firmwareToolToBoardType[
board as CreateBoardConfigDTO['type']
] ?? BoardType.UNKNOWN
]
}`
)}
</div>
))}
</div>
)}
{isFetching && (
<div className="flex justify-center flex-col items-center gap-3 h-44">
<LoaderIcon slimeState={SlimeState.JUMPY}></LoaderIcon>
<Localized id="firmware_tool-loading">
<Typography color="secondary"></Typography>
<div className="flex justify-end">
<Localized id="firmware_tool-next_step">
<Button
variant="primary"
disabled={!newConfig?.boardConfig?.type}
onClick={() => {
if (defaultConfig?.shouldOnlyUseDefaults) {
goTo('SelectFirmware');
} else {
nextStep();
}
}}
/>
</Localized>
</div>
)}
</div>
</div>
)}
{isFetching && (
<div className="flex justify-center flex-col items-center gap-3 h-44">
<LoaderIcon slimeState={SlimeState.JUMPY} />
<Localized id="firmware_tool-loading">
<Typography color="secondary" />
</Localized>
</div>
)}
</div>
</>
</div>
);
}

View File

@@ -26,95 +26,94 @@ export function SelectFirmwareStep({
const showThirdParty = watch('thirdParty');
const getName = (name: string) => {
return showThirdParty ? name : name.substring(name.indexOf('/') + 1);
};
const getName = (name: string) =>
showThirdParty ? name : name.substring(name.indexOf('/') + 1);
const filteredFirmwares = useMemo(() => {
return firmwares?.filter(
({ name }) => name.split('/')[0] === 'SlimeVR' || showThirdParty
);
}, [firmwares, showThirdParty]);
const filteredFirmwares = useMemo(
() =>
firmwares?.filter(
({ name }) => name.split('/')[0] === 'SlimeVR' || showThirdParty
),
[firmwares, showThirdParty]
);
return (
<>
<div className="flex flex-col w-full">
<div className="flex justify-between items-center mobile:flex-col gap-4">
<Typography color="secondary">
{l10n.getString('firmware_tool-select_firmware_step-description')}
</Typography>
<div>
<Localized
id="firmware_tool-select_firmware_step-show-third-party"
attrs={{ label: true }}
>
<CheckBox
control={control}
name="thirdParty"
label="Show third party firmwares"
></CheckBox>
</Localized>
</div>
</div>
<div className="my-4">
{!isFetching && (
<div className="flex flex-col gap-4">
<div className="xs-settings:max-h-96 xs-settings:overflow-y-auto xs-settings:px-2">
<div className="grid sm:grid-cols-2 mobile-settings:grid-cols-1 gap-2">
{filteredFirmwares?.map((firmware) => (
<div
key={firmware.id}
className={classNames(
'p-3 rounded-md hover:bg-background-50',
{
'bg-background-50 text-background-10':
newConfig?.version === firmware.name,
'bg-background-60':
newConfig?.version !== firmware.name,
}
)}
onClick={() => {
selectVersion(firmware.name);
}}
>
{getName(firmware.name)}
</div>
))}
</div>
</div>
<div className="flex justify-between">
<Localized id="firmware_tool-previous_step">
<Button
variant="tertiary"
onClick={() => {
if (defaultConfig?.shouldOnlyUseDefaults) {
goTo('SelectBoard');
} else {
prevStep();
}
}}
></Button>
</Localized>
<Localized id="firmware_tool-next_step">
<Button
variant="primary"
disabled={!newConfig?.version}
onClick={nextStep}
></Button>
</Localized>
</div>
</div>
)}
{isFetching && (
<div className="flex justify-center flex-col items-center gap-3 h-44">
<LoaderIcon slimeState={SlimeState.JUMPY}></LoaderIcon>
<Localized id="firmware_tool-loading">
<Typography color="secondary"></Typography>
</Localized>
</div>
)}
<div className="flex flex-col w-full">
<div className="flex justify-between items-center mobile:flex-col gap-4">
<Typography color="secondary">
{l10n.getString('firmware_tool-select_firmware_step-description')}
</Typography>
<div>
<Localized
id="firmware_tool-select_firmware_step-show-third-party"
attrs={{ label: true }}
>
<CheckBox
control={control}
name="thirdParty"
label="Show third party firmwares"
/>
</Localized>
</div>
</div>
</>
<div className="my-4">
{!isFetching && (
<div className="flex flex-col gap-4">
<div className="xs-settings:max-h-96 xs-settings:overflow-y-auto xs-settings:px-2">
<div className="grid sm:grid-cols-2 mobile-settings:grid-cols-1 gap-2">
{filteredFirmwares?.map((firmware) => (
<div
key={firmware.id}
className={classNames(
'p-3 rounded-md hover:bg-background-50',
{
'bg-background-50 text-background-10':
newConfig?.version === firmware.name,
'bg-background-60':
newConfig?.version !== firmware.name,
}
)}
onClick={() => {
selectVersion(firmware.name);
}}
>
{getName(firmware.name)}
</div>
))}
</div>
</div>
<div className="flex justify-between">
<Localized id="firmware_tool-previous_step">
<Button
variant="tertiary"
onClick={() => {
if (defaultConfig?.shouldOnlyUseDefaults) {
goTo('SelectBoard');
} else {
prevStep();
}
}}
/>
</Localized>
<Localized id="firmware_tool-next_step">
<Button
variant="primary"
disabled={!newConfig?.version}
onClick={nextStep}
/>
</Localized>
</div>
</div>
)}
{isFetching && (
<div className="flex justify-center flex-col items-center gap-3 h-44">
<LoaderIcon slimeState={SlimeState.JUMPY} />
<Localized id="firmware_tool-loading">
<Typography color="secondary" />
</Localized>
</div>
)}
</div>
</div>
);
}

View File

@@ -36,7 +36,7 @@ import { devicesAtom } from '@/store/app-store';
import { checkForUpdate } from '@/hooks/firmware-update';
interface FirmwareUpdateForm {
selectedDevices: { [key: string]: boolean };
selectedDevices: Record<string, boolean>;
}
interface UpdateStatus {
@@ -60,9 +60,9 @@ const DeviceList = ({
}) => {
const { l10n } = useLocalization();
return devices.map((device, index) => (
return devices.map((device) => (
<DeviceCardControl
key={index}
key={device.id?.id}
control={control}
name={`selectedDevices.${device.id?.id ?? 0}`}
deviceNames={deviceNames(device, l10n)}
@@ -74,7 +74,7 @@ const StatusList = ({ status }: { status: Record<string, UpdateStatus> }) => {
const statusKeys = Object.keys(status);
const devices = useAtomValue(devicesAtom);
return statusKeys.map((id, index) => {
return statusKeys.map((id) => {
const val = status[id];
if (!val) throw new Error('there should always be a val');
@@ -85,19 +85,19 @@ const StatusList = ({ status }: { status: Record<string, UpdateStatus> }) => {
<DeviceCardControl
status={val.status}
progress={val.progress}
key={index}
key={id}
deviceNames={val.deviceNames}
online={device?.trackers.some(
({ status }) => status === TrackerStatus.OK
)}
></DeviceCardControl>
/>
);
});
};
const MarkdownLink = (props: ComponentProps<'a'>) => (
<A href={props.href}>{props.children}</A>
);
function MarkdownLink({ href, children }: ComponentProps<'a'>) {
return <A href={href}>{children}</A>;
}
export function FirmwareUpdate() {
const navigate = useNavigate();
@@ -312,15 +312,15 @@ export function FirmwareUpdate() {
<div className="flex flex-col p-4 w-full items-center justify-center">
<div className="mobile:w-full w-10/12 h-full flex flex-col gap-2">
<Localized id="firmware_update-title">
<Typography variant="main-title"></Typography>
<Typography variant="main-title" />
</Localized>
<div className="grid md:grid-cols-2 xs:grid-cols-1 gap-5">
<div className="flex flex-col gap-2">
<Localized id="firmware_update-devices">
<Typography variant="section-title"></Typography>
<Typography variant="section-title" />
</Localized>
<Localized id="firmware_update-devices-description">
<Typography variant="standard" color="secondary"></Typography>
<Typography variant="standard" color="secondary" />
</Localized>
<div className="flex flex-col gap-4 overflow-y-auto xs:max-h-[530px]">
{devices.length === 0 &&
@@ -337,9 +337,9 @@ export function FirmwareUpdate() {
)}
<div className="flex flex-col gap-4 h-full">
{statusKeys.length > 0 ? (
<StatusList status={status}></StatusList>
<StatusList status={status} />
) : (
<DeviceList control={control} devices={devices}></DeviceList>
<DeviceList control={control} devices={devices} />
)}
{devices.length === 0 && statusKeys.length === 0 && (
<div
@@ -347,9 +347,9 @@ export function FirmwareUpdate() {
'rounded-xl bg-background-60 justify-center flex-col items-center flex pb-10 py-5 gap-5'
)}
>
<LoaderIcon slimeState={SlimeState.JUMPY}></LoaderIcon>
<LoaderIcon slimeState={SlimeState.JUMPY} />
<Localized id="firmware_update-looking_for_devices">
<Typography></Typography>
<Typography />
</Localized>
</div>
)}
@@ -361,7 +361,7 @@ export function FirmwareUpdate() {
id="firmware_update-changelog-title"
vars={{ version: currentFirmwareRelease?.name ?? 'unknown' }}
>
<Typography variant="main-title"></Typography>
<Typography variant="main-title" />
</Localized>
<div className="overflow-y-scroll max-h-[430px] md:h-[430px] bg-background-60 rounded-lg p-4">
<Markdown
@@ -380,25 +380,21 @@ export function FirmwareUpdate() {
</div>
<div className="flex justify-end pb-2 gap-2 mobile:flex-col">
<Localized id="firmware_update-retry">
<Button
variant="secondary"
disabled={!canRetry}
onClick={retry}
></Button>
<Button variant="secondary" disabled={!canRetry} onClick={retry} />
</Localized>
<Localized id="firmware_update-update">
<Button
variant="primary"
disabled={!canStartUpdate}
onClick={startUpdate}
></Button>
/>
</Localized>
<Localized id="firmware_update-exit">
<Button
variant="primary"
onClick={exit}
disabled={hasPendingTrackers}
></Button>
/>
</Localized>
</div>
</div>

View File

@@ -17,6 +17,7 @@ import classNames from 'classnames';
import { useAtomValue } from 'jotai';
import { flatTrackersAtom } from '@/store/app-store';
import { useVRCConfig } from '@/hooks/vrc-config';
import { uniqueNumberFromTracker } from '@/hooks/tracker';
const DONT_REPEAT_STATUSES = [StatusData.StatusTrackerReset];
@@ -49,7 +50,7 @@ export function Home() {
to="/vr-mode"
className="xs:hidden absolute z-50 h-12 w-12 rounded-full bg-accent-background-30 bottom-3 right-3 flex justify-center items-center fill-background-10"
>
<HeadsetIcon></HeadsetIcon>
<HeadsetIcon />
</NavLink>
<div className="h-full overflow-y-auto">
<div
@@ -74,12 +75,12 @@ export function Home() {
<WarningBox whitespace={false}>
<div className="flex gap-2 justify-between items-center w-full">
<div className="flex">
<Localized id={'vrc_config-invalid'}></Localized>
<Localized id={'vrc_config-invalid'} />
</div>
<div className="flex">
<Link to="/vrc-warnings">
<div className="rounded-md p-2 bg-background-90 bg-opacity-15 hover:bg-background-10 hover:bg-opacity-25 text-nowrap">
<Localized id={'vrc_config-show_more'}></Localized>
<Localized id={'vrc_config-show_more'} />
</div>
</Link>
</div>
@@ -98,9 +99,9 @@ export function Home() {
{!config?.debug && trackers.length > 0 && (
<div className="grid sm:grid-cols-1 md:grid-cols-2 gap-4 px-5 my-5">
{trackers.map(({ tracker, device }, index) => (
{trackers.map(({ tracker, device }) => (
<TrackerCard
key={index}
key={uniqueNumberFromTracker(tracker)}
tracker={tracker}
device={device}
onClick={() => sendToSettings(tracker)}
@@ -119,7 +120,7 @@ export function Home() {
<TrackersTable
flatTrackers={trackers}
clickedTracker={(tracker) => sendToSettings(tracker)}
></TrackersTable>
/>
</div>
)}
</div>

View File

@@ -127,18 +127,21 @@ export function ResetButton({
switch (type) {
case ResetType.Yaw:
return l10n.getString(
'reset-yaw' +
(bodyPartsToReset !== 'default' ? '-' + bodyPartsToReset : '')
`reset-yaw${
bodyPartsToReset !== 'default' ? `-${bodyPartsToReset}` : ''
}`
);
case ResetType.Mounting:
return l10n.getString(
'reset-mounting' +
(bodyPartsToReset !== 'default' ? '-' + bodyPartsToReset : '')
`reset-mounting${
bodyPartsToReset !== 'default' ? `-${bodyPartsToReset}` : ''
}`
);
case ResetType.Full:
return l10n.getString(
'reset-full' +
(bodyPartsToReset !== 'default' ? '-' + bodyPartsToReset : '')
`reset-full${
bodyPartsToReset !== 'default' ? `-${bodyPartsToReset}` : ''
}`
);
}
}, [type, bodyPartsToReset]);
@@ -149,15 +152,17 @@ export function ResetButton({
return <YawResetIcon width={20} />;
case ResetType.Mounting:
switch (bodyPartsToReset) {
case 'default':
return <MountingResetIcon width={20} />;
case 'feet':
return <FootIcon width={30} />;
case 'fingers':
return <FingersIcon width={20} />;
case 'default':
default:
return <MountingResetIcon width={20} />;
}
case ResetType.Full:
return <FullResetIcon width={20} />;
}
return <FullResetIcon width={20} />;
};
const maybePlaySoundOnResetEnd = (type: ResetType) => {
@@ -177,12 +182,13 @@ export function ResetButton({
maybePlaySoundOnResetStart();
};
useEffect(() => {
return () => {
useEffect(
() => () => {
if (finishedTimeoutRef.current !== -1)
clearTimeout(finishedTimeoutRef.current);
};
}, []);
},
[]
);
return size === 'small' ? (
<Button

View File

@@ -3,10 +3,10 @@ import { BodyPart } from 'solarxr-protocol';
import { AssignMode } from '@/hooks/config';
import { BodyInteractions } from '@/components/commons/BodyInteractions';
import { TrackerPartCard } from '@/components/tracker/TrackerPartCard';
import { BodyPartError } from './pages/trackers-assign/TrackerAssignment';
import { SIDES } from '@/components/commons/PersonFrontIcon';
import { useAtomValue } from 'jotai';
import { assignedTrackersAtom, FlatDeviceTracker } from '@/store/app-store';
import { BodyPartError } from './pages/trackers-assign/TrackerAssignment';
export const ARMS_PARTS = new Set([
BodyPart.LEFT_UPPER_ARM,
@@ -116,7 +116,7 @@ export function BodyAssignment({
const trackerPartGrouped = useMemo(
() =>
assignedTrackers.reduce<{ [key: number]: FlatDeviceTracker[] }>(
assignedTrackers.reduce<Record<number, FlatDeviceTracker[]>>(
(curr, td) => {
if (!td && onlyAssigned) return curr;
@@ -151,251 +151,249 @@ export function BodyAssignment({
);
return (
<>
<BodyInteractions
width={width}
mirror={mirror}
dotsSize={dotSize}
assignedRoles={assignedRoles}
highlightedRoles={highlightedRoles}
onSelectRole={onRoleSelected}
leftControls={
<div className="flex flex-col justify-between h-full text-right">
<div className="flex flex-col gap-2">
{hasBodyPart(BodyPart.HEAD) && (
<TrackerPartCard
onlyAssigned={onlyAssigned}
roleError={rolesWithErrors[BodyPart.HEAD]?.label}
td={trackerPartGrouped[BodyPart.HEAD]}
role={BodyPart.HEAD}
onClick={() => onRoleSelected(BodyPart.HEAD)}
direction="right"
/>
)}
{hasBodyPart(BodyPart.NECK) && (
<TrackerPartCard
onlyAssigned={onlyAssigned}
roleError={rolesWithErrors[BodyPart.NECK]?.label}
td={trackerPartGrouped[BodyPart.NECK]}
role={BodyPart.NECK}
onClick={() => onRoleSelected(BodyPart.NECK)}
direction="right"
/>
)}
</div>
<div className="flex flex-col gap-2">
{hasBodyPart(SIDES[left].shoulder) && (
<TrackerPartCard
onlyAssigned={onlyAssigned}
roleError={rolesWithErrors[SIDES[left].shoulder]?.label}
td={trackerPartGrouped[SIDES[left].shoulder]}
role={SIDES[left].shoulder}
onClick={() => onRoleSelected(SIDES[left].shoulder)}
direction="right"
/>
)}
{hasBodyPart(SIDES[left].upperArm) && (
<TrackerPartCard
onlyAssigned={onlyAssigned}
roleError={rolesWithErrors[SIDES[left].upperArm]?.label}
td={trackerPartGrouped[SIDES[left].upperArm]}
role={SIDES[left].upperArm}
onClick={() => onRoleSelected(SIDES[left].upperArm)}
direction="right"
/>
)}
</div>
<div className="flex flex-col gap-2">
{hasBodyPart(SIDES[left].lowerArm) && (
<TrackerPartCard
onlyAssigned={onlyAssigned}
roleError={rolesWithErrors[SIDES[left].lowerArm]?.label}
td={trackerPartGrouped[SIDES[left].lowerArm]}
role={SIDES[left].lowerArm}
onClick={() => onRoleSelected(SIDES[left].lowerArm)}
direction="right"
/>
)}
{hasBodyPart(SIDES[left].hand) && (
<TrackerPartCard
onlyAssigned={onlyAssigned}
roleError={rolesWithErrors[SIDES[left].hand]?.label}
td={trackerPartGrouped[SIDES[left].hand]}
role={SIDES[left].hand}
onClick={() => onRoleSelected(SIDES[left].hand)}
direction="right"
/>
)}
</div>
<div className="flex flex-col gap-2">
{hasBodyPart(SIDES[left].upperLeg) && (
<TrackerPartCard
onlyAssigned={onlyAssigned}
roleError={rolesWithErrors[SIDES[left].upperLeg]?.label}
td={trackerPartGrouped[SIDES[left].upperLeg]}
role={SIDES[left].upperLeg}
onClick={() => onRoleSelected(SIDES[left].upperLeg)}
direction="right"
/>
)}
{hasBodyPart(SIDES[left].lowerLeg) && (
<TrackerPartCard
onlyAssigned={onlyAssigned}
roleError={rolesWithErrors[SIDES[left].lowerLeg]?.label}
td={trackerPartGrouped[SIDES[left].lowerLeg]}
role={SIDES[left].lowerLeg}
onClick={() => onRoleSelected(SIDES[left].lowerLeg)}
direction="right"
/>
)}
{hasBodyPart(SIDES[left].foot) && (
<TrackerPartCard
onlyAssigned={onlyAssigned}
roleError={rolesWithErrors[SIDES[left].foot]?.label}
td={trackerPartGrouped[SIDES[left].foot]}
role={SIDES[left].foot}
onClick={() => onRoleSelected(SIDES[left].foot)}
direction="right"
/>
)}
</div>
</div>
}
rightControls={
<div className="flex flex-col justify-between h-full">
{hasBodyPart(BodyPart.UPPER_CHEST) && (
<BodyInteractions
width={width}
mirror={mirror}
dotsSize={dotSize}
assignedRoles={assignedRoles}
highlightedRoles={highlightedRoles}
onSelectRole={onRoleSelected}
leftControls={
<div className="flex flex-col justify-between h-full text-right">
<div className="flex flex-col gap-2">
{hasBodyPart(BodyPart.HEAD) && (
<TrackerPartCard
onlyAssigned={onlyAssigned}
roleError={rolesWithErrors[BodyPart.UPPER_CHEST]?.label}
td={trackerPartGrouped[BodyPart.UPPER_CHEST]}
role={BodyPart.UPPER_CHEST}
onClick={() => onRoleSelected(BodyPart.UPPER_CHEST)}
roleError={rolesWithErrors[BodyPart.HEAD]?.label}
td={trackerPartGrouped[BodyPart.HEAD]}
role={BodyPart.HEAD}
onClick={() => onRoleSelected(BodyPart.HEAD)}
direction="right"
/>
)}
{hasBodyPart(BodyPart.NECK) && (
<TrackerPartCard
onlyAssigned={onlyAssigned}
roleError={rolesWithErrors[BodyPart.NECK]?.label}
td={trackerPartGrouped[BodyPart.NECK]}
role={BodyPart.NECK}
onClick={() => onRoleSelected(BodyPart.NECK)}
direction="right"
/>
)}
</div>
<div className="flex flex-col gap-2">
{hasBodyPart(SIDES[left].shoulder) && (
<TrackerPartCard
onlyAssigned={onlyAssigned}
roleError={rolesWithErrors[SIDES[left].shoulder]?.label}
td={trackerPartGrouped[SIDES[left].shoulder]}
role={SIDES[left].shoulder}
onClick={() => onRoleSelected(SIDES[left].shoulder)}
direction="right"
/>
)}
{hasBodyPart(SIDES[left].upperArm) && (
<TrackerPartCard
onlyAssigned={onlyAssigned}
roleError={rolesWithErrors[SIDES[left].upperArm]?.label}
td={trackerPartGrouped[SIDES[left].upperArm]}
role={SIDES[left].upperArm}
onClick={() => onRoleSelected(SIDES[left].upperArm)}
direction="right"
/>
)}
</div>
<div className="flex flex-col gap-2">
{hasBodyPart(SIDES[left].lowerArm) && (
<TrackerPartCard
onlyAssigned={onlyAssigned}
roleError={rolesWithErrors[SIDES[left].lowerArm]?.label}
td={trackerPartGrouped[SIDES[left].lowerArm]}
role={SIDES[left].lowerArm}
onClick={() => onRoleSelected(SIDES[left].lowerArm)}
direction="right"
/>
)}
{hasBodyPart(SIDES[left].hand) && (
<TrackerPartCard
onlyAssigned={onlyAssigned}
roleError={rolesWithErrors[SIDES[left].hand]?.label}
td={trackerPartGrouped[SIDES[left].hand]}
role={SIDES[left].hand}
onClick={() => onRoleSelected(SIDES[left].hand)}
direction="right"
/>
)}
</div>
<div className="flex flex-col gap-2">
{hasBodyPart(SIDES[left].upperLeg) && (
<TrackerPartCard
onlyAssigned={onlyAssigned}
roleError={rolesWithErrors[SIDES[left].upperLeg]?.label}
td={trackerPartGrouped[SIDES[left].upperLeg]}
role={SIDES[left].upperLeg}
onClick={() => onRoleSelected(SIDES[left].upperLeg)}
direction="right"
/>
)}
{hasBodyPart(SIDES[left].lowerLeg) && (
<TrackerPartCard
onlyAssigned={onlyAssigned}
roleError={rolesWithErrors[SIDES[left].lowerLeg]?.label}
td={trackerPartGrouped[SIDES[left].lowerLeg]}
role={SIDES[left].lowerLeg}
onClick={() => onRoleSelected(SIDES[left].lowerLeg)}
direction="right"
/>
)}
{hasBodyPart(SIDES[left].foot) && (
<TrackerPartCard
onlyAssigned={onlyAssigned}
roleError={rolesWithErrors[SIDES[left].foot]?.label}
td={trackerPartGrouped[SIDES[left].foot]}
role={SIDES[left].foot}
onClick={() => onRoleSelected(SIDES[left].foot)}
direction="right"
/>
)}
</div>
</div>
}
rightControls={
<div className="flex flex-col justify-between h-full">
{hasBodyPart(BodyPart.UPPER_CHEST) && (
<TrackerPartCard
onlyAssigned={onlyAssigned}
roleError={rolesWithErrors[BodyPart.UPPER_CHEST]?.label}
td={trackerPartGrouped[BodyPart.UPPER_CHEST]}
role={BodyPart.UPPER_CHEST}
onClick={() => onRoleSelected(BodyPart.UPPER_CHEST)}
direction="left"
/>
)}
{hasBodyPart(BodyPart.CHEST) && (
<TrackerPartCard
onlyAssigned={onlyAssigned}
roleError={rolesWithErrors[BodyPart.CHEST]?.label}
td={trackerPartGrouped[BodyPart.CHEST]}
role={BodyPart.CHEST}
onClick={() => onRoleSelected(BodyPart.CHEST)}
direction="left"
/>
)}
<div className="flex flex-col gap-2">
{hasBodyPart(SIDES[right].shoulder) && (
<TrackerPartCard
onlyAssigned={onlyAssigned}
roleError={rolesWithErrors[SIDES[right].shoulder]?.label}
td={trackerPartGrouped[SIDES[right].shoulder]}
role={SIDES[right].shoulder}
onClick={() => onRoleSelected(SIDES[right].shoulder)}
direction="left"
/>
)}
{hasBodyPart(BodyPart.CHEST) && (
{hasBodyPart(SIDES[right].upperArm) && (
<TrackerPartCard
onlyAssigned={onlyAssigned}
roleError={rolesWithErrors[BodyPart.CHEST]?.label}
td={trackerPartGrouped[BodyPart.CHEST]}
role={BodyPart.CHEST}
onClick={() => onRoleSelected(BodyPart.CHEST)}
roleError={rolesWithErrors[SIDES[right].upperArm]?.label}
td={trackerPartGrouped[SIDES[right].upperArm]}
role={SIDES[right].upperArm}
onClick={() => onRoleSelected(SIDES[right].upperArm)}
direction="left"
/>
)}
</div>
<div className="flex flex-col gap-2">
{hasBodyPart(BodyPart.WAIST) && (
<TrackerPartCard
onlyAssigned={onlyAssigned}
roleError={rolesWithErrors[BodyPart.WAIST]?.label}
td={trackerPartGrouped[BodyPart.WAIST]}
onClick={() => onRoleSelected(BodyPart.WAIST)}
role={BodyPart.WAIST}
direction="left"
/>
)}
<div className="flex flex-col gap-2">
{hasBodyPart(SIDES[right].shoulder) && (
<TrackerPartCard
onlyAssigned={onlyAssigned}
roleError={rolesWithErrors[SIDES[right].shoulder]?.label}
td={trackerPartGrouped[SIDES[right].shoulder]}
role={SIDES[right].shoulder}
onClick={() => onRoleSelected(SIDES[right].shoulder)}
direction="left"
/>
)}
{hasBodyPart(SIDES[right].upperArm) && (
<TrackerPartCard
onlyAssigned={onlyAssigned}
roleError={rolesWithErrors[SIDES[right].upperArm]?.label}
td={trackerPartGrouped[SIDES[right].upperArm]}
role={SIDES[right].upperArm}
onClick={() => onRoleSelected(SIDES[right].upperArm)}
direction="left"
/>
)}
</div>
<div className="flex flex-col gap-2">
{hasBodyPart(BodyPart.WAIST) && (
<TrackerPartCard
onlyAssigned={onlyAssigned}
roleError={rolesWithErrors[BodyPart.WAIST]?.label}
td={trackerPartGrouped[BodyPart.WAIST]}
onClick={() => onRoleSelected(BodyPart.WAIST)}
role={BodyPart.WAIST}
direction="left"
/>
)}
{hasBodyPart(BodyPart.HIP) && (
<TrackerPartCard
onlyAssigned={onlyAssigned}
roleError={rolesWithErrors[BodyPart.HIP]?.label}
td={trackerPartGrouped[BodyPart.HIP]}
onClick={() => onRoleSelected(BodyPart.HIP)}
role={BodyPart.HIP}
direction="left"
/>
)}
</div>
<div className="flex flex-col gap-2">
{hasBodyPart(SIDES[right].lowerArm) && (
<TrackerPartCard
onlyAssigned={onlyAssigned}
roleError={rolesWithErrors[SIDES[right].lowerArm]?.label}
td={trackerPartGrouped[SIDES[right].lowerArm]}
role={SIDES[right].lowerArm}
onClick={() => onRoleSelected(SIDES[right].lowerArm)}
direction="left"
/>
)}
{hasBodyPart(SIDES[right].hand) && (
<TrackerPartCard
onlyAssigned={onlyAssigned}
roleError={rolesWithErrors[SIDES[right].hand]?.label}
td={trackerPartGrouped[SIDES[right].hand]}
onClick={() => onRoleSelected(SIDES[right].hand)}
role={SIDES[right].hand}
direction="left"
/>
)}
</div>
<div className="flex flex-col gap-2">
{hasBodyPart(SIDES[right].upperLeg) && (
<TrackerPartCard
onlyAssigned={onlyAssigned}
roleError={rolesWithErrors[SIDES[right].upperLeg]?.label}
td={trackerPartGrouped[SIDES[right].upperLeg]}
role={SIDES[right].upperLeg}
onClick={() => onRoleSelected(SIDES[right].upperLeg)}
direction="left"
/>
)}
{hasBodyPart(SIDES[right].lowerLeg) && (
<TrackerPartCard
onlyAssigned={onlyAssigned}
roleError={rolesWithErrors[SIDES[right].lowerLeg]?.label}
td={trackerPartGrouped[SIDES[right].lowerLeg]}
role={SIDES[right].lowerLeg}
onClick={() => onRoleSelected(SIDES[right].lowerLeg)}
direction="left"
/>
)}
{hasBodyPart(SIDES[right].foot) && (
<TrackerPartCard
onlyAssigned={onlyAssigned}
roleError={rolesWithErrors[SIDES[right].foot]?.label}
td={trackerPartGrouped[SIDES[right].foot]}
role={SIDES[right].foot}
onClick={() => onRoleSelected(SIDES[right].foot)}
direction="left"
/>
)}
</div>
{hasBodyPart(BodyPart.HIP) && (
<TrackerPartCard
onlyAssigned={onlyAssigned}
roleError={rolesWithErrors[BodyPart.HIP]?.label}
td={trackerPartGrouped[BodyPart.HIP]}
onClick={() => onRoleSelected(BodyPart.HIP)}
role={BodyPart.HIP}
direction="left"
/>
)}
</div>
}
></BodyInteractions>
</>
<div className="flex flex-col gap-2">
{hasBodyPart(SIDES[right].lowerArm) && (
<TrackerPartCard
onlyAssigned={onlyAssigned}
roleError={rolesWithErrors[SIDES[right].lowerArm]?.label}
td={trackerPartGrouped[SIDES[right].lowerArm]}
role={SIDES[right].lowerArm}
onClick={() => onRoleSelected(SIDES[right].lowerArm)}
direction="left"
/>
)}
{hasBodyPart(SIDES[right].hand) && (
<TrackerPartCard
onlyAssigned={onlyAssigned}
roleError={rolesWithErrors[SIDES[right].hand]?.label}
td={trackerPartGrouped[SIDES[right].hand]}
onClick={() => onRoleSelected(SIDES[right].hand)}
role={SIDES[right].hand}
direction="left"
/>
)}
</div>
<div className="flex flex-col gap-2">
{hasBodyPart(SIDES[right].upperLeg) && (
<TrackerPartCard
onlyAssigned={onlyAssigned}
roleError={rolesWithErrors[SIDES[right].upperLeg]?.label}
td={trackerPartGrouped[SIDES[right].upperLeg]}
role={SIDES[right].upperLeg}
onClick={() => onRoleSelected(SIDES[right].upperLeg)}
direction="left"
/>
)}
{hasBodyPart(SIDES[right].lowerLeg) && (
<TrackerPartCard
onlyAssigned={onlyAssigned}
roleError={rolesWithErrors[SIDES[right].lowerLeg]?.label}
td={trackerPartGrouped[SIDES[right].lowerLeg]}
role={SIDES[right].lowerLeg}
onClick={() => onRoleSelected(SIDES[right].lowerLeg)}
direction="left"
/>
)}
{hasBodyPart(SIDES[right].foot) && (
<TrackerPartCard
onlyAssigned={onlyAssigned}
roleError={rolesWithErrors[SIDES[right].foot]?.label}
td={trackerPartGrouped[SIDES[right].foot]}
role={SIDES[right].foot}
onClick={() => onRoleSelected(SIDES[right].foot)}
direction="left"
/>
)}
</div>
</div>
}
/>
);
}

View File

@@ -40,7 +40,7 @@ export function NeckWarningModal({
<div className="flex w-full flex-col flex-grow items-center gap-3">
<Localized
id="tracker_selection_menu-neck_warning"
elems={{ b: <b></b> }}
elems={{ b: <b /> }}
>
<WarningBox>
<b>Warning:</b> A neck tracker can be deadly if adjusted too

View File

@@ -15,22 +15,22 @@ export function OnboardingLayout({ children }: { children: ReactNode }) {
return !state.alonePage ? (
<div className="onboarding-layout h-full">
<div style={{ gridArea: 't' }}>
<TopBar progress={state.progress}></TopBar>
<TopBar progress={state.progress} />
</div>
<div style={{ gridArea: 'c' }} className="mt-2 relative">
<div className="absolute top-12 mobile:top-0 right-2 z-50">
<SkipSetupButton
visible={true}
visible
modalVisible={showWarning}
onClick={() => setShowWarning(true)}
></SkipSetupButton>
/>
</div>
<div className="h-full w-full overflow-y-auto">{children}</div>
<SkipSetupWarningModal
accept={skipSetup}
onClose={() => setShowWarning(false)}
isOpen={showWarning}
></SkipSetupWarningModal>
/>
</div>
</div>
) : (

View File

@@ -11,7 +11,7 @@ export function SkipSetupButton({
modalVisible: boolean;
visible: boolean;
}) {
if (!visible) return <></>;
if (!visible) return null;
useEffect(() => {
if (modalVisible) return;
@@ -35,7 +35,7 @@ export function SkipSetupButton({
onClick={onClick}
>
<div className="flex flex-col justify-center items-center">
<EscapeIcon size={42}></EscapeIcon>
<EscapeIcon size={42} />
<p className="text-standard">ESC</p>
</div>
</button>

View File

@@ -40,7 +40,7 @@ export function SkipSetupWarningModal({
>
<div className="flex w-full h-full flex-col ">
<div className="flex flex-col flex-grow items-center gap-3">
<Localized id="onboarding-setup_warning" elems={{ b: <b></b> }}>
<Localized id="onboarding-setup_warning" elems={{ b: <b /> }}>
<WarningBox>
<b>Warning:</b> The setup is needed for good tracking, this is
required if this is your first time using SlimeVR.

View File

@@ -19,10 +19,10 @@ type StepComponentType = FC<{
variant: 'alone' | 'onboarding';
active: boolean;
}>;
export type Step = {
export interface Step {
type: 'numbered' | 'fullsize';
component: StepComponentType;
};
}
export function StepContainer({
children,
@@ -83,9 +83,7 @@ export function StepDot({
)}
onClick={onClick}
>
{active && (
<div className="flex h-2 w-2 rounded-full bg-background-10"></div>
)}
{active && <div className="flex h-2 w-2 rounded-full bg-background-10" />}
{done && <CheckIcon />}
</div>
);
@@ -162,6 +160,7 @@ export function StepperSlider({
{steps.map(({ type, component: StepComponent }, index) => (
<StepContainer
variant={variant}
// eslint-disable-next-line react/no-array-index-key
key={index}
type={type}
width={width}
@@ -181,9 +180,10 @@ export function StepperSlider({
</div>
<div className="flex justify-center items-center gap-2">
{Array.from({ length: steps.length }).map((_, index) => (
// eslint-disable-next-line react/no-array-index-key
<div key={index} className="flex items-center gap-2">
{index !== 0 && (
<div className="w-5 h-1 bg-background-50 rounded-full"></div>
<div className="w-5 h-1 bg-background-50 rounded-full" />
)}
<StepDot
active={index === step}

View File

@@ -75,7 +75,7 @@ export function CalibrationTutorialPage() {
setCalibrationStatus(CalibrationStatus.ERROR);
abortCountdown();
}
}, [calibrationStatus, rested]);
}, [abortCountdown, calibrationStatus, rested]);
const progressBarClass = useMemo(() => {
switch (calibrationStatus) {
@@ -83,6 +83,8 @@ export function CalibrationTutorialPage() {
return 'bg-status-critical';
case CalibrationStatus.SUCCESS:
return 'bg-status-success';
default:
return undefined;
}
}, [calibrationStatus]);
@@ -115,102 +117,100 @@ export function CalibrationTutorialPage() {
applyProgress(0.43);
return (
<>
<div className="flex flex-col gap-5 h-full items-center w-full justify-center relative">
<div className="flex w-full h-full justify-center xs:px-20 mobile:px-5 pb-5 gap-14">
<div className="flex gap-4 self-center mobile:z-10">
<div className="flex flex-col max-w-md gap-3">
<div>
<Typography variant="mobile-title">
{l10n.getString('onboarding-calibration_tutorial')}
</Typography>
<Typography variant="vr-accessible" italic>
{l10n.getString('onboarding-calibration_tutorial-subtitle')}
</Typography>
</div>
<Localized
id="onboarding-calibration_tutorial-description-v1"
elems={{ b: <b></b> }}
>
<Typography color="secondary">
Description on calibration of IMU
</Typography>
</Localized>
<div>
<div className="xs:hidden flex flex-row justify-center">
<div className="stroke-none fill-background-10 ">
<TaybolIcon width="220"></TaybolIcon>
</div>
<div className="flex flex-col gap-5 h-full items-center w-full justify-center relative">
<div className="flex w-full h-full justify-center xs:px-20 mobile:px-5 pb-5 gap-14">
<div className="flex gap-4 self-center mobile:z-10">
<div className="flex flex-col max-w-md gap-3">
<div>
<Typography variant="mobile-title">
{l10n.getString('onboarding-calibration_tutorial')}
</Typography>
<Typography variant="vr-accessible" italic>
{l10n.getString('onboarding-calibration_tutorial-subtitle')}
</Typography>
</div>
<Localized
id="onboarding-calibration_tutorial-description-v1"
elems={{ b: <b /> }}
>
<Typography color="secondary">
Description on calibration of IMU
</Typography>
</Localized>
<div>
<div className="xs:hidden flex flex-row justify-center">
<div className="stroke-none fill-background-10 ">
<TaybolIcon width="220" />
</div>
<div className="flex justify-center">
<LoaderIcon slimeState={slimeStatus}></LoaderIcon>
</div>
<ProgressBar
progress={
isCounting
? (IMU_CALIBRATION_TIME - timer) / IMU_CALIBRATION_TIME
: calibrationStatus === CalibrationStatus.SUCCESS ||
calibrationStatus === CalibrationStatus.ERROR
? 1
: 0
}
height={14}
animated={true}
colorClass={progressBarClass}
></ProgressBar>
</div>
<div className="flex justify-center">
<Typography variant="section-title">{progressText}</Typography>
</div>
<div className="flex gap-3 mobile:flex-col">
<Button
variant="secondary"
to="/onboarding/wifi-creds"
className="xs:mr-auto"
>
{l10n.getString('onboarding-previous_step')}
</Button>
<Button
variant="primary"
onClick={() => {
setCalibrationStatus(CalibrationStatus.CALIBRATING);
startCountdown();
}}
disabled={isCounting || !rested}
className={classNames(
'xs:ml-auto',
CalibrationStatus.SUCCESS === calibrationStatus && 'hidden'
)}
>
{l10n.getString('onboarding-calibration_tutorial-calibrate')}
</Button>
<Button
variant="primary"
to="/onboarding/assign-tutorial"
className={classNames(
'xs:ml-auto',
CalibrationStatus.SUCCESS !== calibrationStatus && 'hidden'
)}
>
{l10n.getString('onboarding-continue')}
</Button>
<LoaderIcon slimeState={slimeStatus} />
</div>
<ProgressBar
progress={
isCounting
? (IMU_CALIBRATION_TIME - timer) / IMU_CALIBRATION_TIME
: calibrationStatus === CalibrationStatus.SUCCESS ||
calibrationStatus === CalibrationStatus.ERROR
? 1
: 0
}
height={14}
animated
colorClass={progressBarClass}
/>
</div>
<div className="flex justify-center">
<Typography variant="section-title">{progressText}</Typography>
</div>
<div className="flex gap-3 mobile:flex-col">
<Button
variant="secondary"
to="/onboarding/assign-tutorial"
className={classNames('xs:ml-auto', !skipButton && 'hidden')}
to="/onboarding/wifi-creds"
className="xs:mr-auto"
>
{l10n.getString('onboarding-calibration_tutorial-skip')}
{l10n.getString('onboarding-previous_step')}
</Button>
<Button
variant="primary"
onClick={() => {
setCalibrationStatus(CalibrationStatus.CALIBRATING);
startCountdown();
}}
disabled={isCounting || !rested}
className={classNames(
'xs:ml-auto',
CalibrationStatus.SUCCESS === calibrationStatus && 'hidden'
)}
>
{l10n.getString('onboarding-calibration_tutorial-calibrate')}
</Button>
<Button
variant="primary"
to="/onboarding/assign-tutorial"
className={classNames(
'xs:ml-auto',
CalibrationStatus.SUCCESS !== calibrationStatus && 'hidden'
)}
>
{l10n.getString('onboarding-continue')}
</Button>
</div>
<Button
variant="secondary"
to="/onboarding/assign-tutorial"
className={classNames('xs:ml-auto', !skipButton && 'hidden')}
>
{l10n.getString('onboarding-calibration_tutorial-skip')}
</Button>
</div>
<div className="mobile:hidden flex self-center w-[32rem] mobile:absolute">
<div className="stroke-none xs:fill-background-10 mobile:fill-background-50 mobile:blur-sm">
<TaybolIcon width="450"></TaybolIcon>
</div>
</div>
<div className="mobile:hidden flex self-center w-[32rem] mobile:absolute">
<div className="stroke-none xs:fill-background-10 mobile:fill-background-50 mobile:blur-sm">
<TaybolIcon width="450" />
</div>
</div>
</div>
</>
</div>
);
}

View File

@@ -12,9 +12,9 @@
grid-template:
's t' var(--connect-tracker-layout-top)
's c' calc(100% - var(--connect-tracker-layout-top))
/ calc(var(--connect-tracker-layout-sidebar)) calc(100% - var(
--connect-tracker-layout-sidebar
));
/ calc(var(--connect-tracker-layout-sidebar)) calc(
100% - var(--connect-tracker-layout-sidebar)
);
@screen mobile {
grid-template:

View File

@@ -27,6 +27,7 @@ import { BaseModal } from '@/components/commons/BaseModal';
import { useStatusContext } from '@/hooks/status-system';
import { A } from '@/components/commons/A';
import { CONNECT_TRACKER } from '@/utils/tauri';
import { uniqueNumberFromTracker } from '@/hooks/tracker';
const statusLabelMap = {
[WifiProvisioningStatus.NONE]:
@@ -140,7 +141,9 @@ export function ConnectTrackersPage() {
if (provisioningStatus === WifiProvisioningStatus.DONE) {
return 'bg-status-success';
}
}, [provisioningStatus]);
return undefined;
}, [isError, provisioningStatus]);
const slimeStatus = useMemo(() => {
if (isError) {
@@ -165,11 +168,13 @@ export function ConnectTrackersPage() {
[connectedIMUTrackers.length]
);
const filteredStatuses = useMemo(() => {
return Object.entries(statuses).filter(
([, value]) => value.dataType == StatusData.StatusPublicNetwork
);
}, [statuses]);
const filteredStatuses = useMemo(
() =>
Object.entries(statuses).filter(
([, value]) => value.dataType == StatusData.StatusPublicNetwork
),
[statuses]
);
return (
<>
@@ -189,21 +194,21 @@ export function ConnectTrackersPage() {
>
<div className="flex flex-col items-center gap-2 ">
<Localized id={(errorLabelMap as any)[provisioningStatus]}>
<Typography variant="main-title"></Typography>
<Typography variant="main-title" />
</Localized>
<Localized id={`${(errorLabelMap as any)[provisioningStatus]}-desc`}>
<Typography
variant="standard"
whitespace="whitespace-pre-wrap"
block
></Typography>
/>
</Localized>
<video
src={CONNECT_TRACKER}
loop
autoPlay
className="w-full aspect-video rounded-md mt-2"
></video>
/>
<div className="flex gap-3 pt-5 justify-end w-full">
<Button
variant="tertiary"
@@ -239,7 +244,7 @@ export function ConnectTrackersPage() {
</div>
<Localized
id={currentTip}
elems={{ em: <em className="italic"></em>, b: <b></b> }}
elems={{ em: <em className="italic" />, b: <b /> }}
>
<TipBox>Conditional tip</TipBox>
</Localized>
@@ -250,7 +255,7 @@ export function ConnectTrackersPage() {
id={`status_system-${StatusData[status.dataType]}`}
elems={{
PublicFixLink: (
<A href="https://docs.slimevr.dev/common-issues.html#network-profile-is-currently-set-to-public"></A>
<A href="https://docs.slimevr.dev/common-issues.html#network-profile-is-currently-set-to-public" />
),
}}
>
@@ -273,7 +278,7 @@ export function ConnectTrackersPage() {
'right-5 bottom-8'
)}
>
<LoaderIcon slimeState={slimeStatus}></LoaderIcon>
<LoaderIcon slimeState={slimeStatus} />
</div>
<div className="flex flex-col grow self-center">
@@ -288,9 +293,9 @@ export function ConnectTrackersPage() {
<ProgressBar
progress={statusProgressMap[provisioningStatus]}
height={14}
animated={true}
animated
colorClass={progressBarClass}
></ProgressBar>
/>
</div>
</div>
<div className="flex flex-row mt-4 gap-3">
@@ -330,22 +335,22 @@ export function ConnectTrackersPage() {
{Array.from({
...connectedIMUTrackers,
length: Math.max(connectedIMUTrackers.length, 1),
}).map((tracker, index) => (
<div key={index}>
}).map((tracker) => (
<div key={uniqueNumberFromTracker(tracker.tracker)}>
{!tracker && (
<div
className={classNames(
'rounded-xl h-16 animate-pulse',
state.alonePage ? 'bg-background-80' : 'bg-background-70'
)}
></div>
/>
)}
{tracker && (
<TrackerCard
tracker={tracker.tracker}
device={tracker.device}
smol
></TrackerCard>
/>
)}
</div>
))}

View File

@@ -30,21 +30,21 @@ function Error({ title, desc }: { title: string; desc: string }) {
return (
<>
<LoaderIcon slimeState={SlimeState.SAD} size={200}></LoaderIcon>
<LoaderIcon slimeState={SlimeState.SAD} size={200} />
<div>
<Localized id={title}>
<Typography variant="main-title"></Typography>
<Typography variant="main-title" />
</Localized>
<Localized id={desc}>
<Typography variant="standard"></Typography>
<Typography variant="standard" />
</Localized>
{isTauri && (
<div className="flex gap-2 justify-center mt-4">
<Localized id="websocket-error-close">
<Button variant="primary" onClick={closeApp}></Button>
<Button variant="primary" onClick={closeApp} />
</Localized>
<Localized id="websocket-error-logs">
<Button variant="secondary" onClick={openLogsFolder}></Button>
<Button variant="secondary" onClick={openLogsFolder} />
</Localized>
</div>
)}
@@ -65,10 +65,10 @@ export function ConnectionLost() {
<div className="flex flex-col items-center gap-4 -mt-12">
{isLoading && (
<>
<LoaderIcon slimeState={SlimeState.JUMPY} size={200}></LoaderIcon>
<LoaderIcon slimeState={SlimeState.JUMPY} size={200} />
<div>
<Localized id="websocket-connecting">
<Typography variant="main-title"></Typography>
<Typography variant="main-title" />
</Localized>
</div>
</>
@@ -77,13 +77,10 @@ export function ConnectionLost() {
<Error
title="websocket-connection_lost"
desc="websocket-connection_lost-desc"
></Error>
/>
)}
{isTimedOut && (
<Error
title="websocket-timedout"
desc="websocket-timedout-desc"
></Error>
<Error title="websocket-timedout" desc="websocket-timedout-desc" />
)}
</div>
</div>

View File

@@ -15,7 +15,7 @@ export function DonePage() {
return (
<div className="flex flex-col gap-5 h-full items-center w-full justify-center">
<div className="flex flex-col gap-5 items-center z-10">
<SlimeVRIcon></SlimeVRIcon>
<SlimeVRIcon />
<Typography variant="main-title">
{l10n.getString('onboarding-done-title')}
</Typography>

View File

@@ -11,39 +11,37 @@ export function EnterVRPage() {
applyProgress(0.6);
return (
<>
<div className="flex flex-col gap-5 h-full items-center w-full justify-center">
<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-md gap-3">
<ArrowLink to="/onboarding/trackers-assign" direction="left">
{l10n.getString('onboarding-enter_vr-back')}
</ArrowLink>
<Typography variant="main-title">
{l10n.getString('onboarding-enter_vr-title')}
</Typography>
<Typography color="secondary">
{l10n.getString('onboarding-enter_vr-description')}
</Typography>
</div>
{/* <div className="flex flex-col flex-grow gap-3 rounded-xl fill-background-50">
<div className="flex flex-col gap-5 h-full items-center w-full justify-center">
<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-md gap-3">
<ArrowLink to="/onboarding/trackers-assign" direction="left">
{l10n.getString('onboarding-enter_vr-back')}
</ArrowLink>
<Typography variant="main-title">
{l10n.getString('onboarding-enter_vr-title')}
</Typography>
<Typography color="secondary">
{l10n.getString('onboarding-enter_vr-description')}
</Typography>
</div>
{/* <div className="flex flex-col flex-grow gap-3 rounded-xl fill-background-50">
<Typography variant="main-title">Illustration HERE</Typography>
</div> */}
</div>
</div>
<div className="w-full py-4 flex flex-row">
<div className="flex flex-grow">
<Button variant="secondary" to="/" onClick={skipSetup}>
{l10n.getString('onboarding-skip')}
</Button>
</div>
<div className="flex gap-3">
<Button variant="primary" to="/onboarding/mounting/auto">
{l10n.getString('onboarding-enter_vr-ready')}
</Button>
</div>
</div>
</div>
</>
<div className="w-full py-4 flex flex-row">
<div className="flex flex-grow">
<Button variant="secondary" to="/" onClick={skipSetup}>
{l10n.getString('onboarding-skip')}
</Button>
</div>
<div className="flex gap-3">
<Button variant="primary" to="/onboarding/mounting/auto">
{l10n.getString('onboarding-enter_vr-ready')}
</Button>
</div>
</div>
</div>
);
}

View File

@@ -12,49 +12,47 @@ export function HomePage() {
applyProgress(0.1);
return (
<>
<div className="flex relative flex-col gap-5 h-full items-center w-full justify-center px-4 overflow-clip">
<div className="flex flex-col gap-5 items-center z-10 scale-150 mb-20">
<SlimeVRIcon></SlimeVRIcon>
<Typography variant="mobile-title">
{l10n.getString('onboarding-home')}
</Typography>
<Button variant="primary" to="/onboarding/wifi-creds">
{l10n.getString('onboarding-home-start')}
</Button>
</div>
<div className="absolute right-4 bottom-4 z-50">
<LangSelector />
</div>
<div
className="absolute bg-accent-background-50 w-full rounded-full"
style={{
bottom: 'calc(-300vw / 1.04)',
height: '300vw',
width: '300vw',
}}
></div>
<img
className="absolute"
src="/images/slime-girl.webp"
style={{
width: '35%',
maxWidth: 800,
bottom: '1%',
left: '9%',
}}
/>
<img
className="absolute"
src="/images/slimes.webp"
style={{
width: '35%',
maxWidth: 800,
bottom: '1%',
right: '9%',
}}
/>
<div className="flex relative flex-col gap-5 h-full items-center w-full justify-center px-4 overflow-clip">
<div className="flex flex-col gap-5 items-center z-10 scale-150 mb-20">
<SlimeVRIcon />
<Typography variant="mobile-title">
{l10n.getString('onboarding-home')}
</Typography>
<Button variant="primary" to="/onboarding/wifi-creds">
{l10n.getString('onboarding-home-start')}
</Button>
</div>
</>
<div className="absolute right-4 bottom-4 z-50">
<LangSelector />
</div>
<div
className="absolute bg-accent-background-50 w-full rounded-full"
style={{
bottom: 'calc(-300vw / 1.04)',
height: '300vw',
width: '300vw',
}}
/>
<img
className="absolute"
src="/images/slime-girl.webp"
style={{
width: '35%',
maxWidth: 800,
bottom: '1%',
left: '9%',
}}
/>
<img
className="absolute"
src="/images/slimes.webp"
style={{
width: '35%',
maxWidth: 800,
bottom: '1%',
right: '9%',
}}
/>
</div>
);
}

View File

@@ -39,10 +39,8 @@ export function ResetTutorialPage() {
)
.sort(
(a, b) =>
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
TORSO_PARTS.indexOf(a.tracker.info!.bodyPart)! -
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
TORSO_PARTS.indexOf(b.tracker.info!.bodyPart)!
TORSO_PARTS.indexOf(a.tracker.info!.bodyPart) -
TORSO_PARTS.indexOf(b.tracker.info!.bodyPart)
),
[assignedTrackers]
);
@@ -55,10 +53,8 @@ export function ResetTutorialPage() {
)
.sort(
(a, b) =>
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
RIGHT_LEG_PARTS.indexOf(a.tracker.info!.bodyPart)! -
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
RIGHT_LEG_PARTS.indexOf(b.tracker.info!.bodyPart)!
RIGHT_LEG_PARTS.indexOf(a.tracker.info!.bodyPart) -
RIGHT_LEG_PARTS.indexOf(b.tracker.info!.bodyPart)
),
[assignedTrackers]
);
@@ -71,10 +67,8 @@ export function ResetTutorialPage() {
)
.sort(
(a, b) =>
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
LEFT_LEG_PARTS.indexOf(a.tracker.info!.bodyPart)! -
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
LEFT_LEG_PARTS.indexOf(b.tracker.info!.bodyPart)!
LEFT_LEG_PARTS.indexOf(a.tracker.info!.bodyPart) -
LEFT_LEG_PARTS.indexOf(b.tracker.info!.bodyPart)
),
[assignedTrackers]
);
@@ -176,8 +170,8 @@ export function ResetTutorialPage() {
<BodyDisplay
width={isMobile ? 160 : undefined}
trackers={[order[curIndex]]}
hideUnassigned={true}
></BodyDisplay>
hideUnassigned
/>
<div
className={classNames(
'self-center w-72 md-max:hidden',

View File

@@ -30,16 +30,15 @@ export function WifiCredsPage() {
<Typography variant="main-title">
{l10n.getString('onboarding-wifi_creds')}
</Typography>
<>
{l10n
.getString('onboarding-wifi_creds-description')
.split('\n')
.map((line, i) => (
<Typography color="secondary" key={i}>
{line}
</Typography>
))}
</>
{l10n
.getString('onboarding-wifi_creds-description')
.split('\n')
.map((line, i) => (
// eslint-disable-next-line react/no-array-index-key
<Typography color="secondary" key={i}>
{line}
</Typography>
))}
{!state.alonePage && (
<Button
variant="secondary"

View File

@@ -3,11 +3,11 @@ import { useOnboarding } from '@/hooks/onboarding';
import { Button } from '@/components/commons/Button';
import { Typography } from '@/components/commons/Typography';
import { useIsRestCalibrationTrackers } from '@/hooks/imu-logic';
import { useAtomValue } from 'jotai';
import { connectedIMUTrackersAtom } from '@/store/app-store';
import { StickerSlime } from './StickerSlime';
import { TrackerArrow } from './TrackerArrow';
import { ExtensionArrow } from './ExtensionArrow';
import { useAtomValue } from 'jotai';
import { connectedIMUTrackersAtom } from '@/store/app-store';
export function AssignmentTutorialPage() {
const { l10n } = useLocalization();
@@ -18,73 +18,69 @@ export function AssignmentTutorialPage() {
applyProgress(0.46);
return (
<>
<div className="flex flex-col gap-5 h-full items-center overflow-y-auto w-full xs:justify-center relative py-2">
<div className="flex flex-col w-full xs:justify-center xs:px-20 gap-3 pb-2 px-4">
<div className="mb-10 self-center">
<Typography variant="main-title">
{l10n.getString('onboarding-assignment_tutorial')}
</Typography>
<div className="flex flex-col gap-5 h-full items-center overflow-y-auto w-full xs:justify-center relative py-2">
<div className="flex flex-col w-full xs:justify-center xs:px-20 gap-3 pb-2 px-4">
<div className="mb-10 self-center">
<Typography variant="main-title">
{l10n.getString('onboarding-assignment_tutorial')}
</Typography>
</div>
<div className="flex flex-col self-center justify-center gap-5 px-2">
<div className="flex gap-12 xs:flex-row mobile:flex-col self-center justify-center">
<div className="flex flex-col gap-5 xs:w-1/4">
<div>
<Typography variant="section-title">
{l10n.getString('onboarding-assignment_tutorial-first_step')}
</Typography>
</div>
<div className="stroke-background-10 fill-background-10 flex justify-center">
<StickerSlime width="65%" />
</div>
</div>
<div className="flex flex-col gap-10 xs:w-1/4">
<div>
<Typography variant="section-title">
{l10n.getString(
'onboarding-assignment_tutorial-second_step-v2'
)}
</Typography>
</div>
<div className="fill-background-10 stroke-background-10 flex justify-center">
<TrackerArrow width="75%" />
</div>
<div>
<Typography variant="section-title">
{l10n.getString(
'onboarding-assignment_tutorial-second_step-continuation-v2'
)}
</Typography>
</div>
<div className="fill-background-10 stroke-background-10 flex justify-center">
<ExtensionArrow width="75%" />
</div>
</div>
</div>
<div className="flex flex-col self-center justify-center gap-5 px-2">
<div className="flex gap-12 xs:flex-row mobile:flex-col self-center justify-center">
<div className="flex flex-col gap-5 xs:w-1/4">
<div>
<Typography variant="section-title">
{l10n.getString(
'onboarding-assignment_tutorial-first_step'
)}
</Typography>
</div>
<div className="stroke-background-10 fill-background-10 flex justify-center">
<StickerSlime width="65%"></StickerSlime>
</div>
</div>
<div className="flex flex-col gap-10 xs:w-1/4">
<div>
<Typography variant="section-title">
{l10n.getString(
'onboarding-assignment_tutorial-second_step-v2'
)}
</Typography>
</div>
<div className="fill-background-10 stroke-background-10 flex justify-center">
<TrackerArrow width="75%"></TrackerArrow>
</div>
<div>
<Typography variant="section-title">
{l10n.getString(
'onboarding-assignment_tutorial-second_step-continuation-v2'
)}
</Typography>
</div>
<div className="fill-background-10 stroke-background-10 flex justify-center">
<ExtensionArrow width="75%"></ExtensionArrow>
</div>
</div>
</div>
<div className="flex">
<Button
variant="secondary"
to={
isRestCalibration
? '/onboarding/calibration-tutorial'
: '/onboarding/wifi-creds'
}
>
{l10n.getString('onboarding-previous_step')}
</Button>
<Button
variant="primary"
to="/onboarding/trackers-assign"
className="ml-auto"
>
{l10n.getString('onboarding-assignment_tutorial-done')}
</Button>
</div>
<div className="flex">
<Button
variant="secondary"
to={
isRestCalibration
? '/onboarding/calibration-tutorial'
: '/onboarding/wifi-creds'
}
>
{l10n.getString('onboarding-previous_step')}
</Button>
<Button
variant="primary"
to="/onboarding/trackers-assign"
className="ml-auto"
>
{l10n.getString('onboarding-assignment_tutorial-done')}
</Button>
</div>
</div>
</div>
</>
</div>
);
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -3,6 +3,7 @@ import { AutoboneContextC, useProvideAutobone } from '@/hooks/autobone';
import { useOnboarding } from '@/hooks/onboarding';
import { Typography } from '@/components/commons/Typography';
import { StepperSlider } from '@/components/onboarding/StepperSlider';
import { HeightContextC, useProvideHeightContext } from '@/hooks/height';
import { DoneStep } from './autobone-steps/Done';
import { RequirementsStep } from './autobone-steps/Requirements';
import { PutTrackersOnStep } from './autobone-steps/PutTrackersOn';
@@ -11,7 +12,6 @@ import { StartRecording } from './autobone-steps/StartRecording';
import { VerifyResultsStep } from './autobone-steps/VerifyResults';
import { CheckHeightStep } from './autobone-steps/CheckHeight';
import { PreparationStep } from './autobone-steps/Preparation';
import { HeightContextC, useProvideHeightContext } from '@/hooks/height';
import { CheckFloorHeightStep } from './autobone-steps/CheckFloorHeight';
export function AutomaticProportionsPage() {
@@ -53,7 +53,7 @@ export function AutomaticProportionsPage() {
{ type: 'numbered', component: VerifyResultsStep },
{ type: 'fullsize', component: DoneStep },
]}
></StepperSlider>
/>
</div>
</div>
</div>

View File

@@ -141,7 +141,7 @@ function ProportionItem({
<Typography
variant="standard"
whitespace="whitespace-pre-wrap"
></Typography>
/>
</Localized>
}
preferedDirection="bottom"
@@ -228,11 +228,7 @@ function ProportionItem({
)}
onClick={toggleOpen}
>
{open ? (
<ArrowUpIcon size={50}></ArrowUpIcon>
) : (
<ArrowDownIcon size={50}></ArrowDownIcon>
)}
{open ? <ArrowUpIcon size={50} /> : <ArrowDownIcon size={50} />}
</div>
)}
</div>
@@ -252,7 +248,7 @@ function ProportionItem({
precise={precise}
part={part}
onBoneChange={onBoneChange}
></ProportionItem>
/>
))}
</div>
</div>
@@ -285,10 +281,11 @@ export function BodyProportions({
part={part}
precise={precise}
onBoneChange={changeBoneValue}
></ProportionItem>
/>
))}
</div>
</div>
)) || <></>
)) ||
null
);
}

View File

@@ -10,12 +10,10 @@ import {
} from 'solarxr-protocol';
import { useOnboarding } from '@/hooks/onboarding';
import { useWebsocketAPI } from '@/hooks/websocket-api';
import { BodyProportions } from './BodyProportions';
import { Localized, useLocalization } from '@fluent/react';
import { ReactNode, useEffect, useMemo, useRef, useState } from 'react';
import { useBreakpoint, useIsTauri } from '@/hooks/breakpoint';
import { SkeletonVisualizerWidget } from '@/components/widgets/SkeletonVisualizerWidget';
import { ProportionsResetModal } from './ProportionsResetModal';
import { fileOpen, fileSave } from 'browser-fs-access';
import { CURRENT_EXPORT_VERSION, MIN_HEIGHT } from '@/hooks/manual-proportions';
import { save } from '@tauri-apps/plugin-dialog';
@@ -36,6 +34,8 @@ import { useLocaleConfig } from '@/i18n/config';
import { useNavigate } from 'react-router-dom';
import { ResetButton } from '@/components/home/ResetButton';
import { Vector3 } from 'three';
import { ProportionsResetModal } from './ProportionsResetModal';
import { BodyProportions } from './BodyProportions';
function IconButton({
onClick,
@@ -65,6 +65,7 @@ function IconButton({
content={tooltip ?? children}
>
<button
type="button"
onClick={onClick}
disabled={disabled}
className={classNames(
@@ -216,7 +217,7 @@ function ImportExportButtons() {
<>
<div className="flex">
<IconButton
icon={<UploadFileIcon width={25}></UploadFileIcon>}
icon={<UploadFileIcon width={25} />}
onClick={onImport}
className={classNames(
'transition-colors',
@@ -225,13 +226,13 @@ function ImportExportButtons() {
)}
>
<Localized id="onboarding-manual_proportions-import">
<Typography variant="standard"></Typography>
<Typography variant="standard" />
</Localized>
</IconButton>
</div>
<div className="flex">
<IconButton
icon={<ImportIcon size={25}></ImportIcon>}
icon={<ImportIcon size={25} />}
onClick={() => {
exporting.current = true;
@@ -242,7 +243,7 @@ function ImportExportButtons() {
}}
>
<Localized id="onboarding-manual_proportions-export">
<Typography variant="standard"></Typography>
<Typography variant="standard" />
</Localized>
</IconButton>
</div>
@@ -264,26 +265,26 @@ function LinearRatioToggle({ control }: { control: ManualProportionControls }) {
<>
{value ? (
<IconButton
icon={<PercentIcon size={25}></PercentIcon>}
icon={<PercentIcon size={25} />}
onClick={() => onChange(!value)}
>
<Localized id="onboarding-manual_proportions-grouped_proportions">
<Typography variant="standard"></Typography>
<Typography variant="standard" />
</Localized>
</IconButton>
) : (
<IconButton
icon={<RulerIcon width={25}></RulerIcon>}
icon={<RulerIcon width={25} />}
onClick={() => onChange(!value)}
>
<Localized id="onboarding-manual_proportions-all_proportions">
<Typography variant="standard"></Typography>
<Typography variant="standard" />
</Localized>
</IconButton>
)}
</>
)}
></Controller>
/>
);
}
@@ -300,7 +301,7 @@ function PreciseToggle({ control }: { control: ManualProportionControls }) {
onClick={() => onChange(!value)}
>
<Localized id="onboarding-manual_proportions-normal_increment">
<Typography variant="standard"></Typography>
<Typography variant="standard" />
</Localized>
</IconButton>
) : (
@@ -309,13 +310,13 @@ function PreciseToggle({ control }: { control: ManualProportionControls }) {
onClick={() => onChange(!value)}
>
<Localized id="onboarding-manual_proportions-precise_increment">
<Typography variant="standard"></Typography>
<Typography variant="standard" />
</Localized>
</IconButton>
)}
</>
)}
></Controller>
/>
);
}
@@ -347,18 +348,18 @@ function ButtonsControl({ control }: { control: ManualProportionControls }) {
return (
<div className="bg-background-60 rounded-md flex gap-2">
<div className="flex">
<LinearRatioToggle control={control}></LinearRatioToggle>
<LinearRatioToggle control={control} />
</div>
<div className="flex">
<PreciseToggle control={control}></PreciseToggle>
<PreciseToggle control={control} />
</div>
<div className="flex">
<IconButton
icon={<FullResetIcon width={20}></FullResetIcon>}
icon={<FullResetIcon width={20} />}
onClick={() => setShowWarning(true)}
>
<Localized id="reset-reset_all">
<Typography variant="standard"></Typography>
<Typography variant="standard" />
</Localized>
</IconButton>
</div>
@@ -373,11 +374,11 @@ function ButtonsControl({ control }: { control: ManualProportionControls }) {
: 'onboarding-manual_proportions-fine_tuning_button'
}
>
<Typography variant="standard"></Typography>
<Typography variant="standard" />
</Localized>
}
disabled={!canUseFineTuning}
icon={<HumanIcon width={20}></HumanIcon>}
icon={<HumanIcon width={20} />}
onClick={() =>
nav('/onboarding/body-proportions/auto', {
state: { alonePage: state.alonePage },
@@ -385,12 +386,12 @@ function ButtonsControl({ control }: { control: ManualProportionControls }) {
}
>
<Localized id={'onboarding-manual_proportions-fine_tuning_button'}>
<Typography variant="standard"></Typography>
<Typography variant="standard" />
</Localized>
</IconButton>
</div>
<div className="flex flex-grow"></div>
<ImportExportButtons></ImportExportButtons>
<div className="flex flex-grow" />
<ImportExportButtons />
<ProportionsResetModal
accept={() => {
resetAll();
@@ -398,7 +399,7 @@ function ButtonsControl({ control }: { control: ManualProportionControls }) {
}}
onClose={() => setShowWarning(false)}
isOpen={showWarning}
></ProportionsResetModal>
/>
</div>
);
}
@@ -442,62 +443,60 @@ export function ManualProportionsPage() {
);
return (
<>
<div className="flex w-full h-full gap-2 bg-background-70 p-2">
<div className="flex flex-col flex-grow gap-2">
<ButtonsControl control={control}></ButtonsControl>
<div className="bg-background-60 h-20 rounded-md flex-grow overflow-y-auto">
<BodyProportions
precise={precise ?? defaultValues.precise}
type={ratio ? 'ratio' : 'linear'}
variant={state.alonePage ? 'alone' : 'onboarding'}
></BodyProportions>
</div>
</div>
<div className="rounded-md overflow-clip w-1/3 bg-background-60 hidden mobile:hidden sm:flex relative">
<SkeletonVisualizerWidget
onInit={(context) => {
context.addView({
left: 0,
bottom: 0,
width: 1,
height: 1,
position: new Vector3(3, 2.5, -3),
onHeightChange(v, newHeight) {
// retouch the target and scale settings so the height element doesnt hide the head
v.controls.target.set(0, newHeight / 1.7, 0);
const scale = Math.max(1, newHeight) / 1.2;
v.camera.zoom = 1 / scale;
},
});
}}
<div className="flex w-full h-full gap-2 bg-background-70 p-2">
<div className="flex flex-col flex-grow gap-2">
<ButtonsControl control={control} />
<div className="bg-background-60 h-20 rounded-md flex-grow overflow-y-auto">
<BodyProportions
precise={precise ?? defaultValues.precise}
type={ratio ? 'ratio' : 'linear'}
variant={state.alonePage ? 'alone' : 'onboarding'}
/>
<div className="top-4 w-full px-4 absolute flex gap-2 flex-col lg:flex-row md:flex-wrap">
<div className="h-14 flex flex-grow items-center">
<ResetButton
type={ResetType.Full}
size="small"
className="w-full h-full bg-background-50 hover:bg-background-40 text-background-10"
></ResetButton>
</div>
<Tooltip
preferedDirection="bottom"
content={
<Localized id="onboarding-manual_proportions-estimated_height">
<Typography></Typography>
</Localized>
}
>
<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)}
</Typography>
</div>
</Tooltip>
</div>
</div>
</div>
</>
<div className="rounded-md overflow-clip w-1/3 bg-background-60 hidden mobile:hidden sm:flex relative">
<SkeletonVisualizerWidget
onInit={(context) => {
context.addView({
left: 0,
bottom: 0,
width: 1,
height: 1,
position: new Vector3(3, 2.5, -3),
onHeightChange(v, newHeight) {
// retouch the target and scale settings so the height element doesnt hide the head
v.controls.target.set(0, newHeight / 1.7, 0);
const scale = Math.max(1, newHeight) / 1.2;
v.camera.zoom = 1 / scale;
},
});
}}
/>
<div className="top-4 w-full px-4 absolute flex gap-2 flex-col lg:flex-row md:flex-wrap">
<div className="h-14 flex flex-grow items-center">
<ResetButton
type={ResetType.Full}
size="small"
className="w-full h-full bg-background-50 hover:bg-background-40 text-background-10"
/>
</div>
<Tooltip
preferedDirection="bottom"
content={
<Localized id="onboarding-manual_proportions-estimated_height">
<Typography />
</Localized>
}
>
<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)}
</Typography>
</div>
</Tooltip>
</div>
</div>
</div>
);
}

View File

@@ -58,7 +58,7 @@ export function ProportionsResetModal({
? 'reset-reset_all_warning_default-v2'
: 'reset-reset_all_warning-v2'
}
elems={{ b: <b></b> }}
elems={{ b: <b /> }}
>
<WarningBox>
<b>Warning:</b> This will reset your proportions to being just

Some files were not shown because too many files have changed in this diff Show More