refactor: batch 1

This commit is contained in:
鲁树人
2025-05-17 05:59:39 +09:00
parent 089d66cbf4
commit 13c669b4ea
23 changed files with 828 additions and 533 deletions

View File

@@ -1,16 +1,4 @@
import {
Button,
ButtonGroup,
HStack,
Icon,
IconButton,
Menu,
MenuButton,
MenuDivider,
MenuItem,
MenuList,
} from '@chakra-ui/react';
import { MdAdd, MdDeleteForever, MdExpandMore, MdFileUpload } from 'react-icons/md';
import { MdAdd, MdDeleteForever, MdFileUpload } from 'react-icons/md';
export interface AddKeyProps {
addKey: () => void;
@@ -20,28 +8,20 @@ export interface AddKeyProps {
export function AddKey({ addKey, importKeyFromFile, clearKeys }: AddKeyProps) {
return (
<HStack pb={2} pt={2}>
<ButtonGroup isAttached colorScheme="purple" variant="outline">
<Button onClick={addKey} leftIcon={<Icon as={MdAdd} />}>
</Button>
<Menu>
<MenuButton as={IconButton} icon={<MdExpandMore />}></MenuButton>
<MenuList>
{importKeyFromFile && (
<MenuItem onClick={importKeyFromFile} icon={<Icon as={MdFileUpload} boxSize={5} />}>
</MenuItem>
)}
{importKeyFromFile && clearKeys && <MenuDivider />}
{clearKeys && (
<MenuItem color="red" onClick={clearKeys} icon={<Icon as={MdDeleteForever} boxSize={5} />}>
</MenuItem>
)}
</MenuList>
</Menu>
</ButtonGroup>
</HStack>
<div className="flex flex-row justify-between items-center">
<div className="join">
<button className="btn join-item" onClick={addKey}>
<MdAdd />
</button>
<button className="btn join-item" onClick={importKeyFromFile}>
<MdFileUpload />
</button>
<button className="btn btn-error join-item" onClick={clearKeys}>
<MdDeleteForever />
</button>
</div>
</div>
);
}

View File

@@ -1,12 +1,11 @@
import { useEffect } from 'react';
import { BrowserRouter, NavLink, Route, Routes } from 'react-router';
import { MdSettings, MdHome, MdQuestionAnswer } from 'react-icons/md';
import { ChakraProvider, Tabs, TabList, TabPanels, Tab, TabPanel, Icon, chakra } from '@chakra-ui/react';
import { MainTab } from '~/tabs/MainTab';
import { SettingsTab } from '~/tabs/SettingsTab';
import { Provider } from 'react-redux';
import { theme } from '~/theme';
import { persistSettings } from '~/features/settings/persistSettings';
import { setupStore } from '~/store';
import { Footer } from '~/components/Footer';
@@ -15,43 +14,39 @@ import { FaqTab } from '~/tabs/FaqTab';
// Private to this file only.
const store = setupStore();
const tabClassNames = ({ isActive }: { isActive: boolean }) => `tab ${isActive ? 'tab-active' : ''}`;
export function AppRoot() {
useEffect(() => persistSettings(store), []);
return (
<ChakraProvider theme={theme}>
<BrowserRouter>
<Provider store={store}>
<Tabs flex={1} minH={0} display="flex" flexDir="column">
<TabList justifyContent="center">
<Tab>
<Icon as={MdHome} mr="1" />
<chakra.span></chakra.span>
</Tab>
<Tab>
<Icon as={MdSettings} mr="1" />
<chakra.span></chakra.span>
</Tab>
<Tab>
<Icon as={MdQuestionAnswer} mr="1" />
<chakra.span></chakra.span>
</Tab>
</TabList>
<div role="tablist" className="tabs tabs-border w-full justify-center">
<NavLink to="/" role="tab" className={tabClassNames}>
<MdHome />
</NavLink>
<NavLink to="/settings" role="tab" className={tabClassNames}>
<MdSettings />
</NavLink>
<NavLink to="/questions" role="tab" className={tabClassNames}>
<MdQuestionAnswer />
</NavLink>
</div>
<TabPanels overflow="auto" minW={0} flexDir="column" flex={1} display="flex">
<TabPanel>
<MainTab />
</TabPanel>
<TabPanel flex={1} display="flex">
<SettingsTab />
</TabPanel>
<TabPanel>
<FaqTab />
</TabPanel>
</TabPanels>
</Tabs>
<main className="flex-1 flex justify-center">
<Routes>
<Route path="/" Component={MainTab} />
<Route path="/settings" Component={SettingsTab} />
<Route path="/questions" Component={FaqTab} />
</Routes>
</main>
<Footer />
</Provider>
</ChakraProvider>
</BrowserRouter>
);
}

40
src/components/Dialog.tsx Normal file
View File

@@ -0,0 +1,40 @@
import { useEffect, useRef } from 'react';
export interface DialogProps {
closeButton?: boolean;
backdropClose?: boolean;
title?: React.ReactNode;
children: React.ReactNode;
show: boolean;
onClose: () => void;
}
export function Dialog({ closeButton, backdropClose, title, children, show, onClose }: DialogProps) {
const refModel = useRef<HTMLDialogElement>(null);
useEffect(() => {
if (show) {
refModel.current?.showModal();
} else {
refModel.current?.close();
}
}, [show]);
return (
<dialog ref={refModel} className="modal">
<div className="modal-box">
{closeButton && (
<form method="dialog" onSubmit={onClose}>
<button className="btn btn-sm btn-circle btn-ghost absolute right-2 top-2"></button>
</form>
)}
<h3 className="font-bold text-lg">{title}</h3>
{children}
</div>
{backdropClose && (
<form method="dialog" className="modal-backdrop" onSubmit={onClose}>
<button>close</button>
</form>
)}
</dialog>
);
}

View File

@@ -1,12 +1,15 @@
import type { AnchorHTMLAttributes } from 'react';
import { ExternalLinkIcon } from '@chakra-ui/icons';
import { Link } from '@chakra-ui/react';
import { FiExternalLink } from 'react-icons/fi';
export function ExtLink({ children, ...props }: AnchorHTMLAttributes<HTMLAnchorElement>) {
export type ExtLinkProps = AnchorHTMLAttributes<HTMLAnchorElement> & {
icon?: boolean;
};
export function ExtLink({ className, icon = true, children, ...props }: ExtLinkProps) {
return (
<Link isExternal {...props} rel="noreferrer noopener nofollow">
<a rel="noreferrer noopener nofollow" className={`link ${className}`} {...props}>
{children}
<ExternalLinkIcon />
</Link>
{icon && <FiExternalLink />}
</a>
);
}

View File

@@ -1,5 +1,5 @@
import classnames from 'classnames';
import { useDropzone } from 'react-dropzone';
import { Box } from '@chakra-ui/react';
export interface FileInputProps {
onReceiveFiles: (files: File[]) => void;
@@ -14,30 +14,19 @@ export function FileInput({ children, onReceiveFiles }: FileInputProps) {
});
return (
<Box
<div
{...getRootProps()}
w="100%"
maxW={480}
borderWidth="1px"
borderRadius="lg"
transitionDuration="0.5s"
p="6"
cursor="pointer"
display="flex"
flexDir="column"
alignItems="center"
_hover={{
borderColor: 'gray.400',
bg: 'gray.50',
}}
{...(isDragActive && {
bg: 'blue.50',
borderColor: 'blue.700',
})}
className={classnames(
'w-full max-w-xl border rounded-lg transition duration-500 p-6 border-base-300 mx-auto',
'cursor-pointer flex flex-col items-center hover:border-gray-400 hover:bg-gray-50',
{
'bg-blue-50 border-blue-700': isDragActive,
},
)}
tabIndex={0}
>
<input {...getInputProps()} />
{children}
</Box>
</div>
);
}

View File

@@ -1,10 +1,9 @@
import { Code, Text } from '@chakra-ui/react';
import React from 'react';
export function FilePathBlock({ children }: { children: React.ReactNode }) {
return (
<Text as="pre" whiteSpace="pre-wrap" wordBreak="break-all">
<Code>{children}</Code>
</Text>
<pre className="whitespace-pre-wrap break-all">
<code>{children}</code>
</pre>
);
}

View File

@@ -1,45 +1,28 @@
import { Center, Flex, Link, Text } from '@chakra-ui/react';
import { Suspense } from 'react';
import { SDKVersion } from './SDKVersion';
import { CurrentYear } from './CurrentYear';
export function Footer() {
const appVersionShort = '__APP_VERSION_SHORT__';
return (
<Center
fontSize="sm"
textAlign="center"
bottom="0"
w="full"
pt="3"
pb="3"
borderTop="1px solid"
borderColor="gray.300"
bg="gray.100"
color="gray.800"
flexDir="column"
flexShrink={0}
>
<Flex as={Text}>
<Link href="https://git.unlock-music.dev/um/um-react" isExternal>
<footer className="flex flex-col text-center p-4 bg-base-200">
<p className="flex flex-row justify-center items-center h-[1em]">
<a className="link link-info mr-1" href="https://git.unlock-music.dev/um/um-react">
</Link>
{' (__APP_VERSION_SHORT__'}
<Suspense>
<SDKVersion />
</Suspense>
{') - 移除已购音乐的加密保护。'}
</Flex>
<Text>
</a>
(v{appVersionShort}
<SDKVersion />) -
</p>
<p>
{'© 2019 - '}
<CurrentYear />{' '}
<Link href="https://git.unlock-music.dev/um" isExternal>
<a className="link link-info" href="https://git.unlock-music.dev/um">
UnlockMusic
</Link>
</a>
{' | '}
<Link href="https://git.unlock-music.dev/um/um-react/src/branch/main/LICENSE" isExternal>
<a className="link link-info" href="https://git.unlock-music.dev/um/um-react/src/branch/main/LICENSE">
使 MIT
</Link>
</Text>
</Center>
</a>
</p>
</footer>
);
}

View File

@@ -1,15 +1,4 @@
import {
Center,
Flex,
Modal,
ModalBody,
ModalCloseButton,
ModalContent,
ModalHeader,
ModalOverlay,
Tabs,
Text,
} from '@chakra-ui/react';
import { useEffect, useRef } from 'react';
import { FileInput } from '~/components/FileInput';
@@ -18,39 +7,43 @@ export interface ImportSecretModalProps {
children: React.ReactNode;
show: boolean;
onClose: () => void;
onImport: (file: File) => void|Promise<void>;
onImport: (file: File) => void | Promise<void>;
}
export function ImportSecretModal({ clientName, children, show, onClose, onImport }: ImportSecretModalProps) {
const handleFileReceived = (files: File[]) => {
const promise = onImport(files[0]);
if (promise instanceof Promise) {
promise.catch(err => {
promise.catch((err) => {
console.error('could not import: ', err);
});
}
return promise;
};
return (
<Modal isOpen={show} onClose={onClose} closeOnOverlayClick={false} scrollBehavior="inside" size="xl">
<ModalOverlay />
<ModalContent>
<ModalHeader></ModalHeader>
<ModalCloseButton />
<Flex as={ModalBody} gap={2} flexDir="column" flex={1}>
<Center>
<FileInput onReceiveFiles={handleFileReceived}></FileInput>
</Center>
const refModel = useRef<HTMLDialogElement>(null);
useEffect(() => {
if (show) {
refModel.current?.showModal();
} else {
refModel.current?.close();
}
}, [show]);
<Text as="div" mt={2}>
{clientName && <>{clientName}</>}
</Text>
<Flex as={Tabs} variant="enclosed" flexDir="column" flex={1} minH={0}>
{children}
</Flex>
</Flex>
</ModalContent>
</Modal>
return (
<dialog ref={refModel} className="modal">
<div className="modal-box">
<form method="dialog" onSubmit={() => onClose()}>
<button className="btn btn-sm btn-circle btn-ghost absolute right-2 top-2"></button>
</form>
<h3 className="font-bold text-lg"></h3>
<div className="py-4 flex flex-col gap-2 flex-1">
<FileInput onReceiveFiles={handleFileReceived}></FileInput>
<div className="mt-2">{clientName && <>{clientName}</>}</div>
<div>{children}</div>
</div>
</div>
</dialog>
);
}

View File

@@ -1,33 +1,39 @@
import { InfoOutlineIcon } from '@chakra-ui/icons';
import { Tooltip, VStack, Text, Flex } from '@chakra-ui/react';
import { MdInfoOutline } from 'react-icons/md';
import { workerClientBus } from '~/decrypt-worker/client';
import { DECRYPTION_WORKER_ACTION_NAME } from '~/decrypt-worker/constants';
import usePromise from 'react-promise-suspense';
import { useEffect, useRef, useState } from 'react';
const getSDKVersion = async (): Promise<string> => {
return workerClientBus.request(DECRYPTION_WORKER_ACTION_NAME.VERSION, null);
};
export function SDKVersion() {
const sdkVersion = usePromise(getSDKVersion, []);
const refDialog = useRef<HTMLDialogElement>(null);
const [sdkVersion, setSdkVersion] = useState('...');
useEffect(() => {
getSDKVersion().then(setSdkVersion);
}, []);
return (
<Flex as="span" pl="1" alignItems="center" data-testid="sdk-version">
<Tooltip
hasArrow
placement="top"
label={
<VStack>
<Text>App: __APP_VERSION__</Text>
<Text>SDK: {sdkVersion}</Text>
</VStack>
}
bg="gray.300"
color="black"
>
<InfoOutlineIcon />
</Tooltip>
</Flex>
<>
<span className="btn btn-ghost inline-flex p-0 h-[1em]" onClick={() => refDialog.current?.showModal()}>
<MdInfoOutline />
</span>
<dialog ref={refDialog} className="modal text-left">
<div className="modal-box">
<form method="dialog">
<button className="btn btn-sm btn-circle btn-ghost absolute right-2 top-2"></button>
</form>
<h3 className="font-bold text-lg"></h3>
<p>App: __APP_VERSION__</p>
<p>SDK: {sdkVersion}</p>
</div>
<form method="dialog" className="modal-backdrop">
<button></button>
</form>
</dialog>
</>
);
}

View File

@@ -1,5 +1,4 @@
import { Box, Text } from '@chakra-ui/react';
import { UnlockIcon } from '@chakra-ui/icons';
import { FiUnlock } from 'react-icons/fi';
import { useAppDispatch } from '~/hooks';
import { addNewFile, processFile } from '~/features/file-listing/fileListingSlice';
@@ -12,7 +11,7 @@ export function SelectFile() {
console.debug(
'react-dropzone/onDropAccepted(%o, %o)',
files.length,
files.map((x) => x.name)
files.map((x) => x.name),
);
for (const file of files) {
@@ -26,7 +25,7 @@ export function SelectFile() {
id: fileId,
blobURI,
fileName,
})
}),
);
dispatch(processFile({ fileId }));
}
@@ -34,19 +33,13 @@ export function SelectFile() {
return (
<FileInput multiple onReceiveFiles={handleFileReceived}>
<Box pb={3}>
<UnlockIcon boxSize={8} />
</Box>
<Text as="div" textAlign="center">
<FiUnlock className="size-8 mb-4" />
<p className="text-center">
<Text as="span" color="teal.400">
</Text>
<span className="text-teal-700 font-semibold"></span>
<Text fontSize="sm" opacity="50%">
</Text>
</Text>
</p>
<p className="text-sm opacity-50 m-0"></p>
</FileInput>
);
}