new layout

This commit is contained in:
lucas lelievre
2022-03-31 18:10:10 +02:00
parent 9482e60dfa
commit 56402526f7
43 changed files with 38560 additions and 69 deletions

3
.gitignore vendored
View File

@@ -21,3 +21,6 @@
npm-debug.log*
yarn-debug.log*
yarn-error.log*
*.log

33990
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -3,6 +3,7 @@
"version": "0.1.0",
"private": true,
"dependencies": {
"@tauri-apps/api": "^1.0.0-rc.3",
"@testing-library/jest-dom": "^5.16.3",
"@testing-library/react": "^12.1.4",
"@testing-library/user-event": "^13.5.0",
@@ -10,17 +11,24 @@
"@types/node": "^16.11.26",
"@types/react": "^17.0.43",
"@types/react-dom": "^17.0.14",
"add": "^2.0.6",
"classnames": "^2.3.1",
"dreampact": "^0.1.10",
"flatbuffers": "^2.0.6",
"react": "^18.0.0",
"react-dom": "^18.0.0",
"react-router-dom": "^6.2.2",
"react-scripts": "5.0.0",
"slimevr-protocol": "file:../SlimeVR-Server/slimevr_protocol",
"typescript": "^4.6.3",
"web-vitals": "^2.1.4"
},
"scripts": {
"start": "react-scripts start",
"start": "cross-env BROWSER=none react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
"eject": "react-scripts eject",
"tauri": "tauri"
},
"eslintConfig": {
"extends": [
@@ -39,5 +47,12 @@
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"@tauri-apps/cli": "^1.0.0-rc.8",
"autoprefixer": "^10.4.4",
"cross-env": "^7.0.3",
"postcss": "^8.4.12",
"tailwindcss": "^3.0.23"
}
}

6
postcss.config.js Normal file
View File

@@ -0,0 +1,6 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

4
src-tauri/.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
# Generated by Cargo
# will have compiled files and executables
/target/
WixTools

4056
src-tauri/Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

28
src-tauri/Cargo.toml Normal file
View File

@@ -0,0 +1,28 @@
[package]
name = "app"
version = "0.1.0"
description = "A Tauri App"
authors = ["you"]
license = ""
repository = ""
default-run = "app"
edition = "2021"
rust-version = "1.57"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[build-dependencies]
tauri-build = { version = "1.0.0-rc.5", features = [] }
[dependencies]
serde_json = "1.0"
serde = { version = "1.0", features = ["derive"] }
tauri = { version = "1.0.0-rc.5", features = ["api-all"] }
[features]
# by default Tauri runs in production mode
# when `tauri dev` runs it is executed with `cargo run --no-default-features` if `devPath` is an URL
default = [ "custom-protocol" ]
# this feature is used used for production builds where `devPath` points to the filesystem
# DO NOT remove this
custom-protocol = [ "tauri/custom-protocol" ]

3
src-tauri/build.rs Normal file
View File

@@ -0,0 +1,3 @@
fn main() {
tauri_build::build()
}

BIN
src-tauri/icons/128x128.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

BIN
src-tauri/icons/32x32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 974 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 903 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
src-tauri/icons/icon.icns Normal file

Binary file not shown.

BIN
src-tauri/icons/icon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

BIN
src-tauri/icons/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

10
src-tauri/src/main.rs Normal file
View File

@@ -0,0 +1,10 @@
#![cfg_attr(
all(not(debug_assertions), target_os = "windows"),
windows_subsystem = "windows"
)]
fn main() {
tauri::Builder::default()
.run(tauri::generate_context!())
.expect("error while running tauri application");
}

67
src-tauri/tauri.conf.json Normal file
View File

