diff --git a/package.json b/package.json index 4a7ea86..a279d32 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0f6cee2..2c4f8e8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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: diff --git a/src-tauri/src/core/core.rs b/src-tauri/src/core/core.rs index 3495b9f..ef5c2d1 100644 --- a/src-tauri/src/core/core.rs +++ b/src-tauri/src/core/core.rs @@ -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, diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 35a81c6..e5f809b 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -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 { base64_bitmap_list } +#[tauri::command] +async fn get_screenshot_by_config(config: DisplayConfig) -> Result { + 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"); diff --git a/src-tauri/src/picker/config/display_config.rs b/src-tauri/src/picker/config/display_config.rs index ec1be7a..1bb664e 100644 --- a/src-tauri/src/picker/config/display_config.rs +++ b/src-tauri/src/picker/config/display_config.rs @@ -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, diff --git a/src-tauri/src/picker/manager.rs b/src-tauri/src/picker/manager.rs index 5963f6d..0cfb114 100644 --- a/src-tauri/src/picker/manager.rs +++ b/src-tauri/src/picker/manager.rs @@ -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 { + 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) + } } diff --git a/src-tauri/src/picker/preview_manager.rs b/src-tauri/src/picker/preview_manager.rs new file mode 100644 index 0000000..7d46fa2 --- /dev/null +++ b/src-tauri/src/picker/preview_manager.rs @@ -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>>>>, + pub screenshots: Arc>>, +} + +impl PreviewPicker { + pub fn global() -> &'static PreviewPicker { + static SCREEN_COLOR_PREVIEW_PICKER: OnceCell = 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 { + 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 { + 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) + } +} diff --git a/src-tauri/src/picker/screenshot.rs b/src-tauri/src/picker/screenshot.rs index b054c58..4b1a4a9 100644 --- a/src-tauri/src/picker/screenshot.rs +++ b/src-tauri/src/picker/screenshot.rs @@ -51,7 +51,7 @@ impl Screenshot { } }; - let bottom: Vec = match config.top_led_strip { + let bottom: Vec = 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 = match config.top_led_strip { + let left: Vec = 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 = match config.top_led_strip { + let right: Vec = 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 { 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 = (point_start_y..width).step_by(cell_size_y).collect(); let point_x_list: Vec = 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_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) diff --git a/src/App.tsx b/src/App.tsx index 777144e..e916cf8 100644 --- a/src/App.tsx +++ b/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 (
-
+
{ledStripColors.map((it) => ( - + ))}
-
+
{screenshots.map((screenshot) => ( -
+
))}
-
- - - - - + + switchCurrentMode('Follow')}>Follow
-
+
diff --git a/src/commons/components/button.tsx b/src/commons/components/button.tsx new file mode 100644 index 0000000..ac7cff4 --- /dev/null +++ b/src/commons/components/button.tsx @@ -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`, +]); diff --git a/src/configurator/components/display-with-led-strips.tsx b/src/configurator/components/display-with-led-strips.tsx index 18d9c05..b3b2ab5 100644 --- a/src/configurator/components/display-with-led-strips.tsx +++ b/src/configurator/components/display-with-led-strips.tsx @@ -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 { +export interface DisplayWithLedStripsProps + extends Omit, '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 = ({ 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 ( -
- - - - - -
+ + + + + + + onLedStripConfigChange('top_led_strip', value)} + /> + onLedStripConfigChange('left_led_strip', value)} + /> + onLedStripConfigChange('right_led_strip', value)} + /> + onLedStripConfigChange('bottom_led_strip', value)} + /> + ); }; diff --git a/src/configurator/components/led-strip-editor.tsx b/src/configurator/components/led-strip-editor.tsx new file mode 100644 index 0000000..f88ce86 --- /dev/null +++ b/src/configurator/components/led-strip-editor.tsx @@ -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, '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 = ({ + 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 ( + + + + + + + + + + + + ); +}; diff --git a/src/configurator/components/led-strip.tsx b/src/configurator/components/led-strip.tsx index 3293e29..be256cd 100644 --- a/src/configurator/components/led-strip.tsx +++ b/src/configurator/components/led-strip.tsx @@ -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 { - config: LedStripConfig; + config: LedStripConfig | null; + colors: Uint8Array; } -export const LedStrip: FC = ({ config, ...htmlAttrs }) => { - return
...
; +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 = ({ config, colors, ...htmlAttrs }) => { + const pixels = useMemo(() => { + const pixels = splitEvery(3, Array.from(colors)) as Array<[number, number, number]>; + return pixels.map((rgb, index) => ); + }, [colors]); + return ( + + {pixels} + + ); }; diff --git a/src/configurator/configurator.tsx b/src/configurator/configurator.tsx index 7f8c4a2..ea51bd7 100644 --- a/src/configurator/configurator.tsx +++ b/src/configurator/configurator.tsx @@ -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('get_picker_config'); const getScreenshotOfDisplays = () => - invoke('take_snapshot').then((items) => - items?.map((it) => `data:image/webp;base64,${it}`), - ); + invoke('take_snapshot').then((items) => { + console.log(items); + return items; + }); +const getScreenshotByConfig = async (config: DisplayConfig) => { + return await invoke('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([]); + + 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([]); + + 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) => ( onPickerChange(value)} /> )); } - }, [pickerConfig, screenshotOfDisplays]); + }, [displayConfigs, screenshotOfDisplays]); if (pendingPickerConfig || pendingScreenshotOfDisplays) { return (
等待 {JSON.stringify({ pendingPickerConfig, pendingScreenshotOfDisplays })} - {displays}
); } - return
{displays}
; + return ( + +
{displays}
; + + } sx={{ width: '100%' }}> + This is a success message! + + +
+ ); }; diff --git a/src/configurator/models/display-config.ts b/src/configurator/models/display-config.ts index f98a4fb..ef2ad54 100644 --- a/src/configurator/models/display-config.ts +++ b/src/configurator/models/display-config.ts @@ -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, + ) {} } diff --git a/src/configurator/models/led-strip-config.ts b/src/configurator/models/led-strip-config.ts index 5b2ff8f..c3945b5 100644 --- a/src/configurator/models/led-strip-config.ts +++ b/src/configurator/models/led-strip-config.ts @@ -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, + ) {} } diff --git a/src/configurator/models/picker-configuration.ts b/src/configurator/models/picker-configuration.ts index 4b989d0..5d5c426 100644 --- a/src/configurator/models/picker-configuration.ts +++ b/src/configurator/models/picker-configuration.ts @@ -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, + ) {} } diff --git a/src/configurator/models/screenshot.dto.ts b/src/configurator/models/screenshot.dto.ts new file mode 100644 index 0000000..eb245e5 --- /dev/null +++ b/src/configurator/models/screenshot.dto.ts @@ -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; + }; +} diff --git a/src/main.tsx b/src/main.tsx index 1085049..16c8ee2 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -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( + , ); \ No newline at end of file diff --git a/src/styles/global-styles.tsx b/src/styles/global-styles.tsx index 1479d4b..aa78d4d 100644 --- a/src/styles/global-styles.tsx +++ b/src/styles/global-styles.tsx @@ -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`, }, }); diff --git a/tailwind.config.cjs b/tailwind.config.cjs index 8b35f96..70464ef 100644 --- a/tailwind.config.cjs +++ b/tailwind.config.cjs @@ -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: [], -} +};