feat: GUI 配置支持添加和减少 LED。
This commit is contained in:
parent
4ad78ae5cc
commit
366b137258
@ -12,8 +12,14 @@
|
||||
"dependencies": {
|
||||
"@emotion/react": "^11.10.5",
|
||||
"@emotion/styled": "^11.10.5",
|
||||
"@fortawesome/fontawesome-svg-core": "^6.2.1",
|
||||
"@fortawesome/free-regular-svg-icons": "^6.2.1",
|
||||
"@fortawesome/free-solid-svg-icons": "^6.2.1",
|
||||
"@fortawesome/react-fontawesome": "^0.2.0",
|
||||
"@mui/material": "^5.11.1",
|
||||
"@tauri-apps/api": "^1.1.0",
|
||||
"clsx": "^1.2.1",
|
||||
"ramda": "^0.28.0",
|
||||
"react": "^18.2.0",
|
||||
"react-async-hook": "^4.0.0",
|
||||
"react-dom": "^18.2.0"
|
||||
@ -24,6 +30,7 @@
|
||||
"@emotion/serialize": "^1.1.1",
|
||||
"@tauri-apps/cli": "^1.1.0",
|
||||
"@types/node": "^18.7.10",
|
||||
"@types/ramda": "^0.28.20",
|
||||
"@types/react": "^18.0.15",
|
||||
"@types/react-dom": "^18.0.6",
|
||||
"@vitejs/plugin-react": "^2.0.0",
|
||||
|
281
pnpm-lock.yaml
generated
281
pnpm-lock.yaml
generated
@ -6,9 +6,15 @@ specifiers:
|
||||
'@emotion/react': ^11.10.5
|
||||
'@emotion/serialize': ^1.1.1
|
||||
'@emotion/styled': ^11.10.5
|
||||
'@fortawesome/fontawesome-svg-core': ^6.2.1
|
||||
'@fortawesome/free-regular-svg-icons': ^6.2.1
|
||||
'@fortawesome/free-solid-svg-icons': ^6.2.1
|
||||
'@fortawesome/react-fontawesome': ^0.2.0
|
||||
'@mui/material': ^5.11.1
|
||||
'@tauri-apps/api': ^1.1.0
|
||||
'@tauri-apps/cli': ^1.1.0
|
||||
'@types/node': ^18.7.10
|
||||
'@types/ramda': ^0.28.20
|
||||
'@types/react': ^18.0.15
|
||||
'@types/react-dom': ^18.0.6
|
||||
'@vitejs/plugin-react': ^2.0.0
|
||||
@ -22,6 +28,7 @@ specifiers:
|
||||
eslint-plugin-simple-import-sort: ^8.0.0
|
||||
postcss: ^8.4.19
|
||||
prettier: ^2.7.1
|
||||
ramda: ^0.28.0
|
||||
react: ^18.2.0
|
||||
react-async-hook: ^4.0.0
|
||||
react-dom: ^18.2.0
|
||||
@ -33,8 +40,14 @@ specifiers:
|
||||
dependencies:
|
||||
'@emotion/react': 11.10.5_kzbn2opkn2327fwg5yzwzya5o4
|
||||
'@emotion/styled': 11.10.5_qvatmowesywn4ye42qoh247szu
|
||||
'@fortawesome/fontawesome-svg-core': 6.2.1
|
||||
'@fortawesome/free-regular-svg-icons': 6.2.1
|
||||
'@fortawesome/free-solid-svg-icons': 6.2.1
|
||||
'@fortawesome/react-fontawesome': 0.2.0_z27bm67dtmuyyvss23ckjdrcuy
|
||||
'@mui/material': 5.11.1_lskpmcsdi7ipu6qpuapyu56ihm
|
||||
'@tauri-apps/api': 1.2.0
|
||||
clsx: 1.2.1
|
||||
ramda: 0.28.0
|
||||
react: 18.2.0
|
||||
react-async-hook: 4.0.0_react@18.2.0
|
||||
react-dom: 18.2.0_react@18.2.0
|
||||
@ -45,6 +58,7 @@ devDependencies:
|
||||
'@emotion/serialize': 1.1.1
|
||||
'@tauri-apps/cli': 1.2.2
|
||||
'@types/node': 18.11.16
|
||||
'@types/ramda': 0.28.20
|
||||
'@types/react': 18.0.26
|
||||
'@types/react-dom': 18.0.9
|
||||
'@vitejs/plugin-react': 2.2.0_vite@3.2.5
|
||||
@ -505,6 +519,47 @@ packages:
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@fortawesome/fontawesome-common-types/6.2.1:
|
||||
resolution: {integrity: sha512-Sz07mnQrTekFWLz5BMjOzHl/+NooTdW8F8kDQxjWwbpOJcnoSg4vUDng8d/WR1wOxM0O+CY9Zw0nR054riNYtQ==}
|
||||
engines: {node: '>=6'}
|
||||
requiresBuild: true
|
||||
dev: false
|
||||
|
||||
/@fortawesome/fontawesome-svg-core/6.2.1:
|
||||
resolution: {integrity: sha512-HELwwbCz6C1XEcjzyT1Jugmz2NNklMrSPjZOWMlc+ZsHIVk+XOvOXLGGQtFBwSyqfJDNgRq4xBCwWOaZ/d9DEA==}
|
||||
engines: {node: '>=6'}
|
||||
requiresBuild: true
|
||||
dependencies:
|
||||
'@fortawesome/fontawesome-common-types': 6.2.1
|
||||
dev: false
|
||||
|
||||
/@fortawesome/free-regular-svg-icons/6.2.1:
|
||||
resolution: {integrity: sha512-wiqcNDNom75x+pe88FclpKz7aOSqS2lOivZeicMV5KRwOAeypxEYWAK/0v+7r+LrEY30+qzh8r2XDaEHvoLsMA==}
|
||||
engines: {node: '>=6'}
|
||||
requiresBuild: true
|
||||
dependencies:
|
||||
'@fortawesome/fontawesome-common-types': 6.2.1
|
||||
dev: false
|
||||
|
||||
/@fortawesome/free-solid-svg-icons/6.2.1:
|
||||
resolution: {integrity: sha512-oKuqrP5jbfEPJWTij4sM+/RvgX+RMFwx3QZCZcK9PrBDgxC35zuc7AOFsyMjMd/PIFPeB2JxyqDr5zs/DZFPPw==}
|
||||
engines: {node: '>=6'}
|
||||
requiresBuild: true
|
||||
dependencies:
|
||||
'@fortawesome/fontawesome-common-types': 6.2.1
|
||||
dev: false
|
||||
|
||||
/@fortawesome/react-fontawesome/0.2.0_z27bm67dtmuyyvss23ckjdrcuy:
|
||||
resolution: {integrity: sha512-uHg75Rb/XORTtVt7OS9WoK8uM276Ufi7gCzshVWkUJbHhh3svsUUeqXerrM96Wm7fRiDzfKRwSoahhMIkGAYHw==}
|
||||
peerDependencies:
|
||||
'@fortawesome/fontawesome-svg-core': ~1 || ~6
|
||||
react: '>=16.3'
|
||||
dependencies:
|
||||
'@fortawesome/fontawesome-svg-core': 6.2.1
|
||||
prop-types: 15.8.1
|
||||
react: 18.2.0
|
||||
dev: false
|
||||
|
||||
/@jridgewell/gen-mapping/0.1.1:
|
||||
resolution: {integrity: sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
@ -543,6 +598,164 @@ packages:
|
||||
'@jridgewell/sourcemap-codec': 1.4.14
|
||||
dev: true
|
||||
|
||||
/@mui/base/5.0.0-alpha.111_ib3m5ricvtkl2cll7qpr2f6lvq:
|
||||
resolution: {integrity: sha512-2wfIPpl97S4dPzD0QOM3UIzQ/EuXCYQvHmXxTpfKxev/cfkzOe7Ik/McoYUBbtM1bSOqH3W276R/L2LF9cyXqQ==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
peerDependencies:
|
||||
'@types/react': ^17.0.0 || ^18.0.0
|
||||
react: ^17.0.0 || ^18.0.0
|
||||
react-dom: ^17.0.0 || ^18.0.0
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
dependencies:
|
||||
'@babel/runtime': 7.20.6
|
||||
'@emotion/is-prop-valid': 1.2.0
|
||||
'@mui/types': 7.2.3_@types+react@18.0.26
|
||||
'@mui/utils': 5.11.1_react@18.2.0
|
||||
'@popperjs/core': 2.11.6
|
||||
'@types/react': 18.0.26
|
||||
clsx: 1.2.1
|
||||
prop-types: 15.8.1
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0_react@18.2.0
|
||||
react-is: 18.2.0
|
||||
dev: false
|
||||
|
||||
/@mui/core-downloads-tracker/5.11.1:
|
||||
resolution: {integrity: sha512-QVqVNlZ2K+LqUDE5kFgYd0r4KekR/dv2cNYbAutQWbfOA8VPVUVrDz0ELrEcoe8TjM/CwnsmGvaDh/YSNl/ALA==}
|
||||
dev: false
|
||||
|
||||
/@mui/material/5.11.1_lskpmcsdi7ipu6qpuapyu56ihm:
|
||||
resolution: {integrity: sha512-yaZiXvcrl2vgUK+VO24780BWRgwdAMmAyuMVZnRTts1Yu0tWd6PjIYq2ZtaOlpj6/LbaSS+Q2kSfxYnDQ20CEQ==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
peerDependencies:
|
||||
'@emotion/react': ^11.5.0
|
||||
'@emotion/styled': ^11.3.0
|
||||
'@types/react': ^17.0.0 || ^18.0.0
|
||||
react: ^17.0.0 || ^18.0.0
|
||||
react-dom: ^17.0.0 || ^18.0.0
|
||||
peerDependenciesMeta:
|
||||
'@emotion/react':
|
||||
optional: true
|
||||
'@emotion/styled':
|
||||
optional: true
|
||||
'@types/react':
|
||||
optional: true
|
||||
dependencies:
|
||||
'@babel/runtime': 7.20.6
|
||||
'@emotion/react': 11.10.5_kzbn2opkn2327fwg5yzwzya5o4
|
||||
'@emotion/styled': 11.10.5_qvatmowesywn4ye42qoh247szu
|
||||
'@mui/base': 5.0.0-alpha.111_ib3m5ricvtkl2cll7qpr2f6lvq
|
||||
'@mui/core-downloads-tracker': 5.11.1
|
||||
'@mui/system': 5.11.1_ogriz7mfahdh34qnfautfro5yu
|
||||
'@mui/types': 7.2.3_@types+react@18.0.26
|
||||
'@mui/utils': 5.11.1_react@18.2.0
|
||||
'@types/react': 18.0.26
|
||||
'@types/react-transition-group': 4.4.5
|
||||
clsx: 1.2.1
|
||||
csstype: 3.1.1
|
||||
prop-types: 15.8.1
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0_react@18.2.0
|
||||
react-is: 18.2.0
|
||||
react-transition-group: 4.4.5_biqbaboplfbrettd7655fr4n2y
|
||||
dev: false
|
||||
|
||||
/@mui/private-theming/5.11.1_kzbn2opkn2327fwg5yzwzya5o4:
|
||||
resolution: {integrity: sha512-nnHg7kA5RwFRhy0wiDYe59sLCVGORpPypL1JcEdhv0+N0Zbmc2E/y4z2zqMRZ62MAEscpro7cQbvv244ThA84A==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
peerDependencies:
|
||||
'@types/react': ^17.0.0 || ^18.0.0
|
||||
react: ^17.0.0 || ^18.0.0
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
dependencies:
|
||||
'@babel/runtime': 7.20.6
|
||||
'@mui/utils': 5.11.1_react@18.2.0
|
||||
'@types/react': 18.0.26
|
||||
prop-types: 15.8.1
|
||||
react: 18.2.0
|
||||
dev: false
|
||||
|
||||
/@mui/styled-engine/5.11.0_dovxhg2tvkkxkdnqyoum6wzcxm:
|
||||
resolution: {integrity: sha512-AF06K60Zc58qf0f7X+Y/QjaHaZq16znliLnGc9iVrV/+s8Ln/FCoeNuFvhlCbZZQ5WQcJvcy59zp0nXrklGGPQ==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
peerDependencies:
|
||||
'@emotion/react': ^11.4.1
|
||||
'@emotion/styled': ^11.3.0
|
||||
react: ^17.0.0 || ^18.0.0
|
||||
peerDependenciesMeta:
|
||||
'@emotion/react':
|
||||
optional: true
|
||||
'@emotion/styled':
|
||||
optional: true
|
||||
dependencies:
|
||||
'@babel/runtime': 7.20.6
|
||||
'@emotion/cache': 11.10.5
|
||||
'@emotion/react': 11.10.5_kzbn2opkn2327fwg5yzwzya5o4
|
||||
'@emotion/styled': 11.10.5_qvatmowesywn4ye42qoh247szu
|
||||
csstype: 3.1.1
|
||||
prop-types: 15.8.1
|
||||
react: 18.2.0
|
||||
dev: false
|
||||
|
||||
/@mui/system/5.11.1_ogriz7mfahdh34qnfautfro5yu:
|
||||
resolution: {integrity: sha512-BEA2S0hay8n8CcZftkeAVsi0nsb5ZjdnZRCahv5lX7QJYwDjO4ucJ6lnvxHe2v/9Te1LLjTO7ojxu/qM6CE5Cg==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
peerDependencies:
|
||||
'@emotion/react': ^11.5.0
|
||||
'@emotion/styled': ^11.3.0
|
||||
'@types/react': ^17.0.0 || ^18.0.0
|
||||
react: ^17.0.0 || ^18.0.0
|
||||
peerDependenciesMeta:
|
||||
'@emotion/react':
|
||||
optional: true
|
||||
'@emotion/styled':
|
||||
optional: true
|
||||
'@types/react':
|
||||
optional: true
|
||||
dependencies:
|
||||
'@babel/runtime': 7.20.6
|
||||
'@emotion/react': 11.10.5_kzbn2opkn2327fwg5yzwzya5o4
|
||||
'@emotion/styled': 11.10.5_qvatmowesywn4ye42qoh247szu
|
||||
'@mui/private-theming': 5.11.1_kzbn2opkn2327fwg5yzwzya5o4
|
||||
'@mui/styled-engine': 5.11.0_dovxhg2tvkkxkdnqyoum6wzcxm
|
||||
'@mui/types': 7.2.3_@types+react@18.0.26
|
||||
'@mui/utils': 5.11.1_react@18.2.0
|
||||
'@types/react': 18.0.26
|
||||
clsx: 1.2.1
|
||||
csstype: 3.1.1
|
||||
prop-types: 15.8.1
|
||||
react: 18.2.0
|
||||
dev: false
|
||||
|
||||
/@mui/types/7.2.3_@types+react@18.0.26:
|
||||
resolution: {integrity: sha512-tZ+CQggbe9Ol7e/Fs5RcKwg/woU+o8DCtOnccX6KmbBc7YrfqMYEYuaIcXHuhpT880QwNkZZ3wQwvtlDFA2yOw==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
dependencies:
|
||||
'@types/react': 18.0.26
|
||||
dev: false
|
||||
|
||||
/@mui/utils/5.11.1_react@18.2.0:
|
||||
resolution: {integrity: sha512-lMAPgIJoil8V9ZxsMbEflMsvZmWcHbRVMc4JDY9jPO9V4welpF43h/O267b1RqlcRnC5MEbVQV605GYkTZY29Q==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
peerDependencies:
|
||||
react: ^17.0.0 || ^18.0.0
|
||||
dependencies:
|
||||
'@babel/runtime': 7.20.6
|
||||
'@types/prop-types': 15.7.5
|
||||
'@types/react-is': 17.0.3
|
||||
prop-types: 15.8.1
|
||||
react: 18.2.0
|
||||
react-is: 18.2.0
|
||||
dev: false
|
||||
|
||||
/@nodelib/fs.scandir/2.1.5:
|
||||
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
|
||||
engines: {node: '>= 8'}
|
||||
@ -564,6 +777,10 @@ packages:
|
||||
fastq: 1.14.0
|
||||
dev: true
|
||||
|
||||
/@popperjs/core/2.11.6:
|
||||
resolution: {integrity: sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw==}
|
||||
dev: false
|
||||
|
||||
/@tauri-apps/api/1.2.0:
|
||||
resolution: {integrity: sha512-lsI54KI6HGf7VImuf/T9pnoejfgkNoXveP14pVV7XarrQ46rOejIVJLFqHI9sRReJMGdh2YuCoI3cc/yCWCsrw==}
|
||||
engines: {node: '>= 14.6.0', npm: '>= 6.6.0', yarn: '>= 1.19.1'}
|
||||
@ -680,12 +897,30 @@ packages:
|
||||
/@types/prop-types/15.7.5:
|
||||
resolution: {integrity: sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==}
|
||||
|
||||
/@types/ramda/0.28.20:
|
||||
resolution: {integrity: sha512-MeUhzGSXQTRsY19JGn5LIBTLxVEnyF6HDNr08KSJqybsm4DlfLIgK1jBHjhpiSyk252tXYmp+UOe0UFg0UiFsA==}
|
||||
dependencies:
|
||||
ts-toolbelt: 6.15.5
|
||||
dev: true
|
||||
|
||||
/@types/react-dom/18.0.9:
|
||||
resolution: {integrity: sha512-qnVvHxASt/H7i+XG1U1xMiY5t+IHcPGUK7TDMDzom08xa7e86eCeKOiLZezwCKVxJn6NEiiy2ekgX8aQssjIKg==}
|
||||
dependencies:
|
||||
'@types/react': 18.0.26
|
||||
dev: true
|
||||
|
||||
/@types/react-is/17.0.3:
|
||||
resolution: {integrity: sha512-aBTIWg1emtu95bLTLx0cpkxwGW3ueZv71nE2YFBpL8k/z5czEW8yYpOo8Dp+UUAFAtKwNaOsh/ioSeQnWlZcfw==}
|
||||
dependencies:
|
||||
'@types/react': 18.0.26
|
||||
dev: false
|
||||
|
||||
/@types/react-transition-group/4.4.5:
|
||||
resolution: {integrity: sha512-juKD/eiSM3/xZYzjuzH6ZwpP+/lejltmiS3QEzV/vmb/Q8+HfDmxu+Baga8UEMGBqV88Nbg4l2hY/K2DkyaLLA==}
|
||||
dependencies:
|
||||
'@types/react': 18.0.26
|
||||
dev: false
|
||||
|
||||
/@types/react/18.0.26:
|
||||
resolution: {integrity: sha512-hCR3PJQsAIXyxhTNSiDFY//LhnMZWpNNr5etoCqx/iUfGc5gXWtQR2Phl908jVR6uPXacojQWTg4qRpkxTuGug==}
|
||||
dependencies:
|
||||
@ -1039,6 +1274,13 @@ packages:
|
||||
esutils: 2.0.3
|
||||
dev: true
|
||||
|
||||
/dom-helpers/5.2.1:
|
||||
resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==}
|
||||
dependencies:
|
||||
'@babel/runtime': 7.20.6
|
||||
csstype: 3.1.1
|
||||
dev: false
|
||||
|
||||
/electron-to-chromium/1.4.284:
|
||||
resolution: {integrity: sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA==}
|
||||
dev: true
|
||||
@ -1840,6 +2082,11 @@ packages:
|
||||
engines: {node: '>=0.10.0'}
|
||||
dev: true
|
||||
|
||||
/object-assign/4.1.1:
|
||||
resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
dev: false
|
||||
|
||||
/object-hash/3.0.0:
|
||||
resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==}
|
||||
engines: {node: '>= 6'}
|
||||
@ -1992,6 +2239,14 @@ packages:
|
||||
hasBin: true
|
||||
dev: true
|
||||
|
||||
/prop-types/15.8.1:
|
||||
resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==}
|
||||
dependencies:
|
||||
loose-envify: 1.4.0
|
||||
object-assign: 4.1.1
|
||||
react-is: 16.13.1
|
||||
dev: false
|
||||
|
||||
/queue-microtask/1.2.3:
|
||||
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
|
||||
dev: true
|
||||
@ -2001,6 +2256,10 @@ packages:
|
||||
engines: {node: '>=10'}
|
||||
dev: true
|
||||
|
||||
/ramda/0.28.0:
|
||||
resolution: {integrity: sha512-9QnLuG/kPVgWvMQ4aODhsBUFKOUmnbUnsSXACv+NCQZcHbeb+v8Lodp8OVxtRULN1/xOyYLLaL6npE6dMq5QTA==}
|
||||
dev: false
|
||||
|
||||
/react-async-hook/4.0.0_react@18.2.0:
|
||||
resolution: {integrity: sha512-97lgjFkOcHCTYSrsKBpsXg3iVWM0LnzedB749iP76sb3/8Ouu4nHIkCLEOrQWHVYqrYxjF05NN6GHoXWFkB3Kw==}
|
||||
engines: {node: '>=8', npm: '>=5'}
|
||||
@ -2024,11 +2283,29 @@ packages:
|
||||
resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
|
||||
dev: false
|
||||
|
||||
/react-is/18.2.0:
|
||||
resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==}
|
||||
dev: false
|
||||
|
||||
/react-refresh/0.14.0:
|
||||
resolution: {integrity: sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
dev: true
|
||||
|
||||
/react-transition-group/4.4.5_biqbaboplfbrettd7655fr4n2y:
|
||||
resolution: {integrity: sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==}
|
||||
peerDependencies:
|
||||
react: '>=16.6.0'
|
||||
react-dom: '>=16.6.0'
|
||||
dependencies:
|
||||
'@babel/runtime': 7.20.6
|
||||
dom-helpers: 5.2.1
|
||||
loose-envify: 1.4.0
|
||||
prop-types: 15.8.1
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0_react@18.2.0
|
||||
dev: false
|
||||
|
||||
/react/18.2.0:
|
||||
resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
@ -2221,6 +2498,10 @@ packages:
|
||||
is-number: 7.0.0
|
||||
dev: true
|
||||
|
||||
/ts-toolbelt/6.15.5:
|
||||
resolution: {integrity: sha512-FZIXf1ksVyLcfr7M317jbB67XFJhOO1YqdTcuGaq9q5jLUoTikukZ+98TPjKiP2jC5CgmYdWWYs0s2nLSU0/1A==}
|
||||
dev: true
|
||||
|
||||
/tsconfig-paths/3.14.1:
|
||||
resolution: {integrity: sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==}
|
||||
dependencies:
|
||||
|
@ -1,9 +1,9 @@
|
||||
use futures::{future::join_all, stream::FuturesUnordered, StreamExt};
|
||||
use futures::future::join_all;
|
||||
use once_cell::sync::OnceCell;
|
||||
use paris::info;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::value::Index;
|
||||
use std::{collections::HashMap, iter::Map, sync::Arc, thread, time::Duration};
|
||||
|
||||
use std::{collections::HashMap, sync::Arc, time::Duration};
|
||||
use tauri::async_runtime::RwLock;
|
||||
use tokio::{
|
||||
join,
|
||||
|
@ -11,6 +11,7 @@ mod rpc;
|
||||
use crate::core::AmbientLightMode;
|
||||
use crate::core::CoreManager;
|
||||
use paris::*;
|
||||
use picker::config::DisplayConfig;
|
||||
use picker::manager::Picker;
|
||||
use picker::screenshot::ScreenshotDto;
|
||||
use std::vec;
|
||||
@ -35,6 +36,26 @@ async fn take_snapshot() -> Vec<ScreenshotDto> {
|
||||
base64_bitmap_list
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn get_screenshot_by_config(config: DisplayConfig) -> Result<ScreenshotDto, String> {
|
||||
info!("Hi");
|
||||
let manager = Picker::global();
|
||||
|
||||
let start = time::Instant::now();
|
||||
let screenshot_dto = manager.get_screenshot_by_config(config).await;
|
||||
info!("截图耗时 {} s", start.elapsed().as_seconds_f32());
|
||||
match screenshot_dto {
|
||||
Ok(screenshot_dto) => {
|
||||
info!("截图耗时 {} s", start.elapsed().as_seconds_f32());
|
||||
Ok(screenshot_dto)
|
||||
}
|
||||
Err(error) => {
|
||||
error!("get_screenshot_by_config failed. {}", error);
|
||||
Err(format!("get_screenshot_by_config failed. {}", error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn get_picker_config() -> picker::config::Configuration {
|
||||
let configuration = picker::config::Manager::global().get_config();
|
||||
@ -57,6 +78,7 @@ async fn main() {
|
||||
take_snapshot,
|
||||
play_mode,
|
||||
get_picker_config,
|
||||
get_screenshot_by_config,
|
||||
])
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
|
@ -9,6 +9,7 @@ pub struct LedStripConfig {
|
||||
|
||||
#[derive(Clone, Copy, Serialize, Deserialize, Debug)]
|
||||
pub struct DisplayConfig {
|
||||
pub id: usize,
|
||||
pub index_of_display: usize,
|
||||
pub display_width: usize,
|
||||
pub display_height: usize,
|
||||
@ -19,8 +20,9 @@ pub struct DisplayConfig {
|
||||
}
|
||||
|
||||
impl DisplayConfig {
|
||||
pub fn default(index_of_display: usize, display_width: usize, display_height: usize) -> Self {
|
||||
pub fn default(id: usize, index_of_display: usize, display_width: usize, display_height: usize) -> Self {
|
||||
Self {
|
||||
id,
|
||||
index_of_display,
|
||||
display_width,
|
||||
display_height,
|
||||
|
@ -2,7 +2,7 @@ use futures::{stream::FuturesUnordered, StreamExt};
|
||||
use once_cell::sync::OnceCell;
|
||||
use paris::info;
|
||||
use scrap::Display;
|
||||
use std::{borrow::Borrow, sync::Arc};
|
||||
use std::sync::Arc;
|
||||
use tokio::{sync::Mutex, task};
|
||||
|
||||
use crate::picker::{config, screen::Screen};
|
||||
@ -44,7 +44,7 @@ impl Picker {
|
||||
for (index, display) in displays.iter().enumerate() {
|
||||
let height = display.height();
|
||||
let width = display.width();
|
||||
let config = DisplayConfig::default(index, width, height);
|
||||
let config = DisplayConfig::default(index, index, width, height);
|
||||
configs.push(config);
|
||||
}
|
||||
|
||||
@ -76,4 +76,16 @@ impl Picker {
|
||||
|
||||
anyhow::Ok(screenshot.to_dto().await)
|
||||
}
|
||||
|
||||
pub async fn get_screenshot_by_config(
|
||||
&self,
|
||||
config: DisplayConfig,
|
||||
) -> anyhow::Result<ScreenshotDto> {
|
||||
let start = time::Instant::now();
|
||||
let mut picker = DisplayPicker::from_config(config)?;
|
||||
let screenshot = picker.take_screenshot()?;
|
||||
info!("Take Screenshot Spend: {}", start.elapsed());
|
||||
|
||||
anyhow::Ok(screenshot.to_dto().await)
|
||||
}
|
||||
}
|
||||
|
83
src-tauri/src/picker/preview_manager.rs
Normal file
83
src-tauri/src/picker/preview_manager.rs
Normal file
@ -0,0 +1,83 @@
|
||||
use futures::{stream::FuturesUnordered, StreamExt};
|
||||
use once_cell::sync::OnceCell;
|
||||
use paris::{info, warn};
|
||||
use scrap::Display;
|
||||
use std::{borrow::Borrow, sync::Arc};
|
||||
use tokio::{sync::Mutex, task};
|
||||
|
||||
use crate::picker::{config, screen::Screen};
|
||||
|
||||
use super::{
|
||||
config::DisplayConfig,
|
||||
display_picker::DisplayPicker,
|
||||
manager::Picker,
|
||||
screenshot::{Screenshot, ScreenshotDto},
|
||||
};
|
||||
|
||||
pub struct PreviewPicker {
|
||||
pub pickers: Arc<Mutex<Vec<Arc<Mutex<DisplayPicker>>>>>,
|
||||
pub screenshots: Arc<Mutex<Vec<Screenshot>>>,
|
||||
}
|
||||
|
||||
impl PreviewPicker {
|
||||
pub fn global() -> &'static PreviewPicker {
|
||||
static SCREEN_COLOR_PREVIEW_PICKER: OnceCell<PreviewPicker> = OnceCell::new();
|
||||
|
||||
SCREEN_COLOR_PREVIEW_PICKER.get_or_init(|| PreviewPicker {
|
||||
pickers: Arc::new(Mutex::new(vec![])),
|
||||
screenshots: Arc::new(Mutex::new(vec![])),
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn list_displays(&self) {
|
||||
let mut pickers = self.pickers.lock().await;
|
||||
let displays = Display::all()
|
||||
.map_err(|error| anyhow::anyhow!("Can not get all of displays. {}", error))?;
|
||||
|
||||
let mut configs = vec![];
|
||||
let mut futs = FuturesUnordered::new();
|
||||
|
||||
for (index, display) in displays.iter().enumerate() {
|
||||
let height = display.height();
|
||||
let width = display.width();
|
||||
let config = DisplayConfig::default(index, width, height);
|
||||
configs.push(config);
|
||||
}
|
||||
|
||||
for config in configs.iter() {
|
||||
let picker = DisplayPicker::from_config(*config);
|
||||
match picker {
|
||||
Ok(picker) => {
|
||||
pickers.push(Arc::new(Mutex::new(picker)));
|
||||
}
|
||||
Err(_) => {
|
||||
warn!(
|
||||
"can not create DisplayPicker from config. config: {:?}",
|
||||
config
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_screenshot_by_config(
|
||||
&self,
|
||||
config: DisplayConfig,
|
||||
) -> anyhow::Result<ScreenshotDto> {
|
||||
let start = time::Instant::now();
|
||||
let mut picker = DisplayPicker::from_config(config)?;
|
||||
let screenshot = picker.take_screenshot()?;
|
||||
info!("Take Screenshot Spend: {}", start.elapsed());
|
||||
|
||||
anyhow::Ok(screenshot.to_dto().await)
|
||||
}
|
||||
|
||||
pub async fn preview_display_by_config(config: DisplayConfig) -> anyhow::Result<ScreenshotDto> {
|
||||
let start = time::Instant::now();
|
||||
let mut picker = DisplayPicker::from_config(config)?;
|
||||
let screenshot = picker.take_screenshot()?;
|
||||
info!("Take Screenshot Spend: {}", start.elapsed());
|
||||
|
||||
anyhow::Ok(screenshot.to_dto().await)
|
||||
}
|
||||
}
|
@ -51,7 +51,7 @@ impl Screenshot {
|
||||
}
|
||||
};
|
||||
|
||||
let bottom: Vec<LedSamplePoints> = match config.top_led_strip {
|
||||
let bottom: Vec<LedSamplePoints> = match config.bottom_led_strip {
|
||||
Some(led_strip_config) => {
|
||||
let points = Self::get_one_edge_sample_points(
|
||||
config.display_height / 9,
|
||||
@ -76,7 +76,7 @@ impl Screenshot {
|
||||
}
|
||||
};
|
||||
|
||||
let left: Vec<LedSamplePoints> = match config.top_led_strip {
|
||||
let left: Vec<LedSamplePoints> = match config.left_led_strip {
|
||||
Some(led_strip_config) => {
|
||||
let points = Self::get_one_edge_sample_points(
|
||||
config.display_width / 16,
|
||||
@ -98,7 +98,7 @@ impl Screenshot {
|
||||
}
|
||||
};
|
||||
|
||||
let right: Vec<LedSamplePoints> = match config.top_led_strip {
|
||||
let right: Vec<LedSamplePoints> = match config.right_led_strip {
|
||||
Some(led_strip_config) => {
|
||||
let points = Self::get_one_edge_sample_points(
|
||||
config.display_width / 16,
|
||||
@ -113,7 +113,7 @@ impl Screenshot {
|
||||
.map(|groups| -> Vec<Point> {
|
||||
groups
|
||||
.into_iter()
|
||||
.map(|(x, y)| (y, config.display_width - x))
|
||||
.map(|(x, y)| (config.display_width - y, x))
|
||||
.collect()
|
||||
})
|
||||
.collect()
|
||||
@ -145,14 +145,15 @@ impl Screenshot {
|
||||
let point_y_list: Vec<usize> = (point_start_y..width).step_by(cell_size_y).collect();
|
||||
let point_x_list: Vec<usize> = iter::successors(Some(point_start_x), |i| {
|
||||
let next = i + cell_size_x;
|
||||
(next < (width as f64)).then_some(next)
|
||||
(next < (length as f64)).then_some(next)
|
||||
})
|
||||
.map(|i| i as usize)
|
||||
.collect();
|
||||
|
||||
let points: Vec<Point> = point_x_list
|
||||
.into_iter()
|
||||
.zip(point_y_list.into_iter())
|
||||
.iter()
|
||||
.map(|&x| point_y_list.iter().map(move |&y| (x, y)))
|
||||
.flatten()
|
||||
.collect();
|
||||
points
|
||||
.chunks(single_axis_points * single_axis_points)
|
||||
|
58
src/App.tsx
58
src/App.tsx
@ -1,9 +1,10 @@
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import reactLogo from './assets/react.svg';
|
||||
import tw from 'twin.macro';
|
||||
import { invoke } from '@tauri-apps/api/tauri';
|
||||
import './App.css';
|
||||
import clsx from 'clsx';
|
||||
import { Configurator } from './configurator/configurator';
|
||||
import { ButtonSwitch } from './commons/components/button';
|
||||
|
||||
type Mode = 'Flowing' | 'Follow' | null;
|
||||
|
||||
@ -39,63 +40,30 @@ function App() {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="flex justify-between">
|
||||
<div tw="flex justify-between">
|
||||
{ledStripColors.map((it) => (
|
||||
<span className=" h-8 flex-auto" style={{ backgroundColor: it }}></span>
|
||||
<span tw="h-8 flex-auto" style={{ backgroundColor: it }}></span>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="flex gap-1 justify-center w-screen overflow-hidden">
|
||||
<div tw="flex gap-1 justify-center w-screen overflow-hidden">
|
||||
{screenshots.map((screenshot) => (
|
||||
<div className="flex-auto">
|
||||
<div tw="flex-auto">
|
||||
<img src={screenshot} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="flex gap-5 justify-center ">
|
||||
<button
|
||||
className="bg-black bg-opacity-20"
|
||||
type="button"
|
||||
onClick={() => readPickerConfig()}
|
||||
>
|
||||
Refresh Displays
|
||||
</button>
|
||||
<button
|
||||
className="bg-black bg-opacity-20"
|
||||
type="button"
|
||||
onClick={() => takeSnapshot()}
|
||||
>
|
||||
Take Snapshot
|
||||
</button>
|
||||
<button
|
||||
className="bg-black bg-opacity-20"
|
||||
type="button"
|
||||
onClick={() => getLedStripColors()}
|
||||
>
|
||||
Get Colors
|
||||
</button>
|
||||
<button
|
||||
className={clsx('bg-black', 'bg-opacity-20', {
|
||||
'bg-gradient-to-r from-purple-500 to-blue-500': currentMode === 'Flowing',
|
||||
})}
|
||||
type="button"
|
||||
onClick={() => switchCurrentMode('Flowing')}
|
||||
>
|
||||
<div tw="flex gap-5 justify-center ">
|
||||
<ButtonSwitch onClick={() => takeSnapshot()}>Take Snapshot</ButtonSwitch>
|
||||
<ButtonSwitch onClick={() => getLedStripColors()}>Get Colors</ButtonSwitch>
|
||||
<ButtonSwitch onClick={() => switchCurrentMode('Flowing')}>
|
||||
Flowing Light
|
||||
</button>
|
||||
<button
|
||||
className={clsx('bg-black', 'bg-opacity-20', {
|
||||
'bg-gradient-to-r from-purple-500 to-blue-500': currentMode === 'Follow',
|
||||
})}
|
||||
type="button"
|
||||
onClick={() => switchCurrentMode('Follow')}
|
||||
>
|
||||
Follow
|
||||
</button>
|
||||
</ButtonSwitch>
|
||||
<ButtonSwitch onClick={() => switchCurrentMode('Follow')}>Follow</ButtonSwitch>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-5 justify-center">
|
||||
<div tw="flex gap-5 justify-center">
|
||||
<Configurator />
|
||||
</div>
|
||||
</div>
|
||||
|
21
src/commons/components/button.tsx
Normal file
21
src/commons/components/button.tsx
Normal file
@ -0,0 +1,21 @@
|
||||
import { FC } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
import tw, { theme } from 'twin.macro';
|
||||
import { css } from '@emotion/react';
|
||||
|
||||
interface ButtonProps {
|
||||
value?: boolean;
|
||||
isSmall?: boolean;
|
||||
}
|
||||
|
||||
export const ButtonSwitch = styled.button(({ value, isSmall }: ButtonProps) => [
|
||||
// The common button styles
|
||||
tw`px-8 py-2 rounded-xl transform duration-75 dark:bg-black m-2 shadow-lg text-opacity-95 dark:shadow-gray-800`,
|
||||
|
||||
tw`hover:(scale-105)`,
|
||||
tw`focus:(scale-100)`,
|
||||
|
||||
value && 'bg-gradient-to-r from-purple-500 to-blue-500',
|
||||
|
||||
isSmall ? tw`text-sm` : tw`text-lg`,
|
||||
]);
|
@ -1,25 +1,97 @@
|
||||
import { HTMLAttributes } from 'react';
|
||||
import { HTMLAttributes, useCallback, useMemo } from 'react';
|
||||
import { FC } from 'react';
|
||||
import { DisplayConfig } from '../models/display-config';
|
||||
import { LedStrip } from './led-strip';
|
||||
import tw, { css, styled, theme } from 'twin.macro';
|
||||
import { ScreenshotDto } from '../models/screenshot.dto';
|
||||
import { LedStripEditor } from './led-strip-editor';
|
||||
import { LedStripConfig } from '../models/led-strip-config';
|
||||
|
||||
export interface DisplayWithLedStripsProps extends HTMLAttributes<HTMLElement> {
|
||||
export interface DisplayWithLedStripsProps
|
||||
extends Omit<HTMLAttributes<HTMLElement>, 'onChange'> {
|
||||
config: DisplayConfig;
|
||||
screenshot: string;
|
||||
screenshot: ScreenshotDto;
|
||||
onChange?: (config: DisplayConfig) => void;
|
||||
}
|
||||
|
||||
const StyledContainer = styled.section(
|
||||
tw`m-4 grid gap-1`,
|
||||
css`
|
||||
grid-template-columns: ${theme`width.5`} ${theme`width.3`} auto ${theme`width.3`} ${theme`width.5`};
|
||||
`,
|
||||
css`
|
||||
grid-template-rows: ${theme`width.5`} ${theme`width.3`} auto ${theme`width.3`} ${theme`width.5`};
|
||||
`,
|
||||
);
|
||||
|
||||
export const DisplayWithLedStrips: FC<DisplayWithLedStripsProps> = ({
|
||||
config,
|
||||
screenshot,
|
||||
onChange,
|
||||
...htmlAttrs
|
||||
}) => {
|
||||
const screenshotUrl = useMemo(
|
||||
() => `data:image/png;base64,${screenshot.encode_image}`,
|
||||
[screenshot.encode_image],
|
||||
);
|
||||
|
||||
const onLedStripConfigChange = useCallback(
|
||||
(
|
||||
position:
|
||||
| 'top_led_strip'
|
||||
| 'left_led_strip'
|
||||
| 'right_led_strip'
|
||||
| 'bottom_led_strip',
|
||||
value: LedStripConfig | null,
|
||||
) => {
|
||||
const c = { ...config, [position]: value };
|
||||
onChange?.(c);
|
||||
},
|
||||
[config],
|
||||
);
|
||||
return (
|
||||
<section className="m-4 grid grid-rows-3 grid-cols-3 gr" {...htmlAttrs}>
|
||||
<img src={screenshot} className="row-start-2 col-start-2" />
|
||||
<LedStrip config={config.top_led_strip} className="row-start-1 col-start-2 h-1" />
|
||||
<LedStrip config={config.left_led_strip} className="row-start-2 col-start-1 w-1" />
|
||||
<LedStrip config={config.right_led_strip} className="row-start-2 col-start-3" />
|
||||
<LedStrip config={config.bottom_led_strip} className="row-start-3 col-start-2" />
|
||||
</section>
|
||||
<StyledContainer {...htmlAttrs}>
|
||||
<img src={screenshotUrl} tw="row-start-3 col-start-3" />
|
||||
<LedStrip
|
||||
config={config.top_led_strip}
|
||||
colors={screenshot.colors.top}
|
||||
tw="row-start-2 col-start-3"
|
||||
/>
|
||||
<LedStrip
|
||||
config={config.left_led_strip}
|
||||
colors={screenshot.colors.left}
|
||||
tw="row-start-3 col-start-2"
|
||||
/>
|
||||
<LedStrip
|
||||
config={config.right_led_strip}
|
||||
colors={screenshot.colors.right}
|
||||
tw="row-start-3 col-start-4"
|
||||
/>
|
||||
<LedStrip
|
||||
config={config.bottom_led_strip}
|
||||
colors={screenshot.colors.bottom}
|
||||
tw="row-start-4 col-start-3"
|
||||
/>
|
||||
<LedStripEditor
|
||||
config={config.top_led_strip}
|
||||
tw="row-start-1 col-start-3"
|
||||
onChange={(value) => onLedStripConfigChange('top_led_strip', value)}
|
||||
/>
|
||||
<LedStripEditor
|
||||
config={config.left_led_strip}
|
||||
tw="row-start-3 col-start-1"
|
||||
onChange={(value) => onLedStripConfigChange('left_led_strip', value)}
|
||||
/>
|
||||
<LedStripEditor
|
||||
config={config.right_led_strip}
|
||||
tw="row-start-3 col-start-5"
|
||||
onChange={(value) => onLedStripConfigChange('right_led_strip', value)}
|
||||
/>
|
||||
<LedStripEditor
|
||||
config={config.bottom_led_strip}
|
||||
tw="row-start-5 col-start-3"
|
||||
onChange={(value) => onLedStripConfigChange('bottom_led_strip', value)}
|
||||
/>
|
||||
</StyledContainer>
|
||||
);
|
||||
};
|
||||
|
57
src/configurator/components/led-strip-editor.tsx
Normal file
57
src/configurator/components/led-strip-editor.tsx
Normal file
@ -0,0 +1,57 @@
|
||||
import { HTMLAttributes, useCallback } from 'react';
|
||||
import { FC } from 'react';
|
||||
import { LedStripConfig } from '../models/led-strip-config';
|
||||
import tw, { css, styled, theme } from 'twin.macro';
|
||||
import { faLeftRight, faMinus, faPlus } from '@fortawesome/free-solid-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
|
||||
export interface LedStripEditorProps
|
||||
extends Omit<HTMLAttributes<HTMLElement>, 'onChange'> {
|
||||
config: LedStripConfig | null;
|
||||
onChange?: (config: LedStripConfig | null) => void;
|
||||
}
|
||||
|
||||
const StyledContainer = styled.section(
|
||||
tw`flex flex-wrap gap-2 self-start justify-self-start`,
|
||||
);
|
||||
|
||||
const StyledButton = styled.button(
|
||||
tw`
|
||||
bg-yellow-500 rounded-full h-4 w-4 text-xs shadow select-none`,
|
||||
tw`hocus:scale-105 hocus:active:scale-95 active:bg-amber-600`,
|
||||
);
|
||||
|
||||
export const LedStripEditor: FC<LedStripEditorProps> = ({
|
||||
config,
|
||||
onChange,
|
||||
...htmlAttrs
|
||||
}) => {
|
||||
const addLed = useCallback(() => {
|
||||
if (config) {
|
||||
onChange?.({ ...config, global_end_position: config.global_end_position + 1 });
|
||||
} else {
|
||||
onChange?.(new LedStripConfig(0, 0, 1));
|
||||
}
|
||||
}, [config, onChange]);
|
||||
const removeLed = useCallback(() => {
|
||||
if (!config) {
|
||||
onChange?.(null);
|
||||
} else {
|
||||
onChange?.({ ...config, global_end_position: config.global_end_position - 1 });
|
||||
}
|
||||
}, [config, onChange]);
|
||||
|
||||
return (
|
||||
<StyledContainer {...htmlAttrs}>
|
||||
<StyledButton title="Add LED" onClick={addLed}>
|
||||
<FontAwesomeIcon icon={faPlus} />
|
||||
</StyledButton>
|
||||
<StyledButton title="Remove LED" onClick={removeLed}>
|
||||
<FontAwesomeIcon icon={faMinus} />
|
||||
</StyledButton>
|
||||
<StyledButton title="Reverse">
|
||||
<FontAwesomeIcon icon={faLeftRight} />
|
||||
</StyledButton>
|
||||
</StyledContainer>
|
||||
);
|
||||
};
|
@ -1,11 +1,36 @@
|
||||
import { HTMLAttributes } from 'react';
|
||||
import { HTMLAttributes, useMemo } from 'react';
|
||||
import { FC } from 'react';
|
||||
import { LedStripConfig } from '../models/led-strip-config';
|
||||
import tw, { css, styled, theme } from 'twin.macro';
|
||||
import { splitEvery } from 'ramda';
|
||||
|
||||
export interface LedStripProps extends HTMLAttributes<HTMLElement> {
|
||||
config: LedStripConfig;
|
||||
config: LedStripConfig | null;
|
||||
colors: Uint8Array;
|
||||
}
|
||||
|
||||
export const LedStrip: FC<LedStripProps> = ({ config, ...htmlAttrs }) => {
|
||||
return <section {...htmlAttrs}>...</section>;
|
||||
const StyledContainer = styled.section(
|
||||
tw`dark:bg-transparent shadow-xl border-gray-500 border rounded-full flex flex-wrap justify-around items-center`,
|
||||
css``,
|
||||
);
|
||||
|
||||
const StyledPixel = styled.span(
|
||||
({ rgb: [r, g, b] }: { rgb: [number, number, number] }) => [
|
||||
tw`rounded-full h-3 w-3 bg-current block border border-gray-700`,
|
||||
css`
|
||||
color: rgb(${r}, ${g}, ${b});
|
||||
`,
|
||||
],
|
||||
);
|
||||
|
||||
export const LedStrip: FC<LedStripProps> = ({ config, colors, ...htmlAttrs }) => {
|
||||
const pixels = useMemo(() => {
|
||||
const pixels = splitEvery(3, Array.from(colors)) as Array<[number, number, number]>;
|
||||
return pixels.map((rgb, index) => <StyledPixel key={index} rgb={rgb}></StyledPixel>);
|
||||
}, [colors]);
|
||||
return (
|
||||
<StyledContainer {...htmlAttrs} css={[!config && tw`bg-gray-200`]}>
|
||||
{pixels}
|
||||
</StyledContainer>
|
||||
);
|
||||
};
|
||||
|
@ -1,46 +1,96 @@
|
||||
import { invoke } from '@tauri-apps/api';
|
||||
import { FC, useMemo } from 'react';
|
||||
import { useAsync } from 'react-async-hook';
|
||||
import { FC, Fragment, useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import tw from 'twin.macro';
|
||||
import { useAsync, useAsyncCallback } from 'react-async-hook';
|
||||
import { DisplayWithLedStrips } from './components/display-with-led-strips';
|
||||
import { PickerConfiguration } from './models/picker-configuration';
|
||||
import { DisplayConfig } from './models/display-config';
|
||||
import { ScreenshotDto } from './models/screenshot.dto';
|
||||
import { Alert, Snackbar } from '@mui/material';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faSpinner } from '@fortawesome/free-solid-svg-icons';
|
||||
import { update } from 'ramda';
|
||||
|
||||
const getPickerConfig = () => invoke<PickerConfiguration>('get_picker_config');
|
||||
const getScreenshotOfDisplays = () =>
|
||||
invoke<string[]>('take_snapshot').then((items) =>
|
||||
items?.map((it) => `data:image/webp;base64,${it}`),
|
||||
);
|
||||
invoke<ScreenshotDto[]>('take_snapshot').then((items) => {
|
||||
console.log(items);
|
||||
return items;
|
||||
});
|
||||
const getScreenshotByConfig = async (config: DisplayConfig) => {
|
||||
return await invoke<ScreenshotDto>('get_screenshot_by_config', {
|
||||
config,
|
||||
});
|
||||
};
|
||||
|
||||
export const Configurator: FC = () => {
|
||||
const { loading: pendingPickerConfig, result: pickerConfig } = useAsync(
|
||||
const { loading: pendingPickerConfig, result: savedPickerConfig } = useAsync(
|
||||
getPickerConfig,
|
||||
[],
|
||||
);
|
||||
|
||||
const { loading: pendingScreenshotOfDisplays, result: screenshotOfDisplays } = useAsync(
|
||||
getScreenshotOfDisplays,
|
||||
[],
|
||||
);
|
||||
const { loading: pendingScreenshotOfDisplays, result: defaultScreenshotOfDisplays } =
|
||||
useAsync(getScreenshotOfDisplays, []);
|
||||
|
||||
const [screenshotOfDisplays, setScreenshotOfDisplays] = useState<ScreenshotDto[]>([]);
|
||||
|
||||
const { loading: pendingGetLedColorsByConfig, execute: onPickerChange } =
|
||||
useAsyncCallback(async (value: DisplayConfig) => {
|
||||
console.log(value);
|
||||
const screenshot = await getScreenshotByConfig(value);
|
||||
setScreenshotOfDisplays((old) => {
|
||||
const index = old.findIndex((it) => it.config.id === screenshot.config.id);
|
||||
console.log({ old, n: update(index, screenshot, old) });
|
||||
return update(index, screenshot, old);
|
||||
});
|
||||
|
||||
console.log('screenshot', screenshot);
|
||||
});
|
||||
|
||||
const [displayConfigs, setDisplayConfigs] = useState<DisplayConfig[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
const displayConfigs = savedPickerConfig?.display_configs;
|
||||
if (displayConfigs) {
|
||||
setDisplayConfigs(displayConfigs);
|
||||
}
|
||||
}, [savedPickerConfig]);
|
||||
useEffect(() => {
|
||||
if (defaultScreenshotOfDisplays) {
|
||||
setScreenshotOfDisplays(defaultScreenshotOfDisplays);
|
||||
}
|
||||
}, [defaultScreenshotOfDisplays]);
|
||||
|
||||
const displays = useMemo(() => {
|
||||
if (pickerConfig && screenshotOfDisplays) {
|
||||
if (screenshotOfDisplays) {
|
||||
console.log({ c: screenshotOfDisplays });
|
||||
return screenshotOfDisplays.map((screenshot, index) => (
|
||||
<DisplayWithLedStrips
|
||||
key={index}
|
||||
config={pickerConfig.display_configs[index] ?? {}}
|
||||
config={screenshot.config}
|
||||
screenshot={screenshot}
|
||||
onChange={(value) => onPickerChange(value)}
|
||||
/>
|
||||
));
|
||||
}
|
||||
}, [pickerConfig, screenshotOfDisplays]);
|
||||
}, [displayConfigs, screenshotOfDisplays]);
|
||||
|
||||
if (pendingPickerConfig || pendingScreenshotOfDisplays) {
|
||||
return (
|
||||
<section>
|
||||
等待 {JSON.stringify({ pendingPickerConfig, pendingScreenshotOfDisplays })}
|
||||
{displays}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
return <section>{displays}</section>;
|
||||
return (
|
||||
<Fragment>
|
||||
<section>{displays}</section>;
|
||||
<Snackbar open={pendingGetLedColorsByConfig} autoHideDuration={3000}>
|
||||
<Alert icon={<FontAwesomeIcon icon={faSpinner} />} sx={{ width: '100%' }}>
|
||||
This is a success message!
|
||||
</Alert>
|
||||
</Snackbar>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
@ -1,11 +1,15 @@
|
||||
import { LedStripConfig } from './led-strip-config';
|
||||
|
||||
export class DisplayConfig {
|
||||
index_of_display!: number;
|
||||
display_width!: number;
|
||||
display_height!: number;
|
||||
top_led_strip!: LedStripConfig;
|
||||
bottom_led_strip!: LedStripConfig;
|
||||
left_led_strip!: LedStripConfig;
|
||||
right_led_strip!: LedStripConfig;
|
||||
top_led_strip: LedStripConfig | null = null;
|
||||
bottom_led_strip: LedStripConfig | null = null;
|
||||
left_led_strip: LedStripConfig | null = null;
|
||||
right_led_strip: LedStripConfig | null = null;
|
||||
|
||||
constructor(
|
||||
public id: number,
|
||||
public index_of_display: number,
|
||||
public display_width: number,
|
||||
public display_height: number,
|
||||
) {}
|
||||
}
|
||||
|
@ -1,5 +1,7 @@
|
||||
export class LedStripConfig {
|
||||
index!: number;
|
||||
global_start_position!: number;
|
||||
global_end_position!: number;
|
||||
constructor(
|
||||
public index: number,
|
||||
public global_start_position: number,
|
||||
public global_end_position: number,
|
||||
) {}
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
import { DisplayConfig } from './display-config';
|
||||
|
||||
export class PickerConfiguration {
|
||||
config_version!: number;
|
||||
display_configs!: DisplayConfig[];
|
||||
constructor(
|
||||
public display_configs: DisplayConfig[] = [],
|
||||
public config_version: number = 1,
|
||||
) {}
|
||||
}
|
||||
|
12
src/configurator/models/screenshot.dto.ts
Normal file
12
src/configurator/models/screenshot.dto.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { DisplayConfig } from './display-config';
|
||||
|
||||
export class ScreenshotDto {
|
||||
encode_image!: string;
|
||||
config!: DisplayConfig;
|
||||
colors!: {
|
||||
top: Uint8Array;
|
||||
bottom: Uint8Array;
|
||||
left: Uint8Array;
|
||||
right: Uint8Array;
|
||||
};
|
||||
}
|
@ -1,10 +1,11 @@
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom/client";
|
||||
import App from "./App";
|
||||
import "./style.css";
|
||||
import GlobalStyles from './styles/global-styles';
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
|
||||
<React.StrictMode>
|
||||
<GlobalStyles />
|
||||
<App />
|
||||
</React.StrictMode>,
|
||||
);
|
@ -5,7 +5,8 @@ import tw, { theme, GlobalStyles as BaseStyles } from 'twin.macro';
|
||||
const customStyles = css({
|
||||
body: {
|
||||
WebkitTapHighlightColor: theme`colors.purple.500`,
|
||||
...tw`antialiased dark:bg-dark-800 bg-dark-100`,
|
||||
...tw`antialiased`,
|
||||
...tw`dark:bg-dark-800 bg-dark-100 dark:text-gray-100 text-gray-800`,
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -1,12 +1,15 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
content: [
|
||||
"./index.html",
|
||||
"./src/**/*.{js,ts,jsx,tsx}",
|
||||
],
|
||||
content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'],
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
dark: {
|
||||
800: '#0f0f0f',
|
||||
100: '#f6f6f6',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
}
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user