@@ -0,0 +1,67 @@
{
"package": {
"productName": "slimevr-ui",
"version": "0.1.0"
},
"build": {
"distDir": "../build",
"devPath": "http://localhost:3000",
"beforeDevCommand": "npm run start",
"beforeBuildCommand": "npm run build"
},
"tauri": {
"bundle": {
"active": true,
"targets": "all",
"identifier": "com.tauri.dev",
"icon": [
"icons/32x32.png",
"icons/128x128.png",
"icons/128x128@2x.png",
"icons/icon.icns",
"icons/icon.ico"
],
"resources": [],
"externalBin": [],
"copyright": "",
"category": "DeveloperTool",
"shortDescription": "",
"longDescription": "",
"deb": {
"depends": [],
"useBootstrapper": false
},
"macOS": {
"frameworks": [],
"useBootstrapper": false,
"exceptionDomain": "",
"signingIdentity": null,
"providerShortName": null,
"entitlements": null
},
"windows": {
"certificateThumbprint": null,
"digestAlgorithm": "sha256",
"timestampUrl": ""
}
},
"updater": {
"active": false
},
"allowlist": {
"all": true
},
"windows": [
{
"title": "Slimevr UI",
"width": 1033,
"height": 639,
"resizable": true,
"fullscreen": false
}
],
"security": {
"csp": null
}
}
}

View File

@@ -1,38 +0,0 @@
.App {
text-align: center;
}
.App-logo {
height: 40vmin;
pointer-events: none;
}
@media (prefers-reduced-motion: no-preference) {
.App-logo {
animation: App-logo-spin infinite 20s linear;
}
}
.App-header {
background-color: #282c34;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(10px + 2vmin);
color: white;
}
.App-link {
color: #61dafb;
}
@keyframes App-logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}

View File

@@ -1,25 +1,34 @@
import React from 'react';
import logo from './logo.svg';
import './App.css';
import { Navbar } from './components/Navbar';
import { useProvideWebsocketApi, WebSocketApiContext } from './hooks/websocket-api';
import {
BrowserRouter as Router,
Routes,
Route,
} from "react-router-dom";
import { Overview } from './components/Overview';
import { Manage } from './components/Manage';
function App() {
const websocketAPI = useProvideWebsocketApi();
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.tsx</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header>
</div>
<WebSocketApiContext.Provider value={websocketAPI}>
<Router>
<div className='bg-primary h-full w-full overflow-hidden'>
<div className='flex-col h-full'>
<Navbar></Navbar>
<div className='flex-grow h-full'>
<Routes>
<Route path="/" element={<Overview/>}/>
<Route path="manage" element={<Manage/>}/>
</Routes>
</div>
</div>
</div>
</Router>
</WebSocketApiContext.Provider>
);
}

View File

@@ -0,0 +1,14 @@
import { ReactChild } from "react";
export function BigButton({ text, icon }: { text: string, icon: ReactChild }) {
return (
<div className="flex flex-col rounded-md hover:bg-primary-4 bg-primary-3 py-10 gap-8 cursor-pointer">
<div className="flex justify-around">{icon}</div>
<div className="flex justify-around text-2xl text-white">{text}</div>
</div>
)
}

View File

@@ -0,0 +1,5 @@
export function Manage() {
return <h1>Manage</h1>
}

45
src/components/Navbar.tsx Normal file
View File

@@ -0,0 +1,45 @@
import classnames from 'classnames';
import {
useMatch,
NavLink,
} from "react-router-dom";
import { CubeIcon } from './icon/CubeIcon';
import { SlimeVRIcon } from './icon/SimevrIcon';
export function NavButton({ to, children, match }: { to: string, children: React.ReactChild, match?: string }) {
const doesMatch = useMatch({
path: match || to,
});
return (
<NavLink to={to} className={classnames("flex flex-grow flex-row gap-3 py-3 px-8 rounded-t-md group ", { 'bg-primary-2': doesMatch, 'hover:bg-primary-3': !doesMatch })}>
<div className="flex align-middle justify-center justify-items-center flex-col">
<CubeIcon className={ classnames("fill-primary-3 group-hover:fill-white", { 'fill-white': doesMatch })}></CubeIcon>
</div>
<div className="flex text-white text-md">{children}</div>
</NavLink >
)
}
export function Navbar() {
return (
<div className='flex bg-primary-1 gap-2'>
<div className="flex px-8 py-2 justify-around">
<div className="flex flex-row gap-3">
<div className="flex justify-around flex-col">
<SlimeVRIcon></SlimeVRIcon>
</div>
<div className="flex text-white text-xl justify-around flex-col">SlimeVR</div>
</div>
</div>
<div className="flex px-5 gap-5 pt-1">
<NavButton to="/">Overview</NavButton>
<NavButton to="manage">Manage Trackers</NavButton>
</div>
</div>
)
}

