Browse Source

New Connection Dialog

pull/83/head
Sacha Weatherstone 3 years ago
parent
commit
afaafb8050
  1. 1
      package.json
  2. 140
      pnpm-lock.yaml
  3. 10
      src/App.tsx
  4. 42
      src/components/DeviceSelector.tsx
  5. 97
      src/components/Dialog/NewDevice.tsx
  6. 24
      src/components/SidebarButton.tsx
  7. 10
      src/core/stores/appStore.ts

1
package.json

@ -41,7 +41,6 @@
"class-validator": "^0.14.0", "class-validator": "^0.14.0",
"class-variance-authority": "^0.4.0", "class-variance-authority": "^0.4.0",
"clsx": "^1.2.1", "clsx": "^1.2.1",
"cmdk": "^0.1.21",
"geodesy": "^2.4.0", "geodesy": "^2.4.0",
"i18next": "^22.4.9", "i18next": "^22.4.9",
"immer": "^9.0.19", "immer": "^9.0.19",

140
pnpm-lock.yaml

@ -35,7 +35,6 @@ specifiers:
class-validator: ^0.14.0 class-validator: ^0.14.0
class-variance-authority: ^0.4.0 class-variance-authority: ^0.4.0
clsx: ^1.2.1 clsx: ^1.2.1
cmdk: ^0.1.21
eslint: ^8.33.0 eslint: ^8.33.0
eslint-config-prettier: ^8.6.0 eslint-config-prettier: ^8.6.0
eslint-import-resolver-typescript: ^3.5.3 eslint-import-resolver-typescript: ^3.5.3
@ -94,7 +93,6 @@ dependencies:
class-validator: 0.14.0 class-validator: 0.14.0
class-variance-authority: 0.4[email protected] class-variance-authority: 0.4[email protected]
clsx: 1.2.1 clsx: 1.2.1
cmdk: 0.1.21_5ndqzdd6t4rivxsukjv3i3ak2q
geodesy: 2.4.0 geodesy: 2.4.0
i18next: 22.4.9 i18next: 22.4.9
immer: 9.0.19 immer: 9.0.19
@ -1836,33 +1834,6 @@ packages:
react: 18.2.0 react: 18.2.0
dev: false dev: false
/@radix-ui/react-dialog/1.0.0_5ndqzdd6t4rivxsukjv3i3ak2q:
resolution: {integrity: sha512-Yn9YU+QlHYLWwV1XfKiqnGVpWYWk6MeBVM6x/bcoyPvxgjQGoeT35482viLPctTMWoMw0PoHgqfSox7Ig+957Q==}
peerDependencies:
react: ^16.8 || ^17.0 || ^18.0
react-dom: ^16.8 || ^17.0 || ^18.0
dependencies:
'@babel/runtime': 7.20.13
'@radix-ui/primitive': 1.0.0
'@radix-ui/react-compose-refs': 1.0[email protected]
'@radix-ui/react-context': 1.0[email protected]
'@radix-ui/react-dismissable-layer': 1.0.0_biqbaboplfbrettd7655fr4n2y
'@radix-ui/react-focus-guards': 1.0[email protected]
'@radix-ui/react-focus-scope': 1.0.0_biqbaboplfbrettd7655fr4n2y
'@radix-ui/react-id': 1.0[email protected]
'@radix-ui/react-portal': 1.0.0_biqbaboplfbrettd7655fr4n2y
'@radix-ui/react-presence': 1.0.0_biqbaboplfbrettd7655fr4n2y
'@radix-ui/react-primitive': 1.0.0_biqbaboplfbrettd7655fr4n2y
'@radix-ui/react-slot': 1.0[email protected]
'@radix-ui/react-use-controllable-state': 1.0[email protected]
aria-hidden: 1.2.2_3stiutgnnbnfnf3uowm5cip22i
react: 18.2.0
react-dom: 18.2[email protected]
react-remove-scroll: 2.5.4_3stiutgnnbnfnf3uowm5cip22i
transitivePeerDependencies:
- '@types/react'
dev: false
/@radix-ui/react-dialog/1.0.2_5ndqzdd6t4rivxsukjv3i3ak2q: /@radix-ui/react-dialog/1.0.2_5ndqzdd6t4rivxsukjv3i3ak2q:
resolution: {integrity: sha512-EKxxp2WNSmUPkx4trtWNmZ4/vAYEg7JkAfa1HKBUnaubw9eHzf1Orr9B472lJYaYz327RHDrd4R95fsw7VR8DA==} resolution: {integrity: sha512-EKxxp2WNSmUPkx4trtWNmZ4/vAYEg7JkAfa1HKBUnaubw9eHzf1Orr9B472lJYaYz327RHDrd4R95fsw7VR8DA==}
peerDependencies: peerDependencies:
@ -1899,22 +1870,6 @@ packages:
react: 18.2.0 react: 18.2.0
dev: false dev: false
/@radix-ui/react-dismissable-layer/1.0.0_biqbaboplfbrettd7655fr4n2y:
resolution: {integrity: sha512-n7kDRfx+LB1zLueRDvZ1Pd0bxdJWDUZNQ/GWoxDn2prnuJKRdxsjulejX/ePkOsLi2tTm6P24mDqlMSgQpsT6g==}
peerDependencies:
react: ^16.8 || ^17.0 || ^18.0
react-dom: ^16.8 || ^17.0 || ^18.0
dependencies:
'@babel/runtime': 7.20.13
'@radix-ui/primitive': 1.0.0
'@radix-ui/react-compose-refs': 1.0[email protected]
'@radix-ui/react-primitive': 1.0.0_biqbaboplfbrettd7655fr4n2y
'@radix-ui/react-use-callback-ref': 1.0[email protected]
'@radix-ui/react-use-escape-keydown': 1.0[email protected]
react: 18.2.0
react-dom: 18.2[email protected]
dev: false
/@radix-ui/react-dismissable-layer/1.0.2_biqbaboplfbrettd7655fr4n2y: /@radix-ui/react-dismissable-layer/1.0.2_biqbaboplfbrettd7655fr4n2y:
resolution: {integrity: sha512-WjJzMrTWROozDqLB0uRWYvj4UuXsM/2L19EmQ3Au+IJWqwvwq9Bwd+P8ivo0Deg9JDPArR1I6MbWNi1CmXsskg==} resolution: {integrity: sha512-WjJzMrTWROozDqLB0uRWYvj4UuXsM/2L19EmQ3Au+IJWqwvwq9Bwd+P8ivo0Deg9JDPArR1I6MbWNi1CmXsskg==}
peerDependencies: peerDependencies:
@ -1940,20 +1895,6 @@ packages:
react: 18.2.0 react: 18.2.0
dev: false dev: false
/@radix-ui/react-focus-scope/1.0.0_biqbaboplfbrettd7655fr4n2y:
resolution: {integrity: sha512-C4SWtsULLGf/2L4oGeIHlvWQx7Rf+7cX/vKOAD2dXW0A1b5QXwi3wWeaEgW+wn+SEVrraMUk05vLU9fZZz5HbQ==}
peerDependencies:
react: ^16.8 || ^17.0 || ^18.0
react-dom: ^16.8 || ^17.0 || ^18.0
dependencies:
'@babel/runtime': 7.20.13
'@radix-ui/react-compose-refs': 1.0[email protected]
'@radix-ui/react-primitive': 1.0.0_biqbaboplfbrettd7655fr4n2y
'@radix-ui/react-use-callback-ref': 1.0[email protected]
react: 18.2.0
react-dom: 18.2[email protected]
dev: false
/@radix-ui/react-focus-scope/1.0.1_biqbaboplfbrettd7655fr4n2y: /@radix-ui/react-focus-scope/1.0.1_biqbaboplfbrettd7655fr4n2y:
resolution: {integrity: sha512-Ej2MQTit8IWJiS2uuujGUmxXjF/y5xZptIIQnyd2JHLwtV0R2j9NRVoRj/1j/gJ7e3REdaBw4Hjf4a1ImhkZcQ==} resolution: {integrity: sha512-Ej2MQTit8IWJiS2uuujGUmxXjF/y5xZptIIQnyd2JHLwtV0R2j9NRVoRj/1j/gJ7e3REdaBw4Hjf4a1ImhkZcQ==}
peerDependencies: peerDependencies:
@ -2095,18 +2036,6 @@ packages:
- '@types/react' - '@types/react'
dev: false dev: false
/@radix-ui/react-portal/1.0.0_biqbaboplfbrettd7655fr4n2y:
resolution: {integrity: sha512-a8qyFO/Xb99d8wQdu4o7qnigNjTPG123uADNecz0eX4usnQEj7o+cG4ZX4zkqq98NYekT7UoEQIjxBNWIFuqTA==}
peerDependencies:
react: ^16.8 || ^17.0 || ^18.0
react-dom: ^16.8 || ^17.0 || ^18.0
dependencies:
'@babel/runtime': 7.20.13
'@radix-ui/react-primitive': 1.0.0_biqbaboplfbrettd7655fr4n2y
react: 18.2.0
react-dom: 18.2[email protected]
dev: false
/@radix-ui/react-portal/1.0.1_biqbaboplfbrettd7655fr4n2y: /@radix-ui/react-portal/1.0.1_biqbaboplfbrettd7655fr4n2y:
resolution: {integrity: sha512-NY2vUWI5WENgAT1nfC6JS7RU5xRYBfjZVLq0HmgEN1Ezy3rk/UruMV4+Rd0F40PEaFC5SrLS1ixYvcYIQrb4Ig==} resolution: {integrity: sha512-NY2vUWI5WENgAT1nfC6JS7RU5xRYBfjZVLq0HmgEN1Ezy3rk/UruMV4+Rd0F40PEaFC5SrLS1ixYvcYIQrb4Ig==}
peerDependencies: peerDependencies:
@ -2132,18 +2061,6 @@ packages:
react-dom: 18.2[email protected] react-dom: 18.2[email protected]
dev: false dev: false
/@radix-ui/react-primitive/1.0.0_biqbaboplfbrettd7655fr4n2y:
resolution: {integrity: sha512-EyXe6mnRlHZ8b6f4ilTDrXmkLShICIuOTTj0GX4w1rp+wSxf3+TD05u1UOITC8VsJ2a9nwHvdXtOXEOl0Cw/zQ==}
peerDependencies:
react: ^16.8 || ^17.0 || ^18.0
react-dom: ^16.8 || ^17.0 || ^18.0
dependencies:
'@babel/runtime': 7.20.13
'@radix-ui/react-slot': 1.0[email protected]
react: 18.2.0
react-dom: 18.2[email protected]
dev: false
/@radix-ui/react-primitive/1.0.1_biqbaboplfbrettd7655fr4n2y: /@radix-ui/react-primitive/1.0.1_biqbaboplfbrettd7655fr4n2y:
resolution: {integrity: sha512-fHbmislWVkZaIdeF6GZxF0A/NH/3BjrGIYj+Ae6eTmTCr7EB0RQAAVEiqsXK6p3/JcRqVSBQoceZroj30Jj3XA==} resolution: {integrity: sha512-fHbmislWVkZaIdeF6GZxF0A/NH/3BjrGIYj+Ae6eTmTCr7EB0RQAAVEiqsXK6p3/JcRqVSBQoceZroj30Jj3XA==}
peerDependencies: peerDependencies:
@ -2222,16 +2139,6 @@ packages:
react-dom: 18.2[email protected] react-dom: 18.2[email protected]
dev: false dev: false
/@radix-ui/react-slot/[email protected]:
resolution: {integrity: sha512-3mrKauI/tWXo1Ll+gN5dHcxDPdm/Df1ufcDLCecn+pnCIVcdWE7CujXo8QaXOWRJyZyQWWbpB8eFwHzWXlv5mQ==}
peerDependencies:
react: ^16.8 || ^17.0 || ^18.0
dependencies:
'@babel/runtime': 7.20.13
'@radix-ui/react-compose-refs': 1.0[email protected]
react: 18.2.0
dev: false
/@radix-ui/react-slot/[email protected]: /@radix-ui/react-slot/[email protected]:
resolution: {integrity: sha512-avutXAFL1ehGvAXtPquu0YK5oz6ctS474iM3vNGQIkswrVhdrS52e3uoMQBzZhNRAIE0jBnUyXWNmSjGHhCFcw==} resolution: {integrity: sha512-avutXAFL1ehGvAXtPquu0YK5oz6ctS474iM3vNGQIkswrVhdrS52e3uoMQBzZhNRAIE0jBnUyXWNmSjGHhCFcw==}
peerDependencies: peerDependencies:
@ -2346,16 +2253,6 @@ packages:
react: 18.2.0 react: 18.2.0
dev: false dev: false
/@radix-ui/react-use-escape-keydown/[email protected]:
resolution: {integrity: sha512-JwfBCUIfhXRxKExgIqGa4CQsiMemo1Xt0W/B4ei3fpzpvPENKpMKQ8mZSB6Acj3ebrAEgi2xiQvcI1PAAodvyg==}
peerDependencies:
react: ^16.8 || ^17.0 || ^18.0
dependencies:
'@babel/runtime': 7.20.13
'@radix-ui/react-use-callback-ref': 1.0[email protected]
react: 18.2.0
dev: false
/@radix-ui/react-use-escape-keydown/[email protected]: /@radix-ui/react-use-escape-keydown/[email protected]:
resolution: {integrity: sha512-DXGim3x74WgUv+iMNCF+cAo8xUHHeqvjx8zs7trKf+FkQKPQXLk2sX7Gx1ysH7Q76xCpZuxIJE7HLPxRE+Q+GA==} resolution: {integrity: sha512-DXGim3x74WgUv+iMNCF+cAo8xUHHeqvjx8zs7trKf+FkQKPQXLk2sX7Gx1ysH7Q76xCpZuxIJE7HLPxRE+Q+GA==}
peerDependencies: peerDependencies:
@ -4211,20 +4108,6 @@ packages:
engines: {node: '>=6'} engines: {node: '>=6'}
dev: false dev: false
/cmdk/0.1.21_5ndqzdd6t4rivxsukjv3i3ak2q:
resolution: {integrity: sha512-O5oiYmoBdcoqmax4RMOsnJHrseaXlnkzfCSWgNdyBxneTaSCmUByBF5MliJbsveoHJsjkyL2uNUs2kTBBMmIaw==}
peerDependencies:
react: ^18.0.0
react-dom: ^18.0.0
dependencies:
'@radix-ui/react-dialog': 1.0.0_5ndqzdd6t4rivxsukjv3i3ak2q
command-score: 0.1.2
react: 18.2.0
react-dom: 18.2[email protected]
transitivePeerDependencies:
- '@types/react'
dev: false
/color-convert/1.9.3: /color-convert/1.9.3:
resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==}
dependencies: dependencies:
@ -4245,10 +4128,6 @@ packages:
/color-name/1.1.4: /color-name/1.1.4:
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
/command-score/0.1.2:
resolution: {integrity: sha512-VtDvQpIJBvBatnONUsPzXYFVKQQAhuf3XTNOAsdBxCNO/QCtUUd8LSgjn0GVarBkCad6aJCZfXgrjYbl/KRr7w==}
dev: false
/commander/2.20.3: /commander/2.20.3:
resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==}
@ -6300,25 +6179,6 @@ packages:
tslib: 2.5.0 tslib: 2.5.0
dev: false dev: false
/react-remove-scroll/2.5.4_3stiutgnnbnfnf3uowm5cip22i:
resolution: {integrity: sha512-xGVKJJr0SJGQVirVFAUZ2k1QLyO6m+2fy0l8Qawbp5Jgrv3DeLalrfMNBFSlmz5kriGGzsVBtGVnf4pTKIhhWA==}
engines: {node: '>=10'}
peerDependencies:
'@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0
react: ^16.8.0 || ^17.0.0 || ^18.0.0
peerDependenciesMeta:
'@types/react':
optional: true
dependencies:
'@types/react': 18.0.27
react: 18.2.0
react-remove-scroll-bar: 2.3.4_3stiutgnnbnfnf3uowm5cip22i
react-style-singleton: 2.2.1_3stiutgnnbnfnf3uowm5cip22i
tslib: 2.5.0
use-callback-ref: 1.3.0_3stiutgnnbnfnf3uowm5cip22i
use-sidecar: 1.1.2_3stiutgnnbnfnf3uowm5cip22i
dev: false
/react-remove-scroll/2.5.5_3stiutgnnbnfnf3uowm5cip22i: /react-remove-scroll/2.5.5_3stiutgnnbnfnf3uowm5cip22i:
resolution: {integrity: sha512-ImKhrzJJsyXJfBZ4bzu8Bwpka14c/fQt0k+cyFp/PBhTfyDnU5hjOtM4AG/0AMyy8oKzOTR0lDgJIM7pYXI0kw==} resolution: {integrity: sha512-ImKhrzJJsyXJfBZ4bzu8Bwpka14c/fQt0k+cyFp/PBhTfyDnU5hjOtM4AG/0AMyy8oKzOTR0lDgJIM7pYXI0kw==}
engines: {node: '>=10'} engines: {node: '>=10'}

