mirror of
https://git.unlock-music.dev/um/um-react.git
synced 2024-11-24 00:02:16 +00:00
feat: ui improvement, working audio preview
This commit is contained in:
parent
8ab267019c
commit
b99590908e
@ -4,4 +4,5 @@ export enum DECRYPTION_WORKER_ACTION_NAME {
|
|||||||
|
|
||||||
export interface DecryptionResult {
|
export interface DecryptionResult {
|
||||||
decrypted: string; // blob uri
|
decrypted: string; // blob uri
|
||||||
|
ext: string;
|
||||||
}
|
}
|
||||||
|
@ -1,72 +1,17 @@
|
|||||||
import {
|
import { VStack } from '@chakra-ui/react';
|
||||||
Avatar,
|
|
||||||
Box,
|
|
||||||
Link,
|
|
||||||
Table,
|
|
||||||
TableContainer,
|
|
||||||
Tbody,
|
|
||||||
Td,
|
|
||||||
Text,
|
|
||||||
Th,
|
|
||||||
Thead,
|
|
||||||
Tr,
|
|
||||||
Wrap,
|
|
||||||
WrapItem,
|
|
||||||
} from '@chakra-ui/react';
|
|
||||||
|
|
||||||
import { ProcessState, selectFiles } from './fileListingSlice';
|
import { selectFiles } from './fileListingSlice';
|
||||||
import { useAppSelector } from '../../hooks';
|
import { useAppSelector } from '../../hooks';
|
||||||
|
import { FileRow } from './FileRow';
|
||||||
|
|
||||||
export function FileListing() {
|
export function FileListing() {
|
||||||
const files = useAppSelector(selectFiles);
|
const files = useAppSelector(selectFiles);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TableContainer>
|
<VStack>
|
||||||
<Table variant="striped">
|
|
||||||
<Thead>
|
|
||||||
<Tr>
|
|
||||||
<Th w="1%">封面</Th>
|
|
||||||
<Th>元信息</Th>
|
|
||||||
<Th>操作</Th>
|
|
||||||
</Tr>
|
|
||||||
</Thead>
|
|
||||||
<Tbody>
|
|
||||||
{Object.entries(files).map(([id, file]) => (
|
{Object.entries(files).map(([id, file]) => (
|
||||||
<Tr key={id}>
|
<FileRow key={id} id={id} file={file} />
|
||||||
<Td>
|
|
||||||
{file.metadata.cover && <Avatar size="sm" name="专辑封面" src={file.metadata.cover} />}
|
|
||||||
{!file.metadata.cover && <Text>暂无封面</Text>}
|
|
||||||
</Td>
|
|
||||||
<Td>
|
|
||||||
<Box as="h4" fontWeight="semibold" mt="1">
|
|
||||||
{file.metadata.name || file.fileName}
|
|
||||||
</Box>
|
|
||||||
{file.state === ProcessState.COMPLETE && (
|
|
||||||
<>
|
|
||||||
<Text>专辑: {file.metadata.album}</Text>
|
|
||||||
<Text>艺术家: {file.metadata.artist}</Text>
|
|
||||||
<Text>专辑艺术家: {file.metadata.albumArtist}</Text>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Td>
|
|
||||||
<Td>
|
|
||||||
<Wrap>
|
|
||||||
<WrapItem>播放</WrapItem>
|
|
||||||
<WrapItem>
|
|
||||||
{/* TODO: Use correct file name */}
|
|
||||||
{file.decrypted && (
|
|
||||||
<Link isExternal href={file.decrypted} download="test.flac">
|
|
||||||
下载
|
|
||||||
</Link>
|
|
||||||
)}
|
|
||||||
</WrapItem>
|
|
||||||
<WrapItem>删除</WrapItem>
|
|
||||||
</Wrap>
|
|
||||||
</Td>
|
|
||||||
</Tr>
|
|
||||||
))}
|
))}
|
||||||
</Tbody>
|
</VStack>
|
||||||
</Table>
|
|
||||||
</TableContainer>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
107
src/features/file-listing/FileRow.tsx
Normal file
107
src/features/file-listing/FileRow.tsx
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
import {
|
||||||
|
Avatar,
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Card,
|
||||||
|
CardBody,
|
||||||
|
Center,
|
||||||
|
Grid,
|
||||||
|
GridItem,
|
||||||
|
Link,
|
||||||
|
Text,
|
||||||
|
VStack,
|
||||||
|
Wrap,
|
||||||
|
WrapItem,
|
||||||
|
} from '@chakra-ui/react';
|
||||||
|
import { DecryptedAudioFile, ProcessState } from './fileListingSlice';
|
||||||
|
import { useRef } from 'react';
|
||||||
|
|
||||||
|
interface FileRowProps {
|
||||||
|
id: string;
|
||||||
|
file: DecryptedAudioFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function FileRow({ id, file }: FileRowProps) {
|
||||||
|
const isDecrypted = file.state === ProcessState.COMPLETE;
|
||||||
|
|
||||||
|
const decryptedName = file.fileName.replace(/\.[a-z\d]{3,6}$/, '') + '.' + file.ext;
|
||||||
|
|
||||||
|
const audioPlayerRef = useRef<HTMLAudioElement>(null);
|
||||||
|
const togglePlay = () => {
|
||||||
|
const player = audioPlayerRef.current;
|
||||||
|
if (!player) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (player.paused) {
|
||||||
|
player.play();
|
||||||
|
} else {
|
||||||
|
player.pause();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card w="full">
|
||||||
|
<CardBody>
|
||||||
|
<Grid
|
||||||
|
templateAreas={`
|
||||||
|
"cover title title"
|
||||||
|
"cover meta action"
|
||||||
|
`}
|
||||||
|
gridTemplateRows={'min-content 1fr'}
|
||||||
|
gridTemplateColumns={'160px 1fr'}
|
||||||
|
gap="1"
|
||||||
|
>
|
||||||
|
<GridItem area="cover">
|
||||||
|
<Center w="160px" h="160px">
|
||||||
|
{file.metadata.cover && <Avatar size="sm" name="专辑封面" src={file.metadata.cover} />}
|
||||||
|
{!file.metadata.cover && <Text>暂无封面</Text>}
|
||||||
|
</Center>
|
||||||
|
</GridItem>
|
||||||
|
<GridItem area="title">
|
||||||
|
<Box w="full" as="h4" fontWeight="semibold" mt="1">
|
||||||
|
{file.metadata.name || file.fileName}
|
||||||
|
</Box>
|
||||||
|
</GridItem>
|
||||||
|
<GridItem area="meta">
|
||||||
|
{isDecrypted && (
|
||||||
|
<Box>
|
||||||
|
<Text>专辑: {file.metadata.album}</Text>
|
||||||
|
<Text>艺术家: {file.metadata.artist}</Text>
|
||||||
|
<Text>专辑艺术家: {file.metadata.albumArtist}</Text>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</GridItem>
|
||||||
|
<GridItem area="action">
|
||||||
|
<VStack>
|
||||||
|
{file.decrypted && <audio controls autoPlay={false} src={file.decrypted} ref={audioPlayerRef} />}
|
||||||
|
|
||||||
|
<Wrap>
|
||||||
|
{isDecrypted && (
|
||||||
|
<WrapItem>
|
||||||
|
<Button type="button" onClick={togglePlay}>
|
||||||
|
播放/暂停
|
||||||
|
</Button>
|
||||||
|
</WrapItem>
|
||||||
|
)}
|
||||||
|
<WrapItem>
|
||||||
|
{/* TODO: Use correct file name */}
|
||||||
|
{file.decrypted && (
|
||||||
|
<Link type="button" as={Button} isExternal href={file.decrypted} download={decryptedName}>
|
||||||
|
下载
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
|
</WrapItem>
|
||||||
|
<WrapItem>
|
||||||
|
<Button type="button" onClick={() => alert('todo')}>
|
||||||
|
删除
|
||||||
|
</Button>
|
||||||
|
</WrapItem>
|
||||||
|
</Wrap>
|
||||||
|
</VStack>
|
||||||
|
</GridItem>
|
||||||
|
</Grid>
|
||||||
|
</CardBody>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
@ -27,6 +27,7 @@ export interface AudioMetadata {
|
|||||||
export interface DecryptedAudioFile {
|
export interface DecryptedAudioFile {
|
||||||
fileName: string;
|
fileName: string;
|
||||||
raw: string; // blob uri
|
raw: string; // blob uri
|
||||||
|
ext: string;
|
||||||
decrypted: string; // blob uri
|
decrypted: string; // blob uri
|
||||||
state: ProcessState;
|
state: ProcessState;
|
||||||
errorMessage: null | string;
|
errorMessage: null | string;
|
||||||
@ -65,6 +66,7 @@ export const fileListingSlice = createSlice({
|
|||||||
fileName: payload.fileName,
|
fileName: payload.fileName,
|
||||||
raw: payload.blobURI,
|
raw: payload.blobURI,
|
||||||
decrypted: '',
|
decrypted: '',
|
||||||
|
ext: '',
|
||||||
state: ProcessState.UNTOUCHED,
|
state: ProcessState.UNTOUCHED,
|
||||||
errorMessage: null,
|
errorMessage: null,
|
||||||
metadata: {
|
metadata: {
|
||||||
@ -91,6 +93,7 @@ export const fileListingSlice = createSlice({
|
|||||||
|
|
||||||
file.state = ProcessState.COMPLETE;
|
file.state = ProcessState.COMPLETE;
|
||||||
file.decrypted = action.payload.decrypted;
|
file.decrypted = action.payload.decrypted;
|
||||||
|
file.ext = action.payload.ext;
|
||||||
// TODO: populate file metadata
|
// TODO: populate file metadata
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user