View File

@@ -0,0 +1,37 @@
import { useState } from "react";
import { OutboundUnion } from "slimevr-protocol/dist/server";
import { DeviceStatusT } from "slimevr-protocol/dist/slimevr-protocol/server/device-status";
import { TrackersListT } from "slimevr-protocol/dist/slimevr-protocol/server/trackers-list";
import { useWebsocketAPI } from "../hooks/websocket-api";
import { BigButton } from "./BigButton";
import { QuickResetIcon, ResetIcon } from "./icon/ResetIcon";
import { TrackerCard } from "./TrackerCard";
export function Overview() {
const { usePacket } = useWebsocketAPI();
const [list, setTrackersList] = useState<DeviceStatusT[]>([]);
usePacket(OutboundUnion.TrackersList, (packet: TrackersListT) => {
setTrackersList(packet.trackers)
})
return (
<div className="flex bg-primary-1 h-full">
<div className="flex flex-grow gap-10 flex-col bg-primary-2 rounded-tr-3xl">
<div className="flex text-white text-2xl px-8 pt-8">
Tracker Overview
</div>
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-5 sm:grid-cols-1 overflow-y-auto px-8">
{list.map((trackerStatus, index) => <TrackerCard key={index} status={trackerStatus}/>)}
</div>
</div>
<div className="flex flex-col px-8 xs:w-1/4 sm:w-1/4 gap-8">
<BigButton text="Quick reset" icon={<QuickResetIcon/>}></BigButton>
<BigButton text="Reset Position" icon={<ResetIcon />}></BigButton>
</div>
</div>
)
}

View File

@@ -0,0 +1,35 @@
import { DeviceStatusT } from "slimevr-protocol/dist/server";
import { BatteryIcon } from "./icon/BatteryIcon";
import { CircleIcon } from "./icon/CircleIcon";
import { GearIcon } from "./icon/GearIcon";
import { WifiIcon } from "./icon/WifiIcon";
export function TrackerCard({ status }: { status: DeviceStatusT }) {
return (
<div className="flex rounded-l-md rounded-r-xl bg-green-400" >
<div className="flex bg-primary-3 rounded-r-md py-3 ml-1 px-5 w-full">
<div className="flex flex-grow flex-col">
<div className="flex text-white text-ellipsis">{status.name}</div>
<div className="flex flex-row gap-4">
<div className="flex gap-2">
<div className="flex flex-col justify-around">
<WifiIcon />
</div>
<div className="flex text-gray-400 text-sm">{status.ping} ms</div>
</div>
<div className="flex gap-2">
<div className="flex flex-col justify-around">
<BatteryIcon />
</div>
<div className="flex text-gray-400 text-sm">{((status.battery / 256) * 100).toFixed(0)} %</div>
</div>
</div>
</div>
<div className="flex flex-col justify-around">
<GearIcon />
</div>
</div>
</div>
)
}

View File

@@ -0,0 +1,10 @@
export function BatteryIcon() {
return (
<svg width="18" height="9" viewBox="0 0 18 9" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.39 0L1.13298 0C0.832634 0.00131561 0.54496 0.121213 0.332579 0.333594C0.120197 0.545976 0.000300407 0.83365 -0.00101566 1.134V7.383C-0.00128555 7.68458 0.117848 7.97399 0.330344 8.18799C0.54284 8.40198 0.831413 8.52315 1.13298 8.525H10.39V0Z" fill="#50E897"/>
<path d="M14.211 8.525C14.5113 8.52368 14.799 8.40379 15.0114 8.19141C15.2238 7.97902 15.3437 7.69135 15.345 7.391V5.968H17.05V2.558H15.345V1.134C15.3437 0.83365 15.2238 0.545976 15.0114 0.333594C14.799 0.121213 14.5113 0.00131561 14.211 0L10.39 0V8.525H14.211Z" fill="#4C3755"/>
</svg>
)
}

View File

@@ -0,0 +1,8 @@
export function CircleIcon(props: any) {
return (
<svg {...props} width="10" height="10" viewBox="0 0 6 6" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3 6C4.65685 6 6 4.65685 6 3C6 1.34315 4.65685 0 3 0C1.34315 0 0 1.34315 0 3C0 4.65685 1.34315 6 3 6Z" fill="#50E897"/>
</svg>
)
}

View File

@@ -0,0 +1,10 @@
export function CubeIcon(props: any) {
return (
<svg {...props} width="20" height="20" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
<path d="M14.835 3.752L8.377 0.852002C8.31295 0.823464 8.24362 0.808716 8.1735 0.808716C8.10338 0.808716 8.03405 0.823464 7.97 0.852002L1.509 3.752C1.45917 3.77586 1.4169 3.81302 1.38684 3.85938C1.35678 3.90573 1.34011 3.95949 1.33867 4.01472C1.33722 4.06995 1.35105 4.12451 1.37863 4.17237C1.40622 4.22024 1.44649 4.25956 1.495 4.286L7.938 7.679C8.00997 7.71812 8.09058 7.73862 8.1725 7.73862C8.25442 7.73862 8.33503 7.71812 8.407 7.679L14.85 4.286C14.8985 4.25947 14.9387 4.22008 14.9662 4.17215C14.9937 4.12423 15.0074 4.06964 15.0059 4.01441C15.0043 3.95918 14.9875 3.90545 14.9574 3.85915C14.9272 3.81284 14.8849 3.77577 14.835 3.752Z" />
<path d="M9.04301 15.774L15.537 12.338C15.5774 12.3112 15.6104 12.2748 15.6331 12.232C15.6558 12.1892 15.6674 12.1414 15.667 12.093V5.429C15.6697 5.38221 15.6601 5.33552 15.6392 5.29359C15.6182 5.25166 15.5867 5.21596 15.5476 5.19006C15.5085 5.16415 15.4634 5.14894 15.4166 5.14595C15.3698 5.14296 15.3231 5.15228 15.281 5.173L8.78701 8.519C8.74073 8.54365 8.70214 8.58059 8.6755 8.62576C8.64886 8.67092 8.63519 8.72257 8.63601 8.775V15.529C8.63442 15.5774 8.64577 15.6253 8.6689 15.6679C8.69203 15.7104 8.7261 15.746 8.76758 15.771C8.80907 15.796 8.85647 15.8094 8.90489 15.8099C8.95331 15.8105 9.00099 15.7981 9.04301 15.774Z"/>
<path d="M0.67101 5.428V12.088C0.670579 12.1364 0.682243 12.1842 0.704943 12.227C0.727644 12.2698 0.760664 12.3062 0.80101 12.333L7.29601 15.769C7.33755 15.7933 7.38493 15.8059 7.43307 15.8055C7.48122 15.805 7.52834 15.7915 7.5694 15.7663C7.61046 15.7412 7.64392 15.7053 7.66622 15.6627C7.68851 15.62 7.69881 15.5721 7.69601 15.524V8.771C7.69683 8.71856 7.68317 8.66692 7.65652 8.62175C7.62988 8.57659 7.59129 8.53965 7.54501 8.515L1.05501 5.169C1.01276 5.14923 0.966102 5.14076 0.919596 5.14439C0.87309 5.14802 0.828319 5.16364 0.789646 5.18973C0.750973 5.21581 0.719718 5.25147 0.698927 5.29323C0.678135 5.33498 0.668517 5.38142 0.67101 5.428Z"/>
</svg>
)
}

