mirror of https://github.com/jetkvm/kvm.git
Compare commits
6 Commits
dde0f01545
...
740a44fe86
| Author | SHA1 | Date |
|---|---|---|
|
|
740a44fe86 | |
|
|
9438ab7778 | |
|
|
531f6bf65b | |
|
|
25e476e715 | |
|
|
8dd004ab54 | |
|
|
9f27a5d5c3 |
|
|
@ -24,7 +24,7 @@ const (
|
||||||
MaxMacrosPerDevice = 25
|
MaxMacrosPerDevice = 25
|
||||||
MaxStepsPerMacro = 10
|
MaxStepsPerMacro = 10
|
||||||
MaxKeysPerStep = 10
|
MaxKeysPerStep = 10
|
||||||
MinStepDelay = 50
|
MinStepDelay = 10
|
||||||
MaxStepDelay = 2000
|
MaxStepDelay = 2000
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -32,6 +32,10 @@ type KeyboardMacroStep struct {
|
||||||
Keys []string `json:"keys"`
|
Keys []string `json:"keys"`
|
||||||
Modifiers []string `json:"modifiers"`
|
Modifiers []string `json:"modifiers"`
|
||||||
Delay int `json:"delay"`
|
Delay int `json:"delay"`
|
||||||
|
// Optional: when set, this step types the given text using the configured keyboard layout.
|
||||||
|
// The delay value is treated as the per-character delay.
|
||||||
|
Text string `json:"text,omitempty"`
|
||||||
|
Wait bool `json:"wait,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *KeyboardMacroStep) Validate() error {
|
func (s *KeyboardMacroStep) Validate() error {
|
||||||
|
|
|
||||||
|
|
@ -1038,6 +1038,15 @@ func setKeyboardMacros(params KeyboardMacrosParams) (any, error) {
|
||||||
step.Delay = int(delay)
|
step.Delay = int(delay)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Optional text field for advanced steps
|
||||||
|
if txt, ok := stepMap["text"].(string); ok {
|
||||||
|
step.Text = txt
|
||||||
|
}
|
||||||
|
|
||||||
|
if wv, ok := stepMap["wait"].(bool); ok {
|
||||||
|
step.Wait = wv
|
||||||
|
}
|
||||||
|
|
||||||
steps = append(steps, step)
|
steps = append(steps, step)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,14 @@
|
||||||
{
|
{
|
||||||
"name": "kvm-ui",
|
"name": "kvm-ui",
|
||||||
"version": "2025.09.23.0000",
|
"version": "2025.09.26.01300",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "kvm-ui",
|
"name": "kvm-ui",
|
||||||
"version": "2025.09.23.0000",
|
"version": "2025.09.26.01300",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@headlessui/react": "^2.2.8",
|
"@headlessui/react": "^2.2.9",
|
||||||
"@headlessui/tailwindcss": "^0.2.2",
|
"@headlessui/tailwindcss": "^0.2.2",
|
||||||
"@heroicons/react": "^2.2.0",
|
"@heroicons/react": "^2.2.0",
|
||||||
"@vitejs/plugin-basic-ssl": "^2.1.0",
|
"@vitejs/plugin-basic-ssl": "^2.1.0",
|
||||||
|
|
@ -22,7 +22,7 @@
|
||||||
"dayjs": "^1.11.18",
|
"dayjs": "^1.11.18",
|
||||||
"eslint-import-resolver-alias": "^1.1.2",
|
"eslint-import-resolver-alias": "^1.1.2",
|
||||||
"focus-trap-react": "^11.0.4",
|
"focus-trap-react": "^11.0.4",
|
||||||
"framer-motion": "^12.23.18",
|
"framer-motion": "^12.23.22",
|
||||||
"lodash.throttle": "^4.1.1",
|
"lodash.throttle": "^4.1.1",
|
||||||
"mini-svg-data-uri": "^1.4.4",
|
"mini-svg-data-uri": "^1.4.4",
|
||||||
"react": "^19.1.1",
|
"react": "^19.1.1",
|
||||||
|
|
@ -30,7 +30,7 @@
|
||||||
"react-dom": "^19.1.1",
|
"react-dom": "^19.1.1",
|
||||||
"react-hot-toast": "^2.6.0",
|
"react-hot-toast": "^2.6.0",
|
||||||
"react-icons": "^5.5.0",
|
"react-icons": "^5.5.0",
|
||||||
"react-router": "^7.9.1",
|
"react-router": "^7.9.3",
|
||||||
"react-simple-keyboard": "^3.8.122",
|
"react-simple-keyboard": "^3.8.122",
|
||||||
"react-use-websocket": "^4.13.0",
|
"react-use-websocket": "^4.13.0",
|
||||||
"react-xtermjs": "^1.0.10",
|
"react-xtermjs": "^1.0.10",
|
||||||
|
|
@ -46,9 +46,9 @@
|
||||||
"@eslint/js": "^9.36.0",
|
"@eslint/js": "^9.36.0",
|
||||||
"@tailwindcss/forms": "^0.5.10",
|
"@tailwindcss/forms": "^0.5.10",
|
||||||
"@tailwindcss/postcss": "^4.1.13",
|
"@tailwindcss/postcss": "^4.1.13",
|
||||||
"@tailwindcss/typography": "^0.5.18",
|
"@tailwindcss/typography": "^0.5.19",
|
||||||
"@tailwindcss/vite": "^4.1.13",
|
"@tailwindcss/vite": "^4.1.13",
|
||||||
"@types/react": "^19.1.13",
|
"@types/react": "^19.1.14",
|
||||||
"@types/react-dom": "^19.1.9",
|
"@types/react-dom": "^19.1.9",
|
||||||
"@types/semver": "^7.7.1",
|
"@types/semver": "^7.7.1",
|
||||||
"@types/validator": "^13.15.3",
|
"@types/validator": "^13.15.3",
|
||||||
|
|
@ -61,7 +61,7 @@
|
||||||
"eslint-plugin-import": "^2.32.0",
|
"eslint-plugin-import": "^2.32.0",
|
||||||
"eslint-plugin-react": "^7.37.5",
|
"eslint-plugin-react": "^7.37.5",
|
||||||
"eslint-plugin-react-hooks": "^5.2.0",
|
"eslint-plugin-react-hooks": "^5.2.0",
|
||||||
"eslint-plugin-react-refresh": "^0.4.21",
|
"eslint-plugin-react-refresh": "^0.4.22",
|
||||||
"globals": "^16.4.0",
|
"globals": "^16.4.0",
|
||||||
"postcss": "^8.5.6",
|
"postcss": "^8.5.6",
|
||||||
"prettier": "^3.6.2",
|
"prettier": "^3.6.2",
|
||||||
|
|
@ -723,9 +723,9 @@
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@headlessui/react": {
|
"node_modules/@headlessui/react": {
|
||||||
"version": "2.2.8",
|
"version": "2.2.9",
|
||||||
"resolved": "https://registry.npmjs.org/@headlessui/react/-/react-2.2.8.tgz",
|
"resolved": "https://registry.npmjs.org/@headlessui/react/-/react-2.2.9.tgz",
|
||||||
"integrity": "sha512-vkiZulDC0lFeTrZTbA4tHvhZHvkUb2PFh5xJ1BvWAZdRK0fayMKO1QEO4inWkXxK1i0I1rcwwu1d6mo0K7Pcbw==",
|
"integrity": "sha512-Mb+Un58gwBn0/yWZfyrCh0TJyurtT+dETj7YHleylHk5od3dv2XqETPGWMyQ5/7sYN7oWdyM1u9MvC0OC8UmzQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@floating-ui/react": "^0.26.16",
|
"@floating-ui/react": "^0.26.16",
|
||||||
|
|
@ -1043,9 +1043,9 @@
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-android-arm-eabi": {
|
"node_modules/@rollup/rollup-android-arm-eabi": {
|
||||||
"version": "4.52.0",
|
"version": "4.52.2",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.2.tgz",
|
||||||
"integrity": "sha512-VxDYCDqOaR7NXzAtvRx7G1u54d2kEHopb28YH/pKzY6y0qmogP3gG7CSiWsq9WvDFxOQMpNEyjVAHZFXfH3o/A==",
|
"integrity": "sha512-o3pcKzJgSGt4d74lSZ+OCnHwkKBeAbFDmbEm5gg70eA8VkyCuC/zV9TwBnmw6VjDlRdF4Pshfb+WE9E6XY1PoQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
|
|
@ -1056,9 +1056,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-android-arm64": {
|
"node_modules/@rollup/rollup-android-arm64": {
|
||||||
"version": "4.52.0",
|
"version": "4.52.2",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.52.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.52.2.tgz",
|
||||||
"integrity": "sha512-pqDirm8koABIKvzL59YI9W9DWbRlTX7RWhN+auR8HXJxo89m4mjqbah7nJZjeKNTNYopqL+yGg+0mhCpf3xZtQ==",
|
"integrity": "sha512-cqFSWO5tX2vhC9hJTK8WAiPIm4Q8q/cU8j2HQA0L3E1uXvBYbOZMhE2oFL8n2pKB5sOCHY6bBuHaRwG7TkfJyw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
|
@ -1069,9 +1069,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-darwin-arm64": {
|
"node_modules/@rollup/rollup-darwin-arm64": {
|
||||||
"version": "4.52.0",
|
"version": "4.52.2",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.2.tgz",
|
||||||
"integrity": "sha512-YCdWlY/8ltN6H78HnMsRHYlPiKvqKagBP1r+D7SSylxX+HnsgXGCmLiV3Y4nSyY9hW8qr8U9LDUx/Lo7M6MfmQ==",
|
"integrity": "sha512-vngduywkkv8Fkh3wIZf5nFPXzWsNsVu1kvtLETWxTFf/5opZmflgVSeLgdHR56RQh71xhPhWoOkEBvbehwTlVA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
|
@ -1082,9 +1082,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-darwin-x64": {
|
"node_modules/@rollup/rollup-darwin-x64": {
|
||||||
"version": "4.52.0",
|
"version": "4.52.2",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.2.tgz",
|
||||||
"integrity": "sha512-z4nw6y1j+OOSGzuVbSWdIp1IUks9qNw4dc7z7lWuWDKojY38VMWBlEN7F9jk5UXOkUcp97vA1N213DF+Lz8BRg==",
|
"integrity": "sha512-h11KikYrUCYTrDj6h939hhMNlqU2fo/X4NB0OZcys3fya49o1hmFaczAiJWVAFgrM1NCP6RrO7lQKeVYSKBPSQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
|
@ -1095,9 +1095,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-freebsd-arm64": {
|
"node_modules/@rollup/rollup-freebsd-arm64": {
|
||||||
"version": "4.52.0",
|
"version": "4.52.2",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.52.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.52.2.tgz",
|
||||||
"integrity": "sha512-Q/dv9Yvyr5rKlK8WQJZVrp5g2SOYeZUs9u/t2f9cQ2E0gJjYB/BWoedXfUT0EcDJefi2zzVfhcOj8drWCzTviw==",
|
"integrity": "sha512-/eg4CI61ZUkLXxMHyVlmlGrSQZ34xqWlZNW43IAU4RmdzWEx0mQJ2mN/Cx4IHLVZFL6UBGAh+/GXhgvGb+nVxw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
|
@ -1108,9 +1108,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-freebsd-x64": {
|
"node_modules/@rollup/rollup-freebsd-x64": {
|
||||||
"version": "4.52.0",
|
"version": "4.52.2",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.52.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.52.2.tgz",
|
||||||
"integrity": "sha512-kdBsLs4Uile/fbjZVvCRcKB4q64R+1mUq0Yd7oU1CMm1Av336ajIFqNFovByipciuUQjBCPMxwJhCgfG2re3rg==",
|
"integrity": "sha512-QOWgFH5X9+p+S1NAfOqc0z8qEpJIoUHf7OWjNUGOeW18Mx22lAUOiA9b6r2/vpzLdfxi/f+VWsYjUOMCcYh0Ng==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
|
@ -1121,9 +1121,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
|
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
|
||||||
"version": "4.52.0",
|
"version": "4.52.2",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.52.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.52.2.tgz",
|
||||||
"integrity": "sha512-aL6hRwu0k7MTUESgkg7QHY6CoqPgr6gdQXRJI1/VbFlUMwsSzPGSR7sG5d+MCbYnJmJwThc2ol3nixj1fvI/zQ==",
|
"integrity": "sha512-kDWSPafToDd8LcBYd1t5jw7bD5Ojcu12S3uT372e5HKPzQt532vW+rGFFOaiR0opxePyUkHrwz8iWYEyH1IIQA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
|
|
@ -1134,9 +1134,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
|
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
|
||||||
"version": "4.52.0",
|
"version": "4.52.2",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.52.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.52.2.tgz",
|
||||||
"integrity": "sha512-BTs0M5s1EJejgIBJhCeiFo7GZZ2IXWkFGcyZhxX4+8usnIo5Mti57108vjXFIQmmJaRyDwmV59Tw64Ap1dkwMw==",
|
"integrity": "sha512-gKm7Mk9wCv6/rkzwCiUC4KnevYhlf8ztBrDRT9g/u//1fZLapSRc+eDZj2Eu2wpJ+0RzUKgtNijnVIB4ZxyL+w==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
|
|
@ -1147,9 +1147,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-arm64-gnu": {
|
"node_modules/@rollup/rollup-linux-arm64-gnu": {
|
||||||
"version": "4.52.0",
|
"version": "4.52.2",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.52.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.52.2.tgz",
|
||||||
"integrity": "sha512-uj672IVOU9m08DBGvoPKPi/J8jlVgjh12C9GmjjBxCTQc3XtVmRkRKyeHSmIKQpvJ7fIm1EJieBUcnGSzDVFyw==",
|
"integrity": "sha512-66lA8vnj5mB/rtDNwPgrrKUOtCLVQypkyDa2gMfOefXK6rcZAxKLO9Fy3GkW8VkPnENv9hBkNOFfGLf6rNKGUg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
|
@ -1160,9 +1160,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-arm64-musl": {
|
"node_modules/@rollup/rollup-linux-arm64-musl": {
|
||||||
"version": "4.52.0",
|
"version": "4.52.2",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.52.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.52.2.tgz",
|
||||||
"integrity": "sha512-/+IVbeDMDCtB/HP/wiWsSzduD10SEGzIZX2945KSgZRNi4TSkjHqRJtNTVtVb8IRwhJ65ssI56krlLik+zFWkw==",
|
"integrity": "sha512-s+OPucLNdJHvuZHuIz2WwncJ+SfWHFEmlC5nKMUgAelUeBUnlB4wt7rXWiyG4Zn07uY2Dd+SGyVa9oyLkVGOjA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
|
@ -1173,9 +1173,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-loong64-gnu": {
|
"node_modules/@rollup/rollup-linux-loong64-gnu": {
|
||||||
"version": "4.52.0",
|
"version": "4.52.2",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.52.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.52.2.tgz",
|
||||||
"integrity": "sha512-U1vVzvSWtSMWKKrGoROPBXMh3Vwn93TA9V35PldokHGqiUbF6erSzox/5qrSMKp6SzakvyjcPiVF8yB1xKr9Pg==",
|
"integrity": "sha512-8wTRM3+gVMDLLDdaT6tKmOE3lJyRy9NpJUS/ZRWmLCmOPIJhVyXwjBo+XbrrwtV33Em1/eCTd5TuGJm4+DmYjw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"loong64"
|
"loong64"
|
||||||
],
|
],
|
||||||
|
|
@ -1186,9 +1186,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-ppc64-gnu": {
|
"node_modules/@rollup/rollup-linux-ppc64-gnu": {
|
||||||
"version": "4.52.0",
|
"version": "4.52.2",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.52.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.52.2.tgz",
|
||||||
"integrity": "sha512-X/4WfuBAdQRH8cK3DYl8zC00XEE6aM472W+QCycpQJeLWVnHfkv7RyBFVaTqNUMsTgIX8ihMjCvFF9OUgeABzw==",
|
"integrity": "sha512-6yqEfgJ1anIeuP2P/zhtfBlDpXUb80t8DpbYwXQ3bQd95JMvUaqiX+fKqYqUwZXqdJDd8xdilNtsHM2N0cFm6A==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ppc64"
|
"ppc64"
|
||||||
],
|
],
|
||||||
|
|
@ -1199,9 +1199,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
|
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
|
||||||
"version": "4.52.0",
|
"version": "4.52.2",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.52.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.52.2.tgz",
|
||||||
"integrity": "sha512-xIRYc58HfWDBZoLmWfWXg2Sq8VCa2iJ32B7mqfWnkx5mekekl0tMe7FHpY8I72RXEcUkaWawRvl3qA55og+cwQ==",
|
"integrity": "sha512-sshYUiYVSEI2B6dp4jMncwxbrUqRdNApF2c3bhtLAU0qA8Lrri0p0NauOsTWh3yCCCDyBOjESHMExonp7Nzc0w==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"riscv64"
|
"riscv64"
|
||||||
],
|
],
|
||||||
|
|
@ -1212,9 +1212,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-riscv64-musl": {
|
"node_modules/@rollup/rollup-linux-riscv64-musl": {
|
||||||
"version": "4.52.0",
|
"version": "4.52.2",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.52.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.52.2.tgz",
|
||||||
"integrity": "sha512-mbsoUey05WJIOz8U1WzNdf+6UMYGwE3fZZnQqsM22FZ3wh1N887HT6jAOjXs6CNEK3Ntu2OBsyQDXfIjouI4dw==",
|
"integrity": "sha512-duBLgd+3pqC4MMwBrKkFxaZerUxZcYApQVC5SdbF5/e/589GwVvlRUnyqMFbM8iUSb1BaoX/3fRL7hB9m2Pj8Q==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"riscv64"
|
"riscv64"
|
||||||
],
|
],
|
||||||
|
|
@ -1225,9 +1225,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-s390x-gnu": {
|
"node_modules/@rollup/rollup-linux-s390x-gnu": {
|
||||||
"version": "4.52.0",
|
"version": "4.52.2",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.52.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.52.2.tgz",
|
||||||
"integrity": "sha512-qP6aP970bucEi5KKKR4AuPFd8aTx9EF6BvutvYxmZuWLJHmnq4LvBfp0U+yFDMGwJ+AIJEH5sIP+SNypauMWzg==",
|
"integrity": "sha512-tzhYJJidDUVGMgVyE+PmxENPHlvvqm1KILjjZhB8/xHYqAGeizh3GBGf9u6WdJpZrz1aCpIIHG0LgJgH9rVjHQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"s390x"
|
"s390x"
|
||||||
],
|
],
|
||||||
|
|
@ -1238,9 +1238,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-x64-gnu": {
|
"node_modules/@rollup/rollup-linux-x64-gnu": {
|
||||||
"version": "4.52.0",
|
"version": "4.52.2",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.2.tgz",
|
||||||
"integrity": "sha512-nmSVN+F2i1yKZ7rJNKO3G7ZzmxJgoQBQZ/6c4MuS553Grmr7WqR7LLDcYG53Z2m9409z3JLt4sCOhLdbKQ3HmA==",
|
"integrity": "sha512-opH8GSUuVcCSSyHHcl5hELrmnk4waZoVpgn/4FDao9iyE4WpQhyWJ5ryl5M3ocp4qkRuHfyXnGqg8M9oKCEKRA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
|
@ -1251,9 +1251,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-x64-musl": {
|
"node_modules/@rollup/rollup-linux-x64-musl": {
|
||||||
"version": "4.52.0",
|
"version": "4.52.2",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.2.tgz",
|
||||||
"integrity": "sha512-2d0qRo33G6TfQVjaMR71P+yJVGODrt5V6+T0BDYH4EMfGgdC/2HWDVjSSFw888GSzAZUwuska3+zxNUCDco6rQ==",
|
"integrity": "sha512-LSeBHnGli1pPKVJ79ZVJgeZWWZXkEe/5o8kcn23M8eMKCUANejchJbF/JqzM4RRjOJfNRhKJk8FuqL1GKjF5oQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
|
@ -1264,9 +1264,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-openharmony-arm64": {
|
"node_modules/@rollup/rollup-openharmony-arm64": {
|
||||||
"version": "4.52.0",
|
"version": "4.52.2",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.52.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.52.2.tgz",
|
||||||
"integrity": "sha512-A1JalX4MOaFAAyGgpO7XP5khquv/7xKzLIyLmhNrbiCxWpMlnsTYr8dnsWM7sEeotNmxvSOEL7F65j0HXFcFsw==",
|
"integrity": "sha512-uPj7MQ6/s+/GOpolavm6BPo+6CbhbKYyZHUDvZ/SmJM7pfDBgdGisFX3bY/CBDMg2ZO4utfhlApkSfZ92yXw7Q==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
|
@ -1277,9 +1277,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-win32-arm64-msvc": {
|
"node_modules/@rollup/rollup-win32-arm64-msvc": {
|
||||||
"version": "4.52.0",
|
"version": "4.52.2",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.52.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.52.2.tgz",
|
||||||
"integrity": "sha512-YQugafP/rH0eOOHGjmNgDURrpYHrIX0yuojOI8bwCyXwxC9ZdTd3vYkmddPX0oHONLXu9Rb1dDmT0VNpjkzGGw==",
|
"integrity": "sha512-Z9MUCrSgIaUeeHAiNkm3cQyst2UhzjPraR3gYYfOjAuZI7tcFRTOD+4cHLPoS/3qinchth+V56vtqz1Tv+6KPA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
|
@ -1290,9 +1290,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-win32-ia32-msvc": {
|
"node_modules/@rollup/rollup-win32-ia32-msvc": {
|
||||||
"version": "4.52.0",
|
"version": "4.52.2",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.52.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.52.2.tgz",
|
||||||
"integrity": "sha512-zYdUYhi3Qe2fndujBqL5FjAFzvNeLxtIqfzNEVKD1I7C37/chv1VxhscWSQHTNfjPCrBFQMnynwA3kpZpZ8w4A==",
|
"integrity": "sha512-+GnYBmpjldD3XQd+HMejo+0gJGwYIOfFeoBQv32xF/RUIvccUz20/V6Otdv+57NE70D5pa8W/jVGDoGq0oON4A==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ia32"
|
"ia32"
|
||||||
],
|
],
|
||||||
|
|
@ -1303,9 +1303,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-win32-x64-gnu": {
|
"node_modules/@rollup/rollup-win32-x64-gnu": {
|
||||||
"version": "4.52.0",
|
"version": "4.52.2",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.2.tgz",
|
||||||
"integrity": "sha512-fGk03kQylNaCOQ96HDMeT7E2n91EqvCDd3RwvT5k+xNdFCeMGnj5b5hEgTGrQuyidqSsD3zJDQ21QIaxXqTBJw==",
|
"integrity": "sha512-ApXFKluSB6kDQkAqZOKXBjiaqdF1BlKi+/eqnYe9Ee7U2K3pUDKsIyr8EYm/QDHTJIM+4X+lI0gJc3TTRhd+dA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
|
@ -1316,9 +1316,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-win32-x64-msvc": {
|
"node_modules/@rollup/rollup-win32-x64-msvc": {
|
||||||
"version": "4.52.0",
|
"version": "4.52.2",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.52.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.52.2.tgz",
|
||||||
"integrity": "sha512-6iKDCVSIUQ8jPMoIV0OytRKniaYyy5EbY/RRydmLW8ZR3cEBhxbWl5ro0rkUNe0ef6sScvhbY79HrjRm8i3vDQ==",
|
"integrity": "sha512-ARz+Bs8kY6FtitYM96PqPEVvPXqEZmPZsSkXvyX19YzDqkCaIlhCieLLMI5hxO9SRZ2XtCtm8wxhy0iJ2jxNfw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
|
@ -1347,15 +1347,15 @@
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@swc/core": {
|
"node_modules/@swc/core": {
|
||||||
"version": "1.13.5",
|
"version": "1.13.19",
|
||||||
"resolved": "https://registry.npmjs.org/@swc/core/-/core-1.13.5.tgz",
|
"resolved": "https://registry.npmjs.org/@swc/core/-/core-1.13.19.tgz",
|
||||||
"integrity": "sha512-WezcBo8a0Dg2rnR82zhwoR6aRNxeTGfK5QCD6TQ+kg3xx/zNT02s/0o+81h/3zhvFSB24NtqEr8FTw88O5W/JQ==",
|
"integrity": "sha512-V1r4wFdjaZIUIZZrV2Mb/prEeu03xvSm6oatPxsvnXKF9lNh5Jtk9QvUdiVfD9rrvi7bXrAVhg9Wpbmv/2Fl1g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@swc/counter": "^0.1.3",
|
"@swc/counter": "^0.1.3",
|
||||||
"@swc/types": "^0.1.24"
|
"@swc/types": "^0.1.25"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=10"
|
"node": ">=10"
|
||||||
|
|
@ -1365,16 +1365,16 @@
|
||||||
"url": "https://opencollective.com/swc"
|
"url": "https://opencollective.com/swc"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@swc/core-darwin-arm64": "1.13.5",
|
"@swc/core-darwin-arm64": "1.13.19",
|
||||||
"@swc/core-darwin-x64": "1.13.5",
|
"@swc/core-darwin-x64": "1.13.19",
|
||||||
"@swc/core-linux-arm-gnueabihf": "1.13.5",
|
"@swc/core-linux-arm-gnueabihf": "1.13.19",
|
||||||
"@swc/core-linux-arm64-gnu": "1.13.5",
|
"@swc/core-linux-arm64-gnu": "1.13.19",
|
||||||
"@swc/core-linux-arm64-musl": "1.13.5",
|
"@swc/core-linux-arm64-musl": "1.13.19",
|
||||||
"@swc/core-linux-x64-gnu": "1.13.5",
|
"@swc/core-linux-x64-gnu": "1.13.19",
|
||||||
"@swc/core-linux-x64-musl": "1.13.5",
|
"@swc/core-linux-x64-musl": "1.13.19",
|
||||||
"@swc/core-win32-arm64-msvc": "1.13.5",
|
"@swc/core-win32-arm64-msvc": "1.13.19",
|
||||||
"@swc/core-win32-ia32-msvc": "1.13.5",
|
"@swc/core-win32-ia32-msvc": "1.13.19",
|
||||||
"@swc/core-win32-x64-msvc": "1.13.5"
|
"@swc/core-win32-x64-msvc": "1.13.19"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@swc/helpers": ">=0.5.17"
|
"@swc/helpers": ">=0.5.17"
|
||||||
|
|
@ -1386,9 +1386,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@swc/core-darwin-arm64": {
|
"node_modules/@swc/core-darwin-arm64": {
|
||||||
"version": "1.13.5",
|
"version": "1.13.19",
|
||||||
"resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.13.5.tgz",
|
"resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.13.19.tgz",
|
||||||
"integrity": "sha512-lKNv7SujeXvKn16gvQqUQI5DdyY8v7xcoO3k06/FJbHJS90zEwZdQiMNRiqpYw/orU543tPaWgz7cIYWhbopiQ==",
|
"integrity": "sha512-NxDyte9tCJSJ8+R62WDtqwg8eI57lubD52sHyGOfezpJBOPr36bUSGGLyO3Vod9zTGlOu2CpkuzA/2iVw92u1g==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
|
@ -1403,9 +1403,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@swc/core-darwin-x64": {
|
"node_modules/@swc/core-darwin-x64": {
|
||||||
"version": "1.13.5",
|
"version": "1.13.19",
|
||||||
"resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.13.5.tgz",
|
"resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.13.19.tgz",
|
||||||
"integrity": "sha512-ILd38Fg/w23vHb0yVjlWvQBoE37ZJTdlLHa8LRCFDdX4WKfnVBiblsCU9ar4QTMNdeTBEX9iUF4IrbNWhaF1Ng==",
|
"integrity": "sha512-+w5DYrJndSygFFRDcuPYmx5BljD6oYnAohZ15K1L6SfORHp/BTSIbgSFRKPoyhjuIkDiq3W0um8RoMTOBAcQjQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
|
@ -1420,9 +1420,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@swc/core-linux-arm-gnueabihf": {
|
"node_modules/@swc/core-linux-arm-gnueabihf": {
|
||||||
"version": "1.13.5",
|
"version": "1.13.19",
|
||||||
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.13.5.tgz",
|
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.13.19.tgz",
|
||||||
"integrity": "sha512-Q6eS3Pt8GLkXxqz9TAw+AUk9HpVJt8Uzm54MvPsqp2yuGmY0/sNaPPNVqctCX9fu/Nu8eaWUen0si6iEiCsazQ==",
|
"integrity": "sha512-7LlfgpdwwYq2q7himNkAAFo4q6jysMLFNoBH6GRP7WL29NcSsl5mPMJjmYZymK+sYq/9MTVieDTQvChzYDsapw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
|
|
@ -1437,9 +1437,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@swc/core-linux-arm64-gnu": {
|
"node_modules/@swc/core-linux-arm64-gnu": {
|
||||||
"version": "1.13.5",
|
"version": "1.13.19",
|
||||||
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.13.5.tgz",
|
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.13.19.tgz",
|
||||||
"integrity": "sha512-aNDfeN+9af+y+M2MYfxCzCy/VDq7Z5YIbMqRI739o8Ganz6ST+27kjQFd8Y/57JN/hcnUEa9xqdS3XY7WaVtSw==",
|
"integrity": "sha512-ml3I6Lm2marAQ3UC/TS9t/yILBh/eDSVHAdPpikp652xouWAVW1znUeV6bBSxe1sSZIenv+p55ubKAWq/u84sQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
|
@ -1454,9 +1454,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@swc/core-linux-arm64-musl": {
|
"node_modules/@swc/core-linux-arm64-musl": {
|
||||||
"version": "1.13.5",
|
"version": "1.13.19",
|
||||||
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.13.5.tgz",
|
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.13.19.tgz",
|
||||||
"integrity": "sha512-9+ZxFN5GJag4CnYnq6apKTnnezpfJhCumyz0504/JbHLo+Ue+ZtJnf3RhyA9W9TINtLE0bC4hKpWi8ZKoETyOQ==",
|
"integrity": "sha512-M/otFc3/rWWkbF6VgbOXVzUKVoE7MFcphTaStxJp4bwb7oP5slYlxMZN51Dk/OTOfvCDo9pTAFDKNyixbkXMDQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
|
@ -1471,9 +1471,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@swc/core-linux-x64-gnu": {
|
"node_modules/@swc/core-linux-x64-gnu": {
|
||||||
"version": "1.13.5",
|
"version": "1.13.19",
|
||||||
"resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.13.5.tgz",
|
"resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.13.19.tgz",
|
||||||
"integrity": "sha512-WD530qvHrki8Ywt/PloKUjaRKgstQqNGvmZl54g06kA+hqtSE2FTG9gngXr3UJxYu/cNAjJYiBifm7+w4nbHbA==",
|
"integrity": "sha512-NoMUKaOJEdouU4tKF88ggdDHFiRRING+gYLxDqnTfm+sUXaizB5OGBRzvSVDYSXQb1SuUuChnXFPFzwTWbt3ZQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
|
@ -1488,9 +1488,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@swc/core-linux-x64-musl": {
|
"node_modules/@swc/core-linux-x64-musl": {
|
||||||
"version": "1.13.5",
|
"version": "1.13.19",
|
||||||
"resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.13.5.tgz",
|
"resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.13.19.tgz",
|
||||||
"integrity": "sha512-Luj8y4OFYx4DHNQTWjdIuKTq2f5k6uSXICqx+FSabnXptaOBAbJHNbHT/06JZh6NRUouaf0mYXN0mcsqvkhd7Q==",
|
"integrity": "sha512-r6krlZwyu8SBaw24QuS1lau2I9q8M+eJV6ITz0rpb6P1Bx0elf9ii5Bhh8ddmIqXXH8kOGSjC/dwcdHbZqAhgw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
|
@ -1505,9 +1505,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@swc/core-win32-arm64-msvc": {
|
"node_modules/@swc/core-win32-arm64-msvc": {
|
||||||
"version": "1.13.5",
|
"version": "1.13.19",
|
||||||
"resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.13.5.tgz",
|
"resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.13.19.tgz",
|
||||||
"integrity": "sha512-cZ6UpumhF9SDJvv4DA2fo9WIzlNFuKSkZpZmPG1c+4PFSEMy5DFOjBSllCvnqihCabzXzpn6ykCwBmHpy31vQw==",
|
"integrity": "sha512-awcZSIuxyVn0Dw28VjMvgk1qiDJ6CeQwHkZNUjg2UxVlq23zE01NMMp+zkoGFypmLG9gaGmJSzuoqvk/WCQ5tw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
|
@ -1522,9 +1522,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@swc/core-win32-ia32-msvc": {
|
"node_modules/@swc/core-win32-ia32-msvc": {
|
||||||
"version": "1.13.5",
|
"version": "1.13.19",
|
||||||
"resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.13.5.tgz",
|
"resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.13.19.tgz",
|
||||||
"integrity": "sha512-C5Yi/xIikrFUzZcyGj9L3RpKljFvKiDMtyDzPKzlsDrKIw2EYY+bF88gB6oGY5RGmv4DAX8dbnpRAqgFD0FMEw==",
|
"integrity": "sha512-H5d+KO7ISoLNgYvTbOcCQjJZNM3R7yaYlrMAF13lUr6GSiOUX+92xtM31B+HvzAWI7HtvVe74d29aC1b1TpXFA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ia32"
|
"ia32"
|
||||||
],
|
],
|
||||||
|
|
@ -1539,9 +1539,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@swc/core-win32-x64-msvc": {
|
"node_modules/@swc/core-win32-x64-msvc": {
|
||||||
"version": "1.13.5",
|
"version": "1.13.19",
|
||||||
"resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.13.5.tgz",
|
"resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.13.19.tgz",
|
||||||
"integrity": "sha512-YrKdMVxbYmlfybCSbRtrilc6UA8GF5aPmGKBdPvjrarvsmf4i7ZHGCEnLtfOMd3Lwbs2WUZq3WdMbozYeLU93Q==",
|
"integrity": "sha512-qNoyCpXvv2O3JqXKanRIeoMn03Fho/As+N4Fhe7u0FsYh4VYqGQah4DGDzEP/yjl4Gx1IElhqLGDhCCGMwWaDw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
|
@ -1871,9 +1871,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tailwindcss/typography": {
|
"node_modules/@tailwindcss/typography": {
|
||||||
"version": "0.5.18",
|
"version": "0.5.19",
|
||||||
"resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.18.tgz",
|
"resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.19.tgz",
|
||||||
"integrity": "sha512-dDIgwZOlf+tVkZ7A029VvQ1+ngKATENDjMEx2N35s2yPjfTS05RWSM8ilhEWSa5DMJ6ci2Ha9WNZEd2GQjrdQg==",
|
"integrity": "sha512-w31dd8HOx3k9vPtcQh5QHP9GwKcgbMp87j58qi6xgiBnFFtKEAgCWnDw4qUT8aHwkCp8bKvb/KGKWWHedP0AAg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
@ -2007,9 +2007,9 @@
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@types/react": {
|
"node_modules/@types/react": {
|
||||||
"version": "19.1.13",
|
"version": "19.1.14",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.13.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.14.tgz",
|
||||||
"integrity": "sha512-hHkbU/eoO3EG5/MZkuFSKmYqPbSVk5byPFa3e7y/8TybHiLMACgI8seVYlicwk7H5K/rI2px9xrQp/C+AUDTiQ==",
|
"integrity": "sha512-ukd93VGzaNPMAUPy0gRDSC57UuQbnH9Kussp7HBjM06YFi9uZTFhOvMSO2OKqXm1rSgzOE+pVx1k1PYHGwlc8Q==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"csstype": "^3.0.2"
|
"csstype": "^3.0.2"
|
||||||
|
|
@ -2679,9 +2679,9 @@
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/baseline-browser-mapping": {
|
"node_modules/baseline-browser-mapping": {
|
||||||
"version": "2.8.6",
|
"version": "2.8.7",
|
||||||
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.6.tgz",
|
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.7.tgz",
|
||||||
"integrity": "sha512-wrH5NNqren/QMtKUEEJf7z86YjfqW/2uw3IL3/xpqZUC95SSVIFXYQeeGjL6FT/X68IROu6RMehZQS5foy2BXw==",
|
"integrity": "sha512-bxxN2M3a4d1CRoQC//IqsR5XrLh0IJ8TCv2x6Y9N0nckNz/rTjZB3//GGscZziZOxmjP55rzxg/ze7usFI9FqQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"bin": {
|
"bin": {
|
||||||
|
|
@ -2802,9 +2802,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/caniuse-lite": {
|
"node_modules/caniuse-lite": {
|
||||||
"version": "1.0.30001743",
|
"version": "1.0.30001745",
|
||||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001743.tgz",
|
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001745.tgz",
|
||||||
"integrity": "sha512-e6Ojr7RV14Un7dz6ASD0aZDmQPT/A+eZU+nuTNfjqmRrmkmQlnTNWH0SKmqagx9PeW87UVqapSurtAXifmtdmw==",
|
"integrity": "sha512-ywt6i8FzvdgrrrGbr1jZVObnVv6adj+0if2/omv9cmR2oiZs30zL4DIyaptKcbOrBdOIc74QTMoJvSE2QHh5UQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
|
|
@ -3185,9 +3185,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/detect-libc": {
|
"node_modules/detect-libc": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.1.tgz",
|
||||||
"integrity": "sha512-vEtk+OcP7VBRtQZ1EJ3bdgzSfBjgnEalLTp5zjJrS+2Z1w2KZly4SBdac/WDU3hhsNAZ9E8SC96ME4Ey8MZ7cg==",
|
"integrity": "sha512-ecqj/sy1jcK1uWrwpR67UhYrIFQ+5WlGxth34WquCbamhFA6hkkwiu37o6J5xCHdo1oixJRfVRw+ywV+Hq/0Aw==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"engines": {
|
"engines": {
|
||||||
|
|
@ -3221,9 +3221,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/electron-to-chromium": {
|
"node_modules/electron-to-chromium": {
|
||||||
"version": "1.5.222",
|
"version": "1.5.224",
|
||||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.222.tgz",
|
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.224.tgz",
|
||||||
"integrity": "sha512-gA7psSwSwQRE60CEoLz6JBCQPIxNeuzB2nL8vE03GK/OHxlvykbLyeiumQy1iH5C2f3YbRAZpGCMT12a/9ih9w==",
|
"integrity": "sha512-kWAoUu/bwzvnhpdZSIc6KUyvkI1rbRXMT0Eq8pKReyOyaPZcctMli+EgvcN1PAvwVc7Tdo4Fxi2PsLNDU05mdg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
|
|
@ -3716,9 +3716,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/eslint-plugin-react-refresh": {
|
"node_modules/eslint-plugin-react-refresh": {
|
||||||
"version": "0.4.21",
|
"version": "0.4.22",
|
||||||
"resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.21.tgz",
|
"resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.22.tgz",
|
||||||
"integrity": "sha512-MWDWTtNC4voTcWDxXbdmBNe8b/TxfxRFUL6hXgKWJjN9c1AagYEmpiFWBWzDw+5H3SulWUe1pJKTnoSdmk88UA==",
|
"integrity": "sha512-atkAG6QaJMGoTLc4MDAP+rqZcfwQuTIh2IqHWFLy2TEjxr0MOK+5BSG4RzL2564AAPpZkDRsZXAUz68kjnU6Ug==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
|
|
@ -4055,12 +4055,12 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/framer-motion": {
|
"node_modules/framer-motion": {
|
||||||
"version": "12.23.18",
|
"version": "12.23.22",
|
||||||
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.23.18.tgz",
|
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.23.22.tgz",
|
||||||
"integrity": "sha512-HBVXBL5x3nk/0WrYM5G4VgjBey99ytVYET5AX17s/pcnlH90cyaxVUqgoN8cpF4+PqZRVOhwWsv28F+hxA9Tzg==",
|
"integrity": "sha512-ZgGvdxXCw55ZYvhoZChTlG6pUuehecgvEAJz0BHoC5pQKW1EC5xf1Mul1ej5+ai+pVY0pylyFfdl45qnM1/GsA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"motion-dom": "^12.23.18",
|
"motion-dom": "^12.23.21",
|
||||||
"motion-utils": "^12.23.6",
|
"motion-utils": "^12.23.6",
|
||||||
"tslib": "^2.4.0"
|
"tslib": "^2.4.0"
|
||||||
},
|
},
|
||||||
|
|
@ -5296,9 +5296,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/motion-dom": {
|
"node_modules/motion-dom": {
|
||||||
"version": "12.23.18",
|
"version": "12.23.21",
|
||||||
"resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.23.18.tgz",
|
"resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.23.21.tgz",
|
||||||
"integrity": "sha512-9piw3uOcP6DpS0qpnDF95bLDzmgMxLOg/jghLnHwYJ0YFizzuvbH/L8106dy39JNgHYmXFUTztoP9JQvUqlBwQ==",
|
"integrity": "sha512-5xDXx/AbhrfgsQmSE7YESMn4Dpo6x5/DTZ4Iyy4xqDvVHWvFVoV+V2Ri2S/ksx+D40wrZ7gPYiMWshkdoqNgNQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"motion-utils": "^12.23.6"
|
"motion-utils": "^12.23.6"
|
||||||
|
|
@ -5903,9 +5903,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/react-router": {
|
"node_modules/react-router": {
|
||||||
"version": "7.9.1",
|
"version": "7.9.3",
|
||||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.9.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.9.3.tgz",
|
||||||
"integrity": "sha512-pfAByjcTpX55mqSDGwGnY9vDCpxqBLASg0BMNAuMmpSGESo/TaOUG6BllhAtAkCGx8Rnohik/XtaqiYUJtgW2g==",
|
"integrity": "sha512-4o2iWCFIwhI/eYAIL43+cjORXYn/aRQPgtFRRZb3VzoyQ5Uej0Bmqj7437L97N9NJW4wnicSwLOLS+yCXfAPgg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"cookie": "^1.0.1",
|
"cookie": "^1.0.1",
|
||||||
|
|
@ -6080,9 +6080,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/rollup": {
|
"node_modules/rollup": {
|
||||||
"version": "4.52.0",
|
"version": "4.52.2",
|
||||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.0.tgz",
|
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.2.tgz",
|
||||||
"integrity": "sha512-+IuescNkTJQgX7AkIDtITipZdIGcWF0pnVvZTWStiazUmcGA2ag8dfg0urest2XlXUi9kuhfQ+qmdc5Stc3z7g==",
|
"integrity": "sha512-I25/2QgoROE1vYV+NQ1En9T9UFB9Cmfm2CJ83zZOlaDpvz29wGQSZXWKw7MiNXau7wYgB/T9fVIdIuEQ+KbiiA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/estree": "1.0.8"
|
"@types/estree": "1.0.8"
|
||||||
|
|
@ -6095,28 +6095,28 @@
|
||||||
"npm": ">=8.0.0"
|
"npm": ">=8.0.0"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@rollup/rollup-android-arm-eabi": "4.52.0",
|
"@rollup/rollup-android-arm-eabi": "4.52.2",
|
||||||
"@rollup/rollup-android-arm64": "4.52.0",
|
"@rollup/rollup-android-arm64": "4.52.2",
|
||||||
"@rollup/rollup-darwin-arm64": "4.52.0",
|
"@rollup/rollup-darwin-arm64": "4.52.2",
|
||||||
"@rollup/rollup-darwin-x64": "4.52.0",
|
"@rollup/rollup-darwin-x64": "4.52.2",
|
||||||
"@rollup/rollup-freebsd-arm64": "4.52.0",
|
"@rollup/rollup-freebsd-arm64": "4.52.2",
|
||||||
"@rollup/rollup-freebsd-x64": "4.52.0",
|
"@rollup/rollup-freebsd-x64": "4.52.2",
|
||||||
"@rollup/rollup-linux-arm-gnueabihf": "4.52.0",
|
"@rollup/rollup-linux-arm-gnueabihf": "4.52.2",
|
||||||
"@rollup/rollup-linux-arm-musleabihf": "4.52.0",
|
"@rollup/rollup-linux-arm-musleabihf": "4.52.2",
|
||||||
"@rollup/rollup-linux-arm64-gnu": "4.52.0",
|
"@rollup/rollup-linux-arm64-gnu": "4.52.2",
|
||||||
"@rollup/rollup-linux-arm64-musl": "4.52.0",
|
"@rollup/rollup-linux-arm64-musl": "4.52.2",
|
||||||
"@rollup/rollup-linux-loong64-gnu": "4.52.0",
|
"@rollup/rollup-linux-loong64-gnu": "4.52.2",
|
||||||
"@rollup/rollup-linux-ppc64-gnu": "4.52.0",
|
"@rollup/rollup-linux-ppc64-gnu": "4.52.2",
|
||||||
"@rollup/rollup-linux-riscv64-gnu": "4.52.0",
|
"@rollup/rollup-linux-riscv64-gnu": "4.52.2",
|
||||||
"@rollup/rollup-linux-riscv64-musl": "4.52.0",
|
"@rollup/rollup-linux-riscv64-musl": "4.52.2",
|
||||||
"@rollup/rollup-linux-s390x-gnu": "4.52.0",
|
"@rollup/rollup-linux-s390x-gnu": "4.52.2",
|
||||||
"@rollup/rollup-linux-x64-gnu": "4.52.0",
|
"@rollup/rollup-linux-x64-gnu": "4.52.2",
|
||||||
"@rollup/rollup-linux-x64-musl": "4.52.0",
|
"@rollup/rollup-linux-x64-musl": "4.52.2",
|
||||||
"@rollup/rollup-openharmony-arm64": "4.52.0",
|
"@rollup/rollup-openharmony-arm64": "4.52.2",
|
||||||
"@rollup/rollup-win32-arm64-msvc": "4.52.0",
|
"@rollup/rollup-win32-arm64-msvc": "4.52.2",
|
||||||
"@rollup/rollup-win32-ia32-msvc": "4.52.0",
|
"@rollup/rollup-win32-ia32-msvc": "4.52.2",
|
||||||
"@rollup/rollup-win32-x64-gnu": "4.52.0",
|
"@rollup/rollup-win32-x64-gnu": "4.52.2",
|
||||||
"@rollup/rollup-win32-x64-msvc": "4.52.0",
|
"@rollup/rollup-win32-x64-msvc": "4.52.2",
|
||||||
"fsevents": "~2.3.2"
|
"fsevents": "~2.3.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -6559,9 +6559,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/tar": {
|
"node_modules/tar": {
|
||||||
"version": "7.4.4",
|
"version": "7.5.1",
|
||||||
"resolved": "https://registry.npmjs.org/tar/-/tar-7.4.4.tgz",
|
"resolved": "https://registry.npmjs.org/tar/-/tar-7.5.1.tgz",
|
||||||
"integrity": "sha512-O1z7ajPkjTgEgmTGz0v9X4eqeEXTDREPTO77pVC1Nbs86feBU1Zhdg+edzavPmYW1olxkwsqA2v4uOw6E8LeDg==",
|
"integrity": "sha512-nlGpxf+hv0v7GkWBK2V9spgactGOp0qvfWRxUMjqHyzrt3SgwE48DIv/FhqPHJYLHpgW1opq3nERbz5Anq7n1g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "kvm-ui",
|
"name": "kvm-ui",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "2025.09.23.0000",
|
"version": "2025.09.26.01300",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^22.15.0"
|
"node": "^22.15.0"
|
||||||
|
|
@ -19,7 +19,7 @@
|
||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@headlessui/react": "^2.2.8",
|
"@headlessui/react": "^2.2.9",
|
||||||
"@headlessui/tailwindcss": "^0.2.2",
|
"@headlessui/tailwindcss": "^0.2.2",
|
||||||
"@heroicons/react": "^2.2.0",
|
"@heroicons/react": "^2.2.0",
|
||||||
"@vitejs/plugin-basic-ssl": "^2.1.0",
|
"@vitejs/plugin-basic-ssl": "^2.1.0",
|
||||||
|
|
@ -33,7 +33,7 @@
|
||||||
"dayjs": "^1.11.18",
|
"dayjs": "^1.11.18",
|
||||||
"eslint-import-resolver-alias": "^1.1.2",
|
"eslint-import-resolver-alias": "^1.1.2",
|
||||||
"focus-trap-react": "^11.0.4",
|
"focus-trap-react": "^11.0.4",
|
||||||
"framer-motion": "^12.23.18",
|
"framer-motion": "^12.23.22",
|
||||||
"lodash.throttle": "^4.1.1",
|
"lodash.throttle": "^4.1.1",
|
||||||
"mini-svg-data-uri": "^1.4.4",
|
"mini-svg-data-uri": "^1.4.4",
|
||||||
"react": "^19.1.1",
|
"react": "^19.1.1",
|
||||||
|
|
@ -41,7 +41,7 @@
|
||||||
"react-dom": "^19.1.1",
|
"react-dom": "^19.1.1",
|
||||||
"react-hot-toast": "^2.6.0",
|
"react-hot-toast": "^2.6.0",
|
||||||
"react-icons": "^5.5.0",
|
"react-icons": "^5.5.0",
|
||||||
"react-router": "^7.9.1",
|
"react-router": "^7.9.3",
|
||||||
"react-simple-keyboard": "^3.8.122",
|
"react-simple-keyboard": "^3.8.122",
|
||||||
"react-use-websocket": "^4.13.0",
|
"react-use-websocket": "^4.13.0",
|
||||||
"react-xtermjs": "^1.0.10",
|
"react-xtermjs": "^1.0.10",
|
||||||
|
|
@ -57,9 +57,9 @@
|
||||||
"@eslint/js": "^9.36.0",
|
"@eslint/js": "^9.36.0",
|
||||||
"@tailwindcss/forms": "^0.5.10",
|
"@tailwindcss/forms": "^0.5.10",
|
||||||
"@tailwindcss/postcss": "^4.1.13",
|
"@tailwindcss/postcss": "^4.1.13",
|
||||||
"@tailwindcss/typography": "^0.5.18",
|
"@tailwindcss/typography": "^0.5.19",
|
||||||
"@tailwindcss/vite": "^4.1.13",
|
"@tailwindcss/vite": "^4.1.13",
|
||||||
"@types/react": "^19.1.13",
|
"@types/react": "^19.1.14",
|
||||||
"@types/react-dom": "^19.1.9",
|
"@types/react-dom": "^19.1.9",
|
||||||
"@types/semver": "^7.7.1",
|
"@types/semver": "^7.7.1",
|
||||||
"@types/validator": "^13.15.3",
|
"@types/validator": "^13.15.3",
|
||||||
|
|
@ -72,7 +72,7 @@
|
||||||
"eslint-plugin-import": "^2.32.0",
|
"eslint-plugin-import": "^2.32.0",
|
||||||
"eslint-plugin-react": "^7.37.5",
|
"eslint-plugin-react": "^7.37.5",
|
||||||
"eslint-plugin-react-hooks": "^5.2.0",
|
"eslint-plugin-react-hooks": "^5.2.0",
|
||||||
"eslint-plugin-react-refresh": "^0.4.21",
|
"eslint-plugin-react-refresh": "^0.4.22",
|
||||||
"globals": "^16.4.0",
|
"globals": "^16.4.0",
|
||||||
"postcss": "^8.5.6",
|
"postcss": "^8.5.6",
|
||||||
"prettier": "^3.6.2",
|
"prettier": "^3.6.2",
|
||||||
|
|
|
||||||
|
|
@ -66,7 +66,7 @@ export function MacroForm({
|
||||||
newErrors.steps = { 0: { keys: "At least one step is required" } };
|
newErrors.steps = { 0: { keys: "At least one step is required" } };
|
||||||
} else {
|
} else {
|
||||||
const hasKeyOrModifier = macro.steps.some(
|
const hasKeyOrModifier = macro.steps.some(
|
||||||
step => (step.keys?.length || 0) > 0 || (step.modifiers?.length || 0) > 0,
|
step => (step.text && step.text.length > 0) || (step.keys?.length || 0) > 0 || (step.modifiers?.length || 0) > 0,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!hasKeyOrModifier) {
|
if (!hasKeyOrModifier) {
|
||||||
|
|
@ -163,6 +163,40 @@ export function MacroForm({
|
||||||
setMacro({ ...macro, steps: newSteps });
|
setMacro({ ...macro, steps: newSteps });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleStepTypeChange = (stepIndex: number, type: "keys" | "text" | "wait") => {
|
||||||
|
const newSteps = [...(macro.steps || [])];
|
||||||
|
const prev = newSteps[stepIndex] || { keys: [], modifiers: [], delay: DEFAULT_DELAY };
|
||||||
|
if (type === "text") {
|
||||||
|
newSteps[stepIndex] = { keys: [], modifiers: [], delay: prev.delay, text: prev.text || "" } as any;
|
||||||
|
} else if (type === "wait") {
|
||||||
|
newSteps[stepIndex] = { keys: [], modifiers: [], delay: prev.delay, wait: true } as any;
|
||||||
|
} else {
|
||||||
|
// switch back to keys; drop text
|
||||||
|
const { text, wait, ...rest } = prev as any;
|
||||||
|
newSteps[stepIndex] = { ...rest } as any;
|
||||||
|
}
|
||||||
|
setMacro({ ...macro, steps: newSteps });
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleTextChange = (stepIndex: number, text: string) => {
|
||||||
|
const newSteps = [...(macro.steps || [])];
|
||||||
|
// Ensure this step is of text type
|
||||||
|
newSteps[stepIndex] = { ...(newSteps[stepIndex] || { keys: [], modifiers: [], delay: DEFAULT_DELAY }), text } as any;
|
||||||
|
setMacro({ ...macro, steps: newSteps });
|
||||||
|
};
|
||||||
|
|
||||||
|
const insertStepAfter = (index: number) => {
|
||||||
|
if (isMaxStepsReached) {
|
||||||
|
showTemporaryError(
|
||||||
|
`You can only add a maximum of ${MAX_STEPS_PER_MACRO} steps per macro.`,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const newSteps = [...(macro.steps || [])];
|
||||||
|
newSteps.splice(index + 1, 0, { keys: [], modifiers: [], delay: DEFAULT_DELAY });
|
||||||
|
setMacro(prev => ({ ...prev, steps: newSteps }));
|
||||||
|
};
|
||||||
|
|
||||||
const handleStepMove = (stepIndex: number, direction: "up" | "down") => {
|
const handleStepMove = (stepIndex: number, direction: "up" | "down") => {
|
||||||
const newSteps = [...(macro.steps || [])];
|
const newSteps = [...(macro.steps || [])];
|
||||||
const newIndex = direction === "up" ? stepIndex - 1 : stepIndex + 1;
|
const newIndex = direction === "up" ? stepIndex - 1 : stepIndex + 1;
|
||||||
|
|
@ -213,31 +247,46 @@ export function MacroForm({
|
||||||
<Fieldset>
|
<Fieldset>
|
||||||
<div className="mt-2 space-y-4">
|
<div className="mt-2 space-y-4">
|
||||||
{(macro.steps || []).map((step, stepIndex) => (
|
{(macro.steps || []).map((step, stepIndex) => (
|
||||||
<MacroStepCard
|
<div key={stepIndex} className="space-y-3">
|
||||||
key={stepIndex}
|
<MacroStepCard
|
||||||
step={step}
|
step={step}
|
||||||
stepIndex={stepIndex}
|
stepIndex={stepIndex}
|
||||||
onDelete={
|
onDelete={
|
||||||
macro.steps && macro.steps.length > 1
|
macro.steps && macro.steps.length > 1
|
||||||
? () => {
|
? () => {
|
||||||
const newSteps = [...(macro.steps || [])];
|
const newSteps = [...(macro.steps || [])];
|
||||||
newSteps.splice(stepIndex, 1);
|
newSteps.splice(stepIndex, 1);
|
||||||
setMacro(prev => ({ ...prev, steps: newSteps }));
|
setMacro(prev => ({ ...prev, steps: newSteps }));
|
||||||
}
|
}
|
||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
onMoveUp={() => handleStepMove(stepIndex, "up")}
|
onMoveUp={() => handleStepMove(stepIndex, "up")}
|
||||||
onMoveDown={() => handleStepMove(stepIndex, "down")}
|
onMoveDown={() => handleStepMove(stepIndex, "down")}
|
||||||
onKeySelect={option => handleKeySelect(stepIndex, option)}
|
onKeySelect={option => handleKeySelect(stepIndex, option)}
|
||||||
onKeyQueryChange={query => handleKeyQueryChange(stepIndex, query)}
|
onKeyQueryChange={query => handleKeyQueryChange(stepIndex, query)}
|
||||||
keyQuery={keyQueries[stepIndex] || ""}
|
keyQuery={keyQueries[stepIndex] || ""}
|
||||||
onModifierChange={modifiers =>
|
onModifierChange={modifiers =>
|
||||||
handleModifierChange(stepIndex, modifiers)
|
handleModifierChange(stepIndex, modifiers)
|
||||||
}
|
}
|
||||||
onDelayChange={delay => handleDelayChange(stepIndex, delay)}
|
onDelayChange={delay => handleDelayChange(stepIndex, delay)}
|
||||||
isLastStep={stepIndex === (macro.steps?.length || 0) - 1}
|
isLastStep={stepIndex === (macro.steps?.length || 0) - 1}
|
||||||
keyboard={selectedKeyboard}
|
keyboard={selectedKeyboard}
|
||||||
/>
|
onStepTypeChange={type => handleStepTypeChange(stepIndex, type)}
|
||||||
|
onTextChange={text => handleTextChange(stepIndex, text)}
|
||||||
|
/>
|
||||||
|
{stepIndex < (macro.steps?.length || 0) - 1 && (
|
||||||
|
<div className="flex justify-center">
|
||||||
|
<Button
|
||||||
|
size="XS"
|
||||||
|
theme="light"
|
||||||
|
LeadingIcon={LuPlus}
|
||||||
|
text="Insert step here"
|
||||||
|
onClick={() => insertStepAfter(stepIndex)}
|
||||||
|
disabled={isMaxStepsReached}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</Fieldset>
|
</Fieldset>
|
||||||
|
|
|
||||||
|
|
@ -38,16 +38,22 @@ const basePresetDelays = [
|
||||||
];
|
];
|
||||||
|
|
||||||
const PRESET_DELAYS = basePresetDelays.map(delay => {
|
const PRESET_DELAYS = basePresetDelays.map(delay => {
|
||||||
if (parseInt(delay.value, 10) === DEFAULT_DELAY) {
|
if (parseInt(delay.value, 10) === DEFAULT_DELAY) return { ...delay, label: "Default" };
|
||||||
return { ...delay, label: "Default" };
|
|
||||||
}
|
|
||||||
return delay;
|
return delay;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const TEXT_EXTRA_DELAYS = [
|
||||||
|
{ value: "10", label: "10ms" },
|
||||||
|
{ value: "20", label: "20ms" },
|
||||||
|
{ value: "30", label: "30ms" },
|
||||||
|
];
|
||||||
|
|
||||||
interface MacroStep {
|
interface MacroStep {
|
||||||
keys: string[];
|
keys: string[];
|
||||||
modifiers: string[];
|
modifiers: string[];
|
||||||
delay: number;
|
delay: number;
|
||||||
|
text?: string;
|
||||||
|
wait?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface MacroStepCardProps {
|
interface MacroStepCardProps {
|
||||||
|
|
@ -62,7 +68,9 @@ interface MacroStepCardProps {
|
||||||
onModifierChange: (modifiers: string[]) => void;
|
onModifierChange: (modifiers: string[]) => void;
|
||||||
onDelayChange: (delay: number) => void;
|
onDelayChange: (delay: number) => void;
|
||||||
isLastStep: boolean;
|
isLastStep: boolean;
|
||||||
keyboard: KeyboardLayout
|
keyboard: KeyboardLayout;
|
||||||
|
onStepTypeChange: (type: "keys" | "text" | "wait") => void;
|
||||||
|
onTextChange: (text: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ensureArray = <T,>(arr: T[] | null | undefined): T[] => {
|
const ensureArray = <T,>(arr: T[] | null | undefined): T[] => {
|
||||||
|
|
@ -81,7 +89,9 @@ export function MacroStepCard({
|
||||||
onModifierChange,
|
onModifierChange,
|
||||||
onDelayChange,
|
onDelayChange,
|
||||||
isLastStep,
|
isLastStep,
|
||||||
keyboard
|
keyboard,
|
||||||
|
onStepTypeChange,
|
||||||
|
onTextChange,
|
||||||
}: MacroStepCardProps) {
|
}: MacroStepCardProps) {
|
||||||
const { keyDisplayMap } = keyboard;
|
const { keyDisplayMap } = keyboard;
|
||||||
|
|
||||||
|
|
@ -106,6 +116,8 @@ export function MacroStepCard({
|
||||||
}
|
}
|
||||||
}, [keyOptions, keyQuery, step.keys]);
|
}, [keyOptions, keyQuery, step.keys]);
|
||||||
|
|
||||||
|
const stepType: "keys" | "text" | "wait" = step.wait ? "wait" : (step.text !== undefined ? "text" : "keys");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className="p-4">
|
<Card className="p-4">
|
||||||
<div className="mb-2 flex items-center justify-between">
|
<div className="mb-2 flex items-center justify-between">
|
||||||
|
|
@ -146,6 +158,46 @@ export function MacroStepCard({
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-4 mt-2">
|
<div className="space-y-4 mt-2">
|
||||||
|
<div className="w-full flex flex-col gap-2">
|
||||||
|
<FieldLabel label="Step Type" />
|
||||||
|
<div className="inline-flex gap-2">
|
||||||
|
<Button
|
||||||
|
size="XS"
|
||||||
|
theme={stepType === "keys" ? "primary" : "light"}
|
||||||
|
text="Keys/Modifiers"
|
||||||
|
onClick={() => onStepTypeChange("keys")}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
size="XS"
|
||||||
|
theme={stepType === "text" ? "primary" : "light"}
|
||||||
|
text="Text"
|
||||||
|
onClick={() => onStepTypeChange("text")}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
size="XS"
|
||||||
|
theme={stepType === "wait" ? "primary" : "light"}
|
||||||
|
text="Wait"
|
||||||
|
onClick={() => onStepTypeChange("wait")}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{stepType === "text" ? (
|
||||||
|
<div className="w-full flex flex-col gap-1">
|
||||||
|
<FieldLabel label="Text to type" description="Will be typed with this step's delay per character" />
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
className="w-full rounded-md border border-slate-200 px-2 py-1 text-sm dark:border-slate-700 dark:bg-slate-800"
|
||||||
|
value={step.text || ""}
|
||||||
|
onChange={e => onTextChange(e.target.value)}
|
||||||
|
placeholder="Enter text..."
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : stepType === "wait" ? (
|
||||||
|
<div className="w-full flex flex-col gap-1">
|
||||||
|
<FieldLabel label="Wait" description="Pause execution for the specified duration." />
|
||||||
|
<p className="text-xs text-slate-500 dark:text-slate-400">This step waits for the configured duration, no keys are sent.</p>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
<div className="w-full flex flex-col gap-2">
|
<div className="w-full flex flex-col gap-2">
|
||||||
<FieldLabel label="Modifiers" />
|
<FieldLabel label="Modifiers" />
|
||||||
<div className="inline-flex flex-wrap gap-3">
|
<div className="inline-flex flex-wrap gap-3">
|
||||||
|
|
@ -176,7 +228,8 @@ export function MacroStepCard({
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
{stepType === "keys" && (
|
||||||
<div className="w-full flex flex-col gap-1">
|
<div className="w-full flex flex-col gap-1">
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
<FieldLabel label="Keys" description={`Maximum ${MAX_KEYS_PER_STEP} keys per step.`} />
|
<FieldLabel label="Keys" description={`Maximum ${MAX_KEYS_PER_STEP} keys per step.`} />
|
||||||
|
|
@ -223,10 +276,10 @@ export function MacroStepCard({
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
<div className="w-full flex flex-col gap-1">
|
<div className="w-full flex flex-col gap-1">
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
<FieldLabel label="Step Duration" description="Time to wait before executing the next step." />
|
<FieldLabel label="Step Duration" description={stepType === "text" ? "Delay per character when typing text" : stepType === "wait" ? "How long to pause before the next step" : "Time to wait before executing the next step."} />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<SelectMenuBasic
|
<SelectMenuBasic
|
||||||
|
|
@ -234,10 +287,11 @@ export function MacroStepCard({
|
||||||
fullWidth
|
fullWidth
|
||||||
value={step.delay.toString()}
|
value={step.delay.toString()}
|
||||||
onChange={(e) => onDelayChange(parseInt(e.target.value, 10))}
|
onChange={(e) => onDelayChange(parseInt(e.target.value, 10))}
|
||||||
options={PRESET_DELAYS}
|
options={stepType === 'text' ? [...TEXT_EXTRA_DELAYS, ...PRESET_DELAYS] : PRESET_DELAYS}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
import { cx } from "@/cva.config";
|
||||||
|
import LoadingSpinner from "@components/LoadingSpinner";
|
||||||
|
|
||||||
|
interface SettingsItemProps {
|
||||||
|
readonly title: string;
|
||||||
|
readonly description: string | React.ReactNode;
|
||||||
|
readonly badge?: string;
|
||||||
|
readonly className?: string;
|
||||||
|
readonly loading?: boolean;
|
||||||
|
readonly children?: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function SettingsItem(props: SettingsItemProps) {
|
||||||
|
const { title, description, badge, children, className, loading } = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<label
|
||||||
|
className={cx(
|
||||||
|
"flex select-none items-center justify-between gap-x-8 rounded",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div className="space-y-0.5">
|
||||||
|
<div className="flex items-center gap-x-2">
|
||||||
|
<div className="flex items-center text-base font-semibold text-black dark:text-white">
|
||||||
|
{title}
|
||||||
|
{badge && (
|
||||||
|
<span className="ml-2 rounded-full bg-red-500 px-2 py-1 text-[10px] font-medium leading-none text-white dark:border dark:border-red-700 dark:bg-red-800 dark:text-red-50">
|
||||||
|
{badge}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{loading && <LoadingSpinner className="h-4 w-4 text-blue-500" />}
|
||||||
|
</div>
|
||||||
|
<div className="text-sm text-slate-700 dark:text-slate-300">{description}</div>
|
||||||
|
</div>
|
||||||
|
{children ? <div>{children}</div> : null}
|
||||||
|
</label>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
import { useCallback , useEffect, useState } from "react";
|
import { useCallback , useEffect, useState } from "react";
|
||||||
|
|
||||||
|
import { SettingsItem } from "@components/SettingsItem";
|
||||||
|
|
||||||
import { JsonRpcResponse, useJsonRpc } from "../hooks/useJsonRpc";
|
import { JsonRpcResponse, useJsonRpc } from "../hooks/useJsonRpc";
|
||||||
import notifications from "../notifications";
|
import notifications from "../notifications";
|
||||||
import { SettingsItem } from "../routes/devices.$id.settings";
|
|
||||||
|
|
||||||
import Checkbox from "./Checkbox";
|
import Checkbox from "./Checkbox";
|
||||||
import { Button } from "./Button";
|
import { Button } from "./Button";
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,11 @@
|
||||||
import { useMemo , useCallback , useEffect, useState } from "react";
|
import { useMemo , useCallback , useEffect, useState } from "react";
|
||||||
|
|
||||||
import { Button } from "@components/Button";
|
import { Button } from "@components/Button";
|
||||||
|
import { SettingsItem } from "@components/SettingsItem";
|
||||||
|
|
||||||
import { UsbConfigState } from "../hooks/stores";
|
import { UsbConfigState } from "../hooks/stores";
|
||||||
import { JsonRpcResponse, useJsonRpc } from "../hooks/useJsonRpc";
|
import { JsonRpcResponse, useJsonRpc } from "../hooks/useJsonRpc";
|
||||||
import notifications from "../notifications";
|
import notifications from "../notifications";
|
||||||
import { SettingsItem } from "../routes/devices.$id.settings";
|
|
||||||
|
|
||||||
import { InputFieldWithLabel } from "./InputField";
|
import { InputFieldWithLabel } from "./InputField";
|
||||||
import { SelectMenuBasic } from "./SelectMenuBasic";
|
import { SelectMenuBasic } from "./SelectMenuBasic";
|
||||||
|
|
|
||||||
|
|
@ -763,6 +763,8 @@ export interface KeySequenceStep {
|
||||||
keys: string[];
|
keys: string[];
|
||||||
modifiers: string[];
|
modifiers: string[];
|
||||||
delay: number;
|
delay: number;
|
||||||
|
text?: string; // optional: when set, type this text with per-character delay
|
||||||
|
wait?: boolean; // optional: when true, this is a pure wait step (pause for delay ms)
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface KeySequence {
|
export interface KeySequence {
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ import {
|
||||||
import { useHidRpc } from "@/hooks/useHidRpc";
|
import { useHidRpc } from "@/hooks/useHidRpc";
|
||||||
import { JsonRpcResponse, useJsonRpc } from "@/hooks/useJsonRpc";
|
import { JsonRpcResponse, useJsonRpc } from "@/hooks/useJsonRpc";
|
||||||
import { hidKeyToModifierMask, keys, modifiers } from "@/keyboardMappings";
|
import { hidKeyToModifierMask, keys, modifiers } from "@/keyboardMappings";
|
||||||
|
import useKeyboardLayout from "@/hooks/useKeyboardLayout";
|
||||||
|
|
||||||
const MACRO_RESET_KEYBOARD_STATE = {
|
const MACRO_RESET_KEYBOARD_STATE = {
|
||||||
keys: new Array(hidKeyBufferSize).fill(0),
|
keys: new Array(hidKeyBufferSize).fill(0),
|
||||||
|
|
@ -27,6 +28,8 @@ export interface MacroStep {
|
||||||
keys: string[] | null;
|
keys: string[] | null;
|
||||||
modifiers: string[] | null;
|
modifiers: string[] | null;
|
||||||
delay: number;
|
delay: number;
|
||||||
|
text?: string | undefined;
|
||||||
|
wait?: boolean | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type MacroSteps = MacroStep[];
|
export type MacroSteps = MacroStep[];
|
||||||
|
|
@ -34,6 +37,7 @@ export type MacroSteps = MacroStep[];
|
||||||
const sleep = (ms: number): Promise<void> => new Promise(resolve => setTimeout(resolve, ms));
|
const sleep = (ms: number): Promise<void> => new Promise(resolve => setTimeout(resolve, ms));
|
||||||
|
|
||||||
export default function useKeyboard() {
|
export default function useKeyboard() {
|
||||||
|
const { selectedKeyboard } = useKeyboardLayout();
|
||||||
const { send } = useJsonRpc();
|
const { send } = useJsonRpc();
|
||||||
const { rpcDataChannel } = useRTCStore();
|
const { rpcDataChannel } = useRTCStore();
|
||||||
const { keysDownState, setKeysDownState, setKeyboardLedState, setPasteModeEnabled } =
|
const { keysDownState, setKeysDownState, setKeyboardLedState, setPasteModeEnabled } =
|
||||||
|
|
@ -284,9 +288,38 @@ export default function useKeyboard() {
|
||||||
// After the delay, the keys and modifiers are released and the next step is executed.
|
// After the delay, the keys and modifiers are released and the next step is executed.
|
||||||
// If a step has no keys or modifiers, it is treated as a delay-only step.
|
// If a step has no keys or modifiers, it is treated as a delay-only step.
|
||||||
// A small pause is added between steps to ensure that the device can process the events.
|
// A small pause is added between steps to ensure that the device can process the events.
|
||||||
|
const expandTextSteps = useCallback((steps: MacroSteps): MacroSteps => {
|
||||||
|
const expanded: MacroSteps = [];
|
||||||
|
for (const step of steps) {
|
||||||
|
if (step.text && step.text.length > 0 && selectedKeyboard) {
|
||||||
|
for (const char of step.text) {
|
||||||
|
const keyprops = selectedKeyboard.chars[char];
|
||||||
|
if (!keyprops) continue;
|
||||||
|
const { key, shift, altRight, deadKey, accentKey } = keyprops;
|
||||||
|
if (!key) continue;
|
||||||
|
if (accentKey) {
|
||||||
|
const accentModifiers: string[] = [];
|
||||||
|
if (accentKey.shift) accentModifiers.push("ShiftLeft");
|
||||||
|
if (accentKey.altRight) accentModifiers.push("AltRight");
|
||||||
|
expanded.push({ keys: [String(accentKey.key)], modifiers: accentModifiers, delay: step.delay });
|
||||||
|
}
|
||||||
|
const mods: string[] = [];
|
||||||
|
if (shift) mods.push("ShiftLeft");
|
||||||
|
if (altRight) mods.push("AltRight");
|
||||||
|
expanded.push({ keys: [String(key)], modifiers: mods, delay: step.delay });
|
||||||
|
if (deadKey) expanded.push({ keys: ["Space"], modifiers: null, delay: step.delay });
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
expanded.push(step);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return expanded;
|
||||||
|
}, [selectedKeyboard]);
|
||||||
|
|
||||||
const executeMacroRemote = useCallback(async (
|
const executeMacroRemote = useCallback(async (
|
||||||
steps: MacroSteps,
|
stepsIn: MacroSteps,
|
||||||
) => {
|
) => {
|
||||||
|
const steps = expandTextSteps(stepsIn);
|
||||||
const macro: KeyboardMacroStep[] = [];
|
const macro: KeyboardMacroStep[] = [];
|
||||||
|
|
||||||
for (const [_, step] of steps.entries()) {
|
for (const [_, step] of steps.entries()) {
|
||||||
|
|
@ -297,16 +330,22 @@ export default function useKeyboard() {
|
||||||
|
|
||||||
.reduce((acc, val) => acc + val, 0);
|
.reduce((acc, val) => acc + val, 0);
|
||||||
|
|
||||||
// If the step has keys and/or modifiers, press them and hold for the delay
|
if (step.wait) {
|
||||||
if (keyValues.length > 0 || modifierMask > 0) {
|
// pure wait: send a no-op clear state with desired delay
|
||||||
|
macro.push({ ...MACRO_RESET_KEYBOARD_STATE, delay: step.delay || 100 });
|
||||||
|
} else if (keyValues.length > 0 || modifierMask > 0) {
|
||||||
macro.push({ keys: keyValues, modifier: modifierMask, delay: 20 });
|
macro.push({ keys: keyValues, modifier: modifierMask, delay: 20 });
|
||||||
macro.push({ ...MACRO_RESET_KEYBOARD_STATE, delay: step.delay || 100 });
|
macro.push({ ...MACRO_RESET_KEYBOARD_STATE, delay: step.delay || 100 });
|
||||||
|
} else {
|
||||||
|
// empty step (pause only)
|
||||||
|
macro.push({ ...MACRO_RESET_KEYBOARD_STATE, delay: step.delay || 100 });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sendKeyboardMacroEventHidRpc(macro);
|
sendKeyboardMacroEventHidRpc(macro);
|
||||||
}, [sendKeyboardMacroEventHidRpc]);
|
}, [sendKeyboardMacroEventHidRpc, expandTextSteps]);
|
||||||
const executeMacroClientSide = useCallback(async (steps: MacroSteps) => {
|
const executeMacroClientSide = useCallback(async (stepsIn: MacroSteps) => {
|
||||||
|
const steps = expandTextSteps(stepsIn);
|
||||||
const promises: (() => Promise<void>)[] = [];
|
const promises: (() => Promise<void>)[] = [];
|
||||||
|
|
||||||
const ac = new AbortController();
|
const ac = new AbortController();
|
||||||
|
|
@ -318,11 +357,14 @@ export default function useKeyboard() {
|
||||||
.map(mod => modifiers[mod])
|
.map(mod => modifiers[mod])
|
||||||
.reduce((acc, val) => acc + val, 0);
|
.reduce((acc, val) => acc + val, 0);
|
||||||
|
|
||||||
// If the step has keys and/or modifiers, press them and hold for the delay
|
if (step.wait) {
|
||||||
if (keyValues.length > 0 || modifierMask > 0) {
|
promises.push(() => sleep(step.delay || 100));
|
||||||
|
} else if (keyValues.length > 0 || modifierMask > 0) {
|
||||||
promises.push(() => sendKeystrokeLegacy(keyValues, modifierMask, ac));
|
promises.push(() => sendKeystrokeLegacy(keyValues, modifierMask, ac));
|
||||||
promises.push(() => resetKeyboardState());
|
promises.push(() => resetKeyboardState());
|
||||||
promises.push(() => sleep(step.delay || 100));
|
promises.push(() => sleep(step.delay || 100));
|
||||||
|
} else {
|
||||||
|
promises.push(() => sleep(step.delay || 100));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -354,7 +396,7 @@ export default function useKeyboard() {
|
||||||
reject(error);
|
reject(error);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}, [sendKeystrokeLegacy, resetKeyboardState, setAbortController]);
|
}, [sendKeystrokeLegacy, resetKeyboardState, setAbortController, expandTextSteps]);
|
||||||
const executeMacro = useCallback(async (steps: MacroSteps) => {
|
const executeMacro = useCallback(async (steps: MacroSteps) => {
|
||||||
if (rpcHidReady) {
|
if (rpcHidReady) {
|
||||||
return executeMacroRemote(steps);
|
return executeMacroRemote(steps);
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import { GridCard } from "@/components/Card";
|
||||||
import { Button, LinkButton } from "@/components/Button";
|
import { Button, LinkButton } from "@/components/Button";
|
||||||
import { InputFieldWithLabel } from "@/components/InputField";
|
import { InputFieldWithLabel } from "@/components/InputField";
|
||||||
import { SelectMenuBasic } from "@/components/SelectMenuBasic";
|
import { SelectMenuBasic } from "@/components/SelectMenuBasic";
|
||||||
|
import { SettingsItem } from "@components/SettingsItem";
|
||||||
import { SettingsSectionHeader } from "@/components/SettingsSectionHeader";
|
import { SettingsSectionHeader } from "@/components/SettingsSectionHeader";
|
||||||
import { useDeviceUiNavigation } from "@/hooks/useAppNavigation";
|
import { useDeviceUiNavigation } from "@/hooks/useAppNavigation";
|
||||||
import notifications from "@/notifications";
|
import notifications from "@/notifications";
|
||||||
|
|
@ -18,7 +19,6 @@ import { isOnDevice } from "@/main";
|
||||||
import { TextAreaWithLabel } from "@components/TextArea";
|
import { TextAreaWithLabel } from "@components/TextArea";
|
||||||
|
|
||||||
import { LocalDevice } from "./devices.$id";
|
import { LocalDevice } from "./devices.$id";
|
||||||
import { SettingsItem } from "./devices.$id.settings";
|
|
||||||
import { CloudState } from "./adopt";
|
import { CloudState } from "./adopt";
|
||||||
|
|
||||||
export interface TLSState {
|
export interface TLSState {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { useCallback, useEffect, useState } from "react";
|
import { useCallback, useEffect, useState } from "react";
|
||||||
|
|
||||||
import { GridCard } from "@components/Card";
|
import { GridCard } from "@components/Card";
|
||||||
|
import { SettingsItem } from "@components/SettingsItem";
|
||||||
|
|
||||||
import { Button } from "../components/Button";
|
import { Button } from "../components/Button";
|
||||||
import Checkbox from "../components/Checkbox";
|
import Checkbox from "../components/Checkbox";
|
||||||
|
|
@ -12,8 +13,6 @@ import { JsonRpcResponse, useJsonRpc } from "../hooks/useJsonRpc";
|
||||||
import { isOnDevice } from "../main";
|
import { isOnDevice } from "../main";
|
||||||
import notifications from "../notifications";
|
import notifications from "../notifications";
|
||||||
|
|
||||||
import { SettingsItem } from "./devices.$id.settings";
|
|
||||||
|
|
||||||
export default function SettingsAdvancedRoute() {
|
export default function SettingsAdvancedRoute() {
|
||||||
const { send } = useJsonRpc();
|
const { send } = useJsonRpc();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
import { useCallback, useState } from "react";
|
import { useCallback, useState } from "react";
|
||||||
|
|
||||||
|
import { SettingsItem } from "@components/SettingsItem";
|
||||||
|
|
||||||
import { SettingsPageHeader } from "../components/SettingsPageheader";
|
import { SettingsPageHeader } from "../components/SettingsPageheader";
|
||||||
import { SelectMenuBasic } from "../components/SelectMenuBasic";
|
import { SelectMenuBasic } from "../components/SelectMenuBasic";
|
||||||
|
|
||||||
import { SettingsItem } from "./devices.$id.settings";
|
|
||||||
|
|
||||||
export default function SettingsAppearanceRoute() {
|
export default function SettingsAppearanceRoute() {
|
||||||
const [currentTheme, setCurrentTheme] = useState(() => {
|
const [currentTheme, setCurrentTheme] = useState(() => {
|
||||||
return localStorage.theme || "system";
|
return localStorage.theme || "system";
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
|
|
||||||
import { useState , useEffect } from "react";
|
import { useState , useEffect } from "react";
|
||||||
|
|
||||||
|
import { SettingsItem } from "@components/SettingsItem";
|
||||||
import { JsonRpcResponse, useJsonRpc } from "@/hooks/useJsonRpc";
|
import { JsonRpcResponse, useJsonRpc } from "@/hooks/useJsonRpc";
|
||||||
|
|
||||||
import { SettingsPageHeader } from "../components/SettingsPageheader";
|
import { SettingsPageHeader } from "../components/SettingsPageheader";
|
||||||
|
|
@ -10,7 +11,6 @@ import Checkbox from "../components/Checkbox";
|
||||||
import { useDeviceUiNavigation } from "../hooks/useAppNavigation";
|
import { useDeviceUiNavigation } from "../hooks/useAppNavigation";
|
||||||
import { useDeviceStore } from "../hooks/stores";
|
import { useDeviceStore } from "../hooks/stores";
|
||||||
|
|
||||||
import { SettingsItem } from "./devices.$id.settings";
|
|
||||||
|
|
||||||
export default function SettingsGeneralRoute() {
|
export default function SettingsGeneralRoute() {
|
||||||
const { send } = useJsonRpc();
|
const { send } = useJsonRpc();
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
|
|
||||||
|
import { SettingsItem } from "@components/SettingsItem";
|
||||||
import { SettingsPageHeader } from "@components/SettingsPageheader";
|
import { SettingsPageHeader } from "@components/SettingsPageheader";
|
||||||
import { SettingsItem } from "@routes/devices.$id.settings";
|
|
||||||
import { BacklightSettings, useSettingsStore } from "@/hooks/stores";
|
import { BacklightSettings, useSettingsStore } from "@/hooks/stores";
|
||||||
import { JsonRpcResponse, useJsonRpc } from "@/hooks/useJsonRpc";
|
import { JsonRpcResponse, useJsonRpc } from "@/hooks/useJsonRpc";
|
||||||
import { SelectMenuBasic } from "@components/SelectMenuBasic";
|
import { SelectMenuBasic } from "@components/SelectMenuBasic";
|
||||||
|
|
|
||||||
|
|
@ -3,13 +3,12 @@ import { useCallback, useEffect } from "react";
|
||||||
import { useSettingsStore } from "@/hooks/stores";
|
import { useSettingsStore } from "@/hooks/stores";
|
||||||
import { JsonRpcResponse, useJsonRpc } from "@/hooks/useJsonRpc";
|
import { JsonRpcResponse, useJsonRpc } from "@/hooks/useJsonRpc";
|
||||||
import useKeyboardLayout from "@/hooks/useKeyboardLayout";
|
import useKeyboardLayout from "@/hooks/useKeyboardLayout";
|
||||||
|
import { SettingsItem } from "@components/SettingsItem";
|
||||||
import { SettingsPageHeader } from "@components/SettingsPageheader";
|
import { SettingsPageHeader } from "@components/SettingsPageheader";
|
||||||
import { Checkbox } from "@/components/Checkbox";
|
import { Checkbox } from "@/components/Checkbox";
|
||||||
import { SelectMenuBasic } from "@/components/SelectMenuBasic";
|
import { SelectMenuBasic } from "@/components/SelectMenuBasic";
|
||||||
import notifications from "@/notifications";
|
import notifications from "@/notifications";
|
||||||
|
|
||||||
import { SettingsItem } from "./devices.$id.settings";
|
|
||||||
|
|
||||||
export default function SettingsKeyboardRoute() {
|
export default function SettingsKeyboardRoute() {
|
||||||
const { setKeyboardLayout } = useSettingsStore();
|
const { setKeyboardLayout } = useSettingsStore();
|
||||||
const { showPressedKeys, setShowPressedKeys } = useSettingsStore();
|
const { showPressedKeys, setShowPressedKeys } = useSettingsStore();
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { useEffect, Fragment, useMemo, useState, useCallback } from "react";
|
import { useEffect, Fragment, useMemo, useState, useCallback, useRef } from "react";
|
||||||
import { useNavigate } from "react-router";
|
import { useNavigate } from "react-router";
|
||||||
import {
|
import {
|
||||||
LuPenLine,
|
LuPenLine,
|
||||||
|
|
@ -9,6 +9,7 @@ import {
|
||||||
LuArrowDown,
|
LuArrowDown,
|
||||||
LuTrash2,
|
LuTrash2,
|
||||||
LuCommand,
|
LuCommand,
|
||||||
|
LuDownload,
|
||||||
} from "react-icons/lu";
|
} from "react-icons/lu";
|
||||||
|
|
||||||
import { KeySequence, useMacrosStore, generateMacroId } from "@/hooks/stores";
|
import { KeySequence, useMacrosStore, generateMacroId } from "@/hooks/stores";
|
||||||
|
|
@ -22,13 +23,32 @@ import { ConfirmDialog } from "@/components/ConfirmDialog";
|
||||||
import LoadingSpinner from "@/components/LoadingSpinner";
|
import LoadingSpinner from "@/components/LoadingSpinner";
|
||||||
import useKeyboardLayout from "@/hooks/useKeyboardLayout";
|
import useKeyboardLayout from "@/hooks/useKeyboardLayout";
|
||||||
|
|
||||||
const normalizeSortOrders = (macros: KeySequence[]): KeySequence[] => {
|
const normalizeSortOrders = (macros: KeySequence[]): KeySequence[] => macros.map((m, i) => ({ ...m, sortOrder: i + 1 }));
|
||||||
return macros.map((macro, index) => ({
|
|
||||||
...macro,
|
const pad2 = (n: number) => String(n).padStart(2, "0");
|
||||||
sortOrder: index + 1,
|
|
||||||
}));
|
const buildMacroDownloadFilename = (macro: KeySequence) => {
|
||||||
|
const safeName = (macro.name || macro.id).replace(/[^a-z0-9-_]+/gi, "-").toLowerCase();
|
||||||
|
const now = new Date();
|
||||||
|
const ts = `${now.getFullYear()}${pad2(now.getMonth() + 1)}${pad2(now.getDate())}-${pad2(now.getHours())}${pad2(now.getMinutes())}${pad2(now.getSeconds())}`;
|
||||||
|
return `jetkvm-macro-${safeName}-${ts}.json`;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const sanitizeImportedStep = (raw: any) => ({
|
||||||
|
keys: Array.isArray(raw?.keys) ? raw.keys.filter((k: any) => typeof k === "string") : [],
|
||||||
|
modifiers: Array.isArray(raw?.modifiers) ? raw.modifiers.filter((m: any) => typeof m === "string") : [],
|
||||||
|
delay: typeof raw?.delay === "number" ? raw.delay : DEFAULT_DELAY,
|
||||||
|
text: typeof raw?.text === "string" ? raw.text : undefined,
|
||||||
|
wait: typeof raw?.wait === "boolean" ? raw.wait : false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const sanitizeImportedMacro = (raw: any, sortOrder: number): KeySequence => ({
|
||||||
|
id: generateMacroId(),
|
||||||
|
name: (typeof raw?.name === "string" && raw.name.trim() ? raw.name : "Imported Macro").slice(0, 50),
|
||||||
|
steps: Array.isArray(raw?.steps) ? raw.steps.map(sanitizeImportedStep) : [],
|
||||||
|
sortOrder,
|
||||||
|
});
|
||||||
|
|
||||||
export default function SettingsMacrosRoute() {
|
export default function SettingsMacrosRoute() {
|
||||||
const { macros, loading, initialized, loadMacros, saveMacros } = useMacrosStore();
|
const { macros, loading, initialized, loadMacros, saveMacros } = useMacrosStore();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
@ -36,6 +56,7 @@ export default function SettingsMacrosRoute() {
|
||||||
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
|
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
|
||||||
const [macroToDelete, setMacroToDelete] = useState<KeySequence | null>(null);
|
const [macroToDelete, setMacroToDelete] = useState<KeySequence | null>(null);
|
||||||
const { selectedKeyboard } = useKeyboardLayout();
|
const { selectedKeyboard } = useKeyboardLayout();
|
||||||
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
const isMaxMacrosReached = useMemo(
|
const isMaxMacrosReached = useMemo(
|
||||||
() => macros.length >= MAX_TOTAL_MACROS,
|
() => macros.length >= MAX_TOTAL_MACROS,
|
||||||
|
|
@ -48,74 +69,52 @@ export default function SettingsMacrosRoute() {
|
||||||
}
|
}
|
||||||
}, [initialized, loadMacros]);
|
}, [initialized, loadMacros]);
|
||||||
|
|
||||||
const handleDuplicateMacro = useCallback(
|
const handleDuplicateMacro = useCallback(async (macro: KeySequence) => {
|
||||||
async (macro: KeySequence) => {
|
if (!macro?.id || !macro?.name) {
|
||||||
if (!macro?.id || !macro?.name) {
|
notifications.error("Invalid macro data");
|
||||||
notifications.error("Invalid macro data");
|
return;
|
||||||
return;
|
}
|
||||||
}
|
if (isMaxMacrosReached) {
|
||||||
|
notifications.error(`Maximum of ${MAX_TOTAL_MACROS} macros allowed`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setActionLoadingId(macro.id);
|
||||||
|
const newMacroCopy: KeySequence = {
|
||||||
|
...JSON.parse(JSON.stringify(macro)),
|
||||||
|
id: generateMacroId(),
|
||||||
|
name: `${macro.name} ${COPY_SUFFIX}`,
|
||||||
|
sortOrder: macros.length + 1,
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
await saveMacros(normalizeSortOrders([...macros, newMacroCopy]));
|
||||||
|
notifications.success(`Macro "${newMacroCopy.name}" duplicated successfully`);
|
||||||
|
} catch (e: any) {
|
||||||
|
notifications.error(`Failed to duplicate macro: ${e?.message || 'error'}`);
|
||||||
|
} finally {
|
||||||
|
setActionLoadingId(null);
|
||||||
|
}
|
||||||
|
}, [macros, saveMacros, isMaxMacrosReached]);
|
||||||
|
|
||||||
if (isMaxMacrosReached) {
|
const handleMoveMacro = useCallback(async (index: number, direction: "up" | "down", macroId: string) => {
|
||||||
notifications.error(`Maximum of ${MAX_TOTAL_MACROS} macros allowed`);
|
if (!Array.isArray(macros) || macros.length === 0) {
|
||||||
return;
|
notifications.error("No macros available");
|
||||||
}
|
return;
|
||||||
|
}
|
||||||
setActionLoadingId(macro.id);
|
const newIndex = direction === "up" ? index - 1 : index + 1;
|
||||||
|
if (newIndex < 0 || newIndex >= macros.length) return;
|
||||||
const newMacroCopy: KeySequence = {
|
setActionLoadingId(macroId);
|
||||||
...JSON.parse(JSON.stringify(macro)),
|
try {
|
||||||
id: generateMacroId(),
|
const newMacros = [...macros];
|
||||||
name: `${macro.name} ${COPY_SUFFIX}`,
|
[newMacros[index], newMacros[newIndex]] = [newMacros[newIndex], newMacros[index]];
|
||||||
sortOrder: macros.length + 1,
|
const updatedMacros = normalizeSortOrders(newMacros);
|
||||||
};
|
await saveMacros(updatedMacros);
|
||||||
|
notifications.success("Macro order updated successfully");
|
||||||
try {
|
} catch (e: any) {
|
||||||
await saveMacros(normalizeSortOrders([...macros, newMacroCopy]));
|
notifications.error(`Failed to reorder macros: ${e?.message || 'error'}`);
|
||||||
notifications.success(`Macro "${newMacroCopy.name}" duplicated successfully`);
|
} finally {
|
||||||
} catch (error: unknown) {
|
setActionLoadingId(null);
|
||||||
if (error instanceof Error) {
|
}
|
||||||
notifications.error(`Failed to duplicate macro: ${error.message}`);
|
}, [macros, saveMacros]);
|
||||||
} else {
|
|
||||||
notifications.error("Failed to duplicate macro");
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
setActionLoadingId(null);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[isMaxMacrosReached, macros, saveMacros, setActionLoadingId],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleMoveMacro = useCallback(
|
|
||||||
async (index: number, direction: "up" | "down", macroId: string) => {
|
|
||||||
if (!Array.isArray(macros) || macros.length === 0) {
|
|
||||||
notifications.error("No macros available");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const newIndex = direction === "up" ? index - 1 : index + 1;
|
|
||||||
if (newIndex < 0 || newIndex >= macros.length) return;
|
|
||||||
|
|
||||||
setActionLoadingId(macroId);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const newMacros = [...macros];
|
|
||||||
[newMacros[index], newMacros[newIndex]] = [newMacros[newIndex], newMacros[index]];
|
|
||||||
const updatedMacros = normalizeSortOrders(newMacros);
|
|
||||||
|
|
||||||
await saveMacros(updatedMacros);
|
|
||||||
notifications.success("Macro order updated successfully");
|
|
||||||
} catch (error: unknown) {
|
|
||||||
if (error instanceof Error) {
|
|
||||||
notifications.error(`Failed to reorder macros: ${error.message}`);
|
|
||||||
} else {
|
|
||||||
notifications.error("Failed to reorder macros");
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
setActionLoadingId(null);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[macros, saveMacros, setActionLoadingId],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleDeleteMacro = useCallback(async () => {
|
const handleDeleteMacro = useCallback(async () => {
|
||||||
if (!macroToDelete?.id) return;
|
if (!macroToDelete?.id) return;
|
||||||
|
|
@ -140,6 +139,17 @@ export default function SettingsMacrosRoute() {
|
||||||
}
|
}
|
||||||
}, [macroToDelete, macros, saveMacros]);
|
}, [macroToDelete, macros, saveMacros]);
|
||||||
|
|
||||||
|
const handleDownloadMacro = useCallback((macro: KeySequence) => {
|
||||||
|
const data = JSON.stringify(macro, null, 2);
|
||||||
|
const blob = new Blob([data], { type: "application/json" });
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
const a = document.createElement("a");
|
||||||
|
a.href = url;
|
||||||
|
a.download = buildMacroDownloadFilename(macro);
|
||||||
|
a.click();
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const MacroList = useMemo(
|
const MacroList = useMemo(
|
||||||
() => (
|
() => (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
|
|
@ -178,9 +188,12 @@ export default function SettingsMacrosRoute() {
|
||||||
<span key={stepIndex} className="inline-flex items-center">
|
<span key={stepIndex} className="inline-flex items-center">
|
||||||
<StepIcon className="mr-1 h-3 w-3 shrink-0 text-slate-400 dark:text-slate-500" />
|
<StepIcon className="mr-1 h-3 w-3 shrink-0 text-slate-400 dark:text-slate-500" />
|
||||||
<span className="rounded-md border border-slate-200/50 bg-slate-50 px-2 py-0.5 dark:border-slate-700/50 dark:bg-slate-800">
|
<span className="rounded-md border border-slate-200/50 bg-slate-50 px-2 py-0.5 dark:border-slate-700/50 dark:bg-slate-800">
|
||||||
{(Array.isArray(step.modifiers) &&
|
{step.text && step.text.length > 0 ? (
|
||||||
step.modifiers.length > 0) ||
|
<span className="font-medium text-emerald-700 dark:text-emerald-300">Text: "{step.text}"</span>
|
||||||
(Array.isArray(step.keys) && step.keys.length > 0) ? (
|
) : step.wait ? (
|
||||||
|
<span className="font-medium text-amber-600 dark:text-amber-300">Wait</span>
|
||||||
|
) : (Array.isArray(step.modifiers) && step.modifiers.length > 0) ||
|
||||||
|
(Array.isArray(step.keys) && step.keys.length > 0) ? (
|
||||||
<>
|
<>
|
||||||
{Array.isArray(step.modifiers) &&
|
{Array.isArray(step.modifiers) &&
|
||||||
step.modifiers.map((modifier, idx) => (
|
step.modifiers.map((modifier, idx) => (
|
||||||
|
|
@ -224,7 +237,7 @@ export default function SettingsMacrosRoute() {
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<span className="font-medium text-slate-500 dark:text-slate-400">
|
<span className="font-medium text-slate-500 dark:text-slate-400">
|
||||||
Delay only
|
Pause only
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{step.delay !== DEFAULT_DELAY && (
|
{step.delay !== DEFAULT_DELAY && (
|
||||||
|
|
@ -261,6 +274,7 @@ export default function SettingsMacrosRoute() {
|
||||||
disabled={actionLoadingId === macro.id}
|
disabled={actionLoadingId === macro.id}
|
||||||
aria-label={`Duplicate macro ${macro.name}`}
|
aria-label={`Duplicate macro ${macro.name}`}
|
||||||
/>
|
/>
|
||||||
|
<Button size="XS" theme="light" LeadingIcon={LuDownload} onClick={() => handleDownloadMacro(macro)} aria-label={`Download macro ${macro.name}`} disabled={actionLoadingId === macro.id} />
|
||||||
<Button
|
<Button
|
||||||
size="XS"
|
size="XS"
|
||||||
theme="light"
|
theme="light"
|
||||||
|
|
@ -290,19 +304,7 @@ export default function SettingsMacrosRoute() {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
[
|
[macros, showDeleteConfirm, macroToDelete?.name, macroToDelete?.id, actionLoadingId, handleDeleteMacro, handleMoveMacro, selectedKeyboard.modifierDisplayMap, selectedKeyboard.keyDisplayMap, handleDuplicateMacro, navigate, handleDownloadMacro],
|
||||||
macros,
|
|
||||||
showDeleteConfirm,
|
|
||||||
macroToDelete?.name,
|
|
||||||
macroToDelete?.id,
|
|
||||||
actionLoadingId,
|
|
||||||
handleDeleteMacro,
|
|
||||||
handleMoveMacro,
|
|
||||||
selectedKeyboard.modifierDisplayMap,
|
|
||||||
selectedKeyboard.keyDisplayMap,
|
|
||||||
handleDuplicateMacro,
|
|
||||||
navigate
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -312,18 +314,57 @@ export default function SettingsMacrosRoute() {
|
||||||
title="Keyboard Macros"
|
title="Keyboard Macros"
|
||||||
description={`Combine keystrokes into a single action for faster workflows.`}
|
description={`Combine keystrokes into a single action for faster workflows.`}
|
||||||
/>
|
/>
|
||||||
{macros.length > 0 && (
|
<div className="flex items-center pl-2">
|
||||||
<div className="flex items-center pl-2">
|
<Button
|
||||||
|
size="SM"
|
||||||
|
theme="primary"
|
||||||
|
text={isMaxMacrosReached ? `Max Reached` : "Add New Macro"}
|
||||||
|
onClick={() => navigate("add")}
|
||||||
|
disabled={isMaxMacrosReached}
|
||||||
|
aria-label="Add new macro"
|
||||||
|
/>
|
||||||
|
<div className="ml-2 flex items-center gap-2">
|
||||||
|
<input ref={fileInputRef} type="file" accept="application/json" multiple className="hidden" onChange={async e => {
|
||||||
|
const fl = e.target.files;
|
||||||
|
if (!fl || fl.length === 0) return;
|
||||||
|
let working = [...macros];
|
||||||
|
const imported: string[] = [];
|
||||||
|
let errors = 0;
|
||||||
|
let skipped = 0;
|
||||||
|
for (const f of Array.from(fl)) {
|
||||||
|
if (working.length >= MAX_TOTAL_MACROS) { skipped++; continue; }
|
||||||
|
try {
|
||||||
|
const raw = await f.text();
|
||||||
|
const parsed = JSON.parse(raw);
|
||||||
|
const candidates = Array.isArray(parsed) ? parsed : [parsed];
|
||||||
|
for (const c of candidates) {
|
||||||
|
if (working.length >= MAX_TOTAL_MACROS) { skipped += (candidates.length - candidates.indexOf(c)); break; }
|
||||||
|
if (!c || typeof c !== "object") { errors++; continue; }
|
||||||
|
const sanitized = sanitizeImportedMacro(c, working.length + 1);
|
||||||
|
working.push(sanitized);
|
||||||
|
imported.push(sanitized.name);
|
||||||
|
}
|
||||||
|
} catch { errors++; }
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
if (imported.length) {
|
||||||
|
await saveMacros(normalizeSortOrders(working));
|
||||||
|
notifications.success(`Imported ${imported.length} macro${imported.length === 1 ? '' : 's'}`);
|
||||||
|
}
|
||||||
|
if (errors) notifications.error(`${errors} file${errors === 1 ? '' : 's'} failed`);
|
||||||
|
if (skipped) notifications.error(`${skipped} macro${skipped === 1 ? '' : 's'} skipped (limit ${MAX_TOTAL_MACROS})`);
|
||||||
|
} finally {
|
||||||
|
if (fileInputRef.current) fileInputRef.current.value = '';
|
||||||
|
}
|
||||||
|
}} />
|
||||||
<Button
|
<Button
|
||||||
size="SM"
|
size="SM"
|
||||||
theme="primary"
|
theme="light"
|
||||||
text={isMaxMacrosReached ? `Max Reached` : "Add New Macro"}
|
text="Import Macro"
|
||||||
onClick={() => navigate("add")}
|
onClick={() => fileInputRef.current?.click()}
|
||||||
disabled={isMaxMacrosReached}
|
|
||||||
aria-label="Add new macro"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import { GridCard } from "@/components/Card";
|
||||||
import { Checkbox } from "@/components/Checkbox";
|
import { Checkbox } from "@/components/Checkbox";
|
||||||
import { useSettingsStore } from "@/hooks/stores";
|
import { useSettingsStore } from "@/hooks/stores";
|
||||||
import { JsonRpcResponse, useJsonRpc } from "@/hooks/useJsonRpc";
|
import { JsonRpcResponse, useJsonRpc } from "@/hooks/useJsonRpc";
|
||||||
|
import { SettingsItem } from "@components/SettingsItem";
|
||||||
import { SettingsPageHeader } from "@components/SettingsPageheader";
|
import { SettingsPageHeader } from "@components/SettingsPageheader";
|
||||||
import { SelectMenuBasic } from "@components/SelectMenuBasic";
|
import { SelectMenuBasic } from "@components/SelectMenuBasic";
|
||||||
import { JigglerSetting } from "@components/JigglerSetting";
|
import { JigglerSetting } from "@components/JigglerSetting";
|
||||||
|
|
@ -15,8 +16,6 @@ import { cx } from "../cva.config";
|
||||||
import notifications from "../notifications";
|
import notifications from "../notifications";
|
||||||
import SettingsNestedSection from "../components/SettingsNestedSection";
|
import SettingsNestedSection from "../components/SettingsNestedSection";
|
||||||
|
|
||||||
import { SettingsItem } from "./devices.$id.settings";
|
|
||||||
|
|
||||||
export interface JigglerConfig {
|
export interface JigglerConfig {
|
||||||
inactivity_limit_seconds: number;
|
inactivity_limit_seconds: number;
|
||||||
jitter_percentage: number;
|
jitter_percentage: number;
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ import { SelectMenuBasic } from "@/components/SelectMenuBasic";
|
||||||
import { SettingsPageHeader } from "@/components/SettingsPageheader";
|
import { SettingsPageHeader } from "@/components/SettingsPageheader";
|
||||||
import Fieldset from "@/components/Fieldset";
|
import Fieldset from "@/components/Fieldset";
|
||||||
import { ConfirmDialog } from "@/components/ConfirmDialog";
|
import { ConfirmDialog } from "@/components/ConfirmDialog";
|
||||||
|
import { SettingsItem } from "@components/SettingsItem";
|
||||||
import notifications from "@/notifications";
|
import notifications from "@/notifications";
|
||||||
|
|
||||||
import Ipv6NetworkCard from "../components/Ipv6NetworkCard";
|
import Ipv6NetworkCard from "../components/Ipv6NetworkCard";
|
||||||
|
|
@ -28,8 +29,6 @@ import EmptyCard from "../components/EmptyCard";
|
||||||
import AutoHeight from "../components/AutoHeight";
|
import AutoHeight from "../components/AutoHeight";
|
||||||
import DhcpLeaseCard from "../components/DhcpLeaseCard";
|
import DhcpLeaseCard from "../components/DhcpLeaseCard";
|
||||||
|
|
||||||
import { SettingsItem } from "./devices.$id.settings";
|
|
||||||
|
|
||||||
dayjs.extend(relativeTime);
|
dayjs.extend(relativeTime);
|
||||||
|
|
||||||
const defaultNetworkSettings: NetworkSettings = {
|
const defaultNetworkSettings: NetworkSettings = {
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import React, { useEffect, useRef, useState } from "react";
|
||||||
import { NavLink, Outlet, useLocation } from "react-router";
|
import { NavLink, Outlet, useLocation } from "react-router";
|
||||||
import {
|
import {
|
||||||
LuSettings,
|
LuSettings,
|
||||||
|
|
@ -12,17 +13,14 @@ import {
|
||||||
LuCommand,
|
LuCommand,
|
||||||
LuNetwork,
|
LuNetwork,
|
||||||
} from "react-icons/lu";
|
} from "react-icons/lu";
|
||||||
import React, { useEffect, useRef, useState } from "react";
|
|
||||||
import { useResizeObserver } from "usehooks-ts";
|
import { useResizeObserver } from "usehooks-ts";
|
||||||
|
|
||||||
import Card from "@/components/Card";
|
import { cx } from "@/cva.config";
|
||||||
import { LinkButton } from "@/components/Button";
|
import Card from "@components/Card";
|
||||||
import { FeatureFlag } from "@/components/FeatureFlag";
|
import { LinkButton } from "@components/Button";
|
||||||
import LoadingSpinner from "@/components/LoadingSpinner";
|
import { FeatureFlag } from "@components/FeatureFlag";
|
||||||
import { useUiStore } from "@/hooks/stores";
|
import { useUiStore } from "@/hooks/stores";
|
||||||
|
|
||||||
import { cx } from "../cva.config";
|
|
||||||
|
|
||||||
/* TODO: Migrate to using URLs instead of the global state. To simplify the refactoring, we'll keep the global state for now. */
|
/* TODO: Migrate to using URLs instead of the global state. To simplify the refactoring, we'll keep the global state for now. */
|
||||||
export default function SettingsRoute() {
|
export default function SettingsRoute() {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
|
@ -257,40 +255,3 @@ export default function SettingsRoute() {
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SettingsItemProps {
|
|
||||||
readonly title: string;
|
|
||||||
readonly description: string | React.ReactNode;
|
|
||||||
readonly badge?: string;
|
|
||||||
readonly className?: string;
|
|
||||||
readonly loading?: boolean;
|
|
||||||
readonly children?: React.ReactNode;
|
|
||||||
}
|
|
||||||
export function SettingsItem(props: SettingsItemProps) {
|
|
||||||
const { title, description, badge, children, className, loading } = props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<label
|
|
||||||
className={cx(
|
|
||||||
"flex select-none items-center justify-between gap-x-8 rounded",
|
|
||||||
className,
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<div className="space-y-0.5">
|
|
||||||
<div className="flex items-center gap-x-2">
|
|
||||||
<div className="flex items-center text-base font-semibold text-black dark:text-white">
|
|
||||||
{title}
|
|
||||||
{badge && (
|
|
||||||
<span className="ml-2 rounded-full bg-red-500 px-2 py-1 text-[10px] font-medium leading-none text-white dark:border dark:border-red-700 dark:bg-red-800 dark:text-red-50">
|
|
||||||
{badge}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
{loading && <LoadingSpinner className="h-4 w-4 text-blue-500" />}
|
|
||||||
</div>
|
|
||||||
<div className="text-sm text-slate-700 dark:text-slate-300">{description}</div>
|
|
||||||
</div>
|
|
||||||
{children ? <div>{children}</div> : null}
|
|
||||||
</label>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -3,14 +3,13 @@ import { useCallback, useEffect, useState } from "react";
|
||||||
import { Button } from "@/components/Button";
|
import { Button } from "@/components/Button";
|
||||||
import { TextAreaWithLabel } from "@/components/TextArea";
|
import { TextAreaWithLabel } from "@/components/TextArea";
|
||||||
import { JsonRpcResponse, useJsonRpc } from "@/hooks/useJsonRpc";
|
import { JsonRpcResponse, useJsonRpc } from "@/hooks/useJsonRpc";
|
||||||
|
import { SettingsItem } from "@components/SettingsItem";
|
||||||
import { SettingsPageHeader } from "@components/SettingsPageheader";
|
import { SettingsPageHeader } from "@components/SettingsPageheader";
|
||||||
import { useSettingsStore } from "@/hooks/stores";
|
import { useSettingsStore } from "@/hooks/stores";
|
||||||
import { SelectMenuBasic } from "@components/SelectMenuBasic";
|
import { SelectMenuBasic } from "@components/SelectMenuBasic";
|
||||||
import Fieldset from "@components/Fieldset";
|
import Fieldset from "@components/Fieldset";
|
||||||
import notifications from "@/notifications";
|
import notifications from "@/notifications";
|
||||||
|
|
||||||
import { SettingsItem } from "./devices.$id.settings";
|
|
||||||
|
|
||||||
const defaultEdid =
|
const defaultEdid =
|
||||||
"00ffffffffffff0052620188008888881c150103800000780a0dc9a05747982712484c00000001010101010101010101010101010101023a801871382d40582c4500c48e2100001e011d007251d01e206e285500c48e2100001e000000fc00543734392d6648443732300a20000000fd00147801ff1d000a202020202020017b";
|
"00ffffffffffff0052620188008888881c150103800000780a0dc9a05747982712484c00000001010101010101010101010101010101023a801871382d40582c4500c48e2100001e011d007251d01e206e285500c48e2100001e000000fc00543734392d6648443732300a20000000fd00147801ff1d000a202020202020017b";
|
||||||
const edids = [
|
const edids = [
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue