feat: add basic file drag & drop support (#6)
This commit is contained in:
parent
3f225b46dc
commit
6d3b14664a
@ -72,7 +72,7 @@ pnpm link ../libparakeet-js/npm
|
|||||||
|
|
||||||
## TODO
|
## TODO
|
||||||
|
|
||||||
- [ ] #6 文件拖放 (利用 `react-dropzone`?)
|
- [x] #6 文件拖放 (利用 `react-dropzone`?)
|
||||||
- [ ] 各类算法 [追踪 `crypto` 标签](https://git.unlock-music.dev/um/um-react/issues?labels=67)
|
- [ ] 各类算法 [追踪 `crypto` 标签](https://git.unlock-music.dev/um/um-react/issues?labels=67)
|
||||||
- [ ] #7 简易元数据编辑器
|
- [ ] #7 简易元数据编辑器
|
||||||
- [x] ~#8 添加单元测试~ 框架加上了,以后慢慢添加更多测试即可。
|
- [x] ~#8 添加单元测试~ 框架加上了,以后慢慢添加更多测试即可。
|
||||||
|
@ -26,6 +26,7 @@
|
|||||||
"nanoid": "^4.0.2",
|
"nanoid": "^4.0.2",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
|
"react-dropzone": "^14.2.3",
|
||||||
"react-promise-suspense": "^0.3.4",
|
"react-promise-suspense": "^0.3.4",
|
||||||
"react-redux": "^8.0.5"
|
"react-redux": "^8.0.5"
|
||||||
},
|
},
|
||||||
|
@ -31,6 +31,9 @@ dependencies:
|
|||||||
react-dom:
|
react-dom:
|
||||||
specifier: ^18.2.0
|
specifier: ^18.2.0
|
||||||
version: 18.2.0(react@18.2.0)
|
version: 18.2.0(react@18.2.0)
|
||||||
|
react-dropzone:
|
||||||
|
specifier: ^14.2.3
|
||||||
|
version: 14.2.3(react@18.2.0)
|
||||||
react-promise-suspense:
|
react-promise-suspense:
|
||||||
specifier: ^0.3.4
|
specifier: ^0.3.4
|
||||||
version: 0.3.4
|
version: 0.3.4
|
||||||
@ -2879,6 +2882,12 @@ packages:
|
|||||||
{ integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== }
|
{ integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== }
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/attr-accept@2.2.2:
|
||||||
|
resolution:
|
||||||
|
{ integrity: sha512-7prDjvt9HmqiZ0cl5CRjtS84sEyhsHP2coDkaZKRKVfCDo9s7iw7ChVmar78Gu9pC4SoR/28wFu/G5JJhTnqEg== }
|
||||||
|
engines: { node: '>=4' }
|
||||||
|
dev: false
|
||||||
|
|
||||||
/available-typed-arrays@1.0.5:
|
/available-typed-arrays@1.0.5:
|
||||||
resolution:
|
resolution:
|
||||||
{ integrity: sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw== }
|
{ integrity: sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw== }
|
||||||
@ -3671,6 +3680,14 @@ packages:
|
|||||||
flat-cache: 3.0.4
|
flat-cache: 3.0.4
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/file-selector@0.6.0:
|
||||||
|
resolution:
|
||||||
|
{ integrity: sha512-QlZ5yJC0VxHxQQsQhXvBaC7VRJ2uaxTf+Tfpu4Z/OcVQJVpZO+DGU0rkoVW5ce2SccxugvpBJoMvUs59iILYdw== }
|
||||||
|
engines: { node: '>= 12' }
|
||||||
|
dependencies:
|
||||||
|
tslib: 2.5.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/fill-range@7.0.1:
|
/fill-range@7.0.1:
|
||||||
resolution:
|
resolution:
|
||||||
{ integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== }
|
{ integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== }
|
||||||
@ -5041,6 +5058,19 @@ packages:
|
|||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
scheduler: 0.23.0
|
scheduler: 0.23.0
|
||||||
|
|
||||||
|
/react-dropzone@14.2.3(react@18.2.0):
|
||||||
|
resolution:
|
||||||
|
{ integrity: sha512-O3om8I+PkFKbxCukfIR3QAGftYXDZfOE2N1mr/7qebQJHs7U+/RSL/9xomJNpRg9kM5h9soQSdf0Gc7OHF5Fug== }
|
||||||
|
engines: { node: '>= 10.13' }
|
||||||
|
peerDependencies:
|
||||||
|
react: '>= 16.8 || 18.0.0'
|
||||||
|
dependencies:
|
||||||
|
attr-accept: 2.2.2
|
||||||
|
file-selector: 0.6.0
|
||||||
|
prop-types: 15.8.1
|
||||||
|
react: 18.2.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/react-fast-compare@3.2.1:
|
/react-fast-compare@3.2.1:
|
||||||
resolution:
|
resolution:
|
||||||
{ integrity: sha512-xTYf9zFim2pEif/Fw16dBiXpe0hoy5PxcD8+OwBnTtNLfIm3g6WxhKNurY+6OmdH1u6Ta/W/Vl6vjbYP1MFnDg== }
|
{ integrity: sha512-xTYf9zFim2pEif/Fw16dBiXpe0hoy5PxcD8+OwBnTtNLfIm3g6WxhKNurY+6OmdH1u6Ta/W/Vl6vjbYP1MFnDg== }
|
||||||
|
@ -1,21 +1,27 @@
|
|||||||
import React, { useId } from 'react';
|
import { useDropzone } from 'react-dropzone';
|
||||||
|
|
||||||
import { Box, Text } from '@chakra-ui/react';
|
import { Box, Text } from '@chakra-ui/react';
|
||||||
import { UnlockIcon } from '@chakra-ui/icons';
|
import { UnlockIcon } from '@chakra-ui/icons';
|
||||||
|
|
||||||
import { useAppDispatch } from './hooks';
|
import { useAppDispatch } from './hooks';
|
||||||
import { addNewFile, processFile } from './features/file-listing/fileListingSlice';
|
import { addNewFile, processFile } from './features/file-listing/fileListingSlice';
|
||||||
import { nanoid } from 'nanoid';
|
import { nanoid } from 'nanoid';
|
||||||
|
|
||||||
export function SelectFile() {
|
export function SelectFile() {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const id = useId();
|
const { getRootProps, getInputProps, isDragActive } = useDropzone({
|
||||||
|
multiple: true,
|
||||||
|
onDropAccepted(files, _event) {
|
||||||
|
console.debug(
|
||||||
|
'react-dropzone/onDropAccepted(%o, %o)',
|
||||||
|
files.length,
|
||||||
|
files.map((x) => x.name)
|
||||||
|
);
|
||||||
|
|
||||||
const handleFileSelection = (e: React.ChangeEvent<HTMLInputElement>) => {
|
for (const file of files) {
|
||||||
if (e.target.files) {
|
|
||||||
for (const file of e.target.files) {
|
|
||||||
const blobURI = URL.createObjectURL(file);
|
const blobURI = URL.createObjectURL(file);
|
||||||
const fileName = file.name;
|
const fileName = file.name;
|
||||||
const fileId = 'file://' + nanoid();
|
const fileId = 'file://' + nanoid();
|
||||||
|
|
||||||
// FIXME: this should be a single action/thunk that first adds the item, then updates it.
|
// FIXME: this should be a single action/thunk that first adds the item, then updates it.
|
||||||
dispatch(
|
dispatch(
|
||||||
addNewFile({
|
addNewFile({
|
||||||
@ -26,15 +32,12 @@ export function SelectFile() {
|
|||||||
);
|
);
|
||||||
dispatch(processFile({ fileId }));
|
dispatch(processFile({ fileId }));
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
});
|
||||||
e.target.value = '';
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
as="label"
|
{...getRootProps()}
|
||||||
htmlFor={id}
|
|
||||||
w="100%"
|
w="100%"
|
||||||
maxW={480}
|
maxW={480}
|
||||||
borderWidth="1px"
|
borderWidth="1px"
|
||||||
@ -49,17 +52,22 @@ export function SelectFile() {
|
|||||||
borderColor: 'gray.400',
|
borderColor: 'gray.400',
|
||||||
bg: 'gray.50',
|
bg: 'gray.50',
|
||||||
}}
|
}}
|
||||||
|
{...(isDragActive && {
|
||||||
|
bg: 'blue.50',
|
||||||
|
borderColor: 'blue.700',
|
||||||
|
})}
|
||||||
>
|
>
|
||||||
|
<input {...getInputProps()} />
|
||||||
|
|
||||||
<Box pb={3}>
|
<Box pb={3}>
|
||||||
<UnlockIcon boxSize={8} />
|
<UnlockIcon boxSize={8} />
|
||||||
</Box>
|
</Box>
|
||||||
<Box textAlign="center">
|
<Box textAlign="center">
|
||||||
{/* 将文件拖到此处,或 */}
|
将文件拖到此处,或
|
||||||
<Text as="span" color="teal.400">
|
<Text as="span" color="teal.400">
|
||||||
点我选择
|
点我选择
|
||||||
</Text>
|
</Text>
|
||||||
需要解密的文件
|
需要解密的文件
|
||||||
<input id={id} type="file" hidden multiple onChange={handleFileSelection} />
|
|
||||||
<Text fontSize="sm" opacity="50%">
|
<Text fontSize="sm" opacity="50%">
|
||||||
仅在浏览器内对文件进行解锁,无需消耗流量
|
仅在浏览器内对文件进行解锁,无需消耗流量
|
||||||
</Text>
|
</Text>
|
||||||
|
Loading…
Reference in New Issue
Block a user