mirror of
https://github.com/SlimeVR/SlimeVR-Server.git
synced 2026-04-06 02:01:58 +02:00
gui: More info in tracker table and IMU visualizer (#450)
This commit is contained in:
@@ -7,6 +7,7 @@
|
||||
"@fluent/react": "^0.14.1",
|
||||
"@fontsource/poppins": "^4.5.8",
|
||||
"@formatjs/intl-localematcher": "^0.2.32",
|
||||
"@react-three/fiber": "^8.10.0",
|
||||
"@tauri-apps/api": "^1.2.0",
|
||||
"@vitejs/plugin-react": "^3.0.0",
|
||||
"browserslist": "^4.18.1",
|
||||
@@ -25,7 +26,6 @@
|
||||
"postcss-normalize": "^10.0.1",
|
||||
"postcss-preset-env": "^7.0.1",
|
||||
"prompts": "^2.4.2",
|
||||
"quaternion": "^1.4.0",
|
||||
"react": "^18.0.0",
|
||||
"react-dev-utils": "^12.0.0",
|
||||
"react-dom": "^18.0.0",
|
||||
@@ -34,6 +34,7 @@
|
||||
"react-router-dom": "^6.2.2",
|
||||
"semver": "^7.3.5",
|
||||
"solarxr-protocol": "file:../solarxr-protocol",
|
||||
"three": "^0.148.0",
|
||||
"typescript": "^4.6.3"
|
||||
},
|
||||
"scripts": {
|
||||
@@ -71,6 +72,7 @@
|
||||
"@types/react": "18.0.25",
|
||||
"@types/react-dom": "^18.0.5",
|
||||
"@types/react-modal": "3.13.1",
|
||||
"@types/three": "^0.148.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.43.0",
|
||||
"@typescript-eslint/parser": "^5.43.0",
|
||||
"autoprefixer": "^10.4.4",
|
||||
|
||||
@@ -87,9 +87,25 @@ navbar-settings = sewtings
|
||||
bvh-start_recording = wecowd bvh
|
||||
bvh-recording = wecowding...
|
||||
|
||||
## Overlay settings
|
||||
overlay-is_visible_label = show owovelay in steawmvr
|
||||
overlay-is_mirrored_label = dispway owovelay as miwwow
|
||||
## Widget: Overlay settings
|
||||
widget-overlay = owovelay
|
||||
widget-overlay-is_visible_label = show owovelay in steawmvr
|
||||
widget-overlay-is_mirrored_label = dispway owovelay as miwwow
|
||||
|
||||
## Widget: Developer settings
|
||||
widget-developer_mode = devwowwewow mode
|
||||
widget-developer_mode-high_contrast = high contwast
|
||||
widget-developer_mode-precise_rotation = pwecise wotation
|
||||
widget-developer_mode-fast_data_feed = fast data feed
|
||||
widget-developer_mode-filter_slimes_and_hmd = fiwtew swimes a-and HMD
|
||||
widget-developer_mode-sort_by_name = sowt by nyame
|
||||
widget-developer_mode-raw_slime_rotation = waw wotation
|
||||
widget-developer_mode-more_info = mowe info
|
||||
|
||||
## Widget: IMU Visualizer
|
||||
widget-imu_visualizer = wotation
|
||||
widget-imu_visualizer-rotation_raw = waw
|
||||
widget-imu_visualizer-rotation_preview = pwewiew
|
||||
|
||||
## Tracker status
|
||||
tracker-status-none = no stawtus
|
||||
@@ -104,6 +120,9 @@ tracker-table-column-name = nayme
|
||||
tracker-table-column-type = type
|
||||
tracker-table-column-battery = battewy
|
||||
tracker-table-column-ping = pyng
|
||||
tracker-table-column-tps = tps
|
||||
tracker-table-column-temperature = temp. °C
|
||||
tracker-table-column-linear-acceleration = accew. X/Y/Z
|
||||
tracker-table-column-rotation = wotaytion x/y/z
|
||||
tracker-table-column-position = pawsytion x/y/z
|
||||
tracker-table-column-url = uawl
|
||||
|
||||
@@ -87,9 +87,25 @@ navbar-settings = Settings
|
||||
bvh-start_recording = Record BVH
|
||||
bvh-recording = Recording...
|
||||
|
||||
## Overlay settings
|
||||
overlay-is_visible_label = Show Overlay in SteamVR
|
||||
overlay-is_mirrored_label = Display Overlay as Mirror
|
||||
## Widget: Overlay settings
|
||||
widget-overlay = Overlay
|
||||
widget-overlay-is_visible_label = Show Overlay in SteamVR
|
||||
widget-overlay-is_mirrored_label = Display Overlay as Mirror
|
||||
|
||||
## Widget: Developer settings
|
||||
widget-developer_mode = Developer Mode
|
||||
widget-developer_mode-high_contrast = High contrast
|
||||
widget-developer_mode-precise_rotation = Precise rotation
|
||||
widget-developer_mode-fast_data_feed = Fast data feed
|
||||
widget-developer_mode-filter_slimes_and_hmd = Filter slimes and HMD
|
||||
widget-developer_mode-sort_by_name = Sort by name
|
||||
widget-developer_mode-raw_slime_rotation = Raw rotation
|
||||
widget-developer_mode-more_info = More info
|
||||
|
||||
## Widget: IMU Visualizer
|
||||
widget-imu_visualizer = Rotation
|
||||
widget-imu_visualizer-rotation_raw = Raw
|
||||
widget-imu_visualizer-rotation_preview = Preview
|
||||
|
||||
## Tracker status
|
||||
tracker-status-none = No Status
|
||||
@@ -104,6 +120,9 @@ tracker-table-column-name = Name
|
||||
tracker-table-column-type = Type
|
||||
tracker-table-column-battery = Battery
|
||||
tracker-table-column-ping = Ping
|
||||
tracker-table-column-tps = TPS
|
||||
tracker-table-column-temperature = Temp. °C
|
||||
tracker-table-column-linear-acceleration = Accel. X/Y/Z
|
||||
tracker-table-column-rotation = Rotation X/Y/Z
|
||||
tracker-table-column-position = Position X/Y/Z
|
||||
tracker-table-column-url = URL
|
||||
|
||||
@@ -87,9 +87,25 @@ navbar-settings = Ajustes
|
||||
bvh-start_recording = Grabar BVH
|
||||
bvh-recording = Grabando...
|
||||
|
||||
## Overlay settings
|
||||
overlay-is_visible_label = Mostrar interfaz en SteamVR
|
||||
overlay-is_mirrored_label = Mostrar interfaz reflejada
|
||||
## Widget: Overlay settings
|
||||
widget-overlay = Overlay
|
||||
widget-overlay-is_visible_label = Mostrar interfaz en SteamVR
|
||||
widget-overlay-is_mirrored_label = Mostrar interfaz reflejada
|
||||
|
||||
## Widget: Developer settings
|
||||
widget-developer_mode = Developer Mode
|
||||
widget-developer_mode-high_contrast = High contrast
|
||||
widget-developer_mode-precise_rotation = Precise rotation
|
||||
widget-developer_mode-fast_data_feed = Fast data feed
|
||||
widget-developer_mode-filter_slimes_and_hmd = Filter slimes and HMD
|
||||
widget-developer_mode-sort_by_name = Sort by name
|
||||
widget-developer_mode-raw_slime_rotation = Raw rotation
|
||||
widget-developer_mode-more_info = More info
|
||||
|
||||
## Widget: IMU Visualizer
|
||||
widget-imu_visualizer = Rotation
|
||||
widget-imu_visualizer-rotation_raw = Raw
|
||||
widget-imu_visualizer-rotation_preview = Preview
|
||||
|
||||
## Tracker status
|
||||
tracker-status-none = Sin estado
|
||||
@@ -104,6 +120,9 @@ tracker-table-column-name = Nombre
|
||||
tracker-table-column-type = Tipo
|
||||
tracker-table-column-battery = Batería
|
||||
tracker-table-column-ping = Latencia
|
||||
tracker-table-column-tps = TPS
|
||||
tracker-table-column-temperature = Temp. °C
|
||||
tracker-table-column-linear-acceleration = Accel. X/Y/Z
|
||||
tracker-table-column-rotation = Rotación X/Y/Z
|
||||
tracker-table-column-position = Posición X/Y/Z
|
||||
tracker-table-column-url = URL
|
||||
|
||||
@@ -87,9 +87,25 @@ navbar-settings = Réglages
|
||||
bvh-start_recording = Enregistrer BVH
|
||||
bvh-recording = Enregistrement...
|
||||
|
||||
## Overlay settings
|
||||
overlay-is_visible_label = Superposer le squelette dans SteamVR
|
||||
overlay-is_mirrored_label = Afficher le squelette en tant que miroir
|
||||
## Widget: Overlay settings
|
||||
widget-overlay = Squelette
|
||||
widget-overlay-is_visible_label = Superposer le squelette dans SteamVR
|
||||
widget-overlay-is_mirrored_label = Afficher le squelette en tant que miroir
|
||||
|
||||
## Widget: Developer settings
|
||||
widget-developer_mode = Developer Mode
|
||||
widget-developer_mode-high_contrast = High contrast
|
||||
widget-developer_mode-precise_rotation = Precise rotation
|
||||
widget-developer_mode-fast_data_feed = Fast data feed
|
||||
widget-developer_mode-filter_slimes_and_hmd = Filter slimes and HMD
|
||||
widget-developer_mode-sort_by_name = Sort by name
|
||||
widget-developer_mode-raw_slime_rotation = Raw rotation
|
||||
widget-developer_mode-more_info = More info
|
||||
|
||||
## Widget: IMU Visualizer
|
||||
widget-imu_visualizer = Rotation
|
||||
widget-imu_visualizer-rotation_raw = Raw
|
||||
widget-imu_visualizer-rotation_preview = Preview
|
||||
|
||||
## Tracker status
|
||||
tracker-status-none = Pas de statut
|
||||
@@ -104,6 +120,9 @@ tracker-table-column-name = Nom
|
||||
tracker-table-column-type = Type
|
||||
tracker-table-column-battery = Batterie
|
||||
tracker-table-column-ping = Ping
|
||||
tracker-table-column-tps = TPS
|
||||
tracker-table-column-temperature = Temp. °C
|
||||
tracker-table-column-linear-acceleration = Accel. X/Y/Z
|
||||
tracker-table-column-rotation = Rotation X/Y/Z
|
||||
tracker-table-column-position = Position X/Y/Z
|
||||
tracker-table-column-url = URL
|
||||
|
||||
@@ -87,9 +87,25 @@ navbar-settings = Impostazioni
|
||||
bvh-start_recording = Registra BVH
|
||||
bvh-recording = Registrazione in corso...
|
||||
|
||||
## Overlay settings
|
||||
overlay-is_visible_label = Mostra Overlay in SteamVR
|
||||
overlay-is_mirrored_label = Includi uno specchio nel Overlay
|
||||
## Widget: Overlay settings
|
||||
widget-overlay = Overlay
|
||||
widget-overlay-is_visible_label = Mostra Overlay in SteamVR
|
||||
widget-overlay-is_mirrored_label = Includi uno specchio nel Overlay
|
||||
|
||||
## Widget: Developer settings
|
||||
widget-developer_mode = Developer Mode
|
||||
widget-developer_mode-high_contrast = High contrast
|
||||
widget-developer_mode-precise_rotation = Precise rotation
|
||||
widget-developer_mode-fast_data_feed = Fast data feed
|
||||
widget-developer_mode-filter_slimes_and_hmd = Filter slimes and HMD
|
||||
widget-developer_mode-sort_by_name = Sort by name
|
||||
widget-developer_mode-raw_slime_rotation = Raw rotation
|
||||
widget-developer_mode-more_info = More info
|
||||
|
||||
## Widget: IMU Visualizer
|
||||
widget-imu_visualizer = Rotation
|
||||
widget-imu_visualizer-rotation_raw = Raw
|
||||
widget-imu_visualizer-rotation_preview = Preview
|
||||
|
||||
## Tracker status
|
||||
tracker-status-none = Nessuno Stato
|
||||
@@ -104,6 +120,9 @@ tracker-table-column-name = Nome
|
||||
tracker-table-column-type = Tipologia
|
||||
tracker-table-column-battery = Batteria
|
||||
tracker-table-column-ping = Ping
|
||||
tracker-table-column-tps = TPS
|
||||
tracker-table-column-temperature = Temp. °C
|
||||
tracker-table-column-linear-acceleration = Accel. X/Y/Z
|
||||
tracker-table-column-rotation = Rotazione X/Y/Z
|
||||
tracker-table-column-position = Rotazione X/Y/Z
|
||||
tracker-table-column-url = URL
|
||||
|
||||
@@ -87,9 +87,25 @@ navbar-settings = 設定
|
||||
bvh-start_recording = BVHレコーディング
|
||||
bvh-recording = レコーディング中...
|
||||
|
||||
## Overlay settings
|
||||
overlay-is_visible_label = SteamVRでオーバーレイを表示する
|
||||
overlay-is_mirrored_label = オーバーレイをミラーとして表示する
|
||||
## Widget: Overlay settings
|
||||
widget-overlay = オーバーレイ設定
|
||||
widget-overlay-is_visible_label = SteamVRでオーバーレイを表示する
|
||||
widget-overlay-is_mirrored_label = オーバーレイをミラーとして表示する
|
||||
|
||||
## Widget: Developer settings
|
||||
widget-developer_mode = 開発者モード
|
||||
widget-developer_mode-high_contrast = ハイ コントラスト
|
||||
widget-developer_mode-precise_rotation = 正確な回転角度を表示
|
||||
widget-developer_mode-fast_data_feed = 高速表示モード
|
||||
widget-developer_mode-filter_slimes_and_hmd = SlimeVRとHMDのみを表示
|
||||
widget-developer_mode-sort_by_name = 表示名順
|
||||
widget-developer_mode-raw_slime_rotation = 元の回転角度
|
||||
widget-developer_mode-more_info = 他情報
|
||||
|
||||
## Widget: IMU Visualizer
|
||||
widget-imu_visualizer = Rotation
|
||||
widget-imu_visualizer-rotation_raw = Raw
|
||||
widget-imu_visualizer-rotation_preview = Preview
|
||||
|
||||
## Tracker status
|
||||
tracker-status-none = ステータスなし
|
||||
@@ -104,6 +120,9 @@ tracker-table-column-name = Name
|
||||
tracker-table-column-type = Type
|
||||
tracker-table-column-battery = Battery
|
||||
tracker-table-column-ping = Ping
|
||||
tracker-table-column-tps = TPS
|
||||
tracker-table-column-temperature = Temp. °C
|
||||
tracker-table-column-linear-acceleration = Accel. X/Y/Z
|
||||
tracker-table-column-rotation = Rotation X/Y/Z
|
||||
tracker-table-column-position = Position X/Y/Z
|
||||
tracker-table-column-url = URL
|
||||
|
||||
@@ -87,9 +87,25 @@ navbar-settings = 설정
|
||||
bvh-start_recording = BVH 기록
|
||||
bvh-recording = 기록중...
|
||||
|
||||
## Overlay settings
|
||||
overlay-is_visible_label = SteamVR에서 오버레이 표시
|
||||
overlay-is_mirrored_label = 오버레이 반전
|
||||
## Widget: Overlay settings
|
||||
widget-overlay = 오버레이
|
||||
widget-overlay-is_visible_label = SteamVR에서 오버레이 표시
|
||||
widget-overlay-is_mirrored_label = 오버레이 반전
|
||||
|
||||
## Widget: Developer settings
|
||||
widget-developer_mode = 개발자 모드
|
||||
widget-developer_mode-high_contrast = High contrast
|
||||
widget-developer_mode-precise_rotation = Precise rotation
|
||||
widget-developer_mode-fast_data_feed = Fast data feed
|
||||
widget-developer_mode-filter_slimes_and_hmd = Filter slimes and HMD
|
||||
widget-developer_mode-sort_by_name = Sort by name
|
||||
widget-developer_mode-raw_slime_rotation = Raw rotation
|
||||
widget-developer_mode-more_info = More info
|
||||
|
||||
## Widget: IMU Visualizer
|
||||
widget-imu_visualizer = Rotation
|
||||
widget-imu_visualizer-rotation_raw = Raw
|
||||
widget-imu_visualizer-rotation_preview = Preview
|
||||
|
||||
## Tracker status
|
||||
tracker-status-none = No Status
|
||||
@@ -104,6 +120,9 @@ tracker-table-column-name = 이름
|
||||
tracker-table-column-type = 타입
|
||||
tracker-table-column-battery = 배터리
|
||||
tracker-table-column-ping = Ping
|
||||
tracker-table-column-tps = TPS
|
||||
tracker-table-column-temperature = Temp. °C
|
||||
tracker-table-column-linear-acceleration = Accel. X/Y/Z
|
||||
tracker-table-column-rotation = X/Y/Z 회전
|
||||
tracker-table-column-position = X/Y/Z 위치
|
||||
tracker-table-column-url = URL
|
||||
|
||||
@@ -87,9 +87,25 @@ navbar-settings = Ustawienia
|
||||
bvh-start_recording = Nagraj BVH
|
||||
bvh-recording = Nagrywam...
|
||||
|
||||
## Overlay settings
|
||||
overlay-is_visible_label = Pokaż Overlay w SteamVR
|
||||
overlay-is_mirrored_label = Pokaż Overlay jako Lustro
|
||||
## Widget: Overlay settings
|
||||
widget-overlay = Overlay
|
||||
widget-overlay-is_visible_label = Pokaż Overlay w SteamVR
|
||||
widget-overlay-is_mirrored_label = Pokaż Overlay jako Lustro
|
||||
|
||||
## Widget: Developer settings
|
||||
widget-developer_mode = Tryb Dewelopera
|
||||
widget-developer_mode-high_contrast = High contrast
|
||||
widget-developer_mode-precise_rotation = Precise rotation
|
||||
widget-developer_mode-fast_data_feed = Fast data feed
|
||||
widget-developer_mode-filter_slimes_and_hmd = Filter slimes and HMD
|
||||
widget-developer_mode-sort_by_name = Sort by name
|
||||
widget-developer_mode-raw_slime_rotation = Raw rotation
|
||||
widget-developer_mode-more_info = More info
|
||||
|
||||
## Widget: IMU Visualizer
|
||||
widget-imu_visualizer = Rotation
|
||||
widget-imu_visualizer-rotation_raw = Raw
|
||||
widget-imu_visualizer-rotation_preview = Preview
|
||||
|
||||
## Tracker status
|
||||
tracker-status-none = Brak Statusu
|
||||
@@ -104,6 +120,9 @@ tracker-table-column-name = Nazwa
|
||||
tracker-table-column-type = Typ
|
||||
tracker-table-column-battery = Bateria
|
||||
tracker-table-column-ping = Ping
|
||||
tracker-table-column-tps = TPS
|
||||
tracker-table-column-temperature = Temp. °C
|
||||
tracker-table-column-linear-acceleration = Accel. X/Y/Z
|
||||
tracker-table-column-rotation = Rotacja X/Y/Z
|
||||
tracker-table-column-position = Pozycja X/Y/Z
|
||||
tracker-table-column-url = URL
|
||||
|
||||
@@ -87,9 +87,25 @@ navbar-settings = Opções
|
||||
bvh-start_recording = Gravar BVH
|
||||
bvh-recording = Gravando...
|
||||
|
||||
## Overlay settings
|
||||
overlay-is_visible_label = Mostrar Overlay na SteamVR
|
||||
overlay-is_mirrored_label = Mostrar Overlay como espelho
|
||||
## Widget: Overlay settings
|
||||
widget-overlay = Overlay
|
||||
widget-overlay-is_visible_label = Mostrar Overlay na SteamVR
|
||||
widget-overlay-is_mirrored_label = Mostrar Overlay como espelho
|
||||
|
||||
## Widget: Developer settings
|
||||
widget-developer_mode = Modo de desenvolvedor
|
||||
widget-developer_mode-high_contrast = High contrast
|
||||
widget-developer_mode-precise_rotation = Precise rotation
|
||||
widget-developer_mode-fast_data_feed = Fast data feed
|
||||
widget-developer_mode-filter_slimes_and_hmd = Filter slimes and HMD
|
||||
widget-developer_mode-sort_by_name = Sort by name
|
||||
widget-developer_mode-raw_slime_rotation = Raw rotation
|
||||
widget-developer_mode-more_info = More info
|
||||
|
||||
## Widget: IMU Visualizer
|
||||
widget-imu_visualizer = Rotation
|
||||
widget-imu_visualizer-rotation_raw = Raw
|
||||
widget-imu_visualizer-rotation_preview = Preview
|
||||
|
||||
## Tracker status
|
||||
tracker-status-none = Sem Status
|
||||
@@ -104,6 +120,9 @@ tracker-table-column-name = Nome
|
||||
tracker-table-column-type = Tipo
|
||||
tracker-table-column-battery = Bateria
|
||||
tracker-table-column-ping = Ping
|
||||
tracker-table-column-tps = TPS
|
||||
tracker-table-column-temperature = Temp. °C
|
||||
tracker-table-column-linear-acceleration = Accel. X/Y/Z
|
||||
tracker-table-column-rotation = Rotação X/Y/Z
|
||||
tracker-table-column-position = Posição X/Y/Z
|
||||
tracker-table-column-url = URL
|
||||
|
||||
@@ -87,9 +87,25 @@ navbar-settings = Cài đặt
|
||||
bvh-start_recording = Ghi BVH
|
||||
bvh-recording = Đang ghi...
|
||||
|
||||
## Overlay settings
|
||||
overlay-is_visible_label = Xem overlay trên SteamVR
|
||||
overlay-is_mirrored_label = Xem overlay trong gương
|
||||
## Widget: Overlay settings
|
||||
widget-overlay = Overlay
|
||||
widget-overlay-is_visible_label = Xem overlay trên SteamVR
|
||||
widget-overlay-is_mirrored_label = Xem overlay trong gương
|
||||
|
||||
## Widget: Developer settings
|
||||
widget-developer_mode = Chế độ nhà phát triển
|
||||
widget-developer_mode-high_contrast = High contrast
|
||||
widget-developer_mode-precise_rotation = Precise rotation
|
||||
widget-developer_mode-fast_data_feed = Fast data feed
|
||||
widget-developer_mode-filter_slimes_and_hmd = Filter slimes and HMD
|
||||
widget-developer_mode-sort_by_name = Sort by name
|
||||
widget-developer_mode-raw_slime_rotation = Raw rotation
|
||||
widget-developer_mode-more_info = More info
|
||||
|
||||
## Widget: IMU Visualizer
|
||||
widget-imu_visualizer = Rotation
|
||||
widget-imu_visualizer-rotation_raw = Raw
|
||||
widget-imu_visualizer-rotation_preview = Preview
|
||||
|
||||
## Tracker status
|
||||
tracker-status-none = Không có tình trạng
|
||||
@@ -104,6 +120,9 @@ tracker-table-column-name = Tên
|
||||
tracker-table-column-type = Loại
|
||||
tracker-table-column-battery = Pin
|
||||
tracker-table-column-ping = Ping
|
||||
tracker-table-column-tps = TPS
|
||||
tracker-table-column-temperature = Temp. °C
|
||||
tracker-table-column-linear-acceleration = Accel. X/Y/Z
|
||||
tracker-table-column-rotation = Chiều chuyển X/Y/Z
|
||||
tracker-table-column-position = Vị trí X/Y/Z
|
||||
tracker-table-column-url = URL
|
||||
|
||||
@@ -87,9 +87,25 @@ navbar-settings = 设置
|
||||
bvh-start_recording = 录制 BVH 文件
|
||||
bvh-recording = 录制中...
|
||||
|
||||
## Overlay settings
|
||||
overlay-is_visible_label = 在 SteamVR 中显示覆盖层
|
||||
overlay-is_mirrored_label = 镜像显示覆盖层
|
||||
## Widget: Overlay settings
|
||||
widget-overlay = 覆盖层
|
||||
widget-overlay-is_visible_label = 在 SteamVR 中显示覆盖层
|
||||
widget-overlay-is_mirrored_label = 镜像显示覆盖层
|
||||
|
||||
## Widget: Developer settings
|
||||
widget-developer_mode = 开发者模式
|
||||
widget-developer_mode-high_contrast = High contrast
|
||||
widget-developer_mode-precise_rotation = Precise rotation
|
||||
widget-developer_mode-fast_data_feed = Fast data feed
|
||||
widget-developer_mode-filter_slimes_and_hmd = Filter slimes and HMD
|
||||
widget-developer_mode-sort_by_name = Sort by name
|
||||
widget-developer_mode-raw_slime_rotation = Raw rotation
|
||||
widget-developer_mode-more_info = More info
|
||||
|
||||
## Widget: IMU Visualizer
|
||||
widget-imu_visualizer = Rotation
|
||||
widget-imu_visualizer-rotation_raw = Raw
|
||||
widget-imu_visualizer-rotation_preview = Preview
|
||||
|
||||
## Tracker status
|
||||
tracker-status-none = 无状态
|
||||
@@ -104,6 +120,9 @@ tracker-table-column-name = 名字
|
||||
tracker-table-column-type = 类型
|
||||
tracker-table-column-battery = 电量
|
||||
tracker-table-column-ping = 延迟
|
||||
tracker-table-column-tps = TPS
|
||||
tracker-table-column-temperature = Temp. °C
|
||||
tracker-table-column-linear-acceleration = Accel. X/Y/Z
|
||||
tracker-table-column-rotation = 旋转 X/Y/Z
|
||||
tracker-table-column-position = 位置 X/Y/Z
|
||||
tracker-table-column-url = 地址
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import classNames from 'classnames';
|
||||
import { ReactNode } from 'react';
|
||||
import { ResetType } from 'solarxr-protocol';
|
||||
import { useConfig } from '../hooks/config';
|
||||
import { useLayout } from '../hooks/layout';
|
||||
import { BVHButton } from './BVHButton';
|
||||
import { ResetButton } from './home/ResetButton';
|
||||
import { Navbar } from './Navbar';
|
||||
import { TopBar } from './TopBar';
|
||||
import { DeveloperModeWidget } from './widgets/DeveloperModeWidget';
|
||||
import { OverlayWidget } from './widgets/OverlayWidget';
|
||||
|
||||
export function MainLayoutRoute({
|
||||
@@ -19,6 +21,7 @@ export function MainLayoutRoute({
|
||||
}) {
|
||||
const { layoutHeight, ref } = useLayout<HTMLDivElement>();
|
||||
const { layoutWidth, ref: refw } = useLayout<HTMLDivElement>();
|
||||
const { config } = useConfig();
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -59,6 +62,11 @@ export function MainLayoutRoute({
|
||||
<div className="w-full">
|
||||
<OverlayWidget></OverlayWidget>
|
||||
</div>
|
||||
{config?.debug && (
|
||||
<div className="w-full">
|
||||
<DeveloperModeWidget></DeveloperModeWidget>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
17
gui/src/components/commons/icon/WarningIcon.tsx
Normal file
17
gui/src/components/commons/icon/WarningIcon.tsx
Normal file
@@ -0,0 +1,17 @@
|
||||
export function WarningIcon(props: any) {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
{...props}
|
||||
className={`w-7 h-7 ${props.className || ''}`}
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M8.485 2.495c.673-1.167 2.357-1.167 3.03 0l6.28 10.875c.673 1.167-.17 2.625-1.516 2.625H3.72c-1.347 0-2.189-1.458-1.515-2.625L8.485 2.495zM10 5a.75.75 0 01.75.75v3.5a.75.75 0 01-1.5 0v-3.5A.75.75 0 0110 5zm0 9a1 1 0 100-2 1 1 0 000 2z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@@ -1,11 +1,10 @@
|
||||
import Quaternion from 'quaternion';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { AssignTrackerRequestT, BodyPart, RpcMessage } from 'solarxr-protocol';
|
||||
import { FlatDeviceTracker } from '../../../../hooks/app';
|
||||
import { useOnboarding } from '../../../../hooks/onboarding';
|
||||
import { useTrackers } from '../../../../hooks/tracker';
|
||||
import { useWebsocketAPI } from '../../../../hooks/websocket-api';
|
||||
import { QuaternionToQuatT } from '../../../../maths/quaternion';
|
||||
import { MountingOrientationDegreesToQuatT } from '../../../../maths/quaternion';
|
||||
import { ArrowLink } from '../../../commons/ArrowLink';
|
||||
import { Button } from '../../../commons/Button';
|
||||
import { TipBox } from '../../../commons/TipBox';
|
||||
@@ -41,13 +40,13 @@ export function ManualMountingPage() {
|
||||
[assignedTrackers]
|
||||
);
|
||||
|
||||
const onDirectionSelected = (mountingOrientation: number) => {
|
||||
const onDirectionSelected = (mountingOrientationDegrees: number) => {
|
||||
(trackerPartGrouped[selectedRole] || []).forEach((td) => {
|
||||
const assignreq = new AssignTrackerRequestT();
|
||||
|
||||
assignreq.bodyPosition = td.tracker.info?.bodyPart || BodyPart.NONE;
|
||||
assignreq.mountingOrientation = QuaternionToQuatT(
|
||||
Quaternion.fromEuler(0, +mountingOrientation, 0)
|
||||
assignreq.mountingOrientation = MountingOrientationDegreesToQuatT(
|
||||
mountingOrientationDegrees
|
||||
);
|
||||
assignreq.trackerId = td.tracker.trackerId;
|
||||
sendRPCPacket(RpcMessage.AssignTrackerRequest, assignreq);
|
||||
|
||||
@@ -3,21 +3,28 @@ import { Typography } from '../commons/Typography';
|
||||
|
||||
export function TrackerBattery({
|
||||
value,
|
||||
voltage,
|
||||
disabled,
|
||||
textColor = 'secondary',
|
||||
}: {
|
||||
value: number;
|
||||
voltage?: number | null;
|
||||
disabled?: boolean;
|
||||
textColor?: string;
|
||||
}) {
|
||||
const percent = value * 100;
|
||||
|
||||
return (
|
||||
<div className="flex gap-2">
|
||||
<div className="flex flex-col justify-around">
|
||||
<BatteryIcon value={value} disabled={disabled} />
|
||||
<BatteryIcon value={percent} disabled={disabled} />
|
||||
</div>
|
||||
{!disabled && (
|
||||
<div className="w-10">
|
||||
<Typography color="secondary">
|
||||
{(value * 100).toFixed(0)} %
|
||||
</Typography>
|
||||
<Typography color={textColor}>{percent.toFixed(0)} %</Typography>
|
||||
{voltage && (
|
||||
<Typography color={textColor}>{voltage.toFixed(2)} V</Typography>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{disabled && (
|
||||
|
||||
@@ -44,10 +44,11 @@ function TrackerBig({
|
||||
/>
|
||||
)}
|
||||
<div className="flex gap-2">
|
||||
{device.hardwareStatus.rssi && device.hardwareStatus.ping && (
|
||||
{(device.hardwareStatus.rssi != null ||
|
||||
device.hardwareStatus.ping != null) && (
|
||||
<TrackerWifi
|
||||
rssi={device.hardwareStatus.rssi}
|
||||
ping={device.hardwareStatus.ping}
|
||||
rssi={device.hardwareStatus.rssi || 0}
|
||||
ping={device.hardwareStatus.ping || 0}
|
||||
disabled={tracker.status === TrackerStatusEnum.DISCONNECTED}
|
||||
></TrackerWifi>
|
||||
)}
|
||||
@@ -90,10 +91,11 @@ function TrackerSmol({
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-col justify-center items-center">
|
||||
{device.hardwareStatus.rssi && device.hardwareStatus.ping && (
|
||||
{(device.hardwareStatus.rssi != null ||
|
||||
device.hardwareStatus.ping != null) && (
|
||||
<TrackerWifi
|
||||
rssi={device.hardwareStatus.rssi}
|
||||
ping={device.hardwareStatus.ping}
|
||||
rssi={device.hardwareStatus.rssi || 0}
|
||||
ping={device.hardwareStatus.ping || 0}
|
||||
disabled={tracker.status === TrackerStatusEnum.DISCONNECTED}
|
||||
></TrackerWifi>
|
||||
)}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { IPv4 } from 'ip-num/IPNumber';
|
||||
import Quaternion from 'quaternion';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useParams } from 'react-router-dom';
|
||||
@@ -7,8 +6,10 @@ import { AssignTrackerRequestT, BodyPart, RpcMessage } from 'solarxr-protocol';
|
||||
import { useDebouncedEffect } from '../../hooks/timeout';
|
||||
import { useTrackerFromId } from '../../hooks/tracker';
|
||||
import { useWebsocketAPI } from '../../hooks/websocket-api';
|
||||
import { DEG_TO_RAD, RAD_TO_DEG } from '../../maths/angle';
|
||||
import { FixEuler, GetYaw, QuaternionToQuatT } from '../../maths/quaternion';
|
||||
import {
|
||||
getYawInDegrees,
|
||||
MountingOrientationDegreesToQuatT,
|
||||
} from '../../maths/quaternion';
|
||||
import { ArrowLink } from '../commons/ArrowLink';
|
||||
import { Button } from '../commons/Button';
|
||||
import { FootIcon } from '../commons/icon/FootIcon';
|
||||
@@ -19,6 +20,10 @@ import { SingleTrackerBodyAssignmentMenu } from './SingleTrackerBodyAssignmentMe
|
||||
import { TrackerCard } from './TrackerCard';
|
||||
import { CheckBox } from '../commons/Checkbox';
|
||||
import { useLocalization } from '@fluent/react';
|
||||
import { IMUVisualizerWidget } from '../widgets/IMUVisualizerWidget';
|
||||
import { useConfig } from '../../hooks/config';
|
||||
import { WarningIcon } from '../commons/icon/WarningIcon';
|
||||
import classNames from 'classnames';
|
||||
|
||||
export const rotationToQuatMap = {
|
||||
FRONT: 180,
|
||||
@@ -36,6 +41,7 @@ const rotationsLabels = {
|
||||
|
||||
export function TrackerSettingsPage() {
|
||||
const { l10n } = useLocalization();
|
||||
const { config } = useConfig();
|
||||
|
||||
const { sendRPCPacket } = useWebsocketAPI();
|
||||
const [firstLoad, setFirstLoad] = useState(false);
|
||||
@@ -59,18 +65,13 @@ export function TrackerSettingsPage() {
|
||||
|
||||
const tracker = useTrackerFromId(trackernum, deviceid);
|
||||
|
||||
const onDirectionSelected = (mountingOrientation: number) => {
|
||||
const onDirectionSelected = (mountingOrientationDegrees: number) => {
|
||||
if (!tracker) return;
|
||||
|
||||
const assignreq = new AssignTrackerRequestT();
|
||||
|
||||
assignreq.mountingOrientation = QuaternionToQuatT(
|
||||
Quaternion.fromEuler(
|
||||
0,
|
||||
0,
|
||||
FixEuler(+mountingOrientation) * DEG_TO_RAD,
|
||||
'XZY'
|
||||
)
|
||||
assignreq.mountingOrientation = MountingOrientationDegreesToQuatT(
|
||||
mountingOrientationDegrees
|
||||
);
|
||||
assignreq.bodyPosition = tracker?.tracker.info?.bodyPart || BodyPart.NONE;
|
||||
assignreq.trackerId = tracker?.tracker.trackerId;
|
||||
@@ -92,9 +93,9 @@ export function TrackerSettingsPage() {
|
||||
setSelectBodypart(false);
|
||||
};
|
||||
|
||||
const currRotation = useMemo(() => {
|
||||
const currRotationDegrees = useMemo(() => {
|
||||
return tracker?.tracker.info?.mountingOrientation
|
||||
? FixEuler(GetYaw(tracker.tracker.info?.mountingOrientation) * RAD_TO_DEG)
|
||||
? getYawInDegrees(tracker?.tracker.info?.mountingOrientation)
|
||||
: rotationToQuatMap.FRONT;
|
||||
}, [tracker?.tracker.info?.mountingOrientation]);
|
||||
|
||||
@@ -108,10 +109,9 @@ export function TrackerSettingsPage() {
|
||||
return;
|
||||
const assignreq = new AssignTrackerRequestT();
|
||||
assignreq.bodyPosition = tracker?.tracker.info?.bodyPart || BodyPart.NONE;
|
||||
assignreq.mountingOrientation = assignreq.mountingOrientation =
|
||||
QuaternionToQuatT(
|
||||
Quaternion.fromEuler(0, 0, FixEuler(+currRotation) * DEG_TO_RAD, 'XZY')
|
||||
);
|
||||
assignreq.mountingOrientation =
|
||||
MountingOrientationDegreesToQuatT(currRotationDegrees);
|
||||
|
||||
assignreq.displayName = trackerName;
|
||||
assignreq.trackerId = tracker?.tracker.trackerId;
|
||||
assignreq.allowDriftCompensation = allowDriftCompensation;
|
||||
@@ -222,6 +222,11 @@ export function TrackerSettingsPage() {
|
||||
</Typography>
|
||||
</div>
|
||||
</div>
|
||||
{tracker?.tracker && config?.debug && (
|
||||
<IMUVisualizerWidget
|
||||
tracker={tracker?.tracker}
|
||||
></IMUVisualizerWidget>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-col flex-grow bg-background-70 rounded-lg p-5 gap-3">
|
||||
<ArrowLink to="/">
|
||||
@@ -241,8 +246,18 @@ export function TrackerSettingsPage() {
|
||||
</Typography>
|
||||
<div className="flex justify-between bg-background-80 w-full p-3 rounded-lg">
|
||||
<div className="flex gap-3 items-center">
|
||||
<FootIcon></FootIcon>
|
||||
<Typography>
|
||||
{tracker?.tracker.info?.bodyPart !== BodyPart.NONE && (
|
||||
<FootIcon></FootIcon>
|
||||
)}
|
||||
{tracker?.tracker.info?.bodyPart === BodyPart.NONE && (
|
||||
<WarningIcon className="text-yellow-300" />
|
||||
)}
|
||||
<Typography
|
||||
color={classNames({
|
||||
'text-yellow-300':
|
||||
tracker?.tracker.info?.bodyPart === BodyPart.NONE,
|
||||
})}
|
||||
>
|
||||
{l10n.getString(
|
||||
'body_part-' +
|
||||
BodyPart[tracker?.tracker.info?.bodyPart || BodyPart.NONE]
|
||||
@@ -270,7 +285,7 @@ export function TrackerSettingsPage() {
|
||||
<div className="flex gap-3 items-center">
|
||||
<FootIcon></FootIcon>
|
||||
<Typography>
|
||||
{l10n.getString(rotationsLabels[currRotation])}
|
||||
{l10n.getString(rotationsLabels[currRotationDegrees])}
|
||||
</Typography>
|
||||
</div>
|
||||
<div className="flex">
|
||||
|
||||
@@ -4,24 +4,31 @@ import { Typography } from '../commons/Typography';
|
||||
export function TrackerWifi({
|
||||
rssi,
|
||||
ping,
|
||||
rssiShowNumeric,
|
||||
disabled,
|
||||
textColor = 'secondary',
|
||||
}: {
|
||||
rssi: number;
|
||||
ping: number;
|
||||
rssiShowNumeric?: boolean;
|
||||
disabled?: boolean;
|
||||
textColor?: string;
|
||||
}) {
|
||||
return (
|
||||
<div className="flex gap-2">
|
||||
<div className="flex flex-col justify-around ">
|
||||
<div className="flex gap-2 whitespace-nowrap">
|
||||
<div className="flex flex-col justify-around">
|
||||
<WifiIcon value={rssi} disabled={disabled} />
|
||||
</div>
|
||||
{!disabled && (
|
||||
<div className="w-10">
|
||||
<Typography color="secondary">{ping} ms</Typography>
|
||||
<div className="w-12">
|
||||
<Typography color={textColor}>{ping} ms</Typography>
|
||||
{rssiShowNumeric && (
|
||||
<Typography color={textColor}>{rssi} dBm</Typography>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{disabled && (
|
||||
<div className="flex flex-col justify-center w-10">
|
||||
<div className="flex flex-col justify-center w-12">
|
||||
<div className="w-7 h-1 bg-background-30 rounded-full"></div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import classNames from 'classnames';
|
||||
import { IPv4 } from 'ip-num/IPNumber';
|
||||
import { MouseEventHandler, ReactNode, useState } from 'react';
|
||||
import { MouseEventHandler, ReactNode, useState, useMemo } from 'react';
|
||||
import {
|
||||
TrackerDataT,
|
||||
TrackerIdT,
|
||||
@@ -14,8 +14,45 @@ import { TrackerBattery } from './TrackerBattery';
|
||||
import { TrackerStatus } from './TrackerStatus';
|
||||
import { TrackerWifi } from './TrackerWifi';
|
||||
import { useLocalization } from '@fluent/react';
|
||||
import { formatVector3 } from '../utils/formatting';
|
||||
import { useConfig } from '../../hooks/config';
|
||||
|
||||
export function TrackerNameCol({ tracker }: { tracker: TrackerDataT }) {
|
||||
enum DisplayColumn {
|
||||
NAME,
|
||||
TYPE,
|
||||
BATTERY,
|
||||
PING,
|
||||
TPS,
|
||||
ROTATION,
|
||||
TEMPERATURE,
|
||||
LINEAR_ACCELERATION,
|
||||
POSITION,
|
||||
URL,
|
||||
}
|
||||
|
||||
const displayColumns: { [k: string]: boolean } = {
|
||||
[DisplayColumn.NAME]: true,
|
||||
[DisplayColumn.TYPE]: true,
|
||||
[DisplayColumn.BATTERY]: true,
|
||||
[DisplayColumn.PING]: true,
|
||||
[DisplayColumn.TPS]: true,
|
||||
[DisplayColumn.ROTATION]: true,
|
||||
[DisplayColumn.TEMPERATURE]: true,
|
||||
[DisplayColumn.LINEAR_ACCELERATION]: true,
|
||||
[DisplayColumn.POSITION]: true,
|
||||
[DisplayColumn.URL]: true,
|
||||
};
|
||||
|
||||
const isSlime = ({ device }: FlatDeviceTracker) =>
|
||||
device?.hardwareInfo?.manufacturer === 'SlimeVR';
|
||||
|
||||
const getDeviceName = ({ device }: FlatDeviceTracker) =>
|
||||
device?.customName?.toString() || '';
|
||||
|
||||
const getTrackerName = ({ tracker }: FlatDeviceTracker) =>
|
||||
tracker?.info?.customName?.toString() || '';
|
||||
|
||||
export function TrackerNameCell({ tracker }: { tracker: TrackerDataT }) {
|
||||
const { useName } = useTracker(tracker);
|
||||
|
||||
const name = useName();
|
||||
@@ -33,17 +70,28 @@ export function TrackerNameCol({ tracker }: { tracker: TrackerDataT }) {
|
||||
);
|
||||
}
|
||||
|
||||
export function TrackerRotCol({ tracker }: { tracker: TrackerDataT }) {
|
||||
const { useRotationDegrees } = useTracker(tracker);
|
||||
export function TrackerRotCell({
|
||||
tracker,
|
||||
precise,
|
||||
color,
|
||||
referenceAdjusted,
|
||||
}: {
|
||||
tracker: TrackerDataT;
|
||||
precise?: boolean;
|
||||
color?: string;
|
||||
referenceAdjusted?: boolean;
|
||||
}) {
|
||||
const { useRawRotationEulerDegrees, useRefAdjRotationEulerDegrees } =
|
||||
useTracker(tracker);
|
||||
|
||||
const rot = useRotationDegrees();
|
||||
const rot = referenceAdjusted
|
||||
? useRefAdjRotationEulerDegrees()
|
||||
: useRawRotationEulerDegrees();
|
||||
|
||||
return (
|
||||
<Typography color="secondary">
|
||||
<Typography color={color}>
|
||||
<span className="whitespace-nowrap">
|
||||
{`${rot.pitch.toFixed(0)} / ${rot.yaw.toFixed(0)} / ${rot.roll.toFixed(
|
||||
0
|
||||
)}`}
|
||||
{formatVector3(rot, precise ? 2 : 0)}
|
||||
</span>
|
||||
</Typography>
|
||||
);
|
||||
@@ -90,7 +138,7 @@ export function RowContainer({
|
||||
'min-h-[50px] flex flex-col justify-center px-3',
|
||||
rounded === 'left' && 'rounded-l-lg',
|
||||
rounded === 'right' && 'rounded-r-lg',
|
||||
hover ? 'bg-background-50' : 'bg-background-60'
|
||||
hover ? 'bg-background-50 cursor-pointer' : 'bg-background-60'
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
@@ -108,168 +156,218 @@ export function TrackersTable({
|
||||
}) {
|
||||
const { l10n } = useLocalization();
|
||||
const [hoverTracker, setHoverTracker] = useState<TrackerIdT | null>(null);
|
||||
const { config } = useConfig();
|
||||
|
||||
const trackerEqual = (id: TrackerIdT | null) =>
|
||||
id?.trackerNum == hoverTracker?.trackerNum &&
|
||||
(!id?.deviceId || id.deviceId.id == hoverTracker?.deviceId?.id);
|
||||
|
||||
const filteringEnabled =
|
||||
config?.debug && config?.devSettings?.filterSlimesAndHMD;
|
||||
const sortingEnabled = config?.debug && config?.devSettings?.sortByName;
|
||||
// TODO: fix memo
|
||||
const filteredSortedTrackers = useMemo(() => {
|
||||
const list = filteringEnabled
|
||||
? flatTrackers.filter((t) => getDeviceName(t) === 'HMD' || isSlime(t))
|
||||
: flatTrackers;
|
||||
|
||||
if (sortingEnabled) {
|
||||
list.sort((a, b) => getTrackerName(a).localeCompare(getTrackerName(b)));
|
||||
}
|
||||
return list;
|
||||
}, [flatTrackers, filteringEnabled, sortingEnabled]);
|
||||
|
||||
const fontColor = config?.devSettings?.highContrast ? 'primary' : 'secondary';
|
||||
const moreInfo = config?.devSettings?.moreInfo;
|
||||
|
||||
const hasTemperature = !!filteredSortedTrackers.find(
|
||||
({ tracker }) => Number(tracker?.temp?.temp) != 0
|
||||
);
|
||||
displayColumns[DisplayColumn.TEMPERATURE] = hasTemperature || false;
|
||||
displayColumns[DisplayColumn.POSITION] = moreInfo || false;
|
||||
displayColumns[DisplayColumn.LINEAR_ACCELERATION] = moreInfo || false;
|
||||
displayColumns[DisplayColumn.URL] = moreInfo || false;
|
||||
const displayColumnsKeys = Object.keys(displayColumns).filter(
|
||||
(k) => displayColumns[k]
|
||||
);
|
||||
const firstColumnId = +displayColumnsKeys[0];
|
||||
const lastColumnId = +displayColumnsKeys[displayColumnsKeys.length - 1];
|
||||
|
||||
function column({
|
||||
id,
|
||||
label,
|
||||
labelClassName,
|
||||
row,
|
||||
}: {
|
||||
id: DisplayColumn;
|
||||
label: string;
|
||||
labelClassName?: string;
|
||||
row: (data: FlatDeviceTracker) => ReactNode | null;
|
||||
}) {
|
||||
let rounded: 'left' | 'right' | 'none' = 'none';
|
||||
if (firstColumnId === id) rounded = 'left';
|
||||
else if (lastColumnId === id) rounded = 'right';
|
||||
|
||||
if (!displayColumns[id]) return <></>;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames('flex flex-col gap-1', {
|
||||
'flex-grow': lastColumnId === id,
|
||||
})}
|
||||
>
|
||||
<div className={`flex px-3 whitespace-nowrap ${labelClassName}`}>
|
||||
{label}
|
||||
</div>
|
||||
{filteredSortedTrackers.map((data, index) => (
|
||||
<RowContainer
|
||||
rounded={rounded}
|
||||
key={index}
|
||||
tracker={data.tracker}
|
||||
onClick={() => clickedTracker(data.tracker)}
|
||||
hover={trackerEqual(data.tracker.trackerId)}
|
||||
onMouseOver={() => setHoverTracker(data.tracker.trackerId)}
|
||||
onMouseOut={() => setHoverTracker(null)}
|
||||
>
|
||||
{row(data) || <></>}
|
||||
</RowContainer>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex w-full overflow-x-auto py-2">
|
||||
<div className="flex flex-col gap-1">
|
||||
<div className="flex px-3">
|
||||
{l10n.getString('tracker-table-column-name')}
|
||||
</div>
|
||||
{flatTrackers.map(({ tracker }, index) => (
|
||||
<RowContainer
|
||||
key={index}
|
||||
rounded="left"
|
||||
tracker={tracker}
|
||||
onClick={() => clickedTracker(tracker)}
|
||||
hover={trackerEqual(tracker.trackerId)}
|
||||
onMouseOver={() => setHoverTracker(tracker.trackerId)}
|
||||
onMouseOut={() => setHoverTracker(null)}
|
||||
>
|
||||
<TrackerNameCol tracker={tracker}></TrackerNameCol>
|
||||
</RowContainer>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<div className="flex px-3">
|
||||
{l10n.getString('tracker-table-column-type')}
|
||||
</div>
|
||||
{flatTrackers.map(({ device, tracker }, index) => (
|
||||
<RowContainer
|
||||
key={index}
|
||||
tracker={tracker}
|
||||
onClick={() => clickedTracker(tracker)}
|
||||
hover={trackerEqual(tracker.trackerId)}
|
||||
onMouseOver={() => setHoverTracker(tracker.trackerId)}
|
||||
onMouseOut={() => setHoverTracker(null)}
|
||||
>
|
||||
<Typography color="secondary">
|
||||
{device?.hardwareInfo?.manufacturer || '--'}
|
||||
</Typography>
|
||||
</RowContainer>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<div className="flex px-3">
|
||||
{l10n.getString('tracker-table-column-battery')}
|
||||
</div>
|
||||
{flatTrackers.map(({ device, tracker }, index) => (
|
||||
<RowContainer
|
||||
key={index}
|
||||
tracker={tracker}
|
||||
onClick={() => clickedTracker(tracker)}
|
||||
hover={trackerEqual(tracker.trackerId)}
|
||||
onMouseOver={() => setHoverTracker(tracker.trackerId)}
|
||||
onMouseOut={() => setHoverTracker(null)}
|
||||
>
|
||||
{(device &&
|
||||
device.hardwareStatus &&
|
||||
device.hardwareStatus.batteryPctEstimate && (
|
||||
<TrackerBattery
|
||||
value={device.hardwareStatus.batteryPctEstimate / 100}
|
||||
disabled={tracker.status === TrackerStatusEnum.DISCONNECTED}
|
||||
/>
|
||||
)) || <></>}
|
||||
</RowContainer>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<div className="flex px-3">
|
||||
{l10n.getString('tracker-table-column-ping')}
|
||||
</div>
|
||||
{flatTrackers.map(({ device, tracker }, index) => (
|
||||
<RowContainer
|
||||
key={index}
|
||||
tracker={tracker}
|
||||
onClick={() => clickedTracker(tracker)}
|
||||
hover={trackerEqual(tracker.trackerId)}
|
||||
onMouseOver={() => setHoverTracker(tracker.trackerId)}
|
||||
onMouseOut={() => setHoverTracker(null)}
|
||||
>
|
||||
{(device &&
|
||||
device.hardwareStatus &&
|
||||
device.hardwareStatus.rssi &&
|
||||
device.hardwareStatus.ping && (
|
||||
<TrackerWifi
|
||||
rssi={device.hardwareStatus.rssi}
|
||||
ping={device.hardwareStatus.ping}
|
||||
disabled={tracker.status === TrackerStatusEnum.DISCONNECTED}
|
||||
></TrackerWifi>
|
||||
)) || <></>}
|
||||
</RowContainer>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<div className="flex px-3 whitespace-nowrap">
|
||||
{l10n.getString('tracker-table-column-rotation')}
|
||||
</div>
|
||||
{flatTrackers.map(({ tracker }, index) => (
|
||||
<RowContainer
|
||||
key={index}
|
||||
tracker={tracker}
|
||||
onClick={() => clickedTracker(tracker)}
|
||||
hover={trackerEqual(tracker.trackerId)}
|
||||
onMouseOver={() => setHoverTracker(tracker.trackerId)}
|
||||
onMouseOut={() => setHoverTracker(null)}
|
||||
>
|
||||
<TrackerRotCol tracker={tracker} />
|
||||
</RowContainer>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<div className="flex px-3 whitespace-nowrap">
|
||||
{l10n.getString('tracker-table-column-position')}
|
||||
</div>
|
||||
{flatTrackers.map(({ tracker }, index) => (
|
||||
<RowContainer
|
||||
key={index}
|
||||
tracker={tracker}
|
||||
onClick={() => clickedTracker(tracker)}
|
||||
hover={trackerEqual(tracker.trackerId)}
|
||||
onMouseOver={() => setHoverTracker(tracker.trackerId)}
|
||||
onMouseOut={() => setHoverTracker(null)}
|
||||
>
|
||||
{(tracker.position && (
|
||||
<Typography color="secondary">
|
||||
<span className="whitespace-nowrap">
|
||||
{`${tracker.position?.x.toFixed(
|
||||
0
|
||||
)} / ${tracker.position?.y.toFixed(
|
||||
0
|
||||
)} / ${tracker.position?.z.toFixed(0)}`}
|
||||
</span>
|
||||
</Typography>
|
||||
)) || <></>}
|
||||
</RowContainer>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex flex-col gap-1 flex-grow">
|
||||
<div className="flex px-3">
|
||||
{l10n.getString('tracker-table-column-url')}
|
||||
</div>
|
||||
{column({
|
||||
id: DisplayColumn.NAME,
|
||||
label: l10n.getString('tracker-table-column-name'),
|
||||
row: ({ tracker }) => (
|
||||
<TrackerNameCell tracker={tracker}></TrackerNameCell>
|
||||
),
|
||||
})}
|
||||
|
||||
{flatTrackers.map(({ device, tracker }, index) => (
|
||||
<RowContainer
|
||||
key={index}
|
||||
rounded="right"
|
||||
{column({
|
||||
id: DisplayColumn.TYPE,
|
||||
label: l10n.getString('tracker-table-column-type'),
|
||||
row: ({ device }) => (
|
||||
<Typography color={fontColor}>
|
||||
{device?.hardwareInfo?.manufacturer || '--'}
|
||||
</Typography>
|
||||
),
|
||||
})}
|
||||
|
||||
{column({
|
||||
id: DisplayColumn.BATTERY,
|
||||
label: l10n.getString('tracker-table-column-battery'),
|
||||
row: ({ device, tracker }) =>
|
||||
device?.hardwareStatus?.batteryPctEstimate && (
|
||||
<TrackerBattery
|
||||
value={device.hardwareStatus.batteryPctEstimate / 100}
|
||||
voltage={device.hardwareStatus?.batteryVoltage}
|
||||
disabled={tracker.status === TrackerStatusEnum.DISCONNECTED}
|
||||
textColor={fontColor}
|
||||
/>
|
||||
),
|
||||
})}
|
||||
|
||||
{column({
|
||||
id: DisplayColumn.PING,
|
||||
label: l10n.getString('tracker-table-column-ping'),
|
||||
row: ({ device, tracker }) =>
|
||||
(device?.hardwareStatus?.rssi != null ||
|
||||
device?.hardwareStatus?.ping != null) && (
|
||||
<TrackerWifi
|
||||
rssi={device?.hardwareStatus?.rssi || 0}
|
||||
rssiShowNumeric
|
||||
ping={device?.hardwareStatus?.ping || 0}
|
||||
disabled={tracker.status === TrackerStatusEnum.DISCONNECTED}
|
||||
textColor={fontColor}
|
||||
></TrackerWifi>
|
||||
),
|
||||
})}
|
||||
|
||||
{column({
|
||||
id: DisplayColumn.TPS,
|
||||
label: l10n.getString('tracker-table-column-tps'),
|
||||
row: ({ device }) => (
|
||||
<Typography color={fontColor}>
|
||||
{(device?.hardwareStatus?.tps != null && (
|
||||
<>{device.hardwareStatus.tps || 0}</>
|
||||
)) || <></>}
|
||||
</Typography>
|
||||
),
|
||||
})}
|
||||
|
||||
{column({
|
||||
id: DisplayColumn.ROTATION,
|
||||
label: l10n.getString('tracker-table-column-rotation'),
|
||||
labelClassName: classNames({
|
||||
'w-44': config?.devSettings?.preciseRotation,
|
||||
'w-32': !config?.devSettings?.preciseRotation,
|
||||
}),
|
||||
row: ({ tracker }) => (
|
||||
<TrackerRotCell
|
||||
tracker={tracker}
|
||||
onClick={() => clickedTracker(tracker)}
|
||||
hover={trackerEqual(tracker.trackerId)}
|
||||
onMouseOver={() => setHoverTracker(tracker.trackerId)}
|
||||
onMouseOut={() => setHoverTracker(null)}
|
||||
>
|
||||
<Typography color="secondary">
|
||||
udp://
|
||||
{IPv4.fromNumber(
|
||||
device?.hardwareInfo?.ipAddress?.addr || 0
|
||||
).toString()}
|
||||
precise={config?.devSettings?.preciseRotation}
|
||||
referenceAdjusted={!config?.devSettings?.rawSlimeRotation}
|
||||
color={fontColor}
|
||||
/>
|
||||
),
|
||||
})}
|
||||
|
||||
{column({
|
||||
id: DisplayColumn.TEMPERATURE,
|
||||
label: l10n.getString('tracker-table-column-temperature'),
|
||||
row: ({ tracker }) =>
|
||||
tracker.temp?.temp != 0 && (
|
||||
<Typography color={fontColor}>
|
||||
<span className="whitespace-nowrap">
|
||||
{`${tracker.temp?.temp.toFixed(2)}`}
|
||||
</span>
|
||||
</Typography>
|
||||
</RowContainer>
|
||||
))}
|
||||
</div>
|
||||
),
|
||||
})}
|
||||
|
||||
{column({
|
||||
id: DisplayColumn.LINEAR_ACCELERATION,
|
||||
label: l10n.getString('tracker-table-column-linear-acceleration'),
|
||||
labelClassName: 'w-36',
|
||||
row: ({ tracker }) =>
|
||||
tracker.linearAcceleration && (
|
||||
<Typography color={fontColor}>
|
||||
<span className="whitespace-nowrap">
|
||||
{formatVector3(tracker.linearAcceleration, 1)}
|
||||
</span>
|
||||
</Typography>
|
||||
),
|
||||
})}
|
||||
|
||||
{column({
|
||||
id: DisplayColumn.POSITION,
|
||||
label: l10n.getString('tracker-table-column-position'),
|
||||
labelClassName: 'w-36',
|
||||
row: ({ tracker }) =>
|
||||
tracker.position && (
|
||||
<Typography color={fontColor}>
|
||||
<span className="whitespace-nowrap">
|
||||
{formatVector3(tracker.position, 1)}
|
||||
</span>
|
||||
</Typography>
|
||||
),
|
||||
})}
|
||||
|
||||
{column({
|
||||
id: DisplayColumn.URL,
|
||||
label: l10n.getString('tracker-table-column-url'),
|
||||
row: ({ device }) => (
|
||||
<Typography color={fontColor}>
|
||||
udp://
|
||||
{IPv4.fromNumber(
|
||||
device?.hardwareInfo?.ipAddress?.addr || 0
|
||||
).toString()}
|
||||
</Typography>
|
||||
),
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,3 +2,7 @@ import { BodyPart } from 'solarxr-protocol';
|
||||
|
||||
export const bodypartToString = (id: BodyPart) =>
|
||||
BodyPart[id].replace(/_/g, ' ');
|
||||
|
||||
type Vector3 = { x: number; y: number; z: number };
|
||||
export const formatVector3 = ({ x, y, z }: Vector3, precision = 0) =>
|
||||
`${x.toFixed(precision)} / ${y.toFixed(precision)} / ${z.toFixed(precision)}`;
|
||||
|
||||
83
gui/src/components/widgets/DeveloperModeWidget.tsx
Normal file
83
gui/src/components/widgets/DeveloperModeWidget.tsx
Normal file
@@ -0,0 +1,83 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useConfig } from '../../hooks/config';
|
||||
import { useWebsocketAPI } from '../../hooks/websocket-api';
|
||||
import { CheckBox } from '../commons/Checkbox';
|
||||
import { useLocalization } from '@fluent/react';
|
||||
import { Typography } from '../commons/Typography';
|
||||
|
||||
export interface DeveloperModeWidgetForm {
|
||||
highContrast: boolean;
|
||||
preciseRotation: boolean;
|
||||
fastDataFeed: boolean;
|
||||
filterSlimesAndHMD: boolean;
|
||||
sortByName: boolean;
|
||||
rawSlimeRotation: boolean;
|
||||
moreInfo: boolean;
|
||||
}
|
||||
|
||||
export function DeveloperModeWidget() {
|
||||
const { l10n } = useLocalization();
|
||||
const { config, setConfig } = useConfig();
|
||||
const { reconnect } = useWebsocketAPI();
|
||||
|
||||
const { reset, control, handleSubmit, watch } =
|
||||
useForm<DeveloperModeWidgetForm>({
|
||||
defaultValues: {
|
||||
highContrast: false,
|
||||
preciseRotation: false,
|
||||
fastDataFeed: false,
|
||||
filterSlimesAndHMD: false,
|
||||
sortByName: false,
|
||||
rawSlimeRotation: false,
|
||||
moreInfo: false,
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
reset(config?.devSettings || {});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const subscription = watch(() => handleSubmit(onSubmit)());
|
||||
return () => subscription.unsubscribe();
|
||||
}, []);
|
||||
|
||||
const onSubmit = async (formData: DeveloperModeWidgetForm) => {
|
||||
const needReconnect =
|
||||
config?.devSettings?.fastDataFeed !== formData.fastDataFeed;
|
||||
await setConfig({ devSettings: formData });
|
||||
if (needReconnect) reconnect();
|
||||
};
|
||||
|
||||
const makeToggle = ([name, label]: string[], index: number) => (
|
||||
<CheckBox
|
||||
key={index}
|
||||
control={control}
|
||||
variant="toggle"
|
||||
name={name}
|
||||
label={l10n.getString(`widget-developer_mode-${label}`)}
|
||||
></CheckBox>
|
||||
);
|
||||
|
||||
const toggles = {
|
||||
highContrast: 'high_contrast',
|
||||
preciseRotation: 'precise_rotation',
|
||||
fastDataFeed: 'fast_data_feed',
|
||||
filterSlimesAndHMD: 'filter_slimes_and_hmd',
|
||||
sortByName: 'sort_by_name',
|
||||
rawSlimeRotation: 'raw_slime_rotation',
|
||||
moreInfo: 'more_info',
|
||||
};
|
||||
|
||||
return (
|
||||
<form className="bg-background-60 flex flex-col w-full rounded-md px-2">
|
||||
<div className="mt-2 px-1">
|
||||
<Typography color="secondary">
|
||||
{l10n.getString('widget-developer_mode')}
|
||||
</Typography>
|
||||
</div>
|
||||
{Object.entries(toggles).map(makeToggle)}
|
||||
</form>
|
||||
);
|
||||
}
|
||||
169
gui/src/components/widgets/IMUVisualizerWidget.tsx
Normal file
169
gui/src/components/widgets/IMUVisualizerWidget.tsx
Normal file
@@ -0,0 +1,169 @@
|
||||
import { useMemo, useState } from 'react';
|
||||
import { TrackerDataT } from 'solarxr-protocol';
|
||||
import { useTracker } from '../../hooks/tracker';
|
||||
import { Typography } from '../commons/Typography';
|
||||
import { formatVector3 } from '../utils/formatting';
|
||||
import { Canvas, useThree } from '@react-three/fiber';
|
||||
import * as THREE from 'three';
|
||||
import { CanvasTexture, PerspectiveCamera } from 'three';
|
||||
import { Button } from '../commons/Button';
|
||||
import { QuatObject } from '../../maths/quaternion';
|
||||
import { useLocalization } from '@fluent/react';
|
||||
|
||||
const groundColor = '#4444aa';
|
||||
const R = '#f01662';
|
||||
const G = '#92ff1a';
|
||||
const B = '#00b0ff';
|
||||
|
||||
const scale = 1.4;
|
||||
const width = 2 * scale;
|
||||
const height = 1 * scale;
|
||||
const depth = 3 * scale;
|
||||
const defaultFaces = ['Right', 'Left', 'Top', 'Bottom', 'Back', 'Front'];
|
||||
const faceParams = [
|
||||
{ scaleX: depth, scaleY: height, color: R }, // left right
|
||||
{ scaleX: width, scaleY: depth, color: G }, // top bottom
|
||||
{ scaleX: width, scaleY: height, color: B }, // front back
|
||||
];
|
||||
|
||||
type FaceTypeProps = {
|
||||
index: number;
|
||||
scaleX: number;
|
||||
scaleY: number;
|
||||
resolution?: number;
|
||||
font?: string;
|
||||
opacity?: number;
|
||||
color?: string;
|
||||
hoverColor?: string;
|
||||
textColor?: string;
|
||||
strokeColor?: string;
|
||||
faces?: string[];
|
||||
};
|
||||
|
||||
const FaceMaterial = ({
|
||||
index,
|
||||
scaleX = 1,
|
||||
scaleY = 1,
|
||||
resolution = 128,
|
||||
font = 'bold 46px monospace',
|
||||
faces = defaultFaces,
|
||||
color = '#f0f0f0',
|
||||
textColor = 'black',
|
||||
strokeColor = 'black',
|
||||
opacity = 1,
|
||||
}: FaceTypeProps) => {
|
||||
const gl = useThree((state) => state.gl);
|
||||
const texture = useMemo(() => {
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = resolution * scaleX;
|
||||
canvas.height = resolution * scaleY;
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const context = canvas.getContext('2d')!;
|
||||
context.fillStyle = color;
|
||||
context.fillRect(0, 0, canvas.width, canvas.height);
|
||||
context.strokeStyle = strokeColor;
|
||||
context.lineWidth = 2;
|
||||
context.strokeRect(0, 0, canvas.width, canvas.height);
|
||||
context.font = font;
|
||||
context.textAlign = 'center';
|
||||
context.fillStyle = textColor;
|
||||
context.fillText(
|
||||
faces[index].toUpperCase(),
|
||||
canvas.width / 2,
|
||||
canvas.height / 2 + 20
|
||||
);
|
||||
return new CanvasTexture(canvas);
|
||||
}, [index, faces, font, color, textColor, strokeColor]);
|
||||
return (
|
||||
<meshLambertMaterial
|
||||
map={texture}
|
||||
map-encoding={gl.outputEncoding}
|
||||
map-anisotropy={gl.capabilities.getMaxAnisotropy() || 1}
|
||||
attach={`material-${index}`}
|
||||
color={'white'}
|
||||
transparent
|
||||
opacity={opacity}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const FaceCube = (props: Partial<FaceTypeProps>) => (
|
||||
<mesh>
|
||||
{[...Array(6)].map((_, i) => (
|
||||
<FaceMaterial key={i} {...props} index={i} {...faceParams[(i / 2) | 0]} />
|
||||
))}
|
||||
<boxGeometry args={[width, height, depth]} />
|
||||
</mesh>
|
||||
);
|
||||
|
||||
function SceneRenderer({ x, y, z, w }: QuatObject) {
|
||||
return (
|
||||
<Canvas
|
||||
className="container"
|
||||
style={{ height: 200, background: 'transparent' }}
|
||||
onCreated={({ camera }) => {
|
||||
(camera as PerspectiveCamera).fov = 60;
|
||||
}}
|
||||
>
|
||||
<ambientLight intensity={0.5} />
|
||||
<spotLight position={[20, 20, 20]} angle={0.09} penumbra={1} />
|
||||
<group quaternion={new THREE.Quaternion(x, y, z, w)}>
|
||||
<FaceCube />
|
||||
<axesHelper args={[10]} />
|
||||
</group>
|
||||
|
||||
<mesh position={[0, -3, 0]} rotation={[-Math.PI / 2, 0, 0]}>
|
||||
<planeGeometry args={[50, 50, 10, 10]} />
|
||||
<meshBasicMaterial
|
||||
wireframe
|
||||
color={groundColor}
|
||||
transparent
|
||||
opacity={0.2}
|
||||
side={THREE.DoubleSide}
|
||||
/>
|
||||
</mesh>
|
||||
</Canvas>
|
||||
);
|
||||
}
|
||||
|
||||
export function IMUVisualizerWidget({ tracker }: { tracker: TrackerDataT }) {
|
||||
const { l10n } = useLocalization();
|
||||
const [enabled, setEnabled] = useState(false);
|
||||
const quat = tracker?.rotationIdentityAdjusted || new THREE.Quaternion();
|
||||
|
||||
const { useRawRotationEulerDegrees, useIdentAdjRotationEulerDegrees } =
|
||||
useTracker(tracker);
|
||||
|
||||
return (
|
||||
<div className="bg-background-70 flex flex-col p-3 rounded-lg gap-2">
|
||||
<Typography variant="section-title">
|
||||
{l10n.getString('widget-imu_visualizer')}
|
||||
</Typography>
|
||||
|
||||
<div className="flex justify-between">
|
||||
<Typography color="secondary">
|
||||
{l10n.getString('widget-imu_visualizer-rotation_raw')}
|
||||
</Typography>
|
||||
<Typography>
|
||||
{formatVector3(useRawRotationEulerDegrees(), 2)}
|
||||
</Typography>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between">
|
||||
<Typography color="secondary">
|
||||
{l10n.getString('widget-imu_visualizer-rotation_preview')}
|
||||
</Typography>
|
||||
<Typography>
|
||||
{formatVector3(useIdentAdjRotationEulerDegrees(), 2)}
|
||||
</Typography>
|
||||
</div>
|
||||
|
||||
{!enabled && (
|
||||
<Button variant="secondary" onClick={() => setEnabled(true)}>
|
||||
{l10n.getString('widget-imu_visualizer-rotation_preview')}
|
||||
</Button>
|
||||
)}
|
||||
{enabled && <SceneRenderer {...quat}></SceneRenderer>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
} from '../../hooks/pubSub';
|
||||
import { CheckBox } from '../commons/Checkbox';
|
||||
import { useLocalization } from '@fluent/react';
|
||||
import { Typography } from '../commons/Typography';
|
||||
|
||||
export function OverlayWidget() {
|
||||
const { l10n } = useLocalization();
|
||||
@@ -64,17 +65,22 @@ export function OverlayWidget() {
|
||||
|
||||
return (
|
||||
<form className="bg-background-60 flex flex-col w-full rounded-md px-2">
|
||||
<div className="mt-2 px-1">
|
||||
<Typography color="secondary">
|
||||
{l10n.getString('widget-overlay')}
|
||||
</Typography>
|
||||
</div>
|
||||
<CheckBox
|
||||
control={control}
|
||||
name="isVisible"
|
||||
variant="toggle"
|
||||
label={l10n.getString('overlay-is_visible_label')}
|
||||
label={l10n.getString('widget-overlay-is_visible_label')}
|
||||
></CheckBox>
|
||||
<CheckBox
|
||||
control={control}
|
||||
name="isMirrored"
|
||||
variant="toggle"
|
||||
label={l10n.getString('overlay-is_mirrored_label')}
|
||||
label={l10n.getString('widget-overlay-is_mirrored_label')}
|
||||
></CheckBox>
|
||||
</form>
|
||||
);
|
||||
|
||||
@@ -10,16 +10,14 @@ import {
|
||||
} from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import {
|
||||
DataFeedConfigT,
|
||||
DataFeedMessage,
|
||||
DataFeedUpdateT,
|
||||
DeviceDataMaskT,
|
||||
DeviceDataT,
|
||||
StartDataFeedT,
|
||||
TrackerDataMaskT,
|
||||
TrackerDataT,
|
||||
} from 'solarxr-protocol';
|
||||
import { useConfig } from './config';
|
||||
import { useDataFeedConfig } from './datafeed-config';
|
||||
import { useWebsocketAPI } from './websocket-api';
|
||||
|
||||
export interface FlatDeviceTracker {
|
||||
@@ -52,6 +50,7 @@ export function useProvideAppContext(): AppContext {
|
||||
const { sendDataFeedPacket, useDataFeedPacket, isConnected } =
|
||||
useWebsocketAPI();
|
||||
const { config } = useConfig();
|
||||
const { dataFeedConfig } = useDataFeedConfig();
|
||||
const navigate = useNavigate();
|
||||
const [state, dispatch] = useReducer<Reducer<AppState, AppStateAction>>(
|
||||
reducer,
|
||||
@@ -62,24 +61,8 @@ export function useProvideAppContext(): AppContext {
|
||||
|
||||
useEffect(() => {
|
||||
if (isConnected) {
|
||||
const trackerData = new TrackerDataMaskT();
|
||||
trackerData.position = true;
|
||||
trackerData.rotation = true;
|
||||
trackerData.info = true;
|
||||
trackerData.status = true;
|
||||
trackerData.temp = true;
|
||||
|
||||
const dataMask = new DeviceDataMaskT();
|
||||
dataMask.deviceData = true;
|
||||
dataMask.trackerData = trackerData;
|
||||
|
||||
const config = new DataFeedConfigT();
|
||||
config.dataMask = dataMask;
|
||||
config.minimumTimeSinceLast = 100;
|
||||
config.syntheticTrackersMask = trackerData;
|
||||
|
||||
const startDataFeed = new StartDataFeedT();
|
||||
startDataFeed.dataFeeds = [config];
|
||||
startDataFeed.dataFeeds = [dataFeedConfig];
|
||||
sendDataFeedPacket(DataFeedMessage.StartDataFeed, startDataFeed);
|
||||
}
|
||||
}, [isConnected]);
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
} from '@tauri-apps/api/fs';
|
||||
|
||||
import { createContext, useContext, useRef, useState } from 'react';
|
||||
import { DeveloperModeWidgetForm } from '../components/widgets/DeveloperModeWidget';
|
||||
|
||||
export interface WindowConfig {
|
||||
width: number;
|
||||
@@ -20,6 +21,7 @@ export interface Config {
|
||||
lang: string;
|
||||
doneOnboarding: boolean;
|
||||
watchNewDevices: boolean;
|
||||
devSettings: DeveloperModeWidgetForm;
|
||||
}
|
||||
|
||||
export interface ConfigContext {
|
||||
|
||||
37
gui/src/hooks/datafeed-config.ts
Normal file
37
gui/src/hooks/datafeed-config.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import {
|
||||
DataFeedConfigT,
|
||||
DeviceDataMaskT,
|
||||
TrackerDataMaskT,
|
||||
} from 'solarxr-protocol';
|
||||
import { useConfig } from './config';
|
||||
|
||||
export function useDataFeedConfig() {
|
||||
const { config } = useConfig();
|
||||
|
||||
const fastDataFeed = config?.debug && config?.devSettings?.fastDataFeed;
|
||||
const feedMaxTps = fastDataFeed ? 40 : 10;
|
||||
|
||||
const trackerData = new TrackerDataMaskT();
|
||||
trackerData.position = true;
|
||||
trackerData.rotation = true;
|
||||
trackerData.info = true;
|
||||
trackerData.status = true;
|
||||
trackerData.temp = true;
|
||||
trackerData.linearAcceleration = true;
|
||||
trackerData.rotationReferenceAdjusted = true;
|
||||
trackerData.rotationIdentityAdjusted = true;
|
||||
|
||||
const dataMask = new DeviceDataMaskT();
|
||||
dataMask.deviceData = true;
|
||||
dataMask.trackerData = trackerData;
|
||||
|
||||
const dataFeedConfig = new DataFeedConfigT();
|
||||
dataFeedConfig.dataMask = dataMask;
|
||||
dataFeedConfig.minimumTimeSinceLast = 1000 / feedMaxTps;
|
||||
dataFeedConfig.syntheticTrackersMask = trackerData;
|
||||
|
||||
return {
|
||||
dataFeedConfig,
|
||||
feedMaxTps,
|
||||
};
|
||||
}
|
||||
@@ -1,9 +1,14 @@
|
||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { BodyPart, TrackerDataT, TrackerStatus } from 'solarxr-protocol';
|
||||
import { RAD_TO_DEG } from '../maths/angle';
|
||||
import { QuaternionFromQuatT } from '../maths/quaternion';
|
||||
import {
|
||||
QuaternionFromQuatT,
|
||||
QuaternionToEulerDegrees,
|
||||
} from '../maths/quaternion';
|
||||
import { useAppContext } from './app';
|
||||
import { useLocalization } from '@fluent/react';
|
||||
import { useDataFeedConfig } from './datafeed-config';
|
||||
import { Quaternion, Vector3 } from 'three';
|
||||
import { Vector3FromVec3fT } from '../maths/vector3';
|
||||
|
||||
export function useTrackers() {
|
||||
const { trackers } = useAppContext();
|
||||
@@ -39,13 +44,7 @@ export function useTrackers() {
|
||||
|
||||
export function useTracker(tracker: TrackerDataT) {
|
||||
const { l10n } = useLocalization();
|
||||
const computeRot = (rot: { x: number; y: number; z: number; w: number }) =>
|
||||
QuaternionFromQuatT({
|
||||
x: rot.x || 0,
|
||||
y: rot.y || 0,
|
||||
z: rot.z || 0,
|
||||
w: rot.w || 1,
|
||||
}).toEuler();
|
||||
const { feedMaxTps } = useDataFeedConfig();
|
||||
|
||||
return {
|
||||
useName: () =>
|
||||
@@ -57,54 +56,61 @@ export function useTracker(tracker: TrackerDataT) {
|
||||
);
|
||||
return tracker.info?.displayName || 'NONE';
|
||||
}, [tracker.info]),
|
||||
useRotation: () =>
|
||||
useRawRotationEulerDegrees: () =>
|
||||
useMemo(
|
||||
() => computeRot(tracker.rotation || { x: 0, y: 0, z: 0, w: 1 }),
|
||||
() => QuaternionToEulerDegrees(tracker?.rotation),
|
||||
[tracker.rotation]
|
||||
),
|
||||
useRotationDegrees: () =>
|
||||
useMemo(() => {
|
||||
const { yaw, pitch, roll } = computeRot(
|
||||
tracker.rotation || { x: 0, y: 0, z: 0, w: 1 }
|
||||
);
|
||||
return {
|
||||
yaw: yaw * RAD_TO_DEG,
|
||||
pitch: pitch * RAD_TO_DEG,
|
||||
roll: roll * RAD_TO_DEG,
|
||||
};
|
||||
}, [tracker.rotation]),
|
||||
useRefAdjRotationEulerDegrees: () =>
|
||||
useMemo(
|
||||
() => QuaternionToEulerDegrees(tracker?.rotationReferenceAdjusted),
|
||||
[tracker.rotationReferenceAdjusted]
|
||||
),
|
||||
useIdentAdjRotationEulerDegrees: () =>
|
||||
useMemo(
|
||||
() => QuaternionToEulerDegrees(tracker?.rotationIdentityAdjusted),
|
||||
[tracker.rotationIdentityAdjusted]
|
||||
),
|
||||
useVelocity: () => {
|
||||
const previousRot = useRef<{
|
||||
x: number;
|
||||
y: number;
|
||||
z: number;
|
||||
w: number;
|
||||
}>(tracker.rotation || { x: 0, y: 0, z: 0, w: 1 });
|
||||
const previousRot = useRef<Quaternion>(
|
||||
QuaternionFromQuatT(tracker.rotation)
|
||||
);
|
||||
const previousAcc = useRef<Vector3>(
|
||||
Vector3FromVec3fT(tracker.linearAcceleration)
|
||||
);
|
||||
const [velocity, setVelocity] = useState<number>(0);
|
||||
const [rots, setRotation] = useState<number[]>([]);
|
||||
const [deltas] = useState<number[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
if (tracker.rotation) {
|
||||
const rot = QuaternionFromQuatT(tracker.rotation).mul(
|
||||
QuaternionFromQuatT(previousRot.current).inverse()
|
||||
const rot = QuaternionFromQuatT(tracker.rotation).multiply(
|
||||
previousRot.current.clone().invert()
|
||||
);
|
||||
const dif = Math.min(1, (rot.x ** 2 + rot.y ** 2 + rot.z ** 2) * 2.5);
|
||||
// Use sum of rotation of last 3 frames (0.3sec) for smoother movement and better detection of slow movement.
|
||||
if (rots.length === 3) {
|
||||
rots.shift();
|
||||
const acc = Vector3FromVec3fT(tracker.linearAcceleration).sub(
|
||||
previousAcc.current
|
||||
);
|
||||
const dif = Math.min(
|
||||
1,
|
||||
(rot.x ** 2 + rot.y ** 2 + rot.z ** 2) * 2.5 +
|
||||
(acc.x ** 2 + acc.y ** 2 + acc.z ** 2) / 1000
|
||||
);
|
||||
// Use sum of the rotation and acceleration delta vector lengths over 0.3sec
|
||||
// for smoother movement and better detection of slow movement.
|
||||
if (deltas.length >= 0.3 * feedMaxTps) {
|
||||
deltas.shift();
|
||||
}
|
||||
rots.push(dif);
|
||||
setRotation(rots);
|
||||
deltas.push(dif);
|
||||
setVelocity(
|
||||
Math.min(
|
||||
1,
|
||||
Math.max(
|
||||
0,
|
||||
rots.reduce((a, b) => a + b)
|
||||
deltas.reduce((a, b) => a + b)
|
||||
)
|
||||
)
|
||||
);
|
||||
previousRot.current = tracker.rotation;
|
||||
previousRot.current = QuaternionFromQuatT(tracker.rotation);
|
||||
previousAcc.current = Vector3FromVec3fT(tracker.linearAcceleration);
|
||||
}
|
||||
}, [tracker.rotation]);
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ import { useInterval } from './timeout';
|
||||
export interface WebSocketApi {
|
||||
isConnected: boolean;
|
||||
isFirstConnection: boolean;
|
||||
reconnect: () => void;
|
||||
useRPCPacket: <T>(type: RpcMessage, callback: (packet: T) => void) => void;
|
||||
useDataFeedPacket: <T>(
|
||||
type: DataFeedMessage,
|
||||
@@ -51,9 +52,8 @@ export function useProvideWebsocketApi(): WebSocketApi {
|
||||
|
||||
useInterval(() => {
|
||||
if (webSocketRef.current && !isConnected) {
|
||||
disconnect();
|
||||
connect();
|
||||
console.log('Try reconnecting');
|
||||
console.log('Attempting to reconnect');
|
||||
reconnect();
|
||||
}
|
||||
}, 3000);
|
||||
|
||||
@@ -178,6 +178,11 @@ export function useProvideWebsocketApi(): WebSocketApi {
|
||||
}
|
||||
};
|
||||
|
||||
const reconnect = () => {
|
||||
disconnect();
|
||||
connect();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
connect();
|
||||
return () => {
|
||||
@@ -188,6 +193,7 @@ export function useProvideWebsocketApi(): WebSocketApi {
|
||||
return {
|
||||
isConnected,
|
||||
isFirstConnection,
|
||||
reconnect,
|
||||
useDataFeedPacket: <T>(
|
||||
type: DataFeedMessage,
|
||||
callback: (packet: T) => void
|
||||
|
||||
@@ -1,23 +1,15 @@
|
||||
import Quaternion from 'quaternion';
|
||||
import { Euler, Quaternion } from 'three';
|
||||
import { QuatT } from 'solarxr-protocol';
|
||||
import { DEG_TO_RAD } from './angle';
|
||||
|
||||
export function QuaternionFromQuatT(q: {
|
||||
x: number;
|
||||
y: number;
|
||||
z: number;
|
||||
w: number;
|
||||
}) {
|
||||
return new Quaternion(q.w, q.x, q.y, q.z);
|
||||
export type QuatObject = { x: number; y: number; z: number; w: number };
|
||||
|
||||
export function QuaternionFromQuatT(q?: QuatObject | null) {
|
||||
return q ? new Quaternion(q.x, q.y, q.z, q.w) : new Quaternion();
|
||||
}
|
||||
|
||||
export function QuaternionToQuatT(q: {
|
||||
x: number;
|
||||
y: number;
|
||||
z: number;
|
||||
w: number;
|
||||
}) {
|
||||
export function QuaternionToQuatT(q: QuatObject) {
|
||||
const quat = new QuatT();
|
||||
|
||||
quat.x = q.x;
|
||||
quat.y = q.y;
|
||||
quat.z = q.z;
|
||||
@@ -25,39 +17,37 @@ export function QuaternionToQuatT(q: {
|
||||
return quat;
|
||||
}
|
||||
|
||||
export function FixEuler(yaw: number) {
|
||||
if (yaw > 180) {
|
||||
yaw *= -1;
|
||||
yaw += 180;
|
||||
}
|
||||
return Math.round(yaw);
|
||||
export function MountingOrientationDegreesToQuatT(
|
||||
mountingOrientationDegrees: number
|
||||
) {
|
||||
return QuaternionToQuatT(
|
||||
new Quaternion().setFromEuler(
|
||||
new Euler(0, +mountingOrientationDegrees * DEG_TO_RAD, 0)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
export function GetYaw(q: { x: number; y: number; z: number; w: number }) {
|
||||
const squareX = q.x * q.x,
|
||||
squareY = q.y * q.y,
|
||||
squareZ = q.z * q.z,
|
||||
squareW = q.w * q.w;
|
||||
const RAD_TO_DEG = 180 / Math.PI;
|
||||
|
||||
// This value is 1 if the quaternion is a unit (normalized) quaternion,
|
||||
// otherwise this will be a factor to compensate for the singularity pole checks
|
||||
const correctionFactor = squareX + squareY + squareZ + squareW;
|
||||
// This value is to test for a singularity, it will be 0.5 at the north singularity,
|
||||
// -0.5 at the south singularity, and anything else at any other value
|
||||
const singularityTest = q.x * q.y + q.z * q.w;
|
||||
export function getYawInDegrees(q?: QuatObject) {
|
||||
if (!q) return 0;
|
||||
|
||||
// Singularity cutoff points are 0.499 (86.3 degrees) to compensate for error
|
||||
if (singularityTest > 0.499 * correctionFactor) {
|
||||
// Handle the singularity at the attitude of 90 degrees (north pole)
|
||||
return 2 * Math.atan2(q.x, q.w);
|
||||
} else if (singularityTest < -0.499 * correctionFactor) {
|
||||
// Handle the singularity at the attitude of -90 degrees (south pole)
|
||||
return -2 * Math.atan2(q.x, q.w);
|
||||
}
|
||||
// X Y Z Result
|
||||
// back: 0 0 0 0
|
||||
// front: -180 0.. -180 180
|
||||
// left: 0 90 0 90
|
||||
// right: 0 -90 0 -90
|
||||
|
||||
// Otherwise calculate the yaw normally
|
||||
return Math.atan2(
|
||||
2 * q.y * q.w - 2 * q.x * q.z,
|
||||
squareX - squareY - squareZ + squareW
|
||||
);
|
||||
const angles = new Euler().setFromQuaternion(QuaternionFromQuatT(q));
|
||||
return angles.y | 0
|
||||
? Math.round(angles.y * RAD_TO_DEG)
|
||||
: Math.round(-angles.z * RAD_TO_DEG);
|
||||
}
|
||||
|
||||
export function QuaternionToEulerDegrees(q?: QuatObject | null) {
|
||||
const angles = { x: 0, y: 0, z: 0 };
|
||||
if (!q) return angles;
|
||||
|
||||
const a = new Euler().setFromQuaternion(new Quaternion(q.x, q.y, q.z, q.w));
|
||||
return { x: a.x * RAD_TO_DEG, y: a.y * RAD_TO_DEG, z: a.z * RAD_TO_DEG };
|
||||
}
|
||||
|
||||
16
gui/src/maths/vector3.ts
Normal file
16
gui/src/maths/vector3.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { Vec3fT } from 'solarxr-protocol';
|
||||
import { Vector3 } from 'three';
|
||||
|
||||
export type Vector3Object = { x: number; y: number; z: number; };
|
||||
|
||||
export function Vector3FromVec3fT(vec?: Vector3Object | null) {
|
||||
return vec ? new Vector3(vec.x, vec.y, vec.z) : new Vector3();
|
||||
}
|
||||
|
||||
export function Vector3ToVec3fT(q: Vector3Object) {
|
||||
const vec = new Vec3fT();
|
||||
vec.x = q.x;
|
||||
vec.y = q.y;
|
||||
vec.z = q.z;
|
||||
return vec;
|
||||
}
|
||||
5216
package-lock.json
generated
5216
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user