10
src/App.tsx

@ -9,15 +9,23 @@ import { NewDevice } from "@components/NewDevice.js";
import { useDeviceStore } from "@core/stores/deviceStore.js"; import { useDeviceStore } from "@core/stores/deviceStore.js";
import { ThemeController } from "@components/generic/ThemeController.js"; import { ThemeController } from "@components/generic/ThemeController.js";
import { Menu } from "./components/Menu.js"; import { Menu } from "./components/Menu.js";
import { NewDeviceDialog } from "./components/Dialog/NewDevice.js";
export const App = (): JSX.Element => { export const App = (): JSX.Element => {
const { getDevice } = useDeviceStore(); const { getDevice } = useDeviceStore();
const { selectedDevice, darkMode, accent } = useAppStore(); const { selectedDevice, setConnectDialogOpen, connectDialogOpen } =
useAppStore();
const device = getDevice(selectedDevice); const device = getDevice(selectedDevice);
return ( return (
<ThemeController> <ThemeController>
<NewDeviceDialog
open={connectDialogOpen}
onOpenChange={(open) => {
setConnectDialogOpen(open);
}}
/>
<MapProvider> <MapProvider>
<DeviceWrapper device={device}> <DeviceWrapper device={device}>
<div className="flex min-h-screen flex-col"> <div className="flex min-h-screen flex-col">

42
src/components/DeviceSelector.tsx

@ -13,6 +13,7 @@ import {
import { Separator } from "./UI/Seperator.js"; import { Separator } from "./UI/Seperator.js";
import { cn } from "@app/core/utils/cn.js"; import { cn } from "@app/core/utils/cn.js";
import { Code } from "./UI/Typography/Code.js"; import { Code } from "./UI/Typography/Code.js";
import { SidebarButton } from "./SidebarButton.js";
export const DeviceSelector = (): JSX.Element => { export const DeviceSelector = (): JSX.Element => {
const { getDevices } = useDeviceStore(); const { getDevices } = useDeviceStore();
@ -21,45 +22,38 @@ export const DeviceSelector = (): JSX.Element => {
setSelectedDevice, setSelectedDevice,
darkMode, darkMode,
setDarkMode, setDarkMode,
setCommandPaletteOpen setCommandPaletteOpen,
setConnectDialogOpen
} = useAppStore(); } = useAppStore();
return ( return (
<nav className="drag custom-border flex flex-col justify-between border-r-[0.5px] bg-transparent pt-2"> <nav className="drag custom-border flex flex-col justify-between border-r-[0.5px] bg-transparent pt-2">
<div className="flex flex-col overflow-y-hidden"> <div className="flex flex-col overflow-y-hidden">
<ul className="flex w-20 shrink-0 flex-col items-center justify-end space-y-2.5 bg-transparent px-5 pt-1 pb-px">
<li className="aspect-w-1 aspect-h-1 w-full">
<a className="text-primary-500 ring-primary-300 flex flex-col items-center justify-center rounded-full outline-none ring transition-all focus:outline-none sm:duration-300">
<HomeIcon />
</a>
</li>
</ul>
<ul className="flex w-20 grow flex-col items-center space-y-4 bg-transparent py-4 px-5"> <ul className="flex w-20 grow flex-col items-center space-y-4 bg-transparent py-4 px-5">
<SidebarButton active={false} onClick={() => {}}>
<HomeIcon />
</SidebarButton>
{getDevices().map((device) => ( {getDevices().map((device) => (
<li <SidebarButton
key={device.id} key={device.id}
className="aspect-w-1 aspect-h-1 w-full"
onClick={() => { onClick={() => {
setSelectedDevice(device.id); setSelectedDevice(device.id);
}} }}
active={selectedDevice === device.id}
> >
<div <Hashicon
className={cn( size={24}
"flex aspect-square cursor-pointer flex-col items-center justify-center rounded-full ring ring-accent", value={device.hardware.myNodeNum.toString()}
selectedDevice === device.id ? "ring" : "ring-0" />
)} </SidebarButton>
>
<Hashicon
size={24}
value={device.hardware.myNodeNum.toString()}
/>
</div>
</li>
))} ))}
<Separator /> <Separator />
<div className="transition-all duration-300 hover:text-accent"> <button
onClick={() => setConnectDialogOpen(true)}
className="transition-all duration-300 hover:text-accent"
>
<PlusIcon /> <PlusIcon />
</div> </button>
</ul> </ul>
</div> </div>
<div className="flex w-20 flex-col items-center space-y-5 bg-transparent px-5 pb-5"> <div className="flex w-20 flex-col items-center space-y-5 bg-transparent px-5 pb-5">

97
src/components/Dialog/NewDevice.tsx

@ -0,0 +1,97 @@
import { Input } from "@components/form/Input.js";
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle
} from "@components/UI/Dialog.js";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "../UI/Tabs.js";
import { Subtle } from "../UI/Typography/Subtle.js";
import { Link } from "../UI/Typography/Link.js";
import { HTTP } from "../PageComponents/Connect/HTTP.js";
import { BLE } from "../PageComponents/Connect/BLE.js";
import { Serial } from "../PageComponents/Connect/Serial.js";
const tabs = [
{
label: "HTTP",
element: HTTP,
disabled: false,
disabledMessage: "Unsuported connection method"
},
{
label: "Bluetooth",
element: BLE,
disabled: !navigator.bluetooth,
disabledMessage:
"Web Bluetooth is currently only supported by Chromium-based browsers",
disabledLink:
"https://developer.mozilla.org/en-US/docs/Web/API/Web_Serial_API#browser_compatibility"
},
{
label: "Serial",
element: Serial,
disabled: !navigator.serial,
disabledMessage:
"WebSerial is currently only supported by Chromium based browsers: https://developer.mozilla.org/en-US/docs/Web/API/Web_Bluetooth_API#browser_compatibility"
}
];
export interface NewDeviceProps {
open: boolean;
onOpenChange: (open: boolean) => void;
}
export const NewDeviceDialog = ({
open,
onOpenChange
}: NewDeviceProps): JSX.Element => {
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent>
<DialogHeader>
<DialogTitle>Connect New Device</DialogTitle>
</DialogHeader>
<Tabs defaultValue="HTTP">
<TabsList>
{tabs.map((tab) => (
<TabsTrigger value={tab.label} disabled={tab.disabled}>
{tab.label}
</TabsTrigger>
))}
</TabsList>
{tabs.map((tab) => (
<TabsContent value={tab.label}>
{tab.disabled ? (
<p className="text-sm text-slate-500 dark:text-slate-400">
{tab.disabledMessage}
</p>
) : (
<tab.element />
)}
</TabsContent>
))}
</Tabs>
{(!navigator.bluetooth || !navigator.serial) && (
<>
<Subtle>
Web Bluetooth and Web Serial are currently only supported by
Chromium-based browsers.
</Subtle>
<Subtle>
Read more:&nbsp;
<Link href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Bluetooth_API#browser_compatibility">
Web Bluetooth
</Link>
&nbsp;
<Link href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Serial_API#browser_compatibility">
Web Serial
</Link>
</Subtle>
</>
)}
</DialogContent>
</Dialog>
);
};

24
src/components/SidebarButton.tsx

@ -0,0 +1,24 @@
import { cn } from "@app/core/utils/cn.js";
export interface SidebarButtonProps {
active: boolean;
onClick: () => void;
children?: React.ReactNode;
}
export const SidebarButton = ({
active,
onClick,
children
}: SidebarButtonProps): JSX.Element => (
<li className="aspect-w-1 aspect-h-1 w-full" onClick={onClick}>
<div
className={cn(
"flex aspect-square cursor-pointer flex-col items-center justify-center rounded-full ring ring-accent",
active ? "ring" : "ring-0"
)}
>
{children}
</div>
</li>
);

10
src/core/stores/appStore.ts

@ -27,6 +27,7 @@ interface AppState {
commandPaletteOpen: boolean; commandPaletteOpen: boolean;
darkMode: boolean; darkMode: boolean;
accent: accentColor; accent: accentColor;
connectDialogOpen: boolean;
setRasterSources: (sources: RasterSource[]) => void; setRasterSources: (sources: RasterSource[]) => void;
addRasterSource: (source: RasterSource) => void; addRasterSource: (source: RasterSource) => void;
@ -38,6 +39,7 @@ interface AppState {
setCommandPaletteOpen: (open: boolean) => void; setCommandPaletteOpen: (open: boolean) => void;
setDarkMode: (enabled: boolean) => void; setDarkMode: (enabled: boolean) => void;
setAccent: (color: accentColor) => void; setAccent: (color: accentColor) => void;
setConnectDialogOpen: (open: boolean) => void;
} }
export const useAppStore = create<AppState>()((set) => ({ export const useAppStore = create<AppState>()((set) => ({
@ -48,6 +50,7 @@ export const useAppStore = create<AppState>()((set) => ({
commandPaletteOpen: false, commandPaletteOpen: false,
darkMode: true, darkMode: true,
accent: "orange", accent: "orange",
connectDialogOpen: false,
setRasterSources: (sources: RasterSource[]) => { setRasterSources: (sources: RasterSource[]) => {
set( set(
@ -102,5 +105,12 @@ export const useAppStore = create<AppState>()((set) => ({
draft.accent = color; draft.accent = color;
}) })
); );
},
setConnectDialogOpen: (open: boolean) => {
set(
produce<AppState>((draft) => {
draft.connectDialogOpen = open;
})
);
} }
})); }));

Loading…
Cancel
Save