refactor: batch 3

This commit is contained in:
鲁树人
2025-05-18 02:41:20 +09:00
parent 75b43e1e84
commit 2e4e57be45
52 changed files with 933 additions and 1136 deletions

View File

@@ -1,24 +1,46 @@
import type { RefObject } from 'react';
import { MdAdd, MdDeleteForever, MdFileUpload } from 'react-icons/md';
export interface AddKeyProps {
addKey: () => void;
importKeyFromFile?: () => void;
clearKeys?: () => void;
refContainer?: RefObject<HTMLElement | null>;
}
export function AddKey({ addKey, importKeyFromFile, clearKeys }: AddKeyProps) {
export function AddKey({ addKey, refContainer, importKeyFromFile, clearKeys }: AddKeyProps) {
const scrollToLastKey = () => {
const container = refContainer?.current;
if (container) {
const inputs = container.querySelectorAll('input[data-name="key-input--name"]');
const lastInput = inputs[inputs.length - 1] as HTMLInputElement | null;
if (lastInput) {
lastInput.focus({ preventScroll: true });
lastInput.scrollIntoView({
behavior: 'smooth',
block: 'center',
});
}
}
};
const handleAddKey = () => {
addKey();
setTimeout(scrollToLastKey);
};
return (
<div className="flex flex-row justify-between items-center">
<div className="join">
<button className="btn join-item" onClick={addKey}>
<MdAdd />
<button type="button" className="join-item btn flex items-center gap-2" onClick={handleAddKey}>
<MdAdd className="text-lg" />
</button>
<button className="btn join-item" onClick={importKeyFromFile}>
<MdFileUpload />
<button type="button" className="join-item btn flex items-center gap-2" onClick={importKeyFromFile}>
<MdFileUpload className="text-lg" />
</button>
<button className="btn btn-error join-item" onClick={clearKeys}>
<MdDeleteForever />
<button type="button" className="join-item btn flex items-center gap-2 btn-error" onClick={clearKeys}>
<MdDeleteForever className="text-lg" />
</button>
</div>

View File

@@ -0,0 +1,57 @@
import { Light as SyntaxHighlighter } from 'react-syntax-highlighter';
import hljsStyleGitHub from 'react-syntax-highlighter/dist/esm/styles/hljs/github';
import { ExtLink } from '../ExtLink';
import PowerShellAdbDumpCommandTemplate from './adb_dump.ps1?raw';
import ShellAdbDumpCommandTemplate from './adb_dump.sh?raw';
import { applyTemplate } from '~/util/applyTemplate';
export interface AdbInstructionTemplateProps {
dir: string;
file: string;
platform: 'win32' | 'linux';
}
const URL_USB_DEBUGGING = 'https://developer.android.com/studio/debug/dev-options?hl=zh-cn#Enable-debugging';
const LANGUAGE_MAP = {
win32: { language: 'ps1', template: PowerShellAdbDumpCommandTemplate },
linux: { language: 'sh', template: ShellAdbDumpCommandTemplate },
};
export function AdbInstructionTemplate({ dir, file, platform }: AdbInstructionTemplateProps) {
const { language, template } = LANGUAGE_MAP[platform];
const command = applyTemplate(template, { dir, file });
return (
<ol className="list-decimal pl-4">
<li>
<p>
<code>adb</code>
</p>
{platform === 'win32' && (
<div>
<span>
💡
<ExtLink href="https://scoop.sh/#/apps?q=adb">使 Scoop </ExtLink>
</span>
</div>
)}
</li>
<li> PowerShell </li>
<li>
<ExtLink href={URL_USB_DEBUGGING}> USB </ExtLink>
</li>
<li></li>
<li>
<p> USB </p>
<SyntaxHighlighter language={language} style={hljsStyleGitHub}>
{command}
</SyntaxHighlighter>
</li>
<li>
<code>{file}</code>
</li>
</ol>
);
}

View File

@@ -1,171 +1,73 @@
import {
Accordion,
AccordionButton,
AccordionIcon,
AccordionItem,
AccordionPanel,
Box,
Code,
Heading,
ListItem,
OrderedList,
Text,
chakra,
} from '@chakra-ui/react';
import { ExternalLinkIcon } from '@chakra-ui/icons';
import { Light as SyntaxHighlighter } from 'react-syntax-highlighter';
import hljsStyleGitHub from 'react-syntax-highlighter/dist/esm/styles/hljs/github';
import PowerShellAdbDumpCommandTemplate from './adb_dump.ps1?raw';
import ShellAdbDumpCommandTemplate from './adb_dump.sh?raw';
import { ExtLink } from '../ExtLink';
const applyTemplate = (tpl: string, values: Record<string, unknown>) => {
return tpl.replace(/\{\{\s*(\w+)\s*\}\}/g, (_, key) => (Object.hasOwn(values, key) ? String(values[key]) : '<nil>'));
};
import { Ruby } from '../Ruby';
import { useId } from 'react';
import { RootExplorerGuide } from './RootExplorerGuide';
import { AdbInstructionTemplate } from './AdbInstructionTemplate';
import { HiWord } from '../HelpText/HiWord';
export interface AndroidADBPullInstructionProps {
dir: string;
file: string;
}
const URL_AMAZE = 'https://github.com/TeamAmaze/AmazeFileManager/releases/latest';
const URL_MT2 = 'https://mt2.cn/download/';
export function AndroidADBPullInstruction({ dir, file }: AndroidADBPullInstructionProps) {
const psAdbDumpCommand = applyTemplate(PowerShellAdbDumpCommandTemplate, { dir, file });
const shAdbDumpCommand = applyTemplate(ShellAdbDumpCommandTemplate, { dir, file });
const androidInstructionId = useId();
return (
<>
<Text>
<ruby>
<rp> (</rp>
<rt>
<code>root</code>
</rt>
<rp>)</rp>
</ruby>
访访
</Text>
<Text>
<p>
<Ruby caption="root"></Ruby>访访
</p>
<p>
<chakra.span color="red.400"></chakra.span>
</Text>
<HiWord></HiWord>
</p>
<Accordion allowToggle mt="2">
<AccordionItem>
<Heading as="h3" size="md">
<AccordionButton>
<Box as="span" flex="1" textAlign="left">
</Box>
<AccordionIcon />
</AccordionButton>
</Heading>
<AccordionPanel pb={4}>
<OrderedList>
<ListItem>
<Text>
<Code>root</Code>
</Text>
</ListItem>
<ListItem>
<Text>
访 <Code>{dir}/</Code>
</Text>
</ListItem>
<ListItem>
<Text>
<Code>{file}</Code> 访
<br />
</Text>
</ListItem>
<ListItem>
<Text></Text>
</ListItem>
</OrderedList>
</AccordionPanel>
</AccordionItem>
<AccordionItem>
<Heading as="h3" size="md">
<AccordionButton>
<Box as="span" flex="1" textAlign="left">
PC ADB / PowerShell
</Box>
<AccordionIcon />
</AccordionButton>
</Heading>
<AccordionPanel pb={4}>
<OrderedList>
<ListItem>
<Text>
<Code>adb</Code>
</Text>
<Text>
💡
<ExtLink href="https://scoop.sh/#/apps?q=adb">
使 Scoop <ExternalLinkIcon />
</ExtLink>
</Text>
</ListItem>
<ListItem>
<Text> PowerShell 7 </Text>
</ListItem>
<ListItem>
<Text></Text>
</ListItem>
<ListItem>
<Text></Text>
<SyntaxHighlighter language="ps1" style={hljsStyleGitHub}>
{psAdbDumpCommand}
</SyntaxHighlighter>
</ListItem>
<ListItem>
<Text>
<Code>{file}</Code>
</Text>
</ListItem>
</OrderedList>
</AccordionPanel>
</AccordionItem>
<AccordionItem>
<Heading as="h3" size="md">
<AccordionButton>
<Box as="span" flex="1" textAlign="left">
Linux / Mac ADB / Shell
</Box>
<AccordionIcon />
</AccordionButton>
</Heading>
<AccordionPanel pb={4}>
<OrderedList>
<ListItem>
<Text>
<Code>adb</Code>
</Text>
</ListItem>
<ListItem>
<Text></Text>
</ListItem>
<ListItem>
<Text></Text>
<SyntaxHighlighter language="bash" style={hljsStyleGitHub}>
{shAdbDumpCommand}
</SyntaxHighlighter>
</ListItem>
<ListItem>
<Text>
<Code>{file}</Code>
</Text>
</ListItem>
</OrderedList>
</AccordionPanel>
</AccordionItem>
</Accordion>
<div className="join join-vertical bg-base-100 mt-2 max-w-full">
<div className="collapse collapse-arrow join-item border-base-300 border">
<input type="radio" name={androidInstructionId} />
<div className="collapse-title font-semibold"></div>
<div className="collapse-content text-sm min-w-0">
<ol className="list-decimal pl-4">
<li>
<code>root</code> <ExtLink href={URL_AMAZE}>Amaze </ExtLink>
<ExtLink href={URL_MT2}>MT </ExtLink>
</li>
<li>
root
<RootExplorerGuide />
</li>
<li>
<p>
访 <code>{dir}/</code>
</p>
<p> </p>
</li>
<li>
<code>{file}</code> 访
</li>
<li></li>
</ol>
</div>
</div>
<div className="collapse collapse-arrow join-item border-base-300 border">
<input type="radio" name={androidInstructionId} />
<div className="collapse-title font-semibold"> PC 使 ADB / PowerShell</div>
<div className="collapse-content text-sm min-w-0">
<AdbInstructionTemplate dir={dir} file={file} platform="win32" />
</div>
</div>
<div className="collapse collapse-arrow join-item border-base-300 border">
<input type="radio" name={androidInstructionId} />
<div className="collapse-title font-semibold"> Linux / Mac 使 ADB / Shell</div>
<div className="collapse-content text-sm min-w-0">
<AdbInstructionTemplate dir={dir} file={file} platform="linux" />
</div>
</div>
</div>
</>
);
}

View File

@@ -0,0 +1,64 @@
import { FiMenu, FiMoreVertical } from 'react-icons/fi';
import { Header5 } from '../HelpText/Headers';
import { Ruby } from '../Ruby';
import { VQuote } from '../HelpText/VQuote';
export function RootExplorerGuide() {
return (
<div className="@container inline-flex flex-col items-start w-full pl-4">
<div className="flex flex-col items-start gap-4 @md:flex-row">
<div>
<Header5 className="[&]:mt-0 [&]:pt-0">Amaze </Header5>
<ul className="ml-2 list-disc list-inside">
<li>
<div className="inline-flex items-center gap-1">
<FiMenu />
</div>
</li>
<li>
<VQuote>
<Ruby caption="Settings"></Ruby>
</VQuote>
</li>
<li>
<VQuote>
<Ruby caption="Behaviour"></Ruby>
</VQuote>
</li>
<li>
<VQuote>
<Ruby caption="Advanced"></Ruby>
</VQuote>
<VQuote>
<Ruby caption="Root Explorer"></Ruby>
</VQuote>
</li>
</ul>
</div>
<div>
<Header5 className="[&]:mt-0 [&]:pt-0">MT </Header5>
<ul className="ml-2 list-disc list-inside">
<li>
<div className="inline-flex items-center gap-1">
<FiMenu />
</div>
</li>
<li>
<div className="inline-flex items-center">
<FiMoreVertical className="ml-1" />
<VQuote></VQuote>
</div>
</li>
<li>
<VQuote> Root </VQuote>
</li>
</ul>
</div>
</div>
</div>
);
}

View File

@@ -7,7 +7,7 @@ export type ExtLinkProps = AnchorHTMLAttributes<HTMLAnchorElement> & {
export function ExtLink({ className, icon = true, children, ...props }: ExtLinkProps) {
return (
<a rel="noreferrer noopener nofollow" target="_blank" className={`link ${className}`} {...props}>
<a rel="noreferrer noopener nofollow" target="_blank" className={`link ${className}`} {...props}>
{children}
{icon && <FiExternalLink className="inline size-sm ml-1" />}
</a>

View File

@@ -1,6 +1,6 @@
import React from 'react';
import type { ReactNode } from 'react';
export function FilePathBlock({ children }: { children: React.ReactNode }) {
export function FilePathBlock({ children }: { children: ReactNode }) {
return (
<pre className="whitespace-pre-wrap break-all">
<code>{children}</code>

View File

@@ -1,4 +1,3 @@
import { Heading } from '@chakra-ui/react';
import React from 'react';
export interface HeaderProps {
@@ -9,34 +8,24 @@ export interface HeaderProps {
export function Header3({ children, className, id }: HeaderProps) {
return (
<Heading
as="h3"
id={id}
className={className}
pt={3}
pb={1}
borderBottom={'1px solid'}
borderColor="gray.300"
color="gray.800"
size="lg"
>
<h3 id={id} className={`text-2xl pt-3 pb-1 font-bold border-b border-base-300 text-neutral-800 ${className}`}>
{children}
</Heading>
</h3>
);
}
export function Header4({ children, className, id }: HeaderProps) {
return (
<Heading as="h4" id={id} className={className} pt={3} pb={1} color="gray.700" size="md">
<h4 id={id} className={`text-xl pt-3 pb-1 font-semibold text-neutral-800 ${className}`}>
{children}
</Heading>
</h4>
);
}
export function Header5({ children, className, id }: HeaderProps) {
return (
<Heading as="h5" id={id} className={className} pt={3} pb={1} color="gray.700" size="sm">
<h5 id={id} className={`text-lg pt-3 pb-1 font-semibold text-neutral-800 ${className}`}>
{children}
</Heading>
</h5>
);
}

View File

@@ -1,9 +1,3 @@
import { Mark } from '@chakra-ui/react';
export function HiWord({ children }: { children: React.ReactNode }) {
return (
<Mark bg="orange.100" borderRadius={5} px={2} mx={1}>
{children}
</Mark>
);
export function HiWord({ className = '', children }: { className?: string; children: React.ReactNode }) {
return <mark className={`bg-orange-100 rounded-md px-2 mx-1 ${className}`}>{children}</mark>;
}

View File

@@ -1,13 +1,9 @@
import { chakra, css } from '@chakra-ui/react';
const cssUnselectable = css({ pointerEvents: 'none', userSelect: 'none' });
export function VQuote({ children }: { children: React.ReactNode }) {
return (
<>
<chakra.span css={cssUnselectable}></chakra.span>
<span className="select-none"></span>
{children}
<chakra.span css={cssUnselectable}></chakra.span>
<span className="select-none"></span>
</>
);
}

View File

@@ -13,21 +13,18 @@ export interface InstructionsTabsProps {
export function InstructionsTabs({ tabs }: InstructionsTabsProps) {
const id = useId();
return (
<div className="tabs tabs-lift h-[20rem] pb-4">
<div className="tabs tabs-lift max-h-[32rem] pb-4">
{tabs.map(({ id: _tabId, label, content }, index) => (
<Fragment key={_tabId}>
<label className="tab">
<input type="radio" name={id} defaultChecked={index === 0} />
{label}
</label>
<div className="tab-content border-base-300 bg-base-100 px-4 py-2 overflow-y-auto">{content}</div>
<div className="tab-content border-base-300 bg-base-100 px-4 py-2 overflow-y-auto max-h-[30rem]">
{content}
</div>
</Fragment>
))}
{/*<label className="tab">*/}
{/* <input type="radio" name={id} />a*/}
{/*</label>*/}
{/*<div className="tab-content border-base-300 bg-base-100 px-4 py-2 overflow-y-auto"></div>*/}
{/*<input type="radio" name={id} className="tab" aria-label="安卓" defaultChecked />*/}
</div>
);
}

View File

@@ -1,15 +1,12 @@
import { Icon, Kbd } from '@chakra-ui/react';
import { BsCommand } from 'react-icons/bs';
import { Ruby } from '../Ruby';
export function MacCommandKey() {
return (
<ruby>
<Kbd>
<Icon as={BsCommand} />
</Kbd>
<rp> (</rp>
<rt>command</rt>
<rp>)</rp>
</ruby>
<Ruby caption="command">
<kbd className="kbd">
<BsCommand className="text-sm" />
</kbd>
</Ruby>
);
}

View File

@@ -1,15 +1,12 @@
import { Icon, Kbd } from '@chakra-ui/react';
import { BsShift } from 'react-icons/bs';
import { Ruby } from '../Ruby';
export function ShiftKey() {
return (
<ruby>
<Kbd>
<Icon as={BsShift} />
</Kbd>
<rp> (</rp>
<rt>shift</rt>
<rp>)</rp>
</ruby>
<Ruby caption="shift">
<kbd className="kbd">
<BsShift className="text-sm" />
</kbd>
</Ruby>
);
}

View File

@@ -1,6 +1,6 @@
import { PiFileAudio } from 'react-icons/pi';
import { MdDelete, MdVpnKey } from 'react-icons/md';
import React from 'react';
import type { ReactNode } from 'react';
export interface KeyInputProps {
sequence: number;
@@ -12,24 +12,34 @@ export interface KeyInputProps {
onSetValue: (value: string) => void;
onDelete: () => void;
nameLabel?: React.ReactNode;
valueLabel?: React.ReactNode;
quality?: string;
onSetQuality?: (quality: string) => void;
nameLabel?: ReactNode;
valueLabel?: ReactNode;
qualityLabel?: ReactNode;
namePlaceholder?: string;
valuePlaceholder?: string;
qualityPlaceholder?: string;
}
export function KeyInput(props: KeyInputProps) {
const {
nameLabel,
valueLabel,
qualityLabel,
namePlaceholder,
qualityPlaceholder,
valuePlaceholder,
sequence,
name,
quality,
value,
onSetName,
onSetValue,
onDelete,
onSetQuality,
isValidKey,
} = props;
@@ -40,22 +50,39 @@ export function KeyInput(props: KeyInputProps) {
</div>
<div className="join join-vertical flex-1">
<label className="input w-full rounded-tl-md rounded-tr-md">
<span className="cucursor-default inline-flex items-center gap-1 select-none">
{nameLabel || (
<>
<PiFileAudio />
</>
)}
</span>
<input
type="text"
className="font-mono"
placeholder={namePlaceholder}
value={name}
onChange={(e) => onSetName(e.target.value)}
/>
</label>
<div className="flex">
<label className="input w-full rounded-tl-md last:rounded-tr-md">
<span className="cucursor-default inline-flex items-center gap-1 select-none">
{nameLabel || (
<>
<PiFileAudio />
</>
)}
</span>
<input
type="text"
className="font-mono"
placeholder={namePlaceholder}
value={name}
onChange={(e) => onSetName(e.target.value)}
data-name="key-input--name"
/>
</label>
{onSetQuality && (
<label className="input min-w-0 max-w-[10rem] ml-[-1px] rounded-tr-md">
<span className="cucursor-default inline-flex items-center gap-1 select-none">
{qualityLabel || '音质'}
</span>
<input
type="text"
className="font-mono"
placeholder={qualityPlaceholder}
value={quality}
onChange={(e) => onSetQuality(e.target.value)}
/>
</label>
)}
</div>
<label className="input w-full rounded-bl-md rounded-br-md mt-[-1px]">
<span className="cursor-default inline-flex items-center gap-1 select-none">
{valueLabel || (

View File

@@ -0,0 +1,21 @@
import type { ReactNode, RefObject } from 'react';
export interface KeyListContainerProps {
keys: unknown[];
children?: ReactNode;
ref?: RefObject<HTMLDivElement | null>;
}
export function KeyListContainer({ keys, children, ref }: KeyListContainerProps) {
const count = keys.length;
return (
<div ref={ref} className="flex grow min-h-0 overflow-auto pr-4 pt-3">
{count > 0 && (
<ul className="list bg-base-100 rounded-box shadow-md border border-base-300 w-full min-h-0 max-h-[30rem] overflow-auto">
{children}
</ul>
)}
{count === 0 && <p></p>}
</div>
);
}

View File

@@ -1,4 +1,4 @@
import { Link } from '@chakra-ui/react';
import { ExtLink } from './ExtLink';
export interface ProjectIssueProps {
id: number | string;
@@ -7,9 +7,9 @@ export interface ProjectIssueProps {
export function ProjectIssue({ id, title }: ProjectIssueProps) {
return (
<Link isExternal target="_blank" href={`https://git.unlock-music.dev/um/um-react/issues/${id}`}>
<ExtLink target="_blank" href={`https://git.unlock-music.dev/um/um-react/issues/${id}`}>
{`#${id}`}
{title && ` - ${title}`}
</Link>
</ExtLink>
);
}