View File

@@ -0,0 +1,9 @@
export function GearIcon() {
return (
<svg width="20" height="20" viewBox="0 0 14 13" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.00099 11.9C7.31948 11.9003 7.63041 11.997 7.89283 12.1775C8.15524 12.3579 8.35682 12.6137 8.47099 12.911C9.11419 12.757 9.72885 12.5018 10.292 12.155C10.2026 11.9531 10.1559 11.7348 10.155 11.514C10.1546 11.2516 10.2198 10.9933 10.3448 10.7626C10.4697 10.5319 10.6505 10.3362 10.8705 10.1932C11.0905 10.0503 11.3427 9.96464 11.6043 9.94417C11.8659 9.92369 12.1284 9.96902 12.368 10.076C12.7147 9.51424 12.9699 8.90091 13.124 8.259C12.8268 8.1447 12.5711 7.9431 12.3906 7.68071C12.2102 7.41833 12.1134 7.10745 12.113 6.789C12.1134 6.47055 12.2102 6.15968 12.3906 5.89729C12.5711 5.6349 12.8268 5.4333 13.124 5.319C12.97 4.6758 12.7148 4.06115 12.368 3.498C12.1284 3.60499 11.8659 3.65031 11.6043 3.62983C11.3427 3.60936 11.0905 3.52374 10.8705 3.38078C10.6505 3.23782 10.4697 3.04207 10.3448 2.81138C10.2198 2.58068 10.1546 2.32237 10.155 2.06C10.1543 1.83899 10.201 1.62041 10.292 1.419C9.72669 1.07498 9.11135 0.820948 8.46799 0.666C8.35405 0.963474 8.15253 1.21938 7.89007 1.39989C7.6276 1.5804 7.31654 1.67703 6.99799 1.677C6.67886 1.67742 6.36711 1.581 6.10393 1.40049C5.84075 1.21998 5.63853 0.963872 5.52399 0.666C4.882 0.821335 4.26872 1.07752 3.70699 1.425C3.83599 1.71551 3.87394 2.03828 3.81586 2.35079C3.75778 2.66329 3.60638 2.95087 3.38162 3.17563C3.15686 3.40039 2.86928 3.55178 2.55678 3.60987C2.24427 3.66795 1.9215 3.62999 1.63099 3.501C1.28471 4.06442 1.02957 4.67899 0.874993 5.322C1.17184 5.43574 1.42719 5.63685 1.60733 5.89878C1.78746 6.1607 1.8839 6.47111 1.8839 6.789C1.8839 7.10689 1.78746 7.4173 1.60733 7.67922C1.42719 7.94115 1.17184 8.14226 0.874993 8.256C1.02899 8.8992 1.28417 9.51385 1.63099 10.077C1.92149 9.94858 2.24405 9.91106 2.55627 9.96935C2.86849 10.0276 3.15577 10.179 3.38036 10.4036C3.60495 10.6282 3.75634 10.9155 3.81464 11.2277C3.87294 11.5399 3.83541 11.8625 3.70699 12.153C4.26924 12.4985 4.88243 12.7533 5.52399 12.908C5.6399 12.6107 5.84296 12.3554 6.10653 12.1755C6.37011 11.9956 6.68189 11.8996 7.00099 11.9ZM4.45399 6.817C4.4536 6.31238 4.60287 5.81897 4.88294 5.3992C5.16301 4.97942 5.56128 4.65214 6.02738 4.45876C6.49348 4.26537 7.00646 4.21457 7.50144 4.31277C7.99642 4.41098 8.45115 4.65378 8.80811 5.01046C9.16508 5.36715 9.40824 5.82169 9.50683 6.31659C9.60542 6.81149 9.55502 7.32451 9.362 7.79076C9.16898 8.25701 8.84201 8.65554 8.42246 8.93594C8.00291 9.21634 7.50962 9.366 7.00499 9.366C6.67012 9.36613 6.3385 9.3003 6.02907 9.17228C5.71964 9.04425 5.43846 8.85653 5.20158 8.61983C4.96469 8.38313 4.77675 8.1021 4.64848 7.79277C4.52021 7.48344 4.45412 7.15187 4.45399 6.817Z" fill="#AF91BE"/>
</svg>
)
}

