mirror of
https://github.com/sipeed/NanoKVM.git
synced 2026-09-11 00:22:56 -05:00
ui: optimize menu bar ui
This commit is contained in:
@@ -1,3 +1,3 @@
|
||||
VITE_SERVER_IP=192.168.0.65
|
||||
VITE_SERVER_IP=192.168.3.69
|
||||
VITE_SERVER_PORT=80
|
||||
VITE_WITH_CREDENTIALS=false
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@ant-design/icons": "^5.6.1",
|
||||
"@radix-ui/react-scroll-area": "^1.2.10",
|
||||
"@xterm/addon-attach": "^0.11.0",
|
||||
"@xterm/addon-fit": "^0.10.0",
|
||||
"@xterm/xterm": "^5.5.0",
|
||||
|
||||
179
web/pnpm-lock.yaml
generated
179
web/pnpm-lock.yaml
generated
@@ -11,6 +11,9 @@ importers:
|
||||
'@ant-design/icons':
|
||||
specifier: ^5.6.1
|
||||
version: 5.6.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@radix-ui/react-scroll-area':
|
||||
specifier: ^1.2.10
|
||||
version: 1.2.10(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@xterm/addon-attach':
|
||||
specifier: ^0.11.0
|
||||
version: 0.11.0(@xterm/xterm@5.5.0)
|
||||
@@ -640,9 +643,15 @@ packages:
|
||||
'@open-draft/until@2.1.0':
|
||||
resolution: {integrity: sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==}
|
||||
|
||||
'@radix-ui/number@1.1.1':
|
||||
resolution: {integrity: sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==}
|
||||
|
||||
'@radix-ui/primitive@1.1.0':
|
||||
resolution: {integrity: sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA==}
|
||||
|
||||
'@radix-ui/primitive@1.1.3':
|
||||
resolution: {integrity: sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==}
|
||||
|
||||
'@radix-ui/react-compose-refs@1.1.0':
|
||||
resolution: {integrity: sha512-b4inOtiaOnYf9KWyO3jAeeCG6FeyfY6ldiEPanbUjWd+xIk5wZeHa8yVwmrJ2vderhu/BQvzCrJI0lHd+wIiqw==}
|
||||
peerDependencies:
|
||||
@@ -652,6 +661,15 @@ packages:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-compose-refs@1.1.2':
|
||||
resolution: {integrity: sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-context@1.1.1':
|
||||
resolution: {integrity: sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q==}
|
||||
peerDependencies:
|
||||
@@ -661,6 +679,15 @@ packages:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-context@1.1.2':
|
||||
resolution: {integrity: sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-dialog@1.1.2':
|
||||
resolution: {integrity: sha512-Yj4dZtqa2o+kG61fzB0H2qUvmwBA2oyQroGLyNtBj1beo1khoQ3q1a2AO8rrQYjd8256CO9+N8L9tvsS+bnIyA==}
|
||||
peerDependencies:
|
||||
@@ -674,6 +701,15 @@ packages:
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-direction@1.1.1':
|
||||
resolution: {integrity: sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-dismissable-layer@1.1.1':
|
||||
resolution: {integrity: sha512-QSxg29lfr/xcev6kSz7MAlmDnzbP1eI/Dwn3Tp1ip0KT5CUELsxkekFEMVBEoykI3oV39hKT4TKZzBNMbcTZYQ==}
|
||||
peerDependencies:
|
||||
@@ -744,6 +780,19 @@ packages:
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-presence@1.1.5':
|
||||
resolution: {integrity: sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-primitive@2.0.0':
|
||||
resolution: {integrity: sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw==}
|
||||
peerDependencies:
|
||||
@@ -757,6 +806,32 @@ packages:
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-primitive@2.1.3':
|
||||
resolution: {integrity: sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-scroll-area@1.2.10':
|
||||
resolution: {integrity: sha512-tAXIa1g3sM5CGpVT0uIbUx/U3Gs5N8T52IICuCtObaos1S8fzsrPXG5WObkQN3S6NVl6wKgPhAIiBGbWnvc97A==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-slot@1.1.0':
|
||||
resolution: {integrity: sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw==}
|
||||
peerDependencies:
|
||||
@@ -766,6 +841,15 @@ packages:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-slot@1.2.3':
|
||||
resolution: {integrity: sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-use-callback-ref@1.1.0':
|
||||
resolution: {integrity: sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw==}
|
||||
peerDependencies:
|
||||
@@ -775,6 +859,15 @@ packages:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-use-callback-ref@1.1.1':
|
||||
resolution: {integrity: sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-use-controllable-state@1.1.0':
|
||||
resolution: {integrity: sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw==}
|
||||
peerDependencies:
|
||||
@@ -802,6 +895,15 @@ packages:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-use-layout-effect@1.1.1':
|
||||
resolution: {integrity: sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@rc-component/async-validator@5.0.4':
|
||||
resolution: {integrity: sha512-qgGdcVIF604M9EqjNF0hbUTz42bz/RDtxWdWuU5EQe3hi7M8ob54B6B35rOsvX5eSvIHIzT9iH1R3n+hk3CGfg==}
|
||||
engines: {node: '>=14.x'}
|
||||
@@ -3551,20 +3653,36 @@ snapshots:
|
||||
|
||||
'@open-draft/until@2.1.0': {}
|
||||
|
||||
'@radix-ui/number@1.1.1': {}
|
||||
|
||||
'@radix-ui/primitive@1.1.0': {}
|
||||
|
||||
'@radix-ui/primitive@1.1.3': {}
|
||||
|
||||
'@radix-ui/react-compose-refs@1.1.0(@types/react@18.3.12)(react@18.3.1)':
|
||||
dependencies:
|
||||
react: 18.3.1
|
||||
optionalDependencies:
|
||||
'@types/react': 18.3.12
|
||||
|
||||
'@radix-ui/react-compose-refs@1.1.2(@types/react@18.3.12)(react@18.3.1)':
|
||||
dependencies:
|
||||
react: 18.3.1
|
||||
optionalDependencies:
|
||||
'@types/react': 18.3.12
|
||||
|
||||
'@radix-ui/react-context@1.1.1(@types/react@18.3.12)(react@18.3.1)':
|
||||
dependencies:
|
||||
react: 18.3.1
|
||||
optionalDependencies:
|
||||
'@types/react': 18.3.12
|
||||
|
||||
'@radix-ui/react-context@1.1.2(@types/react@18.3.12)(react@18.3.1)':
|
||||
dependencies:
|
||||
react: 18.3.1
|
||||
optionalDependencies:
|
||||
'@types/react': 18.3.12
|
||||
|
||||
'@radix-ui/react-dialog@1.1.2(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||
dependencies:
|
||||
'@radix-ui/primitive': 1.1.0
|
||||
@@ -3587,6 +3705,12 @@ snapshots:
|
||||
'@types/react': 18.3.12
|
||||
'@types/react-dom': 18.3.1
|
||||
|
||||
'@radix-ui/react-direction@1.1.1(@types/react@18.3.12)(react@18.3.1)':
|
||||
dependencies:
|
||||
react: 18.3.1
|
||||
optionalDependencies:
|
||||
'@types/react': 18.3.12
|
||||
|
||||
'@radix-ui/react-dismissable-layer@1.1.1(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||
dependencies:
|
||||
'@radix-ui/primitive': 1.1.0
|
||||
@@ -3644,6 +3768,16 @@ snapshots:
|
||||
'@types/react': 18.3.12
|
||||
'@types/react-dom': 18.3.1
|
||||
|
||||
'@radix-ui/react-presence@1.1.5(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||
dependencies:
|
||||
'@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.12)(react@18.3.1)
|
||||
'@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.12)(react@18.3.1)
|
||||
react: 18.3.1
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
optionalDependencies:
|
||||
'@types/react': 18.3.12
|
||||
'@types/react-dom': 18.3.1
|
||||
|
||||
'@radix-ui/react-primitive@2.0.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||
dependencies:
|
||||
'@radix-ui/react-slot': 1.1.0(@types/react@18.3.12)(react@18.3.1)
|
||||
@@ -3653,6 +3787,32 @@ snapshots:
|
||||
'@types/react': 18.3.12
|
||||
'@types/react-dom': 18.3.1
|
||||
|
||||
'@radix-ui/react-primitive@2.1.3(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||
dependencies:
|
||||
'@radix-ui/react-slot': 1.2.3(@types/react@18.3.12)(react@18.3.1)
|
||||
react: 18.3.1
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
optionalDependencies:
|
||||
'@types/react': 18.3.12
|
||||
'@types/react-dom': 18.3.1
|
||||
|
||||
'@radix-ui/react-scroll-area@1.2.10(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||
dependencies:
|
||||
'@radix-ui/number': 1.1.1
|
||||
'@radix-ui/primitive': 1.1.3
|
||||
'@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.12)(react@18.3.1)
|
||||
'@radix-ui/react-context': 1.1.2(@types/react@18.3.12)(react@18.3.1)
|
||||
'@radix-ui/react-direction': 1.1.1(@types/react@18.3.12)(react@18.3.1)
|
||||
'@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.12)(react@18.3.1)
|
||||
'@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.12)(react@18.3.1)
|
||||
react: 18.3.1
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
optionalDependencies:
|
||||
'@types/react': 18.3.12
|
||||
'@types/react-dom': 18.3.1
|
||||
|
||||
'@radix-ui/react-slot@1.1.0(@types/react@18.3.12)(react@18.3.1)':
|
||||
dependencies:
|
||||
'@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.12)(react@18.3.1)
|
||||
@@ -3660,12 +3820,25 @@ snapshots:
|
||||
optionalDependencies:
|
||||
'@types/react': 18.3.12
|
||||
|
||||
'@radix-ui/react-slot@1.2.3(@types/react@18.3.12)(react@18.3.1)':
|
||||
dependencies:
|
||||
'@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.12)(react@18.3.1)
|
||||
react: 18.3.1
|
||||
optionalDependencies:
|
||||
'@types/react': 18.3.12
|
||||
|
||||
'@radix-ui/react-use-callback-ref@1.1.0(@types/react@18.3.12)(react@18.3.1)':
|
||||
dependencies:
|
||||
react: 18.3.1
|
||||
optionalDependencies:
|
||||
'@types/react': 18.3.12
|
||||
|
||||
'@radix-ui/react-use-callback-ref@1.1.1(@types/react@18.3.12)(react@18.3.1)':
|
||||
dependencies:
|
||||
react: 18.3.1
|
||||
optionalDependencies:
|
||||
'@types/react': 18.3.12
|
||||
|
||||
'@radix-ui/react-use-controllable-state@1.1.0(@types/react@18.3.12)(react@18.3.1)':
|
||||
dependencies:
|
||||
'@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.12)(react@18.3.1)
|
||||
@@ -3686,6 +3859,12 @@ snapshots:
|
||||
optionalDependencies:
|
||||
'@types/react': 18.3.12
|
||||
|
||||
'@radix-ui/react-use-layout-effect@1.1.1(@types/react@18.3.12)(react@18.3.1)':
|
||||
dependencies:
|
||||
react: 18.3.1
|
||||
optionalDependencies:
|
||||
'@types/react': 18.3.12
|
||||
|
||||
'@rc-component/async-validator@5.0.4':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.26.0
|
||||
|
||||
53
web/src/components/ui/scroll-area.tsx
Normal file
53
web/src/components/ui/scroll-area.tsx
Normal file
@@ -0,0 +1,53 @@
|
||||
import * as React from 'react';
|
||||
import * as ScrollAreaPrimitive from '@radix-ui/react-scroll-area';
|
||||
import clsx from 'clsx';
|
||||
|
||||
function ScrollArea({
|
||||
className,
|
||||
children,
|
||||
...props
|
||||
}: React.ComponentProps<typeof ScrollAreaPrimitive.Root>) {
|
||||
return (
|
||||
<ScrollAreaPrimitive.Root
|
||||
data-slot="scroll-area"
|
||||
className={clsx('relative', className)}
|
||||
{...props}
|
||||
>
|
||||
<ScrollAreaPrimitive.Viewport
|
||||
data-slot="scroll-area-viewport"
|
||||
className="focus-visible:ring-ring/50 size-full rounded-[inherit] outline-none transition-[color,box-shadow] focus-visible:outline-1 focus-visible:ring-[3px]"
|
||||
>
|
||||
{children}
|
||||
</ScrollAreaPrimitive.Viewport>
|
||||
<ScrollBar />
|
||||
<ScrollAreaPrimitive.Corner />
|
||||
</ScrollAreaPrimitive.Root>
|
||||
);
|
||||
}
|
||||
|
||||
function ScrollBar({
|
||||
className,
|
||||
orientation = 'vertical',
|
||||
...props
|
||||
}: React.ComponentProps<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>) {
|
||||
return (
|
||||
<ScrollAreaPrimitive.ScrollAreaScrollbar
|
||||
data-slot="scroll-area-scrollbar"
|
||||
orientation={orientation}
|
||||
className={clsx(
|
||||
'flex touch-none select-none p-px transition-colors',
|
||||
orientation === 'vertical' && 'h-full w-2 border-l border-l-transparent',
|
||||
orientation === 'horizontal' && 'h-2 flex-col border-t border-t-transparent',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<ScrollAreaPrimitive.ScrollAreaThumb
|
||||
data-slot="scroll-area-thumb"
|
||||
className="bg-border relative flex-1 rounded-full bg-neutral-500/30"
|
||||
/>
|
||||
</ScrollAreaPrimitive.ScrollAreaScrollbar>
|
||||
);
|
||||
}
|
||||
|
||||
export { ScrollArea, ScrollBar };
|
||||
@@ -8,9 +8,10 @@ export const Fullscreen = () => {
|
||||
const [isFullscreen, setIsFullscreen] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
function onFullscreenChange() {
|
||||
const onFullscreenChange = () => {
|
||||
setIsFullscreen(!!document.fullscreenElement);
|
||||
}
|
||||
};
|
||||
|
||||
onFullscreenChange();
|
||||
|
||||
document.addEventListener('fullscreenchange', onFullscreenChange);
|
||||
@@ -24,8 +25,14 @@ export const Fullscreen = () => {
|
||||
if (!document.fullscreenElement) {
|
||||
const element = document.documentElement;
|
||||
element.requestFullscreen();
|
||||
|
||||
// @ts-expect-error - https://developer.mozilla.org/en-US/docs/Web/API/Keyboard/lock
|
||||
navigator.keyboard?.lock();
|
||||
} else {
|
||||
document.exitFullscreen();
|
||||
|
||||
// @ts-expect-error - https://developer.mozilla.org/en-US/docs/Web/API/Keyboard/unlock
|
||||
navigator.keyboard?.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ export const Power = () => {
|
||||
}
|
||||
|
||||
const icon = (
|
||||
<div className={clsx('h-[18px] w-[18px]', isPowerOn ? 'text-green-600' : 'text-neutral-500')}>
|
||||
<div className={clsx('h-[18px] w-[18px]', isPowerOn ? 'text-green-600' : 'text-neutral-300')}>
|
||||
{isLoading ? (
|
||||
<LoaderCircleIcon className="animate-spin" size={18} />
|
||||
) : (
|
||||
|
||||
@@ -71,10 +71,10 @@ export const Screen = () => {
|
||||
const content = (
|
||||
<div className="flex flex-col space-y-1">
|
||||
<VideoMode />
|
||||
<Scale />
|
||||
<Resolution />
|
||||
<Quality quality={quality} setQuality={setQuality} />
|
||||
<Fps fps={fps} setFps={setFps} />
|
||||
<Scale />
|
||||
{videoMode !== 'mjpeg' && <Gop gop={gop} setGop={setGop} />}
|
||||
{videoMode === 'mjpeg' && <FrameDetect />}
|
||||
<Reset />
|
||||
|
||||
@@ -1,49 +1,56 @@
|
||||
import { ReactElement, useEffect } from 'react'
|
||||
import { Popover, Slider } from 'antd'
|
||||
import { useAtom } from 'jotai'
|
||||
import { ScalingIcon } from 'lucide-react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { ReactElement, useEffect } from 'react';
|
||||
import { Popover } from 'antd';
|
||||
import { useAtom } from 'jotai';
|
||||
import { CheckIcon, PercentIcon, ScalingIcon } from 'lucide-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { videoScaleAtom } from '@/jotai/screen.ts'
|
||||
import * as storage from '@/lib/localstorage.ts'
|
||||
import * as storage from '@/lib/localstorage.ts';
|
||||
import { videoScaleAtom } from '@/jotai/screen.ts';
|
||||
|
||||
const ScaleList = [
|
||||
{ label: '200', value: 2 },
|
||||
{ label: '150', value: 1.5 },
|
||||
{ label: '100', value: 1 },
|
||||
{ label: '75', value: 0.75 },
|
||||
{ label: '50', value: 0.5 }
|
||||
];
|
||||
|
||||
export const Scale = (): ReactElement => {
|
||||
const { t } = useTranslation()
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [videoScale, setVideoScale] = useAtom(videoScaleAtom)
|
||||
const [videoScale, setVideoScale] = useAtom(videoScaleAtom);
|
||||
|
||||
useEffect(() => {
|
||||
const scale = storage.getVideoScale()
|
||||
const scale = storage.getVideoScale();
|
||||
if (scale) {
|
||||
setVideoScale(scale)
|
||||
setVideoScale(scale);
|
||||
}
|
||||
}, [setVideoScale])
|
||||
}, [setVideoScale]);
|
||||
|
||||
async function updateScale(scale: number): Promise<void> {
|
||||
setVideoScale(scale)
|
||||
storage.setVideoScale(scale)
|
||||
async function update(scale: number): Promise<void> {
|
||||
setVideoScale(scale);
|
||||
storage.setVideoScale(scale);
|
||||
}
|
||||
|
||||
const content = (
|
||||
<div className="h-[150px] w-[60px] py-3">
|
||||
<Slider
|
||||
vertical
|
||||
marks={{
|
||||
0.5: <span>x0.5</span>,
|
||||
1: <span>x1.0</span>,
|
||||
1.5: <span>x1.5</span>,
|
||||
2: <span>x2.0</span>
|
||||
}}
|
||||
range={false}
|
||||
included={false}
|
||||
min={0.5}
|
||||
max={2}
|
||||
step={0.1}
|
||||
defaultValue={videoScale}
|
||||
onChange={updateScale}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
<>
|
||||
{ScaleList.map((scale) => (
|
||||
<div
|
||||
key={scale.value}
|
||||
className="flex h-[30px] cursor-pointer select-none items-center rounded pl-1 pr-5 hover:bg-neutral-700/70"
|
||||
onClick={() => update(scale.value)}
|
||||
>
|
||||
<div className="flex h-[14px] w-[20px] items-end text-blue-500">
|
||||
{scale.value === videoScale && <CheckIcon size={14} />}
|
||||
</div>
|
||||
<div className="flex items-center space-x-0.5">
|
||||
<span>{scale.label}</span>
|
||||
<PercentIcon size={12} />
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<Popover content={content} placement="rightTop" arrow={false} align={{ offset: [13, 0] }}>
|
||||
@@ -54,5 +61,5 @@ export const Scale = (): ReactElement => {
|
||||
<span>{t('screen.scale')}</span>
|
||||
</div>
|
||||
</Popover>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
@@ -41,9 +41,9 @@ export const VideoMode = () => {
|
||||
<>
|
||||
{!isDirectSupported && (
|
||||
<Tooltip
|
||||
placement="right"
|
||||
title={t('screen.videoDirectTips')}
|
||||
overlayStyle={{ maxWidth: '270px' }}
|
||||
placement="right"
|
||||
styles={{ root: { maxWidth: '270px' } }}
|
||||
>
|
||||
<div className="flex cursor-not-allowed select-none items-center rounded py-1.5 pl-1 pr-5 text-neutral-500 hover:bg-neutral-700/70">
|
||||
<div className="flex h-[14px] w-[20px] items-end text-blue-500"></div>
|
||||
|
||||
@@ -31,9 +31,9 @@ export const Community = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="pb-5 text-neutral-400">{t('settings.about.community')}</div>
|
||||
<div className="text-neutral-400">{t('settings.about.community')}</div>
|
||||
|
||||
<div className="my-3 flex space-x-3">
|
||||
<div className="mt-5 flex flex-wrap gap-3">
|
||||
{communities.map((community) => (
|
||||
<a
|
||||
key={community.name}
|
||||
|
||||
@@ -65,8 +65,8 @@ export const Hostname = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex w-full items-center justify-between pt-4">
|
||||
<div className="space-y-1">
|
||||
<div className="flex w-full items-center justify-between">
|
||||
<span>{t('settings.about.hostname')}</span>
|
||||
|
||||
{editState === 'editing' ? (
|
||||
@@ -94,10 +94,10 @@ export const Hostname = () => {
|
||||
</div>
|
||||
|
||||
{editState === 'edited' && (
|
||||
<div className="flex w-full justify-end pt-1 text-xs text-green-500">
|
||||
<div className="flex w-full justify-end text-xs text-green-500">
|
||||
{t('settings.about.hostnameUpdated')}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -2,7 +2,6 @@ import { Divider } from 'antd';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { Community } from './community.tsx';
|
||||
import { Hostname } from './hostname.tsx';
|
||||
import { Information } from './information.tsx';
|
||||
|
||||
export const About = () => {
|
||||
@@ -10,12 +9,11 @@ export const About = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="text-base font-bold">{t('settings.about.title')}</div>
|
||||
<Divider />
|
||||
<div className="text-base">{t('settings.about.title')}</div>
|
||||
<Divider className="opacity-50" />
|
||||
|
||||
<Information />
|
||||
<Hostname />
|
||||
<Divider />
|
||||
<Divider className="opacity-50" />
|
||||
|
||||
<Community />
|
||||
</>
|
||||
|
||||
@@ -5,6 +5,8 @@ import { useTranslation } from 'react-i18next';
|
||||
|
||||
import * as api from '@/api/vm.ts';
|
||||
|
||||
import { Hostname } from './hostname.tsx';
|
||||
|
||||
type IP = {
|
||||
name: string;
|
||||
addr: string;
|
||||
@@ -38,9 +40,9 @@ export const Information = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="pb-5 text-neutral-400">{t('settings.about.information')}</div>
|
||||
<div className="text-neutral-400">{t('settings.about.information')}</div>
|
||||
|
||||
<div className="flex w-full flex-col space-y-4">
|
||||
<div className="mt-5 flex w-full flex-col space-y-5">
|
||||
{/* IP list */}
|
||||
<div className="flex w-full items-start justify-between">
|
||||
<span>{t('settings.about.ip')}</span>
|
||||
@@ -103,6 +105,8 @@ export const Information = () => {
|
||||
|
||||
<span>{information ? information.application : '-'}</span>
|
||||
</div>
|
||||
|
||||
<Hostname />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Divider } from 'antd';
|
||||
import { Button, Divider } from 'antd';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
@@ -27,10 +27,10 @@ export const Account = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="text-base font-bold">{t('settings.account.title')}</div>
|
||||
<Divider />
|
||||
<div className="text-base">{t('settings.account.title')}</div>
|
||||
<Divider className="opacity-50" />
|
||||
|
||||
<div className="flex flex-col space-y-5">
|
||||
<div className="flex flex-col space-y-8">
|
||||
<div className="flex items-center justify-between">
|
||||
<span>{t('settings.account.webAccount')}</span>
|
||||
<span>{username ? username : '-'}</span>
|
||||
@@ -38,15 +38,13 @@ export const Account = () => {
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<span>{t('settings.account.password')}</span>
|
||||
<span
|
||||
className="cursor-pointer text-blue-500 hover:text-blue-500/80"
|
||||
onClick={changePassword}
|
||||
>
|
||||
<Button type="primary" onClick={changePassword}>
|
||||
{t('settings.account.updateBtn')}
|
||||
</span>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<Divider />
|
||||
|
||||
<Divider className="opacity-50" />
|
||||
|
||||
<Logout />
|
||||
</>
|
||||
|
||||
@@ -10,14 +10,14 @@ export const Appearance = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="text-base font-bold">{t('settings.appearance.title')}</div>
|
||||
<Divider />
|
||||
<div className="text-base">{t('settings.appearance.title')}</div>
|
||||
<Divider className="opacity-50" />
|
||||
|
||||
<div className="flex flex-col space-y-6">
|
||||
<Language />
|
||||
<WebTitle />
|
||||
</div>
|
||||
<Divider />
|
||||
<div className="text-neutral-400">{t('settings.appearance.display')}</div>
|
||||
<Language />
|
||||
<WebTitle />
|
||||
|
||||
<Divider className="opacity-50" style={{ margin: '32px 0' }} />
|
||||
|
||||
<MenuBar />
|
||||
</>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Select } from 'antd';
|
||||
import { LanguagesIcon } from 'lucide-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import languages from '@/i18n/languages.ts';
|
||||
@@ -21,18 +20,20 @@ export const Language = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center space-x-1">
|
||||
<LanguagesIcon size={16} />
|
||||
<div className="mt-5 flex items-center justify-between space-x-5">
|
||||
<div className="flex flex-col space-y-1">
|
||||
<span>{t('settings.appearance.language')}</span>
|
||||
<span className="text-xs text-neutral-500">{t('settings.appearance.languageDesc')}</span>
|
||||
</div>
|
||||
|
||||
<Select
|
||||
defaultValue={i18n.language}
|
||||
style={{ width: 180 }}
|
||||
options={options}
|
||||
onSelect={changeLanguage}
|
||||
/>
|
||||
<div>
|
||||
<Select
|
||||
defaultValue={i18n.language}
|
||||
style={{ width: 180 }}
|
||||
options={options}
|
||||
onSelect={changeLanguage}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -19,12 +19,12 @@ export const MenuBar = () => {
|
||||
const [menuDisabledItems, setMenuDisabledItems] = useAtom(menuDisabledItemsAtom);
|
||||
|
||||
const items = [
|
||||
{ key: 'image', icon: <DiscIcon size={16} /> },
|
||||
{ key: 'download', icon: <DownloadIcon size={16} /> },
|
||||
{ key: 'script', icon: <FileJsonIcon size={15} /> },
|
||||
{ key: 'terminal', icon: <TerminalSquareIcon size={16} /> },
|
||||
{ key: 'wol', icon: <NetworkIcon size={16} /> },
|
||||
{ key: 'power', icon: <PowerIcon size={16} /> }
|
||||
{ key: 'image', icon: <DiscIcon size={16} className="text-neutral-400" /> },
|
||||
{ key: 'download', icon: <DownloadIcon size={16} className="text-neutral-400" /> },
|
||||
{ key: 'script', icon: <FileJsonIcon size={16} className="text-neutral-400" /> },
|
||||
{ key: 'terminal', icon: <TerminalSquareIcon size={16} className="text-neutral-400" /> },
|
||||
{ key: 'wol', icon: <NetworkIcon size={16} className="text-neutral-400" /> },
|
||||
{ key: 'power', icon: <PowerIcon size={16} className="text-neutral-400" /> }
|
||||
];
|
||||
|
||||
function updateItems(key: string) {
|
||||
@@ -45,10 +45,10 @@ export const MenuBar = () => {
|
||||
<span className="text-xs text-neutral-500">{t('settings.appearance.menuBarDesc')}</span>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col space-y-4 pt-5">
|
||||
<div className="mt-8 flex flex-col space-y-5">
|
||||
{items.map((item) => (
|
||||
<div key={item.key} className="flex items-center justify-between">
|
||||
<div className="flex items-center space-x-1">
|
||||
<div className="flex items-center space-x-2">
|
||||
{item.icon}
|
||||
<span>{t(`${item.key}.title`)}</span>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Input } from 'antd';
|
||||
import { useAtom } from 'jotai';
|
||||
import { GlobeIcon } from 'lucide-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import * as api from '@/api/vm.ts';
|
||||
@@ -46,26 +45,23 @@ export const WebTitle = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="mt-8 flex items-center justify-between space-x-5">
|
||||
<div className="flex flex-col">
|
||||
<div className="flex items-center space-x-1">
|
||||
<div className="flex size-[16px] items-center justify-center">
|
||||
<GlobeIcon size={14} />
|
||||
</div>
|
||||
<span>{t('settings.appearance.webTitle')}</span>
|
||||
</div>
|
||||
<span>{t('settings.appearance.webTitle')}</span>
|
||||
<span className="text-xs text-neutral-500">{t('settings.appearance.webTitleDesc')}</span>
|
||||
</div>
|
||||
|
||||
<Input
|
||||
disabled={isLoading}
|
||||
style={{ width: 180 }}
|
||||
value={webTitle}
|
||||
onChange={(e) => setWebTitle(e.target.value)}
|
||||
onPressEnter={submit}
|
||||
onBlur={submit}
|
||||
placeholder="NanoKVM"
|
||||
/>
|
||||
<div>
|
||||
<Input
|
||||
disabled={isLoading}
|
||||
style={{ width: 180 }}
|
||||
value={webTitle}
|
||||
onChange={(e) => setWebTitle(e.target.value)}
|
||||
onPressEnter={submit}
|
||||
onBlur={submit}
|
||||
placeholder="NanoKVM"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -2,12 +2,11 @@ import { Collapse } from 'antd';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { Swap } from './swap.tsx';
|
||||
import { Autostart } from './autostart.tsx';
|
||||
|
||||
const children = (
|
||||
<div className="py-3 space-y-6">
|
||||
<div className="space-y-6 py-3">
|
||||
<Swap />
|
||||
<Autostart />
|
||||
{/*<Autostart />*/}
|
||||
</div>
|
||||
);
|
||||
|
||||
|
||||
@@ -59,17 +59,17 @@ export const Swap = () => {
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex flex-col">
|
||||
<div className="flex flex-col space-y-1">
|
||||
<div className="flex items-center space-x-2">
|
||||
<span>{t('settings.device.swap.title')}</span>
|
||||
|
||||
<Tooltip
|
||||
title={t('settings.device.swap.tip')}
|
||||
className="cursor-pointer"
|
||||
placement="bottom"
|
||||
overlayStyle={{ maxWidth: '300px' }}
|
||||
placement="right"
|
||||
styles={{ root: { maxWidth: '400px' } }}
|
||||
>
|
||||
<CircleAlertIcon size={15} />
|
||||
<CircleAlertIcon className="text-neutral-500" size={14} />
|
||||
</Tooltip>
|
||||
</div>
|
||||
<span className="text-xs text-neutral-500">{t('settings.device.swap.description')}</span>
|
||||
|
||||
@@ -61,7 +61,7 @@ export const Hdmi = () => {
|
||||
<>
|
||||
{isPcie && (
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex flex-col">
|
||||
<div className="flex flex-col space-y-1">
|
||||
<span>HDMI</span>
|
||||
|
||||
<span className="text-xs text-neutral-500">
|
||||
|
||||
@@ -43,17 +43,17 @@ export const Mdns = () => {
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex flex-col">
|
||||
<div className="flex flex-col space-y-1">
|
||||
<div className="flex items-center space-x-2">
|
||||
<span>mDNS</span>
|
||||
|
||||
<Tooltip
|
||||
title={t('settings.device.mdns.tip')}
|
||||
className="cursor-pointer"
|
||||
placement="bottom"
|
||||
overlayStyle={{ maxWidth: '300px' }}
|
||||
placement="right"
|
||||
styles={{ root: { maxWidth: '400px' } }}
|
||||
>
|
||||
<CircleAlertIcon size={15} />
|
||||
<CircleAlertIcon className="text-neutral-500" size={14} />
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -84,7 +84,7 @@ export const MouseJiggler = () => {
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex flex-col">
|
||||
<div className="flex flex-col space-y-1">
|
||||
<span>{t('settings.device.mouseJiggler.title')}</span>
|
||||
<span className="text-xs text-neutral-500">
|
||||
{t('settings.device.mouseJiggler.description')}
|
||||
|
||||
@@ -54,7 +54,7 @@ export const Oled = () => {
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex flex-col">
|
||||
<div className="flex flex-col space-y-1">
|
||||
<span>{t('settings.device.oled.title')}</span>
|
||||
<span className="text-xs text-neutral-500">{t('settings.device.oled.description')}</span>
|
||||
</div>
|
||||
|
||||
@@ -43,17 +43,17 @@ export const Ssh = () => {
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex flex-col">
|
||||
<div className="flex flex-col space-y-1">
|
||||
<div className="flex items-center space-x-2">
|
||||
<span>SSH</span>
|
||||
|
||||
<Tooltip
|
||||
title={t('settings.device.ssh.tip')}
|
||||
className="cursor-pointer"
|
||||
placement="bottom"
|
||||
overlayStyle={{ maxWidth: '300px' }}
|
||||
placement="right"
|
||||
styles={{ root: { maxWidth: '400px' } }}
|
||||
>
|
||||
<CircleAlertIcon size={15} />
|
||||
<CircleAlertIcon className="text-neutral-500" size={14} />
|
||||
</Tooltip>
|
||||
</div>
|
||||
<span className="text-xs text-neutral-500">{t('settings.device.ssh.description')}</span>
|
||||
|
||||
@@ -47,17 +47,17 @@ export const Tls = () => {
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex flex-col">
|
||||
<div className="flex flex-col space-y-1">
|
||||
<div className="flex items-center space-x-2">
|
||||
<span>HTTPS</span>
|
||||
|
||||
<Tooltip
|
||||
title={t('settings.device.tls.tip')}
|
||||
className="cursor-pointer"
|
||||
placement="bottom"
|
||||
overlayStyle={{ maxWidth: '300px' }}
|
||||
placement="right"
|
||||
styles={{ root: { maxWidth: '400px' } }}
|
||||
>
|
||||
<CircleAlertIcon size={15} />
|
||||
<CircleAlertIcon className="text-neutral-500" size={14} />
|
||||
</Tooltip>
|
||||
</div>
|
||||
<span className="text-xs text-neutral-500">{t('settings.device.tls.description')}</span>
|
||||
|
||||
@@ -126,7 +126,7 @@ export const Wifi = () => {
|
||||
return (
|
||||
<>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex flex-col">
|
||||
<div className="flex flex-col space-y-1">
|
||||
<span>{t('settings.device.wifi.title')}</span>
|
||||
<span className="text-xs text-neutral-500">{t('settings.device.wifi.description')}</span>
|
||||
</div>
|
||||
|
||||
@@ -17,6 +17,7 @@ import * as api from '@/api/application.ts';
|
||||
import * as ls from '@/lib/localstorage.ts';
|
||||
import { isKeyboardEnableAtom } from '@/jotai/keyboard.ts';
|
||||
import { Tailscale as TailscaleIcon } from '@/components/icons/tailscale';
|
||||
import { ScrollArea } from '@/components/ui/scroll-area';
|
||||
|
||||
import { About } from './about';
|
||||
import { Account } from './account';
|
||||
@@ -119,23 +120,21 @@ export const Settings = () => {
|
||||
width={'80%'}
|
||||
centered={true}
|
||||
footer={null}
|
||||
destroyOnClose={true}
|
||||
destroyOnHidden={true}
|
||||
onCancel={closeModal}
|
||||
style={{ maxWidth: '1080px' }}
|
||||
styles={{ content: { padding: 0 } }}
|
||||
>
|
||||
<div className="flex h-[80vh] max-h-[700px] rounded-lg outline outline-1 outline-neutral-700">
|
||||
<div className="flex h-full max-w-[240px] flex-col space-y-0.5 rounded-l-lg bg-neutral-800 px-1 sm:w-1/5 md:px-2">
|
||||
<div className="flex h-full max-w-[260px] flex-col space-y-0.5 rounded-l-lg bg-neutral-800/90 px-1 sm:w-1/5 md:w-1/4 md:px-2">
|
||||
<div className="hidden px-3 pt-10 text-xl sm:block">{t('settings.title')}</div>
|
||||
|
||||
<div className="h-10 sm:h-5" />
|
||||
|
||||
{tabs.map((tab) => (
|
||||
<div
|
||||
key={tab.id}
|
||||
className={clsx(
|
||||
'flex cursor-pointer select-none items-center space-x-2 rounded-lg p-2 sm:px-3',
|
||||
currentTab === tab.id ? 'bg-neutral-700/70' : 'hover:bg-neutral-700'
|
||||
currentTab === tab.id ? 'bg-neutral-700/50' : 'hover:bg-neutral-700/50'
|
||||
)}
|
||||
onClick={() => changeTab(tab.id)}
|
||||
>
|
||||
@@ -156,11 +155,13 @@ export const Settings = () => {
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="flex h-full w-full flex-col items-center overflow-y-auto rounded-r-lg bg-neutral-900 px-3">
|
||||
<div className="w-full max-w-[600px] pb-10 pt-14">
|
||||
<>{tabs.find((tab) => tab.id === currentTab)?.component}</>
|
||||
<ScrollArea className="h-full w-full rounded-r-lg bg-neutral-900/50 px-3">
|
||||
<div className="flex h-full w-full justify-center">
|
||||
<div className="w-full max-w-[600px] pb-10 pt-14">
|
||||
<>{tabs.find((tab) => tab.id === currentTab)?.component}</>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</div>
|
||||
</Modal>
|
||||
</>
|
||||
|
||||
@@ -44,7 +44,7 @@ export const Header = ({ state, onSuccess }: HeaderProps) => {
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-base font-bold">{t('settings.tailscale.title')}</span>
|
||||
<span className="text-base">{t('settings.tailscale.title')}</span>
|
||||
|
||||
<div className="flex items-center space-x-2">
|
||||
{state && ['notLogin', 'stopped', 'running'].includes(state) && (
|
||||
@@ -101,7 +101,7 @@ export const Header = ({ state, onSuccess }: HeaderProps) => {
|
||||
placement="bottom"
|
||||
trigger="click"
|
||||
>
|
||||
<div className="flex cursor-pointer rounded p-1 text-white hover:bg-neutral-600">
|
||||
<div className="flex cursor-pointer rounded p-1 text-white hover:bg-neutral-700/50">
|
||||
<EllipsisIcon size={18} />
|
||||
</div>
|
||||
</Popover>
|
||||
|
||||
@@ -49,10 +49,10 @@ export const Tailscale = ({ setIsLocked }: TailscaleProps) => {
|
||||
return (
|
||||
<>
|
||||
<Header state={status?.state} onSuccess={getStatus} />
|
||||
<Divider />
|
||||
<Divider className="opacity-50" />
|
||||
|
||||
{isLoading ? (
|
||||
<div className="flex items-center justify-center space-x-2 pt-5 text-neutral-500">
|
||||
<div className="flex w-full items-center justify-center space-x-2 pt-5 text-neutral-500">
|
||||
<LoaderCircleIcon className="animate-spin" size={18} />
|
||||
<span>{t('settings.tailscale.loading')}</span>
|
||||
</div>
|
||||
|
||||
@@ -52,7 +52,7 @@ export const Memory = () => {
|
||||
title={t('settings.tailscale.memory.tip')}
|
||||
className="cursor-pointer text-neutral-500"
|
||||
placement="top"
|
||||
overlayStyle={{ maxWidth: '400px' }}
|
||||
styles={{ root: { maxWidth: '400px' } }}
|
||||
>
|
||||
<CircleHelpIcon size={15} />
|
||||
</Tooltip>
|
||||
|
||||
@@ -59,7 +59,7 @@ export const Swap = () => {
|
||||
title={t('settings.tailscale.swap.tip')}
|
||||
className="cursor-pointer text-neutral-500"
|
||||
placement="top"
|
||||
overlayStyle={{ maxWidth: '400px' }}
|
||||
styles={{ root: { maxWidth: '400px' } }}
|
||||
>
|
||||
<CircleHelpIcon size={15} />
|
||||
</Tooltip>
|
||||
|
||||
@@ -77,12 +77,12 @@ export const Update = ({ setIsLocked }: UpdateProps) => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="text-base font-bold">{t('settings.update.title')}</div>
|
||||
<Divider />
|
||||
<div className="text-base">{t('settings.update.title')}</div>
|
||||
<Divider className="opacity-50" />
|
||||
|
||||
<Preview />
|
||||
|
||||
<div className="my-[40px] h-px bg-neutral-500/10" />
|
||||
<Divider style={{ margin: '50px 0 10px 0', opacity: 0.5 }} />
|
||||
|
||||
<div className="flex min-h-[400px] flex-col justify-between">
|
||||
{status === 'loading' && (
|
||||
|
||||
@@ -56,17 +56,17 @@ export const Preview = () => {
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex flex-col">
|
||||
<div className="flex flex-col space-y-1">
|
||||
<div className="flex items-center space-x-2">
|
||||
<span>{t('settings.update.preview')}</span>
|
||||
|
||||
<Tooltip
|
||||
title={t('settings.update.previewTip')}
|
||||
className="cursor-pointer"
|
||||
placement="bottom"
|
||||
overlayStyle={{ maxWidth: '350px' }}
|
||||
placement="right"
|
||||
styles={{ root: { maxWidth: '400px' } }}
|
||||
>
|
||||
<CircleAlertIcon size={15} />
|
||||
<CircleAlertIcon className="text-neutral-500" size={15} />
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user