mirror of
https://github.com/rustmailer/bichon.git
synced 2026-08-03 07:48:34 +02:00
fix(ui): fix sync folder selection jump issue; add auto-select children/parents and expand/collapse all folders button #21
This commit is contained in:
@@ -13,7 +13,11 @@
|
||||
"knip": "knip"
|
||||
},
|
||||
"dependencies": {
|
||||
"@emotion/react": "^11.14.0",
|
||||
"@emotion/styled": "^11.14.1",
|
||||
"@hookform/resolvers": "^3.9.1",
|
||||
"@mui/material": "^7.3.5",
|
||||
"@mui/x-tree-view": "^8.19.0",
|
||||
"@radix-ui/react-accordion": "^1.2.2",
|
||||
"@radix-ui/react-alert-dialog": "^1.1.2",
|
||||
"@radix-ui/react-avatar": "^1.1.1",
|
||||
@@ -37,6 +41,7 @@
|
||||
"@radix-ui/react-toast": "^1.2.2",
|
||||
"@radix-ui/react-tooltip": "^1.1.4",
|
||||
"@radix-ui/react-visually-hidden": "^1.1.0",
|
||||
"@react-spring/web": "^10.0.3",
|
||||
"@tabler/icons-react": "^3.24.0",
|
||||
"@tanstack/react-query": "^5.62.3",
|
||||
"@tanstack/react-router": "^1.86.1",
|
||||
|
||||
Generated
+564
-17
@@ -8,9 +8,21 @@ importers:
|
||||
|
||||
.:
|
||||
dependencies:
|
||||
'@emotion/react':
|
||||
specifier: ^11.14.0
|
||||
version: 11.14.0(@types/react@18.3.18)(react@18.3.1)
|
||||
'@emotion/styled':
|
||||
specifier: ^11.14.1
|
||||
version: 11.14.1(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1)
|
||||
'@hookform/resolvers':
|
||||
specifier: ^3.9.1
|
||||
version: 3.9.1(react-hook-form@7.54.0(react@18.3.1))
|
||||
'@mui/material':
|
||||
specifier: ^7.3.5
|
||||
version: 7.3.5(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@mui/x-tree-view':
|
||||
specifier: ^8.19.0
|
||||
version: 8.19.0(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1))(@mui/material@7.3.5(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@mui/system@7.3.5(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@radix-ui/react-accordion':
|
||||
specifier: ^1.2.2
|
||||
version: 1.2.2(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
@@ -80,6 +92,9 @@ importers:
|
||||
'@radix-ui/react-visually-hidden':
|
||||
specifier: ^1.1.0
|
||||
version: 1.1.0(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@react-spring/web':
|
||||
specifier: ^10.0.3
|
||||
version: 10.0.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@tabler/icons-react':
|
||||
specifier: ^3.24.0
|
||||
version: 3.24.0(react@18.3.1)
|
||||
@@ -350,10 +365,6 @@ packages:
|
||||
peerDependencies:
|
||||
'@babel/core': ^7.0.0-0
|
||||
|
||||
'@babel/runtime@7.26.0':
|
||||
resolution: {integrity: sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/runtime@7.28.4':
|
||||
resolution: {integrity: sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
@@ -378,6 +389,70 @@ packages:
|
||||
resolution: {integrity: sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@base-ui-components/utils@0.1.2':
|
||||
resolution: {integrity: sha512-aEitDGpMsYO2qnSpYOwZNykn9Rzn2ioyEVk2fyDRH7t+TIHVKpp9CeV7SPTq43M9mMSDxQ+7UeZJVkrj2dCVIQ==}
|
||||
peerDependencies:
|
||||
'@types/react': ^17 || ^18 || ^19
|
||||
react: ^17 || ^18 || ^19
|
||||
react-dom: ^17 || ^18 || ^19
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@emotion/babel-plugin@11.13.5':
|
||||
resolution: {integrity: sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==}
|
||||
|
||||
'@emotion/cache@11.14.0':
|
||||
resolution: {integrity: sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==}
|
||||
|
||||
'@emotion/hash@0.9.2':
|
||||
resolution: {integrity: sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==}
|
||||
|
||||
'@emotion/is-prop-valid@1.4.0':
|
||||
resolution: {integrity: sha512-QgD4fyscGcbbKwJmqNvUMSE02OsHUa+lAWKdEUIJKgqe5IwRSKd7+KhibEWdaKwgjLj0DRSHA9biAIqGBk05lw==}
|
||||
|
||||
'@emotion/memoize@0.9.0':
|
||||
resolution: {integrity: sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==}
|
||||
|
||||
'@emotion/react@11.14.0':
|
||||
resolution: {integrity: sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
react: '>=16.8.0'
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@emotion/serialize@1.3.3':
|
||||
resolution: {integrity: sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==}
|
||||
|
||||
'@emotion/sheet@1.4.0':
|
||||
resolution: {integrity: sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==}
|
||||
|
||||
'@emotion/styled@11.14.1':
|
||||
resolution: {integrity: sha512-qEEJt42DuToa3gurlH4Qqc1kVpNq8wO8cJtDzU46TjlzWjDlsVyevtYCRijVq3SrHsROS+gVQ8Fnea108GnKzw==}
|
||||
peerDependencies:
|
||||
'@emotion/react': ^11.0.0-rc.0
|
||||
'@types/react': '*'
|
||||
react: '>=16.8.0'
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@emotion/unitless@0.10.0':
|
||||
resolution: {integrity: sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==}
|
||||
|
||||
'@emotion/use-insertion-effect-with-fallbacks@1.2.0':
|
||||
resolution: {integrity: sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==}
|
||||
peerDependencies:
|
||||
react: '>=16.8.0'
|
||||
|
||||
'@emotion/utils@1.4.2':
|
||||
resolution: {integrity: sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==}
|
||||
|
||||
'@emotion/weak-memoize@0.4.0':
|
||||
resolution: {integrity: sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==}
|
||||
|
||||
'@esbuild/aix-ppc64@0.23.1':
|
||||
resolution: {integrity: sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==}
|
||||
engines: {node: '>=18'}
|
||||
@@ -722,6 +797,9 @@ packages:
|
||||
react: '>=16.8.0'
|
||||
react-dom: '>=16.8.0'
|
||||
|
||||
'@floating-ui/utils@0.2.10':
|
||||
resolution: {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==}
|
||||
|
||||
'@floating-ui/utils@0.2.8':
|
||||
resolution: {integrity: sha512-kym7SodPp8/wloecOpcmSnWJsK7M0E5Wg8UcFA+uO4B9s5d0ywXOEro/8HM9x0rW+TljRzul/14UYz3TleT3ig==}
|
||||
|
||||
@@ -775,6 +853,108 @@ packages:
|
||||
'@jridgewell/trace-mapping@0.3.25':
|
||||
resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==}
|
||||
|
||||
'@mui/core-downloads-tracker@7.3.5':
|
||||
resolution: {integrity: sha512-kOLwlcDPnVz2QMhiBv0OQ8le8hTCqKM9cRXlfVPL91l3RGeOsxrIhNRsUt3Xb8wb+pTVUolW+JXKym93vRKxCw==}
|
||||
|
||||
'@mui/material@7.3.5':
|
||||
resolution: {integrity: sha512-8VVxFmp1GIm9PpmnQoCoYo0UWHoOrdA57tDL62vkpzEgvb/d71Wsbv4FRg7r1Gyx7PuSo0tflH34cdl/NvfHNQ==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
peerDependencies:
|
||||
'@emotion/react': ^11.5.0
|
||||
'@emotion/styled': ^11.3.0
|
||||
'@mui/material-pigment-css': ^7.3.5
|
||||
'@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
react: ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
peerDependenciesMeta:
|
||||
'@emotion/react':
|
||||
optional: true
|
||||
'@emotion/styled':
|
||||
optional: true
|
||||
'@mui/material-pigment-css':
|
||||
optional: true
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@mui/private-theming@7.3.5':
|
||||
resolution: {integrity: sha512-cTx584W2qrLonwhZLbEN7P5pAUu0nZblg8cLBlTrZQ4sIiw8Fbvg7GvuphQaSHxPxrCpa7FDwJKtXdbl2TSmrA==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
peerDependencies:
|
||||
'@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
react: ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@mui/styled-engine@7.3.5':
|
||||
resolution: {integrity: sha512-zbsZ0uYYPndFCCPp2+V3RLcAN6+fv4C8pdwRx6OS3BwDkRCN8WBehqks7hWyF3vj1kdQLIWrpdv/5Y0jHRxYXQ==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
peerDependencies:
|
||||
'@emotion/react': ^11.4.1
|
||||
'@emotion/styled': ^11.3.0
|
||||
react: ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
peerDependenciesMeta:
|
||||
'@emotion/react':
|
||||
optional: true
|
||||
'@emotion/styled':
|
||||
optional: true
|
||||
|
||||
'@mui/system@7.3.5':
|
||||
resolution: {integrity: sha512-yPaf5+gY3v80HNkJcPi6WT+r9ebeM4eJzrREXPxMt7pNTV/1eahyODO4fbH3Qvd8irNxDFYn5RQ3idHW55rA6g==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
peerDependencies:
|
||||
'@emotion/react': ^11.5.0
|
||||
'@emotion/styled': ^11.3.0
|
||||
'@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
react: ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
peerDependenciesMeta:
|
||||
'@emotion/react':
|
||||
optional: true
|
||||
'@emotion/styled':
|
||||
optional: true
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@mui/types@7.4.8':
|
||||
resolution: {integrity: sha512-ZNXLBjkPV6ftLCmmRCafak3XmSn8YV0tKE/ZOhzKys7TZXUiE0mZxlH8zKDo6j6TTUaDnuij68gIG+0Ucm7Xhw==}
|
||||
peerDependencies:
|
||||
'@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@mui/utils@7.3.5':
|
||||
resolution: {integrity: sha512-jisvFsEC3sgjUjcPnR4mYfhzjCDIudttSGSbe1o/IXFNu0kZuR+7vqQI0jg8qtcVZBHWrwTfvAZj9MNMumcq1g==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
peerDependencies:
|
||||
'@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
react: ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@mui/x-internals@8.19.0':
|
||||
resolution: {integrity: sha512-mMmiyJAN5fW27srXJjhXhXJa+w2xGO45rwcjws6OQc9rdXGdJqRXhBwJd+OT7J1xwSdFIIUhjZRTz1KAfCSGBg==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
peerDependencies:
|
||||
react: ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
|
||||
'@mui/x-tree-view@8.19.0':
|
||||
resolution: {integrity: sha512-1JPIczqK5qdDmo4p8KEpR4XZKxDHkw5dBoY1o+FNUdKmOipklQvg0+XL5wY75pjwzADngZR/6RYW08bhkt+Z3Q==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
peerDependencies:
|
||||
'@emotion/react': ^11.9.0
|
||||
'@emotion/styled': ^11.8.1
|
||||
'@mui/material': ^5.15.14 || ^6.0.0 || ^7.0.0
|
||||
'@mui/system': ^5.15.14 || ^6.0.0 || ^7.0.0
|
||||
react: ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
peerDependenciesMeta:
|
||||
'@emotion/react':
|
||||
optional: true
|
||||
'@emotion/styled':
|
||||
optional: true
|
||||
|
||||
'@nodelib/fs.scandir@2.1.5':
|
||||
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
|
||||
engines: {node: '>= 8'}
|
||||
@@ -879,6 +1059,9 @@ packages:
|
||||
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
|
||||
engines: {node: '>=14'}
|
||||
|
||||
'@popperjs/core@2.11.8':
|
||||
resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==}
|
||||
|
||||
'@radix-ui/number@1.1.0':
|
||||
resolution: {integrity: sha512-V3gRzhVNU1ldS5XhAPTom1fOIo4ccrjjJgmE+LI2h/WaFpHmx0MQApT+KZHnx8abG6Avtfcz4WoEciMnpFT3HQ==}
|
||||
|
||||
@@ -1648,6 +1831,33 @@ packages:
|
||||
'@radix-ui/rect@1.1.0':
|
||||
resolution: {integrity: sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==}
|
||||
|
||||
'@react-spring/animated@10.0.3':
|
||||
resolution: {integrity: sha512-7MrxADV3vaUADn2V9iYhaIL6iOWRx9nCJjYrsk2AHD2kwPr6fg7Pt0v+deX5RnCDmCKNnD6W5fasiyM8D+wzJQ==}
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
|
||||
'@react-spring/core@10.0.3':
|
||||
resolution: {integrity: sha512-D4DwNO68oohDf/0HG2G0Uragzb9IA1oXblxrd6MZAcBcUQG2EHUWXewjdECMPLNmQvlYVyyBRH6gPxXM5DX7DQ==}
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
|
||||
'@react-spring/rafz@10.0.3':
|
||||
resolution: {integrity: sha512-Ri2/xqt8OnQ2iFKkxKMSF4Nqv0LSWnxXT4jXFzBDsHgeeH/cHxTLupAWUwmV9hAGgmEhBmh5aONtj3J6R/18wg==}
|
||||
|
||||
'@react-spring/shared@10.0.3':
|
||||
resolution: {integrity: sha512-geCal66nrkaQzUVhPkGomylo+Jpd5VPK8tPMEDevQEfNSWAQP15swHm+MCRG4wVQrQlTi9lOzKzpRoTL3CA84Q==}
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
|
||||
'@react-spring/types@10.0.3':
|
||||
resolution: {integrity: sha512-H5Ixkd2OuSIgHtxuHLTt7aJYfhMXKXT/rK32HPD/kSrOB6q6ooeiWAXkBy7L8F3ZxdkBb9ini9zP9UwnEFzWgQ==}
|
||||
|
||||
'@react-spring/web@10.0.3':
|
||||
resolution: {integrity: sha512-ndU+kWY81rHsT7gTFtCJ6mrVhaJ6grFmgTnENipzmKqot4HGf5smPNK+cZZJqoGeDsj9ZsiWPW4geT/NyD484A==}
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
|
||||
'@rollup/rollup-android-arm-eabi@4.26.0':
|
||||
resolution: {integrity: sha512-gJNwtPDGEaOEgejbaseY6xMFu+CPltsc8/T+diUTTbOQLqD+bnrJq9ulH6WD69TqwqWmrfRAtUv30cCFZlbGTQ==}
|
||||
cpu: [arm]
|
||||
@@ -2020,14 +2230,25 @@ packages:
|
||||
'@types/node@22.10.1':
|
||||
resolution: {integrity: sha512-qKgsUwfHZV2WCWLAnVP1JqnpE6Im6h3Y0+fYgMTasNQ7V++CBX5OT1as0g0f+OyubbFqhf6XVNIsmN4IIhEgGQ==}
|
||||
|
||||
'@types/parse-json@4.0.2':
|
||||
resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==}
|
||||
|
||||
'@types/prop-types@15.7.14':
|
||||
resolution: {integrity: sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==}
|
||||
|
||||
'@types/prop-types@15.7.15':
|
||||
resolution: {integrity: sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==}
|
||||
|
||||
'@types/react-dom@18.3.5':
|
||||
resolution: {integrity: sha512-P4t6saawp+b/dFrUr2cvkVsfvPguwsxtH6dNIYRllMsefqFzkZk5UIjzyDOv5g1dXIPdG4Sp1yCR4Z6RCUsG/Q==}
|
||||
peerDependencies:
|
||||
'@types/react': ^18.0.0
|
||||
|
||||
'@types/react-transition-group@4.4.12':
|
||||
resolution: {integrity: sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
|
||||
'@types/react@18.3.18':
|
||||
resolution: {integrity: sha512-t4yC+vtgnkYjNSKlFx1jkAhH8LgTo2N/7Qvi83kdEaUtMDiwpbLAktKDaAMlRcJ5eSxZkH74eEGt1ky31d7kfQ==}
|
||||
|
||||
@@ -2243,6 +2464,10 @@ packages:
|
||||
babel-dead-code-elimination@1.0.6:
|
||||
resolution: {integrity: sha512-JxFi9qyRJpN0LjEbbjbN8g0ux71Qppn9R8Qe3k6QzHg2CaKsbUQtbn307LQGiDLGjV6JCtEFqfxzVig9MyDCHQ==}
|
||||
|
||||
babel-plugin-macros@3.1.0:
|
||||
resolution: {integrity: sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==}
|
||||
engines: {node: '>=10', npm: '>=6'}
|
||||
|
||||
bail@2.0.2:
|
||||
resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==}
|
||||
|
||||
@@ -2358,9 +2583,16 @@ packages:
|
||||
concat-map@0.0.1:
|
||||
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
|
||||
|
||||
convert-source-map@1.9.0:
|
||||
resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==}
|
||||
|
||||
convert-source-map@2.0.0:
|
||||
resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
|
||||
|
||||
cosmiconfig@7.1.0:
|
||||
resolution: {integrity: sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
cross-spawn@7.0.5:
|
||||
resolution: {integrity: sha512-ZVJrKKYunU38/76t0RMOulHOnUcbU9GbpWKAOZ0mhjr7CX6FVrH+4FrAapSOekrgFQ3f/8gwMEuIft0aKq6Hug==}
|
||||
engines: {node: '>= 8'}
|
||||
@@ -2491,6 +2723,9 @@ packages:
|
||||
resolution: {integrity: sha512-0/r0MySGYG8YqlayBZ6MuCfECmHFdJ5qyPh8s8wa5Hnm6SaFLSK1VYCbj+NKp090Nm1caZhD+QTnmxO7esYGyQ==}
|
||||
engines: {node: '>=10.13.0'}
|
||||
|
||||
error-ex@1.3.4:
|
||||
resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==}
|
||||
|
||||
es-module-lexer@1.7.0:
|
||||
resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==}
|
||||
|
||||
@@ -2624,6 +2859,9 @@ packages:
|
||||
resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
find-root@1.1.0:
|
||||
resolution: {integrity: sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==}
|
||||
|
||||
find-up@5.0.0:
|
||||
resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==}
|
||||
engines: {node: '>=10'}
|
||||
@@ -2731,6 +2969,9 @@ packages:
|
||||
hast-util-whitespace@3.0.0:
|
||||
resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==}
|
||||
|
||||
hoist-non-react-statics@3.3.2:
|
||||
resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==}
|
||||
|
||||
html-parse-stringify@3.0.1:
|
||||
resolution: {integrity: sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==}
|
||||
|
||||
@@ -2780,6 +3021,9 @@ packages:
|
||||
is-alphanumerical@2.0.1:
|
||||
resolution: {integrity: sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==}
|
||||
|
||||
is-arrayish@0.2.1:
|
||||
resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==}
|
||||
|
||||
is-binary-path@2.1.0:
|
||||
resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -3166,6 +3410,10 @@ packages:
|
||||
parse-entities@4.0.2:
|
||||
resolution: {integrity: sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==}
|
||||
|
||||
parse-json@5.2.0:
|
||||
resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
parse-ms@4.0.0:
|
||||
resolution: {integrity: sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==}
|
||||
engines: {node: '>=18'}
|
||||
@@ -3185,6 +3433,10 @@ packages:
|
||||
resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==}
|
||||
engines: {node: '>=16 || 14 >=14.18'}
|
||||
|
||||
path-type@4.0.0:
|
||||
resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
picocolors@1.1.1:
|
||||
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
|
||||
|
||||
@@ -3377,6 +3629,9 @@ packages:
|
||||
react-is@18.3.1:
|
||||
resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==}
|
||||
|
||||
react-is@19.2.0:
|
||||
resolution: {integrity: sha512-x3Ax3kNSMIIkyVYhWPyO09bu0uttcAIoecO/um/rKGQ4EltYWVYtyiGkS/3xMynrbVQdS69Jhlv8FXUEZehlzA==}
|
||||
|
||||
react-markdown@10.1.0:
|
||||
resolution: {integrity: sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==}
|
||||
peerDependencies:
|
||||
@@ -3456,9 +3711,6 @@ packages:
|
||||
react: ^16.0.0 || ^17.0.0 || ^18.0.0
|
||||
react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0
|
||||
|
||||
regenerator-runtime@0.14.1:
|
||||
resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==}
|
||||
|
||||
remark-gfm@4.0.1:
|
||||
resolution: {integrity: sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==}
|
||||
|
||||
@@ -3475,6 +3727,9 @@ packages:
|
||||
resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
reselect@5.1.1:
|
||||
resolution: {integrity: sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==}
|
||||
|
||||
resolve-from@4.0.0:
|
||||
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
|
||||
engines: {node: '>=4'}
|
||||
@@ -3592,6 +3847,9 @@ packages:
|
||||
style-to-object@1.0.8:
|
||||
resolution: {integrity: sha512-xT47I/Eo0rwJmaXC4oilDGDWLohVhR6o/xAQcPQN8q6QBuZVL8qMYL85kLmST5cPjAorwvqIA4qXTRQoYHaL6g==}
|
||||
|
||||
stylis@4.2.0:
|
||||
resolution: {integrity: sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==}
|
||||
|
||||
sucrase@3.35.0:
|
||||
resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==}
|
||||
engines: {node: '>=16 || 14 >=14.17'}
|
||||
@@ -3891,6 +4149,10 @@ packages:
|
||||
yallist@3.1.1:
|
||||
resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
|
||||
|
||||
yaml@1.10.2:
|
||||
resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==}
|
||||
engines: {node: '>= 6'}
|
||||
|
||||
yaml@2.6.0:
|
||||
resolution: {integrity: sha512-a6ae//JvKDEra2kdi1qzCyrJW/WZCgFi8ydDV+eXExl95t+5R+ijnqHJbz9tmMh8FUjx3iv2fCQ4dclAQlO2UQ==}
|
||||
engines: {node: '>= 14'}
|
||||
@@ -4049,10 +4311,6 @@ snapshots:
|
||||
'@babel/core': 7.26.0
|
||||
'@babel/helper-plugin-utils': 7.25.9
|
||||
|
||||
'@babel/runtime@7.26.0':
|
||||
dependencies:
|
||||
regenerator-runtime: 0.14.1
|
||||
|
||||
'@babel/runtime@7.28.4': {}
|
||||
|
||||
'@babel/template@7.25.9':
|
||||
@@ -4098,6 +4356,100 @@ snapshots:
|
||||
'@babel/helper-string-parser': 7.25.9
|
||||
'@babel/helper-validator-identifier': 7.25.9
|
||||
|
||||
'@base-ui-components/utils@0.1.2(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.28.4
|
||||
'@floating-ui/utils': 0.2.10
|
||||
react: 18.3.1
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
reselect: 5.1.1
|
||||
use-sync-external-store: 1.6.0(react@18.3.1)
|
||||
optionalDependencies:
|
||||
'@types/react': 18.3.18
|
||||
|
||||
'@emotion/babel-plugin@11.13.5':
|
||||
dependencies:
|
||||
'@babel/helper-module-imports': 7.25.9
|
||||
'@babel/runtime': 7.28.4
|
||||
'@emotion/hash': 0.9.2
|
||||
'@emotion/memoize': 0.9.0
|
||||
'@emotion/serialize': 1.3.3
|
||||
babel-plugin-macros: 3.1.0
|
||||
convert-source-map: 1.9.0
|
||||
escape-string-regexp: 4.0.0
|
||||
find-root: 1.1.0
|
||||
source-map: 0.5.7
|
||||
stylis: 4.2.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@emotion/cache@11.14.0':
|
||||
dependencies:
|
||||
'@emotion/memoize': 0.9.0
|
||||
'@emotion/sheet': 1.4.0
|
||||
'@emotion/utils': 1.4.2
|
||||
'@emotion/weak-memoize': 0.4.0
|
||||
stylis: 4.2.0
|
||||
|
||||
'@emotion/hash@0.9.2': {}
|
||||
|
||||
'@emotion/is-prop-valid@1.4.0':
|
||||
dependencies:
|
||||
'@emotion/memoize': 0.9.0
|
||||
|
||||
'@emotion/memoize@0.9.0': {}
|
||||
|
||||
'@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.28.4
|
||||
'@emotion/babel-plugin': 11.13.5
|
||||
'@emotion/cache': 11.14.0
|
||||
'@emotion/serialize': 1.3.3
|
||||
'@emotion/use-insertion-effect-with-fallbacks': 1.2.0(react@18.3.1)
|
||||
'@emotion/utils': 1.4.2
|
||||
'@emotion/weak-memoize': 0.4.0
|
||||
hoist-non-react-statics: 3.3.2
|
||||
react: 18.3.1
|
||||
optionalDependencies:
|
||||
'@types/react': 18.3.18
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@emotion/serialize@1.3.3':
|
||||
dependencies:
|
||||
'@emotion/hash': 0.9.2
|
||||
'@emotion/memoize': 0.9.0
|
||||
'@emotion/unitless': 0.10.0
|
||||
'@emotion/utils': 1.4.2
|
||||
csstype: 3.1.3
|
||||
|
||||
'@emotion/sheet@1.4.0': {}
|
||||
|
||||
'@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.28.4
|
||||
'@emotion/babel-plugin': 11.13.5
|
||||
'@emotion/is-prop-valid': 1.4.0
|
||||
'@emotion/react': 11.14.0(@types/react@18.3.18)(react@18.3.1)
|
||||
'@emotion/serialize': 1.3.3
|
||||
'@emotion/use-insertion-effect-with-fallbacks': 1.2.0(react@18.3.1)
|
||||
'@emotion/utils': 1.4.2
|
||||
react: 18.3.1
|
||||
optionalDependencies:
|
||||
'@types/react': 18.3.18
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@emotion/unitless@0.10.0': {}
|
||||
|
||||
'@emotion/use-insertion-effect-with-fallbacks@1.2.0(react@18.3.1)':
|
||||
dependencies:
|
||||
react: 18.3.1
|
||||
|
||||
'@emotion/utils@1.4.2': {}
|
||||
|
||||
'@emotion/weak-memoize@0.4.0': {}
|
||||
|
||||
'@esbuild/aix-ppc64@0.23.1':
|
||||
optional: true
|
||||
|
||||
@@ -4303,6 +4655,8 @@ snapshots:
|
||||
react: 18.3.1
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
|
||||
'@floating-ui/utils@0.2.10': {}
|
||||
|
||||
'@floating-ui/utils@0.2.8': {}
|
||||
|
||||
'@hookform/resolvers@3.9.1(react-hook-form@7.54.0(react@18.3.1))':
|
||||
@@ -4354,6 +4708,115 @@ snapshots:
|
||||
'@jridgewell/resolve-uri': 3.1.2
|
||||
'@jridgewell/sourcemap-codec': 1.5.0
|
||||
|
||||
'@mui/core-downloads-tracker@7.3.5': {}
|
||||
|
||||
'@mui/material@7.3.5(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.28.4
|
||||
'@mui/core-downloads-tracker': 7.3.5
|
||||
'@mui/system': 7.3.5(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1)
|
||||
'@mui/types': 7.4.8(@types/react@18.3.18)
|
||||
'@mui/utils': 7.3.5(@types/react@18.3.18)(react@18.3.1)
|
||||
'@popperjs/core': 2.11.8
|
||||
'@types/react-transition-group': 4.4.12(@types/react@18.3.18)
|
||||
clsx: 2.1.1
|
||||
csstype: 3.1.3
|
||||
prop-types: 15.8.1
|
||||
react: 18.3.1
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
react-is: 19.2.0
|
||||
react-transition-group: 4.4.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
optionalDependencies:
|
||||
'@emotion/react': 11.14.0(@types/react@18.3.18)(react@18.3.1)
|
||||
'@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1)
|
||||
'@types/react': 18.3.18
|
||||
|
||||
'@mui/private-theming@7.3.5(@types/react@18.3.18)(react@18.3.1)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.28.4
|
||||
'@mui/utils': 7.3.5(@types/react@18.3.18)(react@18.3.1)
|
||||
prop-types: 15.8.1
|
||||
react: 18.3.1
|
||||
optionalDependencies:
|
||||
'@types/react': 18.3.18
|
||||
|
||||
'@mui/styled-engine@7.3.5(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1))(react@18.3.1)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.28.4
|
||||
'@emotion/cache': 11.14.0
|
||||
'@emotion/serialize': 1.3.3
|
||||
'@emotion/sheet': 1.4.0
|
||||
csstype: 3.1.3
|
||||
prop-types: 15.8.1
|
||||
react: 18.3.1
|
||||
optionalDependencies:
|
||||
'@emotion/react': 11.14.0(@types/react@18.3.18)(react@18.3.1)
|
||||
'@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1)
|
||||
|
||||
'@mui/system@7.3.5(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.28.4
|
||||
'@mui/private-theming': 7.3.5(@types/react@18.3.18)(react@18.3.1)
|
||||
'@mui/styled-engine': 7.3.5(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1))(react@18.3.1)
|
||||
'@mui/types': 7.4.8(@types/react@18.3.18)
|
||||
'@mui/utils': 7.3.5(@types/react@18.3.18)(react@18.3.1)
|
||||
clsx: 2.1.1
|
||||
csstype: 3.1.3
|
||||
prop-types: 15.8.1
|
||||
react: 18.3.1
|
||||
optionalDependencies:
|
||||
'@emotion/react': 11.14.0(@types/react@18.3.18)(react@18.3.1)
|
||||
'@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1)
|
||||
'@types/react': 18.3.18
|
||||
|
||||
'@mui/types@7.4.8(@types/react@18.3.18)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.28.4
|
||||
optionalDependencies:
|
||||
'@types/react': 18.3.18
|
||||
|
||||
'@mui/utils@7.3.5(@types/react@18.3.18)(react@18.3.1)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.28.4
|
||||
'@mui/types': 7.4.8(@types/react@18.3.18)
|
||||
'@types/prop-types': 15.7.15
|
||||
clsx: 2.1.1
|
||||
prop-types: 15.8.1
|
||||
react: 18.3.1
|
||||
react-is: 19.2.0
|
||||
optionalDependencies:
|
||||
'@types/react': 18.3.18
|
||||
|
||||
'@mui/x-internals@8.19.0(@types/react@18.3.18)(react@18.3.1)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.28.4
|
||||
'@mui/utils': 7.3.5(@types/react@18.3.18)(react@18.3.1)
|
||||
react: 18.3.1
|
||||
reselect: 5.1.1
|
||||
use-sync-external-store: 1.6.0(react@18.3.1)
|
||||
transitivePeerDependencies:
|
||||
- '@types/react'
|
||||
|
||||
'@mui/x-tree-view@8.19.0(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1))(@mui/material@7.3.5(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@mui/system@7.3.5(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.28.4
|
||||
'@base-ui-components/utils': 0.1.2(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@mui/material': 7.3.5(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@mui/system': 7.3.5(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1)
|
||||
'@mui/utils': 7.3.5(@types/react@18.3.18)(react@18.3.1)
|
||||
'@mui/x-internals': 8.19.0(@types/react@18.3.18)(react@18.3.1)
|
||||
'@types/react-transition-group': 4.4.12(@types/react@18.3.18)
|
||||
clsx: 2.1.1
|
||||
prop-types: 15.8.1
|
||||
react: 18.3.1
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
react-transition-group: 4.4.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
optionalDependencies:
|
||||
'@emotion/react': 11.14.0(@types/react@18.3.18)(react@18.3.1)
|
||||
'@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1)
|
||||
transitivePeerDependencies:
|
||||
- '@types/react'
|
||||
|
||||
'@nodelib/fs.scandir@2.1.5':
|
||||
dependencies:
|
||||
'@nodelib/fs.stat': 2.0.5
|
||||
@@ -4430,6 +4893,8 @@ snapshots:
|
||||
'@pkgjs/parseargs@0.11.0':
|
||||
optional: true
|
||||
|
||||
'@popperjs/core@2.11.8': {}
|
||||
|
||||
'@radix-ui/number@1.1.0': {}
|
||||
|
||||
'@radix-ui/primitive@1.1.0': {}
|
||||
@@ -5201,6 +5666,38 @@ snapshots:
|
||||
|
||||
'@radix-ui/rect@1.1.0': {}
|
||||
|
||||
'@react-spring/animated@10.0.3(react@18.3.1)':
|
||||
dependencies:
|
||||
'@react-spring/shared': 10.0.3(react@18.3.1)
|
||||
'@react-spring/types': 10.0.3
|
||||
react: 18.3.1
|
||||
|
||||
'@react-spring/core@10.0.3(react@18.3.1)':
|
||||
dependencies:
|
||||
'@react-spring/animated': 10.0.3(react@18.3.1)
|
||||
'@react-spring/shared': 10.0.3(react@18.3.1)
|
||||
'@react-spring/types': 10.0.3
|
||||
react: 18.3.1
|
||||
|
||||
'@react-spring/rafz@10.0.3': {}
|
||||
|
||||
'@react-spring/shared@10.0.3(react@18.3.1)':
|
||||
dependencies:
|
||||
'@react-spring/rafz': 10.0.3
|
||||
'@react-spring/types': 10.0.3
|
||||
react: 18.3.1
|
||||
|
||||
'@react-spring/types@10.0.3': {}
|
||||
|
||||
'@react-spring/web@10.0.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||
dependencies:
|
||||
'@react-spring/animated': 10.0.3(react@18.3.1)
|
||||
'@react-spring/core': 10.0.3(react@18.3.1)
|
||||
'@react-spring/shared': 10.0.3(react@18.3.1)
|
||||
'@react-spring/types': 10.0.3
|
||||
react: 18.3.1
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
|
||||
'@rollup/rollup-android-arm-eabi@4.26.0':
|
||||
optional: true
|
||||
|
||||
@@ -5526,12 +6023,20 @@ snapshots:
|
||||
dependencies:
|
||||
undici-types: 6.20.0
|
||||
|
||||
'@types/parse-json@4.0.2': {}
|
||||
|
||||
'@types/prop-types@15.7.14': {}
|
||||
|
||||
'@types/prop-types@15.7.15': {}
|
||||
|
||||
'@types/react-dom@18.3.5(@types/react@18.3.18)':
|
||||
dependencies:
|
||||
'@types/react': 18.3.18
|
||||
|
||||
'@types/react-transition-group@4.4.12(@types/react@18.3.18)':
|
||||
dependencies:
|
||||
'@types/react': 18.3.18
|
||||
|
||||
'@types/react@18.3.18':
|
||||
dependencies:
|
||||
'@types/prop-types': 15.7.14
|
||||
@@ -5822,6 +6327,12 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
babel-plugin-macros@3.1.0:
|
||||
dependencies:
|
||||
'@babel/runtime': 7.28.4
|
||||
cosmiconfig: 7.1.0
|
||||
resolve: 1.22.8
|
||||
|
||||
bail@2.0.2: {}
|
||||
|
||||
balanced-match@1.0.2: {}
|
||||
@@ -5934,8 +6445,18 @@ snapshots:
|
||||
|
||||
concat-map@0.0.1: {}
|
||||
|
||||
convert-source-map@1.9.0: {}
|
||||
|
||||
convert-source-map@2.0.0: {}
|
||||
|
||||
cosmiconfig@7.1.0:
|
||||
dependencies:
|
||||
'@types/parse-json': 4.0.2
|
||||
import-fresh: 3.3.0
|
||||
parse-json: 5.2.0
|
||||
path-type: 4.0.0
|
||||
yaml: 1.10.2
|
||||
|
||||
cross-spawn@7.0.5:
|
||||
dependencies:
|
||||
path-key: 3.1.1
|
||||
@@ -6024,7 +6545,7 @@ snapshots:
|
||||
|
||||
dom-helpers@5.2.1:
|
||||
dependencies:
|
||||
'@babel/runtime': 7.26.0
|
||||
'@babel/runtime': 7.28.4
|
||||
csstype: 3.1.3
|
||||
|
||||
eastasianwidth@0.2.0: {}
|
||||
@@ -6046,6 +6567,10 @@ snapshots:
|
||||
graceful-fs: 4.2.11
|
||||
tapable: 2.2.1
|
||||
|
||||
error-ex@1.3.4:
|
||||
dependencies:
|
||||
is-arrayish: 0.2.1
|
||||
|
||||
es-module-lexer@1.7.0:
|
||||
optional: true
|
||||
|
||||
@@ -6237,6 +6762,8 @@ snapshots:
|
||||
dependencies:
|
||||
to-regex-range: 5.0.1
|
||||
|
||||
find-root@1.1.0: {}
|
||||
|
||||
find-up@5.0.0:
|
||||
dependencies:
|
||||
locate-path: 6.0.0
|
||||
@@ -6350,6 +6877,10 @@ snapshots:
|
||||
dependencies:
|
||||
'@types/hast': 3.0.4
|
||||
|
||||
hoist-non-react-statics@3.3.2:
|
||||
dependencies:
|
||||
react-is: 16.13.1
|
||||
|
||||
html-parse-stringify@3.0.1:
|
||||
dependencies:
|
||||
void-elements: 3.1.0
|
||||
@@ -6391,6 +6922,8 @@ snapshots:
|
||||
is-alphabetical: 2.0.1
|
||||
is-decimal: 2.0.1
|
||||
|
||||
is-arrayish@0.2.1: {}
|
||||
|
||||
is-binary-path@2.1.0:
|
||||
dependencies:
|
||||
binary-extensions: 2.3.0
|
||||
@@ -6450,8 +6983,7 @@ snapshots:
|
||||
|
||||
json-buffer@3.0.1: {}
|
||||
|
||||
json-parse-even-better-errors@2.3.1:
|
||||
optional: true
|
||||
json-parse-even-better-errors@2.3.1: {}
|
||||
|
||||
json-schema-traverse@0.4.1: {}
|
||||
|
||||
@@ -6965,6 +7497,13 @@ snapshots:
|
||||
is-decimal: 2.0.1
|
||||
is-hexadecimal: 2.0.1
|
||||
|
||||
parse-json@5.2.0:
|
||||
dependencies:
|
||||
'@babel/code-frame': 7.26.2
|
||||
error-ex: 1.3.4
|
||||
json-parse-even-better-errors: 2.3.1
|
||||
lines-and-columns: 1.2.4
|
||||
|
||||
parse-ms@4.0.0: {}
|
||||
|
||||
path-exists@4.0.0: {}
|
||||
@@ -6978,6 +7517,8 @@ snapshots:
|
||||
lru-cache: 10.4.3
|
||||
minipass: 7.1.2
|
||||
|
||||
path-type@4.0.0: {}
|
||||
|
||||
picocolors@1.1.1: {}
|
||||
|
||||
picomatch@2.3.1: {}
|
||||
@@ -7098,6 +7639,8 @@ snapshots:
|
||||
|
||||
react-is@18.3.1: {}
|
||||
|
||||
react-is@19.2.0: {}
|
||||
|
||||
react-markdown@10.1.0(@types/react@18.3.18)(react@18.3.1):
|
||||
dependencies:
|
||||
'@types/hast': 3.0.4
|
||||
@@ -7159,7 +7702,7 @@ snapshots:
|
||||
|
||||
react-transition-group@4.4.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
|
||||
dependencies:
|
||||
'@babel/runtime': 7.26.0
|
||||
'@babel/runtime': 7.28.4
|
||||
dom-helpers: 5.2.1
|
||||
loose-envify: 1.4.0
|
||||
prop-types: 15.8.1
|
||||
@@ -7198,8 +7741,6 @@ snapshots:
|
||||
tiny-invariant: 1.3.3
|
||||
victory-vendor: 36.9.2
|
||||
|
||||
regenerator-runtime@0.14.1: {}
|
||||
|
||||
remark-gfm@4.0.1:
|
||||
dependencies:
|
||||
'@types/mdast': 4.0.4
|
||||
@@ -7237,6 +7778,8 @@ snapshots:
|
||||
require-from-string@2.0.2:
|
||||
optional: true
|
||||
|
||||
reselect@5.1.1: {}
|
||||
|
||||
resolve-from@4.0.0: {}
|
||||
|
||||
resolve-pkg-maps@1.0.0: {}
|
||||
@@ -7371,6 +7914,8 @@ snapshots:
|
||||
dependencies:
|
||||
inline-style-parser: 0.2.4
|
||||
|
||||
stylis@4.2.0: {}
|
||||
|
||||
sucrase@3.35.0:
|
||||
dependencies:
|
||||
'@jridgewell/gen-mapping': 0.3.5
|
||||
@@ -7701,6 +8246,8 @@ snapshots:
|
||||
|
||||
yallist@3.1.1: {}
|
||||
|
||||
yaml@1.10.2: {}
|
||||
|
||||
yaml@2.6.0: {}
|
||||
|
||||
yocto-queue@0.1.0: {}
|
||||
|
||||
@@ -82,7 +82,7 @@ export function EnvelopeListPagination({
|
||||
<SelectValue placeholder={pageSize} />
|
||||
</SelectTrigger>
|
||||
<SelectContent side='top'>
|
||||
{[10, 20, 30, 40, 50].map((size) => (
|
||||
{[10, 20, 30, 40, 50, 100].map((size) => (
|
||||
<SelectItem key={size} value={`${size}`}>
|
||||
{size}
|
||||
</SelectItem>
|
||||
@@ -91,7 +91,7 @@ export function EnvelopeListPagination({
|
||||
</Select>
|
||||
</div>
|
||||
<div className='flex items-center justify-center text-sm font-medium'>
|
||||
{t("table.page")} {pageIndex + 1} {t("table.of")} {pageCount}
|
||||
{t("table.page")} {pageIndex + 1} {t("table.of")} {pageCount}
|
||||
</div>
|
||||
<div className='flex items-center space-x-2'>
|
||||
<Button
|
||||
|
||||
@@ -1,529 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2025 rustmailer.com (https://rustmailer.com)
|
||||
//
|
||||
// This file is part of the Bichon Email Archiving Project
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
import React from 'react'
|
||||
import * as AccordionPrimitive from '@radix-ui/react-accordion'
|
||||
import { ChevronRight } from 'lucide-react'
|
||||
import { cva } from 'class-variance-authority'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const treeVariants = cva(
|
||||
'group hover:before:opacity-100 before:absolute before:rounded-lg before:left-0 px-2 before:w-full before:opacity-0 before:bg-accent/70 before:h-[2rem] before:-z-10'
|
||||
)
|
||||
|
||||
const selectedTreeVariants = cva(
|
||||
'before:opacity-100 before:bg-accent/70 text-accent-foreground'
|
||||
)
|
||||
|
||||
interface TreeDataItem {
|
||||
id: string
|
||||
name: string
|
||||
icon?: React.ComponentType<{ className?: string }>
|
||||
openIcon?: React.ComponentType<{ className?: string }>
|
||||
children?: TreeDataItem[]
|
||||
badge?: React.ReactNode,
|
||||
attributes?: React.ReactNode,
|
||||
onClick?: () => void
|
||||
}
|
||||
|
||||
type TreeProps = React.HTMLAttributes<HTMLDivElement> & {
|
||||
data: TreeDataItem[] | TreeDataItem
|
||||
onSelectChange?: (item: TreeDataItem | undefined) => void
|
||||
onSelectItemsChange?: (items: TreeDataItem[]) => void
|
||||
expandAll?: boolean
|
||||
multiple?: boolean
|
||||
clickRowToSelect?: boolean
|
||||
initialSelectedItemIds?: string[]
|
||||
defaultNodeIcon?: React.ComponentType<{ className?: string }>
|
||||
defaultLeafIcon?: React.ComponentType<{ className?: string }>
|
||||
}
|
||||
|
||||
const TreeView = React.forwardRef<HTMLDivElement, TreeProps>(
|
||||
(
|
||||
{
|
||||
data,
|
||||
onSelectChange,
|
||||
onSelectItemsChange,
|
||||
expandAll,
|
||||
defaultLeafIcon,
|
||||
defaultNodeIcon,
|
||||
clickRowToSelect = true,
|
||||
className,
|
||||
multiple,
|
||||
initialSelectedItemIds = [],
|
||||
...props
|
||||
},
|
||||
ref
|
||||
) => {
|
||||
const [selectedItemIds, setSelectedItemIds] = React.useState<Set<string>>(
|
||||
new Set(initialSelectedItemIds)
|
||||
);
|
||||
|
||||
const callbacksRef = React.useRef({
|
||||
onSelectChange,
|
||||
onSelectItemsChange
|
||||
});
|
||||
|
||||
React.useEffect(() => {
|
||||
callbacksRef.current = {
|
||||
onSelectChange,
|
||||
onSelectItemsChange
|
||||
};
|
||||
}, [onSelectChange, onSelectItemsChange]);
|
||||
|
||||
|
||||
const handleSelectChange = React.useCallback(
|
||||
(item: TreeDataItem | undefined) => {
|
||||
if (!item) return;
|
||||
|
||||
setSelectedItemIds(prev => {
|
||||
const newSet = new Set(prev);
|
||||
if (newSet.has(item.id)) {
|
||||
newSet.delete(item.id);
|
||||
} else {
|
||||
if (!multiple) {
|
||||
newSet.clear();
|
||||
}
|
||||
newSet.add(item.id);
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
if (callbacksRef.current.onSelectChange) {
|
||||
callbacksRef.current.onSelectChange(newSet.has(item.id) ? item : undefined);
|
||||
}
|
||||
if (callbacksRef.current.onSelectItemsChange) {
|
||||
const selectedItems = Array.from(newSet)
|
||||
.map(id => findItemById(data, id))
|
||||
.filter(Boolean) as TreeDataItem[];
|
||||
callbacksRef.current.onSelectItemsChange(selectedItems);
|
||||
}
|
||||
}, 0);
|
||||
return newSet;
|
||||
});
|
||||
},
|
||||
[multiple, onSelectChange, onSelectItemsChange, data]
|
||||
);
|
||||
|
||||
const expandedItemIds = React.useMemo(() => {
|
||||
if (!initialSelectedItemIds || initialSelectedItemIds.length === 0) {
|
||||
return [] as string[]
|
||||
}
|
||||
|
||||
const ids: string[] = []
|
||||
|
||||
function walkTreeItems(
|
||||
items: TreeDataItem[] | TreeDataItem,
|
||||
targetIds: string[]
|
||||
) {
|
||||
if (Array.isArray(items)) {
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
ids.push(items[i]!.id)
|
||||
if (walkTreeItems(items[i]!, targetIds) && !expandAll) {
|
||||
return true
|
||||
}
|
||||
if (!expandAll) ids.pop()
|
||||
}
|
||||
} else if (!expandAll && targetIds.includes(items.id)) {
|
||||
return true
|
||||
} else if (items.children) {
|
||||
return walkTreeItems(items.children, targetIds)
|
||||
}
|
||||
}
|
||||
|
||||
walkTreeItems(data, initialSelectedItemIds)
|
||||
return ids
|
||||
}, [data, expandAll, initialSelectedItemIds])
|
||||
|
||||
return (
|
||||
<div className={cn('overflow-hidden relative p-2', className)}>
|
||||
<TreeItem
|
||||
data={data}
|
||||
ref={ref}
|
||||
clickRowToSelect={clickRowToSelect}
|
||||
selectedItemIds={selectedItemIds}
|
||||
handleSelectChange={handleSelectChange}
|
||||
expandedItemIds={expandedItemIds}
|
||||
defaultLeafIcon={defaultLeafIcon}
|
||||
defaultNodeIcon={defaultNodeIcon}
|
||||
{...props}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
)
|
||||
TreeView.displayName = 'TreeView'
|
||||
|
||||
// Helper function to find item by ID in tree
|
||||
function findItemById(items: TreeDataItem[] | TreeDataItem, id: string): TreeDataItem | undefined {
|
||||
if (Array.isArray(items)) {
|
||||
for (const item of items) {
|
||||
const found = findItemById(item, id);
|
||||
if (found) return found;
|
||||
}
|
||||
} else {
|
||||
if (items.id === id) return items;
|
||||
if (items.children) {
|
||||
return findItemById(items.children, id);
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
type TreeItemProps = TreeProps & {
|
||||
selectedItemIds: Set<string>
|
||||
handleSelectChange: (item: TreeDataItem | undefined) => void
|
||||
expandedItemIds: string[]
|
||||
clickRowToSelect?: boolean
|
||||
defaultNodeIcon?: React.ComponentType<{ className?: string }>
|
||||
defaultLeafIcon?: React.ComponentType<{ className?: string }>
|
||||
}
|
||||
|
||||
const TreeItem = React.forwardRef<HTMLDivElement, TreeItemProps>(
|
||||
(
|
||||
{
|
||||
className,
|
||||
data,
|
||||
selectedItemIds,
|
||||
handleSelectChange,
|
||||
clickRowToSelect,
|
||||
expandedItemIds,
|
||||
defaultNodeIcon,
|
||||
defaultLeafIcon,
|
||||
...props
|
||||
},
|
||||
ref
|
||||
) => {
|
||||
if (!Array.isArray(data)) {
|
||||
data = [data]
|
||||
}
|
||||
|
||||
return (
|
||||
<div ref={ref} role="tree" className={className} {...props}>
|
||||
<ul>
|
||||
{data.map((item) => (
|
||||
<li key={item.id}>
|
||||
{item.children ? (
|
||||
<TreeNode
|
||||
item={item}
|
||||
selectedItemIds={selectedItemIds}
|
||||
expandedItemIds={expandedItemIds}
|
||||
clickRowToSelect={clickRowToSelect}
|
||||
handleSelectChange={handleSelectChange}
|
||||
defaultNodeIcon={defaultNodeIcon}
|
||||
defaultLeafIcon={defaultLeafIcon}
|
||||
/>
|
||||
) : (
|
||||
<TreeLeaf
|
||||
item={item}
|
||||
clickRowToSelect={clickRowToSelect}
|
||||
selectedItemIds={selectedItemIds}
|
||||
handleSelectChange={handleSelectChange}
|
||||
defaultLeafIcon={defaultLeafIcon}
|
||||
/>
|
||||
)}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
)
|
||||
TreeItem.displayName = 'TreeItem'
|
||||
|
||||
interface TreeNodeProps {
|
||||
item: TreeDataItem
|
||||
handleSelectChange: (item: TreeDataItem | undefined) => void
|
||||
expandedItemIds: string[]
|
||||
clickRowToSelect?: boolean
|
||||
selectedItemIds: Set<string>
|
||||
defaultNodeIcon?: React.ComponentType<{ className?: string }>
|
||||
defaultLeafIcon?: React.ComponentType<{ className?: string }>
|
||||
}
|
||||
|
||||
const TreeNode = ({
|
||||
item,
|
||||
handleSelectChange,
|
||||
expandedItemIds,
|
||||
clickRowToSelect,
|
||||
selectedItemIds,
|
||||
defaultNodeIcon,
|
||||
defaultLeafIcon
|
||||
}: TreeNodeProps) => {
|
||||
const [value, setValue] = React.useState(
|
||||
expandedItemIds.includes(item.id) ? [item.id] : []
|
||||
)
|
||||
|
||||
const isSelected = selectedItemIds.has(item.id);
|
||||
|
||||
return (
|
||||
<AccordionPrimitive.Root
|
||||
type="multiple"
|
||||
value={value}
|
||||
onValueChange={(s) => setValue(s)}
|
||||
>
|
||||
<AccordionPrimitive.Item value={item.id}>
|
||||
<AccordionTrigger
|
||||
className={cn(
|
||||
"flex items-center w-full py-2",
|
||||
treeVariants(),
|
||||
isSelected && selectedTreeVariants()
|
||||
)}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
if (clickRowToSelect) {
|
||||
handleSelectChange(item);
|
||||
}
|
||||
item.onClick?.();
|
||||
}}
|
||||
>
|
||||
<div className="flex items-center min-w-0 flex-shrink-0">
|
||||
<TreeIcon
|
||||
item={item}
|
||||
isSelected={isSelected}
|
||||
isOpen={value.includes(item.id)}
|
||||
default={defaultNodeIcon}
|
||||
onCheck={() => { handleSelectChange(item) }}
|
||||
/>
|
||||
<span className="ml-2 text-sm truncate">
|
||||
{item.name}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{item.attributes && (
|
||||
<span className="mx-auto text-sm text-muted-foreground whitespace-nowrap">
|
||||
{item.attributes}
|
||||
</span>
|
||||
)}
|
||||
|
||||
{item.badge && (
|
||||
<TreeBadge isSelected={isSelected}>
|
||||
{item.badge}
|
||||
</TreeBadge>
|
||||
)}
|
||||
</AccordionTrigger>
|
||||
<AccordionContent className="ml-4 pl-1 border-l">
|
||||
<TreeItem
|
||||
data={item.children ? item.children : item}
|
||||
selectedItemIds={selectedItemIds}
|
||||
clickRowToSelect={clickRowToSelect}
|
||||
handleSelectChange={handleSelectChange}
|
||||
expandedItemIds={expandedItemIds}
|
||||
defaultLeafIcon={defaultLeafIcon}
|
||||
defaultNodeIcon={defaultNodeIcon}
|
||||
/>
|
||||
</AccordionContent>
|
||||
</AccordionPrimitive.Item>
|
||||
</AccordionPrimitive.Root>
|
||||
)
|
||||
}
|
||||
|
||||
// function hasSelectedChildrenRecursive(item: TreeDataItem, selectedItemIds: Set<string>): boolean {
|
||||
// if (!item.children) return false;
|
||||
// return item.children.some(child =>
|
||||
// selectedItemIds.has(child.id) ||
|
||||
// (child.children && hasSelectedChildrenRecursive(child, selectedItemIds)));
|
||||
// }
|
||||
|
||||
interface TreeLeafProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
item: TreeDataItem
|
||||
selectedItemIds: Set<string>
|
||||
clickRowToSelect?: boolean
|
||||
handleSelectChange: (item: TreeDataItem | undefined) => void
|
||||
defaultLeafIcon?: React.ComponentType<{ className?: string }>
|
||||
}
|
||||
|
||||
const TreeLeaf = React.forwardRef<HTMLDivElement, TreeLeafProps>(
|
||||
(
|
||||
{
|
||||
className,
|
||||
item,
|
||||
clickRowToSelect,
|
||||
selectedItemIds,
|
||||
handleSelectChange,
|
||||
defaultLeafIcon,
|
||||
...props
|
||||
},
|
||||
ref
|
||||
) => {
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"ml-5 flex items-center py-2 cursor-pointer before:right-1",
|
||||
treeVariants(),
|
||||
className,
|
||||
selectedItemIds.has(item.id) && selectedTreeVariants()
|
||||
)}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
if (clickRowToSelect) {
|
||||
handleSelectChange(item);
|
||||
}
|
||||
item.onClick?.();
|
||||
}}
|
||||
{...props}
|
||||
>
|
||||
<div className="flex items-center min-w-0 flex-shrink-0">
|
||||
<TreeIcon
|
||||
item={item}
|
||||
isSelected={selectedItemIds.has(item.id)}
|
||||
default={defaultLeafIcon}
|
||||
onCheck={() => { handleSelectChange(item) }}
|
||||
/>
|
||||
<span className="ml-2 text-sm truncate">
|
||||
{item.name}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{item.attributes && (
|
||||
<span className="mx-auto text-sm text-muted-foreground whitespace-nowrap">
|
||||
{item.attributes}
|
||||
</span>
|
||||
)}
|
||||
|
||||
{item.badge && (
|
||||
<TreeBadge isSelected={selectedItemIds.has(item.id)}>
|
||||
{item.badge}
|
||||
</TreeBadge>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
)
|
||||
TreeLeaf.displayName = 'TreeLeaf'
|
||||
|
||||
const AccordionTrigger = React.forwardRef<
|
||||
React.ElementRef<typeof AccordionPrimitive.Trigger>,
|
||||
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<AccordionPrimitive.Header>
|
||||
<AccordionPrimitive.Trigger
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'flex flex-1 w-full items-center py-2 transition-all first:[&[data-state=open]>svg]:rotate-90',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
if (props.onClick) {
|
||||
props.onClick(e)
|
||||
}
|
||||
}}
|
||||
>
|
||||
<ChevronRight className="h-4 w-4 shrink-0 transition-transform duration-200 text-accent-foreground/50 mr-1" />
|
||||
{children}
|
||||
</AccordionPrimitive.Trigger>
|
||||
</AccordionPrimitive.Header>
|
||||
))
|
||||
AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName
|
||||
|
||||
const AccordionContent = React.forwardRef<
|
||||
React.ElementRef<typeof AccordionPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<AccordionPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'overflow-hidden text-sm transition-all data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<div className="pb-1 pt-0">{children}</div>
|
||||
</AccordionPrimitive.Content>
|
||||
))
|
||||
AccordionContent.displayName = AccordionPrimitive.Content.displayName
|
||||
|
||||
interface TreeIconProps {
|
||||
item: TreeDataItem;
|
||||
isOpen?: boolean;
|
||||
isSelected?: boolean;
|
||||
default?: React.ComponentType<{ className?: string }>;
|
||||
onCheck?: (checked: boolean) => void;
|
||||
}
|
||||
|
||||
const TreeIcon = ({
|
||||
item,
|
||||
isOpen,
|
||||
isSelected,
|
||||
default: defaultIcon,
|
||||
onCheck,
|
||||
}: TreeIconProps) => {
|
||||
let Icon = defaultIcon;
|
||||
if (isOpen && item.openIcon) {
|
||||
Icon = item.openIcon;
|
||||
} else if (item.icon) {
|
||||
Icon = item.icon;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={isSelected}
|
||||
onChange={() => onCheck?.(!isSelected)}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
className={cn(
|
||||
"h-4 w-4 rounded border border-primary dark:border-white shadow transition-all duration-200",
|
||||
"focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
|
||||
isSelected
|
||||
? "bg-black dark:bg-white text-primary-foreground"
|
||||
: "bg-transparent",
|
||||
"appearance-none cursor-pointer flex items-center justify-center relative",
|
||||
"after:content-[''] after:w-1.5 after:h-2",
|
||||
"after:border-r-2 after:border-b-2 after:rotate-45 after:mt-[-2px]",
|
||||
isSelected
|
||||
? "after:block after:border-white dark:after:border-black after:z-10"
|
||||
: "after:hidden"
|
||||
)}
|
||||
/>
|
||||
{Icon && <Icon className="h-4 w-4 shrink-0" />}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
interface TreeBadgeProps {
|
||||
children: React.ReactNode
|
||||
isSelected: boolean
|
||||
showOnSelectedOnly?: boolean
|
||||
}
|
||||
|
||||
const TreeBadge = ({
|
||||
children,
|
||||
isSelected,
|
||||
showOnSelectedOnly = false
|
||||
}: TreeBadgeProps) => {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
showOnSelectedOnly
|
||||
? isSelected
|
||||
? 'block'
|
||||
: 'hidden'
|
||||
: 'block',
|
||||
'absolute right-3 group-hover:block'
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export { TreeView, type TreeDataItem }
|
||||
@@ -32,14 +32,113 @@ import { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { AccountModel } from '../data/schema'
|
||||
import { toast } from '@/hooks/use-toast'
|
||||
import { list_mailboxes, MailboxData } from '@/api/mailbox/api'
|
||||
import { buildTree } from '@/lib/build-tree'
|
||||
import { TreeDataItem, TreeView } from '@/components/tree-view'
|
||||
import { buildTree, ExtendedTreeItemProps } from '@/lib/build-tree'
|
||||
import { Skeleton } from '@/components/ui/skeleton'
|
||||
import { update_account } from '@/api/account/api'
|
||||
import { ToastAction } from '@/components/ui/toast'
|
||||
import axios, { AxiosError } from 'axios'
|
||||
import { ScrollArea } from '@/components/ui/scroll-area'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { RichTreeView } from '@mui/x-tree-view/RichTreeView';
|
||||
import { useTheme } from '@/context/theme-context'
|
||||
import React from 'react'
|
||||
import Collapse from '@mui/material/Collapse';
|
||||
import { styled } from '@mui/material/styles';
|
||||
import { TreeItemCheckbox, TreeItemContent, TreeItemIconContainer, TreeItemLabel, TreeItemRoot } from '@mui/x-tree-view/TreeItem'
|
||||
import { TreeItemDragAndDropOverlay, TreeItemIcon, TreeItemProvider, TreeViewBaseItem, TreeViewSelectionPropagation, useTreeItem, useTreeItemModel, UseTreeItemParameters } from '@mui/x-tree-view'
|
||||
import { animated, useSpring } from '@react-spring/web';
|
||||
import { TransitionProps } from '@mui/material/transitions'
|
||||
|
||||
|
||||
function getParentIds(tree: TreeViewBaseItem[]): string[] {
|
||||
const result: string[] = [];
|
||||
|
||||
function traverse(nodes: TreeViewBaseItem[]) {
|
||||
for (const node of nodes) {
|
||||
if (node.children && node.children.length > 0) {
|
||||
result.push(node.id);
|
||||
traverse(node.children);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
traverse(tree);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
interface CustomLabelProps {
|
||||
exists?: number;
|
||||
attributes?: { attr: string; extension: string | null }[],
|
||||
children: React.ReactNode;
|
||||
icon?: React.ElementType;
|
||||
expandable?: boolean;
|
||||
}
|
||||
|
||||
function CustomLabel({
|
||||
expandable,
|
||||
exists,
|
||||
attributes,
|
||||
children,
|
||||
...other
|
||||
}: CustomLabelProps) {
|
||||
return (
|
||||
<TreeItemLabel
|
||||
{...other}
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<span className="font-medium text-sm text-inherit">
|
||||
{children}
|
||||
</span>
|
||||
<div className="flex gap-2 ml-auto mr-3 opacity-70 text-xs">
|
||||
{attributes?.map((attr) => {
|
||||
const text =
|
||||
attr.attr === 'Extension'
|
||||
? attr.extension
|
||||
: attr.attr;
|
||||
|
||||
return (
|
||||
<span key={attr.attr} className="text-inherit">
|
||||
{text}
|
||||
</span>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
{exists !== undefined && (
|
||||
<span
|
||||
className="text-sm opacity-60 min-w-[40px] text-right text-inherit"
|
||||
>
|
||||
{exists}
|
||||
</span>
|
||||
)}
|
||||
</TreeItemLabel>
|
||||
);
|
||||
}
|
||||
|
||||
const CustomCollapse = styled(Collapse)({
|
||||
padding: 0,
|
||||
});
|
||||
|
||||
const AnimatedCollapse = animated(CustomCollapse);
|
||||
|
||||
function TransitionComponent(props: TransitionProps) {
|
||||
const style = useSpring({
|
||||
to: {
|
||||
opacity: props.in ? 1 : 0,
|
||||
transform: `translate3d(0,${props.in ? 0 : 20}px,0)`,
|
||||
},
|
||||
});
|
||||
|
||||
return <AnimatedCollapse style={style} {...props} />;
|
||||
}
|
||||
|
||||
interface CustomTreeItemProps
|
||||
extends Omit<UseTreeItemParameters, 'rootRef'>,
|
||||
Omit<React.HTMLAttributes<HTMLLIElement>, 'onFocus'> { }
|
||||
|
||||
|
||||
interface Props {
|
||||
open: boolean
|
||||
@@ -48,15 +147,24 @@ interface Props {
|
||||
}
|
||||
|
||||
export function SyncFoldersDialog({ currentRow, open, onOpenChange }: Props) {
|
||||
const [selectedFolders, setSelectedFolders] = useState<string[]>(currentRow.sync_folders || []);
|
||||
const [selectedItems, setSelectedItems] = React.useState<string[]>([]);
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
|
||||
const [allIds, setAllIds] = useState<string[]>([]);
|
||||
const [expandedItems, setExpandedItems] = useState<string[]>([]);
|
||||
const [itemsWithChildren, setItemsWithChildren] = useState<string[]>([]);
|
||||
const [mailboxes, setMailboxes] = useState<MailboxData[]>([]);
|
||||
const [selectionPropagation, setSelectionPropagation] =
|
||||
React.useState<TreeViewSelectionPropagation>({
|
||||
parents: false,
|
||||
descendants: true,
|
||||
});
|
||||
const [treeData, setTreeData] = useState<TreeViewBaseItem[]>([]);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [error, setError] = useState<string | undefined>(undefined);
|
||||
const queryClient = useQueryClient();
|
||||
const { t } = useTranslation()
|
||||
|
||||
const { theme } = useTheme()
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) return;
|
||||
@@ -67,6 +175,18 @@ export function SyncFoldersDialog({ currentRow, open, onOpenChange }: Props) {
|
||||
const data = await list_mailboxes(currentRow.id, true);
|
||||
if (!cancelled) {
|
||||
setMailboxes(data);
|
||||
const allIds = data.map(mailbox => String(mailbox.id));
|
||||
setAllIds(allIds);
|
||||
|
||||
const tree = buildTree(data);
|
||||
setTreeData(tree);
|
||||
const itemsWithChildren = getParentIds(tree);
|
||||
setItemsWithChildren(itemsWithChildren);
|
||||
setExpandedItems(itemsWithChildren);
|
||||
const sync_folders = data
|
||||
.filter(mailbox => currentRow.sync_folders.includes(mailbox.name))
|
||||
.map(mailbox => mailbox.id.toString());
|
||||
setSelectedItems(sync_folders);
|
||||
setError(undefined);
|
||||
}
|
||||
} catch (err: any) {
|
||||
@@ -77,11 +197,6 @@ export function SyncFoldersDialog({ currentRow, open, onOpenChange }: Props) {
|
||||
} else {
|
||||
setError(err.message);
|
||||
}
|
||||
} else {
|
||||
console.error('Other error:', err);
|
||||
}
|
||||
if (!cancelled) {
|
||||
setMailboxes([]);
|
||||
}
|
||||
} finally {
|
||||
if (!cancelled) setIsLoading(false);
|
||||
@@ -93,61 +208,90 @@ export function SyncFoldersDialog({ currentRow, open, onOpenChange }: Props) {
|
||||
};
|
||||
}, [currentRow, open]);
|
||||
|
||||
// Convert mailbox names to IDs for initial selection
|
||||
const initialSelectedItemIds = useMemo(() => {
|
||||
if (!mailboxes) return [];
|
||||
return mailboxes
|
||||
.filter(mailbox => selectedFolders.includes(mailbox.name))
|
||||
.map(mailbox => mailbox.id.toString());
|
||||
}, [mailboxes, selectedFolders]);
|
||||
|
||||
const treeData = useMemo(() => {
|
||||
if (!mailboxes) return [];
|
||||
return buildTree(mailboxes, undefined, true, true);
|
||||
}, [mailboxes]);
|
||||
|
||||
const handleSelectItems = useCallback((selectedItems: TreeDataItem[]) => {
|
||||
const allMailboxes = mailboxes || [];
|
||||
const selected = selectedItems
|
||||
.map(item => mailboxes?.find(m => m.id === parseInt(item.id, 10))?.name)
|
||||
.filter(Boolean) as string[];
|
||||
const handleExpandedItemsChange = (
|
||||
_event: React.SyntheticEvent | null,
|
||||
itemIds: string[],
|
||||
) => {
|
||||
setExpandedItems(itemIds);
|
||||
};
|
||||
|
||||
const allMailSelected = selected.some(selectedName => {
|
||||
const mailbox = allMailboxes.find(m => m.name === selectedName);
|
||||
if (!mailbox) return false;
|
||||
return mailbox.attributes.some(a => a.attr === 'All');
|
||||
const handleExpandClick = () => {
|
||||
setExpandedItems((oldExpanded) =>
|
||||
oldExpanded.length === 0 ? itemsWithChildren : [],
|
||||
);
|
||||
};
|
||||
|
||||
const CustomTreeItem = useMemo(() => {
|
||||
return React.forwardRef(function CustomTreeItem(
|
||||
props: CustomTreeItemProps,
|
||||
ref: React.Ref<HTMLLIElement>,
|
||||
) {
|
||||
const { id, itemId, label, disabled, children, ...other } = props;
|
||||
|
||||
const {
|
||||
getContextProviderProps,
|
||||
getRootProps,
|
||||
getContentProps,
|
||||
getIconContainerProps,
|
||||
getCheckboxProps,
|
||||
getLabelProps,
|
||||
getGroupTransitionProps,
|
||||
getDragAndDropOverlayProps,
|
||||
status,
|
||||
} = useTreeItem({ id, itemId, children, label, disabled, rootRef: ref });
|
||||
|
||||
const item = useTreeItemModel<ExtendedTreeItemProps>(itemId)!;
|
||||
|
||||
return (
|
||||
<TreeItemProvider {...getContextProviderProps()}>
|
||||
<TreeItemRoot {...getRootProps(other)}>
|
||||
<TreeItemContent {...getContentProps()}>
|
||||
<TreeItemIconContainer {...getIconContainerProps()}>
|
||||
<TreeItemIcon status={status} />
|
||||
</TreeItemIconContainer>
|
||||
<TreeItemCheckbox {...getCheckboxProps()} />
|
||||
<CustomLabel
|
||||
{...getLabelProps({
|
||||
exists: item.exists,
|
||||
attributes: item.attributes,
|
||||
expandable: status.expandable && status.expanded,
|
||||
})}
|
||||
/>
|
||||
<TreeItemDragAndDropOverlay {...getDragAndDropOverlayProps()} />
|
||||
</TreeItemContent>
|
||||
{children && <TransitionComponent {...getGroupTransitionProps()} />}
|
||||
</TreeItemRoot>
|
||||
</TreeItemProvider>
|
||||
);
|
||||
});
|
||||
}, [theme]);
|
||||
|
||||
if (allMailSelected) {
|
||||
|
||||
|
||||
const handleSelectAll = useCallback(() => {
|
||||
const selectedSet = new Set(allIds);
|
||||
|
||||
const selectedAllMailbox = mailboxes.find((mb) =>
|
||||
selectedSet.has(String(mb.id)) &&
|
||||
mb.attributes?.some(a => a.attr === 'All')
|
||||
);
|
||||
|
||||
if (selectedAllMailbox) {
|
||||
toast({
|
||||
title: t('accounts.allMailFolderSelected'),
|
||||
description: t('accounts.allMailFolderSelectedDesc'),
|
||||
action: <ToastAction altText={t('common.ok')}>{t('common.ok')}</ToastAction>,
|
||||
});
|
||||
}
|
||||
setSelectedFolders(selected);
|
||||
}, [mailboxes]);
|
||||
|
||||
|
||||
const handleSelectAll = useCallback(() => {
|
||||
if (!mailboxes) return;
|
||||
const validFolderNames = mailboxes
|
||||
.filter(mailbox => {
|
||||
const isAllMail = mailbox.attributes.some(a => a.attr === 'All');
|
||||
if (isAllMail) return false;
|
||||
return true;
|
||||
})
|
||||
.map(m => m.name);
|
||||
setSelectedFolders(validFolderNames);
|
||||
if (validFolderNames.length < mailboxes.length) {
|
||||
toast({
|
||||
description: t('accounts.allMailSkipped'),
|
||||
});
|
||||
}
|
||||
}, [mailboxes]);
|
||||
setSelectedItems(allIds);
|
||||
}, [allIds]);
|
||||
|
||||
const handleDeselectAll = useCallback(() => {
|
||||
setSelectedFolders([]);
|
||||
setSelectedItems([]);
|
||||
}, []);
|
||||
|
||||
|
||||
@@ -184,8 +328,30 @@ export function SyncFoldersDialog({ currentRow, open, onOpenChange }: Props) {
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
const handleSelectedItemsChange = (
|
||||
_event: React.SyntheticEvent | null,
|
||||
newSelectedItems: string[],
|
||||
) => {
|
||||
const selectedSet = new Set(newSelectedItems);
|
||||
|
||||
const selectedAllMailbox = mailboxes.find((mb) =>
|
||||
selectedSet.has(String(mb.id)) &&
|
||||
mb.attributes?.some(a => a.attr === 'All')
|
||||
);
|
||||
|
||||
if (selectedAllMailbox) {
|
||||
toast({
|
||||
title: t('accounts.allMailFolderSelected'),
|
||||
description: t('accounts.allMailFolderSelectedDesc'),
|
||||
action: <ToastAction altText={t('common.ok')}>{t('common.ok')}</ToastAction>,
|
||||
});
|
||||
}
|
||||
|
||||
setSelectedItems(newSelectedItems);
|
||||
};
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (selectedFolders.length === 0) {
|
||||
if (selectedItems.length === 0) {
|
||||
toast({
|
||||
title: t('common.error'),
|
||||
description: t('accounts.selectAtLeastOneFolder'),
|
||||
@@ -194,14 +360,23 @@ export function SyncFoldersDialog({ currentRow, open, onOpenChange }: Props) {
|
||||
return;
|
||||
}
|
||||
setIsSubmitting(true);
|
||||
|
||||
const selectedNames: string[] = [];
|
||||
const idSet = new Set(selectedItems);
|
||||
for (const mailbox of mailboxes) {
|
||||
if (idSet.has(String(mailbox.id))) {
|
||||
selectedNames.push(mailbox.name);
|
||||
}
|
||||
}
|
||||
|
||||
updateMutation.mutate({
|
||||
sync_folders: selectedFolders,
|
||||
sync_folders: selectedNames,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="sm:max-w-2xl">
|
||||
<DialogContent className="sm:max-w-3xl">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{t('accounts.selectSyncFolders')}</DialogTitle>
|
||||
<DialogDescription>
|
||||
@@ -210,13 +385,13 @@ export function SyncFoldersDialog({ currentRow, open, onOpenChange }: Props) {
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between pt-2">
|
||||
<div className="flex gap-2">
|
||||
<div className="flex flex-col pt-2 gap-2">
|
||||
<div className="flex gap-2 flex-wrap">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={handleSelectAll}
|
||||
disabled={isLoading || !mailboxes || mailboxes.length === 0}
|
||||
disabled={isLoading || !allIds || allIds.length === 0}
|
||||
className="h-8"
|
||||
>
|
||||
<CheckSquare className="w-4 h-4 mr-2" />
|
||||
@@ -226,23 +401,62 @@ export function SyncFoldersDialog({ currentRow, open, onOpenChange }: Props) {
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={handleDeselectAll}
|
||||
disabled={isLoading || selectedFolders.length === 0}
|
||||
disabled={isLoading || allIds.length === 0}
|
||||
className="h-8"
|
||||
>
|
||||
<Square className="w-4 h-4 mr-2" />
|
||||
{t('common.deselectAll')}
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() =>
|
||||
setSelectionPropagation(prev => ({
|
||||
...prev,
|
||||
descendants: !prev.descendants,
|
||||
}))
|
||||
}
|
||||
disabled={isLoading}
|
||||
className="h-8"
|
||||
>
|
||||
{selectionPropagation.descendants ? <CheckSquare className="w-4 h-4 mr-2" /> : <Square className="w-4 h-4 mr-2" />}
|
||||
{t('accounts.folderSync.autoSelectDescendants')}
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() =>
|
||||
setSelectionPropagation(prev => ({
|
||||
...prev,
|
||||
parents: !prev.parents,
|
||||
}))
|
||||
}
|
||||
disabled={isLoading}
|
||||
className="h-8"
|
||||
>
|
||||
{selectionPropagation.parents ? <CheckSquare className="w-4 h-4 mr-2" /> : <Square className="w-4 h-4 mr-2" />}
|
||||
{t('accounts.folderSync.autoSelectParents')}
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={handleExpandClick}
|
||||
disabled={isLoading}>
|
||||
{expandedItems.length === 0 ? t('accounts.folderSync.expandAll') : t('accounts.folderSync.collapseAll')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="text-sm text-muted-foreground">
|
||||
{t('accounts.foldersSelected', { count: selectedFolders.length })}
|
||||
{t('accounts.foldersSelected', { count: selectedItems.length })}
|
||||
</div>
|
||||
</div>
|
||||
<ScrollArea className="h-[30rem] w-full pr-4 -mr-4 py-1">
|
||||
|
||||
<ScrollArea className="h-[35rem] w-full pr-4 -mr-4 py-1">
|
||||
{isLoading && (
|
||||
<div className="p-8 space-y-8">
|
||||
<div className="flex flex-col items-center gap-3 text-muted-foreground">
|
||||
<Loader2 className="h-6 w-6 animate-spin" />
|
||||
<span className="text-sm font-medium">Loading mailbox folders…</span>
|
||||
<span className="text-sm font-medium">{t('accounts.folderSync.loadingMailboxFolders')}</span>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
@@ -253,14 +467,16 @@ export function SyncFoldersDialog({ currentRow, open, onOpenChange }: Props) {
|
||||
</div>
|
||||
)}
|
||||
{!isLoading && (
|
||||
<TreeView
|
||||
key={selectedFolders.length > 0 ? `tree-${selectedFolders.length}-${selectedFolders[0]}` : 'tree-empty'}
|
||||
data={treeData}
|
||||
multiple
|
||||
expandAll
|
||||
clickRowToSelect={false}
|
||||
initialSelectedItemIds={initialSelectedItemIds}
|
||||
onSelectItemsChange={handleSelectItems}
|
||||
<RichTreeView
|
||||
multiSelect
|
||||
checkboxSelection
|
||||
items={treeData}
|
||||
expandedItems={expandedItems}
|
||||
onExpandedItemsChange={handleExpandedItemsChange}
|
||||
selectionPropagation={selectionPropagation}
|
||||
selectedItems={selectedItems}
|
||||
onSelectedItemsChange={handleSelectedItemsChange}
|
||||
slots={{ item: CustomTreeItem }}
|
||||
/>
|
||||
)}
|
||||
{error && (
|
||||
|
||||
@@ -60,7 +60,6 @@ export function MailList({
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const hasSelected = (mailId: number) => {
|
||||
return selected.has(mailId);
|
||||
}
|
||||
@@ -77,15 +76,14 @@ export function MailList({
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="divide-y divide-border">
|
||||
{Array.from({ length: 8 }).map((_, i) => (
|
||||
<div key={i} className="flex items-center gap-3 px-3 py-2.5">
|
||||
<Skeleton className="h-4 w-4 rounded-full" />
|
||||
<Skeleton className="h-4 flex-1 max-w-xs" />
|
||||
<Skeleton className="h-3 w-16 ml-auto" />
|
||||
<div key={i} className="flex items-center gap-2 px-2 py-1.5">
|
||||
<Skeleton className="h-3 w-3 rounded-full" />
|
||||
<Skeleton className="h-3 flex-1 max-w-xs" />
|
||||
<Skeleton className="h-2.5 w-12 ml-auto" />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
@@ -95,7 +93,7 @@ export function MailList({
|
||||
return (
|
||||
<div className="divide-y divide-border">
|
||||
{items.length > 0 && (
|
||||
<div className="flex items-center gap-3 px-3 py-2 bg-muted/30">
|
||||
<div className="flex items-center gap-2 px-2 py-1 bg-muted/30">
|
||||
<Checkbox
|
||||
checked={
|
||||
selected.size === items.length && items.length > 0
|
||||
@@ -123,7 +121,7 @@ export function MailList({
|
||||
<div
|
||||
key={index}
|
||||
className={cn(
|
||||
"group flex items-center gap-3 px-3 py-2.5 cursor-pointer transition-colors",
|
||||
"group flex items-center gap-2 px-2 py-1.5 cursor-pointer transition-colors",
|
||||
"hover:bg-accent/50",
|
||||
isSelected && "bg-accent"
|
||||
)}
|
||||
@@ -140,12 +138,11 @@ export function MailList({
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
className="h-4 w-4 shrink-0"
|
||||
/>
|
||||
<MailIcon className="h-4 w-4 text-muted-foreground shrink-0" />
|
||||
<MailIcon className="h-3.5 w-3.5 text-muted-foreground shrink-0" />
|
||||
<div className="flex-1 min-w-0 grid grid-cols-1 sm:grid-cols-12 gap-1 sm:gap-0">
|
||||
|
||||
{/* LEFT AREA: From + Subject + Tags */}
|
||||
<div className="col-span-1 sm:col-span-8 flex flex-col min-w-0">
|
||||
<div className="flex items-center gap-2 min-w-0">
|
||||
<div className="col-span-1 sm:col-span-8 flex flex-col min-w-0 gap-1">
|
||||
<div className="flex items-center gap-1 min-w-0">
|
||||
<p className="text-sm font-medium truncate">{item.from}</p>
|
||||
<h3 className="text-sm text-muted-foreground truncate hidden sm:block">
|
||||
{item.subject}
|
||||
@@ -156,7 +153,7 @@ export function MailList({
|
||||
</h3>
|
||||
|
||||
{/* TAGS BELOW SUBJECT */}
|
||||
<div className="flex flex-wrap gap-1 mt-0.5">
|
||||
<div className="flex flex-wrap gap-1 mt-0.25">
|
||||
{item.tags?.map((tag, i) => (
|
||||
<Badge
|
||||
key={i}
|
||||
@@ -169,10 +166,10 @@ export function MailList({
|
||||
</div>
|
||||
|
||||
{/* RIGHT AREA – actions & meta */}
|
||||
<div className="col-span-1 sm:col-span-4 flex items-center justify-end gap-2 text-xs text-muted-foreground">
|
||||
<div className="col-span-1 sm:col-span-4 flex items-center justify-end gap-1 text-xs text-muted-foreground">
|
||||
{hasAttachments && (
|
||||
<div className="flex items-center gap-1">
|
||||
<Paperclip className="h-3.5 w-3.5" />
|
||||
<Paperclip className="h-3 w-3" />
|
||||
<span>{item.attachments?.length}</span>
|
||||
</div>
|
||||
)}
|
||||
@@ -190,7 +187,7 @@ export function MailList({
|
||||
}}
|
||||
className="p-1 rounded hover:bg-destructive/10 hover:text-destructive transition-all"
|
||||
>
|
||||
<Trash2 className="h-3.5 w-3.5" />
|
||||
<Trash2 className="h-3 w-3" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -200,4 +197,4 @@ export function MailList({
|
||||
{totalSelected > 0 && <MailBulkActions />}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,11 +27,9 @@ import {
|
||||
import { Separator } from "@/components/ui/separator"
|
||||
import { TooltipProvider } from "@/components/ui/tooltip"
|
||||
import { AccountSwitcher } from "./account-switcher"
|
||||
import { TreeView } from "@/components/tree-view"
|
||||
import { ScrollArea } from "@/components/ui/scroll-area"
|
||||
import { list_mailboxes, MailboxData } from "@/api/mailbox/api"
|
||||
import { useQuery } from "@tanstack/react-query"
|
||||
import { buildTree } from "../../../lib/build-tree"
|
||||
import { Skeleton } from "@/components/ui/skeleton"
|
||||
import MailboxProvider, { MailboxDialogType } from "../context"
|
||||
import useDialogState from "@/hooks/use-dialog-state"
|
||||
@@ -44,6 +42,14 @@ import { EnvelopeDeleteDialog } from "./delete-dialog"
|
||||
import Logo from '@/assets/logo.svg'
|
||||
import { EmailEnvelope } from "@/api"
|
||||
import { EnvelopeListPagination } from "@/components/pagination"
|
||||
import { RichTreeView, TreeItemCheckbox, TreeItemContent, TreeItemDragAndDropOverlay, TreeItemIcon, TreeItemIconContainer, TreeItemLabel, TreeItemProvider, TreeItemRoot, useTreeItem, useTreeItemModel, UseTreeItemParameters } from "@mui/x-tree-view"
|
||||
import { buildTree, ExtendedTreeItemProps } from "@/lib/build-tree"
|
||||
import { useTheme } from "@/context/theme-context"
|
||||
import { styled } from "@mui/material/styles"
|
||||
import { animated, useSpring } from "@react-spring/web"
|
||||
import { TransitionProps } from "@mui/material/transitions"
|
||||
import Collapse from "@mui/material/Collapse"
|
||||
import { FolderIcon } from "lucide-react"
|
||||
|
||||
|
||||
interface MailProps {
|
||||
@@ -73,6 +79,83 @@ const useListMessages = ({ accountId, mailboxId, page, page_size }: ListMessages
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
interface CustomLabelProps {
|
||||
exists?: number;
|
||||
attributes?: { attr: string; extension: string | null }[],
|
||||
children: React.ReactNode;
|
||||
icon?: React.ElementType;
|
||||
expandable?: boolean;
|
||||
}
|
||||
|
||||
function CustomLabel({
|
||||
expandable,
|
||||
exists,
|
||||
attributes,
|
||||
children,
|
||||
...other
|
||||
}: CustomLabelProps) {
|
||||
return (
|
||||
<TreeItemLabel
|
||||
{...other}
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<FolderIcon className="mr-2"/>
|
||||
<span className="font-medium text-sm text-inherit">
|
||||
{children}
|
||||
</span>
|
||||
{/* <div className="flex gap-2 ml-auto mr-3 opacity-70 text-xs">
|
||||
{attributes?.map((attr) => {
|
||||
const text =
|
||||
attr.attr === 'Extension'
|
||||
? attr.extension
|
||||
: attr.attr;
|
||||
|
||||
return (
|
||||
<span key={attr.attr} className="text-inherit">
|
||||
{text}
|
||||
</span>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
{exists !== undefined && (
|
||||
<span
|
||||
className="text-sm opacity-60 min-w-[40px] text-right text-inherit"
|
||||
>
|
||||
{exists}
|
||||
</span>
|
||||
)} */}
|
||||
</TreeItemLabel>
|
||||
);
|
||||
}
|
||||
|
||||
const CustomCollapse = styled(Collapse)({
|
||||
padding: 0,
|
||||
});
|
||||
|
||||
const AnimatedCollapse = animated(CustomCollapse);
|
||||
|
||||
function TransitionComponent(props: TransitionProps) {
|
||||
const style = useSpring({
|
||||
to: {
|
||||
opacity: props.in ? 1 : 0,
|
||||
transform: `translate3d(0,${props.in ? 0 : 20}px,0)`,
|
||||
},
|
||||
});
|
||||
|
||||
return <AnimatedCollapse style={style} {...props} />;
|
||||
}
|
||||
|
||||
interface CustomTreeItemProps
|
||||
extends Omit<UseTreeItemParameters, 'rootRef'>,
|
||||
Omit<React.HTMLAttributes<HTMLLIElement>, 'onFocus'> { }
|
||||
|
||||
|
||||
|
||||
export function Mail({
|
||||
defaultLayout = [20, 80],
|
||||
defaultCollapsed = false,
|
||||
@@ -88,6 +171,7 @@ export function Mail({
|
||||
const [pageSize, setPageSize] = React.useState(30);
|
||||
const [deleteIds, setDeleteIds] = React.useState<Set<number>>(() => new Set());
|
||||
const [selected, setSelected] = React.useState<Set<number>>(() => new Set());
|
||||
const { theme } = useTheme()
|
||||
|
||||
const { data: mailboxes, isLoading: isMailboxesLoading } = useQuery({
|
||||
queryKey: ['account-mailboxes', `${selectedAccountId}`],
|
||||
@@ -95,6 +179,9 @@ export function Mail({
|
||||
enabled: !!selectedAccountId,
|
||||
})
|
||||
|
||||
|
||||
const tree = buildTree(mailboxes ?? []);
|
||||
|
||||
const { data: envelopes, isLoading: isMessagesLoading, isError, error } = useListMessages({
|
||||
accountId: selectedAccountId,
|
||||
mailboxId: selectedMailbox?.id,
|
||||
@@ -126,6 +213,65 @@ export function Mail({
|
||||
}
|
||||
}, [isError, error]);
|
||||
|
||||
const handleItemSelectionToggle = (
|
||||
_event: React.SyntheticEvent | null,
|
||||
itemId: string,
|
||||
isSelected: boolean,
|
||||
) => {
|
||||
if (isSelected) {
|
||||
setSelectedMailbox(mailboxes?.find(m => String(m.id) === itemId))
|
||||
setPage(0);
|
||||
}
|
||||
};
|
||||
|
||||
const CustomTreeItem = React.useMemo(() => {
|
||||
return React.forwardRef(function CustomTreeItem(
|
||||
props: CustomTreeItemProps,
|
||||
ref: React.Ref<HTMLLIElement>,
|
||||
) {
|
||||
const { id, itemId, label, disabled, children, ...other } = props;
|
||||
|
||||
const {
|
||||
getContextProviderProps,
|
||||
getRootProps,
|
||||
getContentProps,
|
||||
getIconContainerProps,
|
||||
getCheckboxProps,
|
||||
getLabelProps,
|
||||
getGroupTransitionProps,
|
||||
getDragAndDropOverlayProps,
|
||||
status,
|
||||
} = useTreeItem({ id, itemId, children, label, disabled, rootRef: ref });
|
||||
|
||||
const item = useTreeItemModel<ExtendedTreeItemProps>(itemId)!;
|
||||
|
||||
return (
|
||||
<TreeItemProvider {...getContextProviderProps()}>
|
||||
<TreeItemRoot {...getRootProps(other)}>
|
||||
<TreeItemContent {...getContentProps()}>
|
||||
<TreeItemIconContainer {...getIconContainerProps()}>
|
||||
<TreeItemIcon status={status} />
|
||||
</TreeItemIconContainer>
|
||||
<TreeItemCheckbox {...getCheckboxProps()} />
|
||||
<CustomLabel
|
||||
{...getLabelProps({
|
||||
exists: item.exists,
|
||||
attributes: item.attributes,
|
||||
expandable: status.expandable && status.expanded,
|
||||
})}
|
||||
/>
|
||||
<TreeItemDragAndDropOverlay {...getDragAndDropOverlayProps()} />
|
||||
</TreeItemContent>
|
||||
{children && <TransitionComponent {...getGroupTransitionProps()} />}
|
||||
</TreeItemRoot>
|
||||
</TreeItemProvider>
|
||||
);
|
||||
});
|
||||
}, [theme]);
|
||||
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<MailboxProvider value={{ open, setOpen, currentMailbox: selectedMailbox, selectedAccountId, setCurrentMailbox: setSelectedMailbox, currentEnvelope: selectedEvelope, setCurrentEnvelope: setSelectedEvelope, deleteIds, setDeleteIds, selected, setSelected }}>
|
||||
<TooltipProvider delayDuration={0}>
|
||||
@@ -184,17 +330,23 @@ export function Mail({
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<TreeView
|
||||
data={buildTree(mailboxes ?? [])}
|
||||
clickRowToSelect={true}
|
||||
onSelectChange={(item) => {
|
||||
if (item) {
|
||||
setSelectedMailbox(mailboxes?.find(m => m.id === parseInt(item.id, 10)))
|
||||
setPage(0);
|
||||
} else {
|
||||
setSelectedMailbox(undefined)
|
||||
}
|
||||
}}
|
||||
// <TreeView
|
||||
// data={buildTree(mailboxes ?? [])}
|
||||
// clickRowToSelect={true}
|
||||
// onSelectChange={(item) => {
|
||||
// if (item) {
|
||||
// setSelectedMailbox(mailboxes?.find(m => m.id === parseInt(item.id, 10)))
|
||||
// setPage(0);
|
||||
// } else {
|
||||
// setSelectedMailbox(undefined)
|
||||
// }
|
||||
// }}
|
||||
// />
|
||||
<RichTreeView
|
||||
//checkboxSelection
|
||||
items={tree}
|
||||
onItemSelectionToggle={handleItemSelectionToggle}
|
||||
slots={{ item: CustomTreeItem }}
|
||||
/>
|
||||
)}
|
||||
</ScrollArea>
|
||||
|
||||
@@ -21,7 +21,7 @@ import { cn, formatBytes } from "@/lib/utils"
|
||||
import { formatDistanceToNow } from "date-fns"
|
||||
import { MailIcon, MoreVertical, Paperclip, TagIcon, Trash2 } from "lucide-react"
|
||||
import { Skeleton } from "@/components/ui/skeleton"
|
||||
import { Checkbox } from "@/components/ui/checkbox" // shadcn Checkbox
|
||||
import { Checkbox } from "@/components/ui/checkbox"
|
||||
import { EmailEnvelope } from "@/api"
|
||||
import { useSearchContext } from "./context"
|
||||
import { MailBulkActions } from "./bulk-actions"
|
||||
@@ -62,6 +62,7 @@ export function MailList({
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const toggleToDelete = (accountId: number, mailId: number) => {
|
||||
setToDelete(prev => {
|
||||
const next = new Map(prev);
|
||||
@@ -115,11 +116,11 @@ export function MailList({
|
||||
return (
|
||||
<div className="divide-y divide-border">
|
||||
{Array.from({ length: 8 }).map((_, i) => (
|
||||
<div key={i} className="flex items-center gap-3 p-3">
|
||||
<Skeleton className="h-4 w-4" />
|
||||
<Skeleton className="h-4 w-4 rounded-full" />
|
||||
<Skeleton className="h-4 flex-1" />
|
||||
<Skeleton className="h-3 w-20" />
|
||||
<div key={i} className="flex items-center gap-2 px-2 py-1.5">
|
||||
<Skeleton className="h-3 w-3" />
|
||||
<Skeleton className="h-3 w-3 rounded-full" />
|
||||
<Skeleton className="h-3 flex-1" />
|
||||
<Skeleton className="h-2.5 w-16" />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
@@ -129,7 +130,7 @@ export function MailList({
|
||||
return (
|
||||
<div className="divide-y divide-border">
|
||||
{items.length > 0 && (
|
||||
<div className="flex items-center gap-3 px-3 py-2 bg-muted/30">
|
||||
<div className="flex items-center gap-2 px-2 py-1 bg-muted/30">
|
||||
<Checkbox
|
||||
checked={
|
||||
totalSelected === items.length && items.length > 0
|
||||
@@ -158,7 +159,7 @@ export function MailList({
|
||||
<div
|
||||
key={index}
|
||||
className={cn(
|
||||
"flex items-center gap-3 px-3 py-2.5 cursor-pointer transition-colors",
|
||||
"flex items-center gap-2 px-2 py-1.5 cursor-pointer transition-colors",
|
||||
"hover:bg-accent/50",
|
||||
isSelectedRow && "bg-accent"
|
||||
)}
|
||||
@@ -175,41 +176,35 @@ export function MailList({
|
||||
className="h-4 w-4 shrink-0"
|
||||
/>
|
||||
|
||||
<MailIcon className="h-4 w-4 text-muted-foreground shrink-0" />
|
||||
<MailIcon className="h-3.5 w-3.5 text-muted-foreground shrink-0" />
|
||||
<div className="flex-1 min-w-0 grid grid-cols-1 sm:grid-cols-12 gap-1 sm:gap-0">
|
||||
|
||||
{/* LEFT AREA: From + Subject + Tags */}
|
||||
<div className="col-span-1 sm:col-span-8 flex flex-col min-w-0">
|
||||
|
||||
{/* from + subject (large screen side by side, small screen subject hidden) */}
|
||||
<div className="flex items-center gap-2 min-w-0">
|
||||
<div className="col-span-1 sm:col-span-8 flex flex-col min-w-0 gap-0.5">
|
||||
<div className="flex items-center gap-1 min-w-0">
|
||||
<p className="text-sm font-medium truncate">{item.from}</p>
|
||||
|
||||
{/* subject on large screens */}
|
||||
<h3 className="text-sm text-muted-foreground truncate hidden sm:block">
|
||||
{item.subject}
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
{/* subject on small screens */}
|
||||
<h3 className="text-sm text-muted-foreground truncate sm:hidden">
|
||||
{item.subject}
|
||||
</h3>
|
||||
|
||||
{/* TAGS (always below on small screen, inline on large screen) */}
|
||||
<div className="flex flex-wrap gap-1 mt-0.5">
|
||||
{item.tags?.map((tag, index) => (
|
||||
<Badge className="px-1 py-0.5 text-[10px] h-auto leading-none" key={index}>{tag}</Badge>
|
||||
<div className="flex flex-wrap gap-1 mt-0.25">
|
||||
{item.tags?.map((tag, i) => (
|
||||
<Badge className="px-1 py-0.5 text-[10px] h-auto leading-none" key={i}>{tag}</Badge>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* RIGHT AREA – actions & meta */}
|
||||
<div className="col-span-1 sm:col-span-4 flex items-center justify-end gap-2 text-xs text-muted-foreground">
|
||||
<div className="col-span-1 sm:col-span-4 flex items-center justify-end gap-1 text-xs text-muted-foreground">
|
||||
|
||||
{hasAttachments && (
|
||||
<div className="flex items-center gap-1">
|
||||
<Paperclip className="h-3.5 w-3.5" />
|
||||
<Paperclip className="h-3 w-3" />
|
||||
<span>{item.attachments?.length}</span>
|
||||
</div>
|
||||
)}
|
||||
@@ -225,14 +220,14 @@ export function MailList({
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-7 w-7 p-0 hover:bg-muted rounded-md"
|
||||
className="h-6 w-6 p-0 hover:bg-muted rounded-md"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<MoreVertical className="h-3.5 w-3.5" />
|
||||
<MoreVertical className="h-3 w-3" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
|
||||
<DropdownMenuContent align="end" className="w-48">
|
||||
<DropdownMenuContent align="end" className="w-44">
|
||||
<DropdownMenuItem
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
onSelect={(e) => {
|
||||
@@ -241,7 +236,7 @@ export function MailList({
|
||||
setOpen("edit-tags");
|
||||
}}
|
||||
>
|
||||
<TagIcon className="ml-2 h-4 w-4" />
|
||||
<TagIcon className="ml-2 h-3.5 w-3.5" />
|
||||
{t('search.editTag')}
|
||||
</DropdownMenuItem>
|
||||
|
||||
@@ -253,7 +248,7 @@ export function MailList({
|
||||
handleDelete(item);
|
||||
}}
|
||||
>
|
||||
<Trash2 className="ml-2 h-4 w-4" />
|
||||
<Trash2 className="ml-2 h-3.5 w-3.5" />
|
||||
{t('common.delete')}
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
@@ -263,9 +258,7 @@ export function MailList({
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
{totalSelected > 0 && (
|
||||
<MailBulkActions />
|
||||
)}
|
||||
{totalSelected > 0 && <MailBulkActions />}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+78
-88
@@ -18,100 +18,90 @@
|
||||
|
||||
|
||||
import { MailboxData } from "@/api/mailbox/api";
|
||||
import { TreeDataItem } from "@/components/tree-view";
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { FolderClosed, FolderOpen } from "lucide-react";
|
||||
import React from 'react';
|
||||
import { TreeViewBaseItem } from '@mui/x-tree-view/models';
|
||||
|
||||
type BadgeContentFunction = (item: MailboxData) => React.ReactNode;
|
||||
|
||||
export const buildTree = (data: MailboxData[], badgeContent?: BadgeContentFunction, showAttributes?: boolean, showExists?: boolean): TreeDataItem[] => {
|
||||
const root: TreeDataItem = {
|
||||
id: 'root',
|
||||
name: 'Root',
|
||||
icon: FolderClosed,
|
||||
openIcon: FolderOpen,
|
||||
children: [], // Ensure children is initialized as an array
|
||||
};
|
||||
export type ExtendedTreeItemProps = {
|
||||
exists?: number;
|
||||
attributes?: { attr: string; extension: string | null }[],
|
||||
id: string;
|
||||
label: string;
|
||||
};
|
||||
|
||||
const nodeMap: { [key: string]: TreeDataItem } = {};
|
||||
data.sort((a, b) => a.name.localeCompare(b.name));
|
||||
data.forEach((item) => {
|
||||
const { id, name, delimiter, exists, attributes } = item;
|
||||
const badge = showExists ? (badgeContent ? badgeContent(item) : React.createElement(Badge, {
|
||||
className: 'text-[12px]',
|
||||
variant: 'secondary',
|
||||
}, exists)) : null;
|
||||
|
||||
const attributesNode = showAttributes
|
||||
? attributes.map((item, index) =>
|
||||
React.createElement(
|
||||
Badge,
|
||||
{
|
||||
key: index,
|
||||
className: 'text-[12px] mr-1 last:mr-0',
|
||||
variant: 'secondary',
|
||||
},
|
||||
item.attr === 'Extension' ? item.extension || '' : item.attr
|
||||
)
|
||||
)
|
||||
: null;
|
||||
// const badge = badgeContent || React.createElement(Badge, {
|
||||
// className: 'text-xs',
|
||||
// }, exists);
|
||||
// If there is no delimiter, add the item directly as a child of the root
|
||||
if (!delimiter) {
|
||||
root.children!.push({
|
||||
id: id.toString(),
|
||||
name,
|
||||
icon: FolderClosed,
|
||||
openIcon: FolderOpen,
|
||||
badge,
|
||||
attributes: showAttributes ? attributesNode : null,
|
||||
children: undefined, // Leaf node, so children is undefined
|
||||
});
|
||||
return;
|
||||
export function buildTree(items: MailboxData[]): TreeViewBaseItem<ExtendedTreeItemProps>[] {
|
||||
const nodeByName = new Map<string, TreeViewBaseItem<ExtendedTreeItemProps>>();
|
||||
|
||||
for (const mb of items) {
|
||||
if (!mb.name) continue;
|
||||
|
||||
const delimiter = mb.delimiter ?? '/';
|
||||
const parts = mb.name.split(delimiter);
|
||||
|
||||
let currentFullName = '';
|
||||
|
||||
for (let i = 0; i < parts.length; i++) {
|
||||
const part = parts[i];
|
||||
currentFullName = currentFullName ? `${currentFullName}${delimiter}${part}` : part;
|
||||
|
||||
if (!nodeByName.has(currentFullName)) {
|
||||
nodeByName.set(currentFullName, {
|
||||
id: currentFullName,
|
||||
label: part,
|
||||
exists: mb.exists,
|
||||
attributes: mb.attributes,
|
||||
children: [],
|
||||
});
|
||||
}
|
||||
|
||||
if (i === parts.length - 1) {
|
||||
const node = nodeByName.get(currentFullName)!;
|
||||
node.id = String(mb.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const roots: TreeViewBaseItem<ExtendedTreeItemProps>[] = [];
|
||||
for (const [fullName, node] of nodeByName.entries()) {
|
||||
const delim = mbDelimiterOrDefault(fullName, items);
|
||||
const lastDelimIndex = fullName.lastIndexOf(delim);
|
||||
|
||||
if (lastDelimIndex === -1) {
|
||||
roots.push(node);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Split the name into parts based on the delimiter
|
||||
const parts = name.split(delimiter); // dir/sub1/sub2
|
||||
let currentParent = root;
|
||||
const parentFullName = fullName.substring(0, lastDelimIndex);
|
||||
const parentNode = nodeByName.get(parentFullName);
|
||||
|
||||
// Traverse or create nodes for each part of the path
|
||||
parts.forEach((part, index) => {
|
||||
const path = parts.slice(0, index + 1).join(delimiter);
|
||||
|
||||
// If the node already exists, set it as the current parent
|
||||
if (nodeMap[path]) {
|
||||
currentParent = nodeMap[path];
|
||||
} else {
|
||||
// Determine if this is a leaf node (last part of the path)
|
||||
const isLeaf = index === parts.length - 1;
|
||||
|
||||
// Create a new node
|
||||
const newNode: TreeDataItem = {
|
||||
id: isLeaf ? id.toString() : path, // Use item.id for leaf nodes, path for non-leaf nodes
|
||||
name: part,
|
||||
icon: FolderClosed,
|
||||
openIcon: FolderOpen,
|
||||
badge,
|
||||
attributes: showAttributes ? attributesNode : null,
|
||||
children: isLeaf ? undefined : [], // Ensure children is initialized as an array for non-leaf nodes
|
||||
};
|
||||
|
||||
// Ensure currentParent.children is initialized as an array
|
||||
if (!currentParent.children) {
|
||||
currentParent.children = [];
|
||||
}
|
||||
|
||||
// Add the new node to the current parent's children
|
||||
currentParent.children.push(newNode);
|
||||
currentParent = newNode; // Update the current parent to the new node
|
||||
nodeMap[path] = newNode; // Store the node in the map for quick lookup
|
||||
if (parentNode) {
|
||||
parentNode.children = parentNode.children ?? [];
|
||||
if (!parentNode.children.includes(node)) {
|
||||
parentNode.children.push(node);
|
||||
}
|
||||
});
|
||||
});
|
||||
} else {
|
||||
roots.push(node);
|
||||
}
|
||||
}
|
||||
|
||||
// Return the children of the root as the final tree structure
|
||||
return root.children!;
|
||||
};
|
||||
const sortNodes = (nodes: TreeViewBaseItem[]) => {
|
||||
nodes.sort((a, b) => a.label.localeCompare(b.label, undefined, { numeric: true }));
|
||||
for (const n of nodes) {
|
||||
if (n.children && n.children.length) sortNodes(n.children);
|
||||
}
|
||||
};
|
||||
|
||||
const uniqueRoots = Array.from(new Set(roots));
|
||||
sortNodes(uniqueRoots);
|
||||
return uniqueRoots;
|
||||
}
|
||||
|
||||
function mbDelimiterOrDefault(fullName: string, items: MailboxData[]): string {
|
||||
const mb = items.find(it => it.name === fullName || fullName.startsWith(it.name + (it.delimiter ?? '/')));
|
||||
if (mb?.delimiter) return mb.delimiter;
|
||||
|
||||
const withDelim = items.find(it => it.delimiter);
|
||||
if (withDelim?.delimiter) return withDelim.delimiter;
|
||||
|
||||
return '.';
|
||||
}
|
||||
@@ -255,7 +255,12 @@
|
||||
"folderSync": {
|
||||
"noData": "لا توجد بيانات",
|
||||
"noFolders": "لا توجد مجلدات للمزامنة",
|
||||
"batches": "دفعات"
|
||||
"batches": "دفعات",
|
||||
"autoSelectDescendants": "تحديد العناصر الفرعية تلقائيًا",
|
||||
"autoSelectParents": "تحديد العناصر الأصلية تلقائيًا",
|
||||
"expandAll": "توسيع الكل",
|
||||
"collapseAll": "طي الكل",
|
||||
"loadingMailboxFolders": "جارٍ تحميل مجلدات البريد…"
|
||||
},
|
||||
"viewDetails": "عرض التفاصيل",
|
||||
"runningState": {
|
||||
|
||||
@@ -255,7 +255,12 @@
|
||||
"folderSync": {
|
||||
"noData": "Ingen data",
|
||||
"noFolders": "Ingen mapper at synkronisere",
|
||||
"batches": "batcher"
|
||||
"batches": "batcher",
|
||||
"autoSelectDescendants": "Vælg efterkommere automatisk",
|
||||
"autoSelectParents": "Vælg forældre automatisk",
|
||||
"expandAll": "Udvid alle",
|
||||
"collapseAll": "Skjul alle",
|
||||
"loadingMailboxFolders": "Indlæser postkassemapper…"
|
||||
},
|
||||
"viewDetails": "vis detaljer",
|
||||
"runningState": {
|
||||
|
||||
@@ -255,7 +255,12 @@
|
||||
"folderSync": {
|
||||
"noData": "Keine Daten",
|
||||
"noFolders": "Keine Ordner zum Synchronisieren",
|
||||
"batches": "Batches"
|
||||
"batches": "Batches",
|
||||
"autoSelectDescendants": "Unterelemente automatisch auswählen",
|
||||
"autoSelectParents": "Elternelemente automatisch auswählen",
|
||||
"expandAll": "Alle erweitern",
|
||||
"collapseAll": "Alle reduzieren",
|
||||
"loadingMailboxFolders": "Postfachordner werden geladen…"
|
||||
},
|
||||
"viewDetails": "Details anzeigen",
|
||||
"runningState": {
|
||||
|
||||
@@ -255,7 +255,12 @@
|
||||
"folderSync": {
|
||||
"noData": "No Data",
|
||||
"noFolders": "No folders to sync",
|
||||
"batches": "batches"
|
||||
"batches": "batches",
|
||||
"autoSelectDescendants": "Auto select descendants",
|
||||
"autoSelectParents": "Auto select parents",
|
||||
"expandAll": "Expand all",
|
||||
"collapseAll": "Collapse all",
|
||||
"loadingMailboxFolders": "Loading mailbox folders…"
|
||||
},
|
||||
"viewDetails": "view details",
|
||||
"runningState": {
|
||||
|
||||
@@ -255,7 +255,12 @@
|
||||
"folderSync": {
|
||||
"noData": "Sin datos",
|
||||
"noFolders": "No hay carpetas para sincronizar",
|
||||
"batches": "lotes"
|
||||
"batches": "lotes",
|
||||
"autoSelectDescendants": "Seleccionar automáticamente los descendientes",
|
||||
"autoSelectParents": "Seleccionar automáticamente los padres",
|
||||
"expandAll": "Expandir todo",
|
||||
"collapseAll": "Contraer todo",
|
||||
"loadingMailboxFolders": "Cargando carpetas del buzón…"
|
||||
},
|
||||
"viewDetails": "ver detalles",
|
||||
"runningState": {
|
||||
|
||||
@@ -255,7 +255,12 @@
|
||||
"folderSync": {
|
||||
"noData": "Ei tietoja",
|
||||
"noFolders": "Ei synkronoitavia kansioita",
|
||||
"batches": "erät"
|
||||
"batches": "erät",
|
||||
"autoSelectDescendants": "Valitse alisolmut automaattisesti",
|
||||
"autoSelectParents": "Valitse yläsolmut automaattisesti",
|
||||
"expandAll": "Laajenna kaikki",
|
||||
"collapseAll": "Kutista kaikki",
|
||||
"loadingMailboxFolders": "Ladataan postilaatikon kansioita…"
|
||||
},
|
||||
"viewDetails": "katso tiedot",
|
||||
"runningState": {
|
||||
|
||||
@@ -255,7 +255,12 @@
|
||||
"folderSync": {
|
||||
"noData": "Aucune Donnée",
|
||||
"noFolders": "Aucun dossier à synchroniser",
|
||||
"batches": "lots"
|
||||
"batches": "lots",
|
||||
"autoSelectDescendants": "Sélectionner automatiquement les descendants",
|
||||
"autoSelectParents": "Sélectionner automatiquement les parents",
|
||||
"expandAll": "Tout développer",
|
||||
"collapseAll": "Tout réduire",
|
||||
"loadingMailboxFolders": "Chargement des dossiers de la boîte…"
|
||||
},
|
||||
"viewDetails": "voir les détails",
|
||||
"runningState": {
|
||||
|
||||
@@ -255,7 +255,12 @@
|
||||
"folderSync": {
|
||||
"noData": "Nessun Dato",
|
||||
"noFolders": "Nessuna cartella da sincronizzare",
|
||||
"batches": "lotti"
|
||||
"batches": "lotti",
|
||||
"autoSelectDescendants": "Seleziona automaticamente i discendenti",
|
||||
"autoSelectParents": "Seleziona automaticamente i genitori",
|
||||
"expandAll": "Espandi tutto",
|
||||
"collapseAll": "Comprimi tutto",
|
||||
"loadingMailboxFolders": "Caricamento delle cartelle della casella…"
|
||||
},
|
||||
"viewDetails": "visualizza dettagli",
|
||||
"runningState": {
|
||||
|
||||
@@ -255,7 +255,12 @@
|
||||
"folderSync": {
|
||||
"noData": "データなし",
|
||||
"noFolders": "同期するフォルダーがありません",
|
||||
"batches": "バッチ"
|
||||
"batches": "バッチ",
|
||||
"autoSelectDescendants": "子項目を自動選択",
|
||||
"autoSelectParents": "親項目を自動選択",
|
||||
"expandAll": "すべて展開",
|
||||
"collapseAll": "すべて折りたたむ",
|
||||
"loadingMailboxFolders": "メールボックスフォルダーを読み込み中…"
|
||||
},
|
||||
"viewDetails": "詳細を見る",
|
||||
"runningState": {
|
||||
|
||||
@@ -255,7 +255,12 @@
|
||||
"folderSync": {
|
||||
"noData": "데이터 없음",
|
||||
"noFolders": "동기화할 폴더가 없습니다",
|
||||
"batches": "배치"
|
||||
"batches": "배치",
|
||||
"autoSelectDescendants": "하위 항목 자동 선택",
|
||||
"autoSelectParents": "상위 항목 자동 선택",
|
||||
"expandAll": "모두 펼치기",
|
||||
"collapseAll": "모두 접기",
|
||||
"loadingMailboxFolders": "메일 폴더 로딩 중…"
|
||||
},
|
||||
"viewDetails": "세부 정보 보기",
|
||||
"runningState": {
|
||||
|
||||
@@ -255,7 +255,12 @@
|
||||
"folderSync": {
|
||||
"noData": "Geen Gegevens",
|
||||
"noFolders": "Geen mappen om te synchroniseren",
|
||||
"batches": "batches"
|
||||
"batches": "batches",
|
||||
"autoSelectDescendants": "Automatisch onderliggende items selecteren",
|
||||
"autoSelectParents": "Automatisch bovenliggende items selecteren",
|
||||
"expandAll": "Alles uitklappen",
|
||||
"collapseAll": "Alles inklappen",
|
||||
"loadingMailboxFolders": "Postvakken laden…"
|
||||
},
|
||||
"viewDetails": "details bekijken",
|
||||
"runningState": {
|
||||
|
||||
@@ -255,7 +255,12 @@
|
||||
"folderSync": {
|
||||
"noData": "Ingen data",
|
||||
"noFolders": "Ingen mapper å synkronisere",
|
||||
"batches": "partier"
|
||||
"batches": "partier",
|
||||
"autoSelectDescendants": "Velg etterkommere automatisk",
|
||||
"autoSelectParents": "Velg foreldre automatisk",
|
||||
"expandAll": "Utvid alle",
|
||||
"collapseAll": "Skjul alle",
|
||||
"loadingMailboxFolders": "Laster inn postkassemapper…"
|
||||
},
|
||||
"viewDetails": "se detaljer",
|
||||
"runningState": {
|
||||
|
||||
@@ -255,7 +255,12 @@
|
||||
"folderSync": {
|
||||
"noData": "Sem Dados",
|
||||
"noFolders": "Nenhuma pasta para sincronizar",
|
||||
"batches": "Lotes"
|
||||
"batches": "Lotes",
|
||||
"autoSelectDescendants": "Selecionar automaticamente os descendentes",
|
||||
"autoSelectParents": "Selecionar automaticamente os pais",
|
||||
"expandAll": "Expandir tudo",
|
||||
"collapseAll": "Recolher tudo",
|
||||
"loadingMailboxFolders": "Carregando pastas da caixa de correio…"
|
||||
},
|
||||
"viewDetails": "Ver Detalhes",
|
||||
"runningState": {
|
||||
|
||||
@@ -255,7 +255,12 @@
|
||||
"folderSync": {
|
||||
"noData": "Нет данных",
|
||||
"noFolders": "Нет папок для синхронизации",
|
||||
"batches": "пакетов"
|
||||
"batches": "пакетов",
|
||||
"autoSelectDescendants": "Автоматически выбирать дочерние элементы",
|
||||
"autoSelectParents": "Автоматически выбирать родительские элементы",
|
||||
"expandAll": "Развернуть все",
|
||||
"collapseAll": "Свернуть все",
|
||||
"loadingMailboxFolders": "Загрузка папок почтового ящика…"
|
||||
},
|
||||
"viewDetails": "смотреть детали",
|
||||
"runningState": {
|
||||
|
||||
@@ -255,7 +255,12 @@
|
||||
"folderSync": {
|
||||
"noData": "Inga data",
|
||||
"noFolders": "Inga mappar att synkronisera",
|
||||
"batches": "satser"
|
||||
"batches": "satser",
|
||||
"autoSelectDescendants": "Välj underordnade automatiskt",
|
||||
"autoSelectParents": "Välj överordnade automatiskt",
|
||||
"expandAll": "Expandera alla",
|
||||
"collapseAll": "Komprimera alla",
|
||||
"loadingMailboxFolders": "Laddar brevlådemappar…"
|
||||
},
|
||||
"viewDetails": "visa detaljer",
|
||||
"runningState": {
|
||||
|
||||
@@ -255,7 +255,12 @@
|
||||
"folderSync": {
|
||||
"noData": "無資料",
|
||||
"noFolders": "沒有可同步的資料夾",
|
||||
"batches": "批次"
|
||||
"batches": "批次",
|
||||
"autoSelectDescendants": "自動選取子項",
|
||||
"autoSelectParents": "自動選取父項",
|
||||
"expandAll": "全部展開",
|
||||
"collapseAll": "全部收合",
|
||||
"loadingMailboxFolders": "正在載入郵件資料夾…"
|
||||
},
|
||||
"viewDetails": "檢視詳細資訊",
|
||||
"runningState": {
|
||||
|
||||
@@ -255,7 +255,12 @@
|
||||
"folderSync": {
|
||||
"noData": "无数据",
|
||||
"noFolders": "没有需要同步的文件夹",
|
||||
"batches": "批次"
|
||||
"batches": "批次",
|
||||
"autoSelectDescendants": "自动选择子项",
|
||||
"autoSelectParents": "自动选择父项",
|
||||
"expandAll": "全部展开",
|
||||
"collapseAll": "全部收起",
|
||||
"loadingMailboxFolders": "正在加载邮箱文件夹…"
|
||||
},
|
||||
"viewDetails": "查看详情",
|
||||
"runningState": {
|
||||
|
||||
Reference in New Issue
Block a user