View File

@@ -0,0 +1,20 @@
export function ResetIcon() {
return (
<svg width="33" height="29" viewBox="0 0 33 29" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M2.39801 3.061V11.524H10.861" stroke="white" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round"/>
<path d="M5.93801 18.576C6.85461 21.1713 8.58991 23.3983 10.8824 24.9215C13.1749 26.4447 15.9003 27.1816 18.648 27.021C21.3957 26.8604 24.0168 25.8111 26.1162 24.0312C28.2157 22.2514 29.6797 19.8373 30.2878 17.153C30.8958 14.4686 30.6149 11.6593 29.4874 9.14845C28.3599 6.63762 26.4468 4.56127 24.0365 3.23233C21.6262 1.90339 18.8493 1.39384 16.1242 1.7805C13.3991 2.16715 10.8735 3.42904 8.92801 5.376L2.39301 11.523" stroke="white" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round"/>
</svg>
)
}
export function QuickResetIcon() {
return (
<svg width="33" height="29" viewBox="0 0 33 29" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M2.39804 3.04599V11.509H10.861" stroke="white" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round"/>
<path d="M5.93804 18.561C6.85464 21.1563 8.58994 23.3833 10.8824 24.9065C13.1749 26.4297 15.9003 27.1666 18.648 27.006C21.3957 26.8454 24.0168 25.7961 26.1163 24.0162C28.2157 22.2363 29.6798 19.8223 30.2878 17.1379C30.8959 14.4536 30.615 11.6443 29.4874 9.13344C28.3599 6.6226 26.4468 4.54626 24.0365 3.21732C21.6262 1.88837 18.8493 1.37883 16.1242 1.76548C13.3991 2.15213 10.8735 3.41402 8.92804 5.36099L2.39304 11.508" stroke="white" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round"/>
<path d="M16.633 9.33699C17.879 11.837 14.133 13.083 14.133 15.581C14.133 18.079 16.633 19.327 16.633 19.327C15.407 16.855 19.133 15.581 19.133 13.083C19.133 10.585 16.633 9.33699 16.633 9.33699ZM20.377 13.083C21.626 15.583 17.877 16.829 17.877 19.327H21.626C22.126 19.327 22.875 18.703 22.875 16.827C22.875 14.337 20.377 13.083 20.377 13.083Z" fill="white"/>
</svg>
)
}

View File

@@ -0,0 +1,12 @@
export function SlimeVRIcon() {
return (
<svg width="49" height="29" viewBox="0 0 49 29" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M2 26.996C10.44 25.59 29.16 23.1571 46.509 26.9091C46.509 26.9091 48.89 -0.199966 35.761 2.14503" stroke="#A44FED" strokeWidth="3" strokeLinecap="round"/>
<path d="M7.52161 15.0107L12.3649 9.20459L17.5044 13.9572" stroke="#A44FED" strokeWidth="3.00157" strokeLinecap="round"/>
<path d="M27.9566 14.1435L33.7372 9.27062L37.9695 14.8458" stroke="#A44FED" strokeWidth="3.00136" strokeLinecap="round"/>
</svg>
)
}

View File

@@ -0,0 +1,10 @@
export function WifiIcon() {
return (
<svg width="16" height="14" viewBox="0 0 16 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.799 13.378L15.585 3.67801C13.3492 1.95947 10.6129 1.01903 7.793 1.00001C4.9725 1.0172 2.23528 1.95782 0 3.67801L7.786 13.378L7.793 13.385L7.799 13.378Z" fill="#4C3755"/>
<path d="M0.00700378 3.691L7.789 13.371V13.386L7.802 13.375L15.584 3.695C13.3519 1.95931 10.6114 1.00504 7.784 0.979004C4.96356 1.00083 2.22942 1.95428 0.00700378 3.691Z" fill="#50E897"/>
</svg>
)
}

105
src/hooks/websocket-api.ts Normal file
View File

@@ -0,0 +1,105 @@
import { createContext, MutableRefObject, useContext, useEffect, useRef, useState } from "react";
import { ApplicationType, HandshakeRequestT, InboundPacketT, InboundUnion, OutboundPacket, OutboundUnion } from 'slimevr-protocol/dist/server'
import { Builder, ByteBuffer } from 'flatbuffers'
export interface WebSocketApi {
isConnected: boolean,
eventlistenerRef: MutableRefObject<EventTarget>,
usePacket: <T>(type: OutboundUnion, callback: (packet: T) => void) => void
}
export const WebSocketApiContext = createContext<WebSocketApi>(undefined as any);
export function useProvideWebsocketApi(): WebSocketApi {
const webSocketRef = useRef<WebSocket | null>(null);
const eventlistenerRef = useRef<EventTarget>(new EventTarget());
const [isConnected, setConnected] = useState(false);
const onConnected = (event: Event) => {
if (!webSocketRef.current) return ;
setConnected(true);
let fbb = new Builder(1);
const hand = new HandshakeRequestT();
hand.applicationType = ApplicationType.UI;
const inbound = new InboundPacketT();
inbound.acknowledgeMe = true;
inbound.packet = hand;
inbound.packetType = InboundUnion.HandshakeRequest
fbb.finish(inbound.pack(fbb));
webSocketRef.current.send(fbb.asUint8Array());
}
const onConnectionClose = (event: Event) => {
setConnected(false);
}
const onMessage = async (event: { data: Blob }) => {
const buffer = await event.data.arrayBuffer();
const fbb = new ByteBuffer(new Uint8Array(buffer));
const outbountPacket = OutboundPacket.getRootAsOutboundPacket(fbb).unpack();
eventlistenerRef.current?.dispatchEvent(new CustomEvent(OutboundUnion[outbountPacket.packetType], { detail: outbountPacket }))
}
useEffect(() => {
webSocketRef.current = new WebSocket('ws://localhost:21110');
// Connection opened
webSocketRef.current.addEventListener('open', onConnected);
webSocketRef.current.addEventListener('close', onConnectionClose);
webSocketRef.current.addEventListener('message', onMessage);
return () => {
if (!webSocketRef.current) return ;
webSocketRef.current.removeEventListener('open', onConnected);
webSocketRef.current.removeEventListener('close', onConnectionClose);
webSocketRef.current.removeEventListener('message', onMessage);
}
}, [])
return {
isConnected,
eventlistenerRef,
usePacket: <T>(type: OutboundUnion, callback: (packet: T) => void) => {
const onEvent = (event: CustomEventInit) => {
callback(event.detail.packet)
}
useEffect(() => {
eventlistenerRef.current.addEventListener(OutboundUnion[type], onEvent)
return () => {
eventlistenerRef.current.removeEventListener(OutboundUnion[type], onEvent)
}
}, [])
}
}
}
export function useWebsocketAPI(): WebSocketApi {
const context = useContext<WebSocketApi>(WebSocketApiContext);
if (!context) {
throw new Error('useWebsocketAPI must be within a WebSocketApi Provider')
}
return context;
}

View File

@@ -1,13 +1,14 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
background-color: theme('colors.primary.2');
font-variant-numeric: tabular-nums;
height: 100vh;
width: 100vw;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}
#root {
height: 100%;
}

17
tailwind.config.js Normal file
View File

@@ -0,0 +1,17 @@
module.exports = {
content: ["./src/**/*.{js,jsx,ts,tsx}",],
theme: {
extend: {
colors: {
primary: {
1: '#201527',
2: '#26192E',
3: '#362640',
4: '#AF91BE',
5: '#A44FED'
}
}
},
},
plugins: [],
}