Compare commits

...

2 Commits

Author SHA1 Message Date
鲁树人
6cb1f9f87f fix: adjust layout for settings
All checks were successful
Build and Deploy / build (push) Successful in 1m26s
2025-05-18 11:05:50 +09:00
鲁树人
9518b813bd refactor: batch 4 2025-05-18 09:58:34 +09:00
21 changed files with 12195 additions and 1541 deletions

View File

@ -1,8 +1,8 @@
<!DOCTYPE html> <!doctype html>
<html lang="zh-cmn-Hans-CN"> <html lang="zh-cmn-Hans-CN">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<title>音乐解锁 - Unlock Music</title> <title>音乐解锁 - Unlock Music</title>
<meta name="description" content="音乐解锁 - Unlock Music" /> <meta name="description" content="音乐解锁 - Unlock Music" />
@ -10,6 +10,7 @@
<link rel="apple-touch-icon" href="/pwa-512x512.png" sizes="512x512" /> <link rel="apple-touch-icon" href="/pwa-512x512.png" sizes="512x512" />
<meta name="theme-color" content="#4DBA87" /> <meta name="theme-color" content="#4DBA87" />
</head> </head>
<body> <body>
<main id="root"></main> <main id="root"></main>
<script type="module" src="/src/main.tsx"></script> <script type="module" src="/src/main.tsx"></script>

11870
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -6,7 +6,7 @@
"scripts": { "scripts": {
"start": "vite", "start": "vite",
"build": "tsc -p tsconfig.prod.json && vite build && pnpm build:finalize", "build": "tsc -p tsconfig.prod.json && vite build && pnpm build:finalize",
"build:finalize": "node scripts/write-version.mjs && node scripts/minify-mjs.mjs", "build:finalize": "node scripts/write-version.mjs",
"lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0", "lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"format": "prettier -w .", "format": "prettier -w .",
"test": "vitest run", "test": "vitest run",
@ -17,17 +17,10 @@
"prepare": "simple-git-hooks" "prepare": "simple-git-hooks"
}, },
"dependencies": { "dependencies": {
"@chakra-ui/anatomy": "^2.3.4",
"@chakra-ui/icons": "^2.2.4",
"@chakra-ui/react": "^2.10.8",
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.0",
"@reduxjs/toolkit": "^2.8.2", "@reduxjs/toolkit": "^2.8.2",
"@unlock-music/crypto": "0.1.10", "@unlock-music/crypto": "0.1.10",
"classnames": "^2.5.1", "classnames": "^2.5.1",
"framer-motion": "^12.12.1",
"nanoid": "^5.1.5", "nanoid": "^5.1.5",
"next-themes": "^0.4.6",
"radash": "^12.1.0", "radash": "^12.1.0",
"react": "^19.1.0", "react": "^19.1.0",
"react-dom": "^19.1.0", "react-dom": "^19.1.0",
@ -37,9 +30,7 @@
"react-router": "^7.6.0", "react-router": "^7.6.0",
"react-syntax-highlighter": "^15.6.1", "react-syntax-highlighter": "^15.6.1",
"react-toastify": "^11.0.5", "react-toastify": "^11.0.5",
"sass": "^1.89.0", "sql.js": "^1.13.0"
"sql.js": "^1.13.0",
"workbox-build": "^7.3.0"
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.26.0", "@eslint/js": "^9.26.0",
@ -69,6 +60,7 @@
"lint-staged": "^16.0.0", "lint-staged": "^16.0.0",
"prettier": "^3.5.3", "prettier": "^3.5.3",
"rollup": "^4.40.2", "rollup": "^4.40.2",
"sass": "^1.89.0",
"simple-git-hooks": "^2.13.0", "simple-git-hooks": "^2.13.0",
"tailwindcss": "^4.1.7", "tailwindcss": "^4.1.7",
"terser": "^5.39.2", "terser": "^5.39.2",
@ -79,6 +71,7 @@
"vite-plugin-top-level-await": "^1.5.0", "vite-plugin-top-level-await": "^1.5.0",
"vite-plugin-wasm": "^3.4.1", "vite-plugin-wasm": "^3.4.1",
"vitest": "^3.1.3", "vitest": "^3.1.3",
"workbox-build": "^7.3.0",
"workbox-window": "^7.3.0" "workbox-window": "^7.3.0"
}, },
"lint-staged": { "lint-staged": {

1190
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -12,11 +12,12 @@ import { Footer } from '~/components/Footer';
import { FaqTab } from '~/tabs/FaqTab'; import { FaqTab } from '~/tabs/FaqTab';
import { SETTINGS_TABS } from '~/features/settings/settingsTabs'; import { SETTINGS_TABS } from '~/features/settings/settingsTabs';
import { Bounce, ToastContainer } from 'react-toastify'; import { Bounce, ToastContainer } from 'react-toastify';
import { SettingsHome } from '~/features/settings/SettingsHome';
// Private to this file only. // Private to this file only.
const store = setupStore(); const store = setupStore();
const tabClassNames = ({ isActive }: { isActive: boolean }) => `tab ${isActive ? 'tab-active' : ''}`; const tabClassNames = ({ isActive }: { isActive: boolean }) => `mb-[-2px] tab ${isActive ? 'tab-active' : ''}`;
export function AppRoot() { export function AppRoot() {
useEffect(() => persistSettings(store), []); useEffect(() => persistSettings(store), []);
@ -24,7 +25,7 @@ export function AppRoot() {
return ( return (
<BrowserRouter> <BrowserRouter>
<Provider store={store}> <Provider store={store}>
<div role="tablist" className="tabs tabs-border w-full justify-center"> <div role="tablist" className="tabs tabs-border w-full justify-center border-b-2 border-base-200 box-content">
<NavLink to="/" role="tab" className={tabClassNames}> <NavLink to="/" role="tab" className={tabClassNames}>
<MdHome /> <MdHome />
@ -39,10 +40,11 @@ export function AppRoot() {
</NavLink> </NavLink>
</div> </div>
<main className="flex-1 flex justify-center"> <main className="flex-1 flex justify-center min-h-0 overflow-auto">
<Routes> <Routes>
<Route path="/" Component={MainTab} /> <Route path="/" Component={MainTab} />
<Route path="/settings" Component={SettingsTab}> <Route path="/settings" Component={SettingsTab}>
<Route index Component={SettingsHome} />
{Object.entries(SETTINGS_TABS).map(([key, { Tab }]) => ( {Object.entries(SETTINGS_TABS).map(([key, { Tab }]) => (
<Route key={key} path={key} Component={Tab} /> <Route key={key} path={key} Component={Tab} />
))} ))}

View File

@ -1,9 +1,9 @@
import { BsCommand } from 'react-icons/bs'; import { BsCommand } from 'react-icons/bs';
import { Ruby } from '../Ruby'; import { Ruby } from '../Ruby';
export function MacCommandKey() { export function MacCommandKey({ className }: { className?: string }) {
return ( return (
<Ruby caption="command"> <Ruby caption="command" className={className}>
<kbd className="kbd"> <kbd className="kbd">
<BsCommand className="text-sm" /> <BsCommand className="text-sm" />
</kbd> </kbd>

View File

@ -1,9 +1,9 @@
import { BsShift } from 'react-icons/bs'; import { BsShift } from 'react-icons/bs';
import { Ruby } from '../Ruby'; import { Ruby } from '../Ruby';
export function ShiftKey() { export function ShiftKey({ className }: { className?: string }) {
return ( return (
<Ruby caption="shift"> <Ruby caption="shift" className={className}>
<kbd className="kbd"> <kbd className="kbd">
<BsShift className="text-sm" /> <BsShift className="text-sm" />
</kbd> </kbd>

View File

@ -9,9 +9,9 @@ export interface KeyListContainerProps {
export function KeyListContainer({ keys, children, ref }: KeyListContainerProps) { export function KeyListContainer({ keys, children, ref }: KeyListContainerProps) {
const count = keys.length; const count = keys.length;
return ( return (
<div ref={ref} className="flex grow min-h-0 overflow-auto pr-4 pt-3"> <div ref={ref} className="flex grow min-h-0 pr-4 pt-3">
{count > 0 && ( {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"> <ul className="list bg-base-100 rounded-box shadow-sm border border-base-300 w-full min-h-0 overflow-auto">
{children} {children}
</ul> </ul>
)} )}

View File

@ -1,27 +0,0 @@
import { Grid, chakra } from '@chakra-ui/react';
export const FileRowResponsiveGrid = chakra(Grid, {
baseStyle: {
gridTemplateAreas: {
base: `
"cover"
"title"
"meta"
"action"
`,
md: `
"cover title action"
"cover meta action"
`,
},
gridTemplateRows: {
base: 'repeat(auto-fill)',
md: 'min-content 1fr',
},
gridTemplateColumns: {
base: '1fr',
md: '160px 1fr',
},
gap: 3,
},
});

View File

@ -0,0 +1,29 @@
export interface ResponsiveNavProps {
navigationClassName?: string;
navigation?: React.ReactNode;
className?: string;
contentClassName?: string;
children?: React.ReactNode;
}
export function ResponsiveNav({
className = '',
navigationClassName = '',
contentClassName = '',
children,
navigation,
}: ResponsiveNavProps) {
return (
<div
className={`@container/nav grow grid grid-cols-1 grid-rows-[auto_1fr] md:grid-rows-1 md:grid-cols-[10rem_1fr] ${className}`}
>
{/* Sidebar */}
<aside className={`bg-gray-100 md:p-4 md:block ${navigationClassName}`}>{navigation}</aside>
{/* Main content */}
<div className={`p-4 grow ${contentClassName}`}>{children}</div>
</div>
);
}

View File

@ -5,6 +5,8 @@ import { NavLink, Outlet } from 'react-router';
import { SETTINGS_TABS } from '~/features/settings/settingsTabs.tsx'; import { SETTINGS_TABS } from '~/features/settings/settingsTabs.tsx';
import { MdOutlineSettingsBackupRestore } from 'react-icons/md'; import { MdOutlineSettingsBackupRestore } from 'react-icons/md';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import { ResponsiveNav } from '../nav/ResponsiveNav';
import classNames from 'classnames';
export function Settings() { export function Settings() {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
@ -25,23 +27,37 @@ export function Settings() {
}; };
const isSettingsNotSaved = useAppSelector(selectIsSettingsNotSaved); const isSettingsNotSaved = useAppSelector(selectIsSettingsNotSaved);
const tabClassNames = ({ isActive }: { isActive: boolean }) =>
classNames(
'link inline-flex text-nowrap mb-[-2px] no-underline w-full',
'border-b-2 md:border-b-0 md:border-r-2',
'tab md:grow',
{
'tab-active bg-accent/10 border-accent': isActive,
},
);
return ( return (
<div className="flex flex-col flex-1 container w-full"> <div className="flex flex-col flex-1 container w-full">
<div role="tablist" className="tabs tabs-border w-full justify-center gap-2"> <ResponsiveNav
className="grow h-full overflow-auto"
contentClassName="flex flex-col overflow-auto"
navigationClassName="overflow-x-auto pb-[2px] md:pb-0 h-full md:items-center [&]:md:flex"
navigation={
<div role="tablist" className="tabs gap-1 flex-nowrap md:flex-col grow items-center">
{Object.entries(SETTINGS_TABS).map(([id, { name }]) => ( {Object.entries(SETTINGS_TABS).map(([id, { name }]) => (
<NavLink className="link link-neutral" key={id} to={`/settings/${id}`} role="tab"> <NavLink className={tabClassNames} key={id} to={`/settings/${id}`} role="tab">
{name} {name}
</NavLink> </NavLink>
))} ))}
</div> </div>
}
{/* TODO: ensure this flex div does not overflow */} >
<div className="flex flex-1 flex-col h-full overflow-auto">
<Outlet /> <Outlet />
</div> </ResponsiveNav>
<footer className="flex flex-row gap-2 w-full py-3"> <footer className="flex flex-row gap-2 w-full p-2 border-t border-base-200 bg-base-100">
<div className="grow"> <div className="grow inline-flex items-center">
{isSettingsNotSaved ? ( {isSettingsNotSaved ? (
<span> <span>
<span className="text-red-600"></span> <span className="text-red-600"></span>
@ -64,39 +80,6 @@ export function Settings() {
</button> </button>
</div> </div>
</footer> </footer>
{/* <VStack mt="4" alignItems="flex-start" w="full">*/}
{/* <Flex flexDir="row" gap="2" w="full">*/}
{/* <Center>*/}
{/* {isSettingsNotSaved ? (*/}
{/* <Box color="gray">*/}
{/* 有未储存的更改{' '}*/}
{/* <chakra.span color="red" wordBreak="keep-all">*/}
{/* 设定将在保存后生效*/}
{/* </chakra.span>*/}
{/* </Box>*/}
{/* ) : (*/}
{/* <Box color="gray">设定将在保存后生效</Box>*/}
{/* )}*/}
{/* </Center>*/}
{/* <Spacer />*/}
{/* <HStack gap="2" justifyContent="flex-end">*/}
{/* <IconButton*/}
{/* icon={<Icon as={MdOutlineSettingsBackupRestore} />}*/}
{/* onClick={handleResetSettings}*/}
{/* colorScheme="red"*/}
{/* variant="ghost"*/}
{/* title="放弃未储存的更改,将设定还原未储存前的状态。"*/}
{/* aria-label="放弃未储存的更改"*/}
{/* />*/}
{/* <Button onClick={handleApplySettings}>保存</Button>*/}
{/* </HStack>*/}
{/* </Flex>*/}
{/* </VStack>*/}
{/* </Flex>*/}
{/* ))}*/}
{/* </TabPanels>*/}
{/*</Tabs>*/}
</div> </div>
); );
} }

View File

@ -0,0 +1,8 @@
export function SettingsHome() {
return (
<div className="flex flex-col gap-4">
<h1 className="text-2xl font-bold"></h1>
<p></p>
</div>
);
}

View File

@ -26,9 +26,7 @@ export function InstructionsPC() {
</p> </p>
<p></p> <p></p>
<p className="flex items-center gap-1">
<FilePathBlock>{DB_PATH}</FilePathBlock> <FilePathBlock>{DB_PATH}</FilePathBlock>
</p>
<h3 className="font-bold text-xl mt-4"></h3> <h3 className="font-bold text-xl mt-4"></h3>
<ol className="list-decimal pl-6"> <ol className="list-decimal pl-6">

View File

@ -56,10 +56,7 @@ export function PanelQingTing() {
return ( return (
<div className="min-h-0 flex-col grow px-1"> <div className="min-h-0 flex-col grow px-1">
<h2 className="text-2xl font-bold mb-4"> <h2 className="text-2xl font-bold mb-4"> FM</h2>
<VQuote> FM</VQuote>
</h2>
<p> <p>
<VQuote> FM</VQuote> <VQuote> FM</VQuote>
@ -78,7 +75,7 @@ export function PanelQingTing() {
value={secretKey} value={secretKey}
onChange={handleDataInput} onChange={handleDataInput}
/> />
<p className="label"> <p className="label flex-wrap">
<ExtLink href={QTFM_DEVICE_ID_URL}> <ExtLink href={QTFM_DEVICE_ID_URL}>
<code>qtfm-device-id</code> <code>qtfm-device-id</code>
@ -94,8 +91,15 @@ export function PanelQingTing() {
<p> <p>
<VQuote> <VQuote>
<code> <code className="break-words">
<HiWord>[]</HiWord>/Android/data/fm.qingting.qtradio/files/Music/ <HiWord>[]</HiWord>/<wbr />
Android/
<wbr />
data/
<wbr />
fm.qingting.qtradio/
<wbr />
files/Music/
</code> </code>
</VQuote> </VQuote>
</p> </p>

View File

@ -1,51 +1,36 @@
import {
Accordion,
AccordionButton,
AccordionIcon,
AccordionItem,
AccordionPanel,
Box,
Heading,
Text,
} from '@chakra-ui/react';
import { InstructionsIOSCondition } from './InstructionsIOSCondition'; import { InstructionsIOSCondition } from './InstructionsIOSCondition';
import { useId } from 'react';
export function InstructionsIOS() { export function InstructionsIOS() {
const iosInstructionId = useId();
return ( return (
<> <>
<Box> <div>
<Text>iOS 使 PC Mac iOS </Text> <p>iOS 使 PC Mac iOS </p>
<Text> PC Mac </Text> <p> PC Mac </p>
</Box> </div>
<Accordion allowToggle mt="2">
<AccordionItem>
<Heading as="h3" size="md">
<AccordionButton>
<Box as="span" flex="1" textAlign="left">
iOS
</Box>
<AccordionIcon />
</AccordionButton>
</Heading>
<AccordionPanel pb={4}>
<InstructionsIOSCondition jailbreak={true} />
</AccordionPanel>
</AccordionItem>
<AccordionItem> <div className="join join-vertical bg-base-100 mt-2 max-w-full">
<Heading as="h3" size="md"> <div className="collapse collapse-arrow join-item border-base-300 border">
<AccordionButton> <input type="radio" name={iosInstructionId} />
<Box as="span" flex="1" textAlign="left"> <div className="collapse-title font-semibold">
iOS iOS <strong></strong>{' '}
</Box> </div>
<AccordionIcon /> <div className="collapse-content text-sm min-w-0">
</AccordionButton> <InstructionsIOSCondition jailbreak={true} />
</Heading> </div>
<AccordionPanel pb={4}> </div>
<div className="collapse collapse-arrow join-item border-base-300 border">
<input type="radio" name={iosInstructionId} />
<div className="collapse-title font-semibold">
iOS <strong></strong>
</div>
<div className="collapse-content text-sm min-w-0">
<InstructionsIOSCondition jailbreak={false} /> <InstructionsIOSCondition jailbreak={false} />
</AccordionPanel> </div>
</AccordionItem> </div>
</Accordion> </div>
</> </>
); );
} }

View File

@ -1,6 +1,6 @@
import { Box, Code, Heading, Image, ListItem, OrderedList, Text } from '@chakra-ui/react';
import iosAllowBackup from './iosAllowBackup.webp'; import iosAllowBackup from './iosAllowBackup.webp';
import { FilePathBlock } from '~/components/FilePathBlock'; import { FilePathBlock } from '~/components/FilePathBlock';
import { HiWord } from '~/components/HelpText/HiWord';
const EXAMPLE_MEDIA_ID = '0011wjLv1bIkvv'; const EXAMPLE_MEDIA_ID = '0011wjLv1bIkvv';
const EXAMPLE_NAME_IOS = '333407709-0011wjLv1bIkvv-1.mgalaxy'; const EXAMPLE_NAME_IOS = '333407709-0011wjLv1bIkvv-1.mgalaxy';
@ -10,92 +10,77 @@ export function InstructionsIOSCondition({ jailbreak }: { jailbreak: boolean })
const useJailbreak = jailbreak; const useJailbreak = jailbreak;
const useBackup = !jailbreak; const useBackup = !jailbreak;
const pathPrefix = jailbreak ? '/var/mobile/Containers/Data/Application/<随机>/' : '/AppDomain-'; const pathPrefix = jailbreak ? (
<>
/var/mobile/Containers/Data/Application/<HiWord className="text-nowrap">[]</HiWord>/
</>
) : (
'/AppDomain-'
);
return ( return (
<> <>
<Heading as="h3" size="md"> <h4 className="text-lg font-semibold"></h4>
<ol className="list-decimal pl-4">
</Heading>
<OrderedList>
{useBackup && ( {useBackup && (
<ListItem> <li>
<Text> iOS </Text> iOS
<Image src={iosAllowBackup}></Image> <br />
</ListItem> <img src={iosAllowBackup}></img>
</li>
)} )}
{useBackup && ( {useBackup && <li>使 iOS </li>}
<ListItem> <li>
<Text>使 iOS </Text> {useBackup && <span></span>}
</ListItem> {useJailbreak && <span>访</span>}
)}
<ListItem>
{useBackup && <Text></Text>}
{useJailbreak && <Text>访</Text>}
<FilePathBlock>{pathPrefix}com.tencent.QQMusic/Documents/mmkv/</FilePathBlock> <FilePathBlock>{pathPrefix}com.tencent.QQMusic/Documents/mmkv/</FilePathBlock>
</ListItem> </li>
<ListItem> <li>
<Text> <code>filenameEkeyMap</code>
<Code>filenameEkeyMap</Code> </li>
</Text> <li>
</ListItem> <code>filenameEkeyMap</code>
<ListItem> </li>
<Text> <li></li>
<Code>filenameEkeyMap</Code> </ol>
</Text>
</ListItem>
<ListItem>
<Text></Text>
</ListItem>
</OrderedList>
<Heading as="h3" size="md" mt="3"> <h3 className="text-lg font-semibold mt-3">线</h3>
线 <section>
</Heading> <p>访</p>
<Box>
<Text>访</Text>
<FilePathBlock> <FilePathBlock>
{pathPrefix}com.tencent.QQMusic/Library/Application Support/com.tencent.QQMusic/iData/iMusic {pathPrefix}com.tencent.QQMusic/Library/Application Support/com.tencent.QQMusic/iData/iMusic
</FilePathBlock> </FilePathBlock>
<Text> <p>
<Code>[].m[]</Code> <code>[].m[]</code>
</Text> </p>
<Text> <p>
<Code>[song_id]-[mid]-[].m[]</Code> <code>[song_id]-[mid]-[].m[]</code>
</Text> </p>
<Text> <p>
&#x3000;<Code>{EXAMPLE_NAME_IOS}</Code> &#x3000;<code>{EXAMPLE_NAME_IOS}</code>
</Text> </p>
</Box> </section>
<Heading as="h3" size="md" mt="3"> <h4 className="text-lg font-semibold mt-3">线</h4>
线 <p>使</p>
</Heading> <p> </p>
<Text>使</Text> <ol className="list-decimal pl-4 mt-1">
<Text> </Text> <li>
<OrderedList> <code>[mid]</code> <code>{EXAMPLE_MEDIA_ID}</code>
<ListItem> </li>
<Text> <li>
<Code>[mid]</Code> <Code>{EXAMPLE_MEDIA_ID}</Code> <code>{EXAMPLE_NAME_DB}</code>
</Text> </li>
</ListItem> <li>
<ListItem>
<Text> <br />
<Code>{EXAMPLE_NAME_DB}</Code> <code>{EXAMPLE_NAME_IOS}</code>
</Text> <br /> <code>{EXAMPLE_NAME_DB}</code>
</ListItem> </li>
<ListItem> <li>
<Text> <code>{EXAMPLE_NAME_DB}</code>
<Code display="inline">{EXAMPLE_NAME_IOS}</Code> </li>
<Code display="inline">{EXAMPLE_NAME_DB}</Code> </ol>
</Text>
</ListItem>
<ListItem>
<Text>
<Code>{EXAMPLE_NAME_DB}</Code>
</Text>
</ListItem>
</OrderedList>
</> </>
); );
} }

View File

@ -1,59 +1,79 @@
import { Heading, Text, Code, OrderedList, ListItem, Link } from '@chakra-ui/react'; import { RiFileCopyLine } from 'react-icons/ri';
import { toast } from 'react-toastify';
import { ExtLink } from '~/components/ExtLink';
import { FilePathBlock } from '~/components/FilePathBlock'; import { FilePathBlock } from '~/components/FilePathBlock';
import { VQuote } from '~/components/HelpText/VQuote';
import { MacCommandKey } from '~/components/Key/MacCommandKey'; import { MacCommandKey } from '~/components/Key/MacCommandKey';
import { ShiftKey } from '~/components/Key/ShiftKey'; import { ShiftKey } from '~/components/Key/ShiftKey';
const MAC_CLIENT_URL = const MAC_CLIENT_URL =
'https://web.archive.org/web/20230903/https://dldir1.qq.com/music/clntupate/mac/QQMusicMac_Mgr.dmg'; 'https://web.archive.org/web/20230903/https://dldir1.qq.com/music/clntupate/mac/QQMusicMac_Mgr.dmg';
const MAC_CLIENT_TG_URL = 'https://t.me/um_lsr_ch/21';
const DB_PATH =
'~/Library/Containers/com.tencent.QQMusicMac/Data/Library/Application Support/QQMusicMac/mmkv/MMKVStreamEncryptId';
export function InstructionsMac() { export function InstructionsMac() {
const copyDbPathToClipboard = () => {
navigator.clipboard
.writeText(DB_PATH)
.then(() => {
toast.success('已复制到剪贴板');
})
.catch((err) => {
toast.error(`复制失败,请手动复制\n${err}`);
});
};
return ( return (
<> <>
<Text>Mac 使 mmkv </Text> <p>Mac 使 mmkv </p>
<Text> <p> v8.8.0 </p>
{'此外,你需要降级到 '}
<Link isExternal href={MAC_CLIENT_URL}>
2023.09.03
</Link>
{'。'}
mmkv
</Text>
<Text></Text>
<FilePathBlock>
~/Library/Containers/com.tencent.QQMusicMac/Data/Library/Application Support/QQMusicMac/mmkv/MMKVStreamEncryptId
</FilePathBlock>
<Heading as="h3" size="md" mt="4"> <p className="mt-4"> QQ Mac 8.8.0:</p>
<ul className="list-disc pl-6">
</Heading> <li>
<OrderedList> <ExtLink className="link-info" href={MAC_CLIENT_URL}>
<ListItem> <code>Archive.org</code>
<Text> </ExtLink>
<Code>MMKVStreamEncryptId</Code> </li>
</Text> <li>
</ListItem> <ExtLink className="link-info" href={MAC_CLIENT_TG_URL}>
<ListItem> Telegram
<Text></Text> </ExtLink>
</ListItem> </li>
<ListItem> </ul>
<Text>
<p className="mt-4"></p>
<ShiftKey /> <FilePathBlock>{DB_PATH}</FilePathBlock>
{' + '}
<MacCommandKey /> <h4 className="font-bold text-lg mt-4"></h4>
{' + '} <ol className="list-decimal pl-6">
<kbd className="kbd">{'G'}</kbd> <li>
</Text> <button className="btn btn-sm btn-outline btn-accent mr-2" onClick={copyDbPathToClipboard}>
</ListItem> <RiFileCopyLine className="text-xl" />
<ListItem> <span></span>
<Text> </button>
<Code>MMKVStreamEncryptId</Code> <code>MMKVStreamEncryptId</code>
</Text> </li>
</ListItem> <li>
<ListItem> <VQuote></VQuote><VQuote></VQuote>
<Text></Text> </li>
</ListItem> <li>
</OrderedList>
<VQuote>
<ShiftKey className="mx-1" />
{'+'}
<MacCommandKey className="mx-1" />
{'+'}
<kbd className="kbd mx-1">G</kbd>
</VQuote>
<VQuote></VQuote>
</li>
<li>
<code>MMKVStreamEncryptId</code>
</li>
<li></li>
</ol>
</> </>
); );
} }

View File

@ -3,6 +3,9 @@ import { HiWord } from '~/components/HelpText/HiWord';
import NoopExecutable from './assets/noop.exe?base64'; import NoopExecutable from './assets/noop.exe?base64';
import NoopExecutableSource from './assets/noop.asm.txt?base64'; import NoopExecutableSource from './assets/noop.asm.txt?base64';
const PC_CLIENT_URL = 'https://web.archive.org/web/2023/https://dldir1v6.qq.com/music/clntupate/QQMusic_Setup_1951.exe';
const PC_CLIENT_TG_URL = 'https://t.me/um_lsr_ch/24';
export function InstructionsPC() { export function InstructionsPC() {
return ( return (
<> <>
@ -23,33 +26,35 @@ export function InstructionsPC() {
</p> </p>
<ul className="list-disc pl-6"> <ul className="list-disc pl-6">
<li> <li>
<ExtLink href="https://web.archive.org/web/2023/https://dldir1v6.qq.com/music/clntupate/QQMusic_Setup_1951.exe"> <ExtLink className="link-info" href={PC_CLIENT_URL}>
<code>Archive.org</code> <code>Archive.org</code>
</ExtLink> </ExtLink>
</li> </li>
<li> <li>
<ExtLink href="https://t.me/um_lsr_ch/24"> Telegram </ExtLink> <ExtLink className="link-info" href={PC_CLIENT_TG_URL}>
Telegram
</ExtLink>
</li> </li>
</ul> </ul>
<p className="mt-4"> <p className="mt-4">
QQ QQ
<a <a
className="link px-1" className="link link-info mx-1"
download="QQMusicUp.exe" download="QQMusicUp.exe"
href={`data:application/vnd.microsoft.portable-executable;base64,${NoopExecutable}`} href={`data:application/vnd.microsoft.portable-executable;base64,${NoopExecutable}`}
> >
<code>QQMusicUp.exe</code> <code>QQMusicUp.exe</code>
</a> </a>
<a <a
className="link px-1" className="link"
download="QQMusicUp.asm" download="QQMusicUp.asm"
href={`data:text/x-asm;charset=utf-8;base64,${NoopExecutableSource}`} href={`data:text/x-asm;charset=utf-8;base64,${NoopExecutableSource}`}
> >
</a> </a>
</p> </p>
<p className="mt-2">使</p> <p className="mt-2">使</p>
</> </>

View File

@ -1,61 +0,0 @@
import { extendTheme } from '@chakra-ui/react';
import { tabsTheme } from './themes/Tabs';
export const theme = extendTheme({
fonts: {
body: [
'-system-ui,-apple-system,BlinkMacSystemFont',
'Source Han Sans CN,Noto Sans CJK SC',
'Segoe UI,Helvetica,Arial,sans-serif',
'Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol',
].join(','),
mono: [
'SFMono-Regular,Menlo,Monaco',
'"Sarasa Mono CJK SC"',
'Consolas,"Liberation Mono","Courier New",monospace',
'"Microsoft YaHei UI"',
].join(','),
},
components: {
Button: {
baseStyle: {
fontWeight: 'normal',
},
defaultProps: {
colorScheme: 'teal',
},
},
Tabs: tabsTheme,
Link: {
baseStyle: {
color: 'blue.600',
},
},
Text: {
baseStyle: {
mt: 1,
},
},
Header: {
baseStyle: {
mt: 3,
},
},
},
styles: {
global: {
'#root': {
minHeight: '100vh',
maxHeight: '100vh',
display: 'flex',
flexDirection: 'column',
},
},
},
sizes: {
footer: {
container: '5rem',
content: '4rem',
},
},
});

View File

@ -1,74 +0,0 @@
import { tabsAnatomy } from '@chakra-ui/anatomy';
import { createMultiStyleConfigHelpers, cssVar } from '@chakra-ui/react';
const $fg = cssVar('tabs-color');
const $bg = cssVar('tabs-bg');
const { definePartsStyle, defineMultiStyleConfig } = createMultiStyleConfigHelpers(tabsAnatomy.keys);
const variantLineInvert = definePartsStyle((props) => {
const { colorScheme: c, orientation } = props;
const isVertical = orientation === 'vertical';
const borderProp = isVertical ? 'borderEnd' : 'borderTop';
const marginProp = isVertical ? 'marginEnd' : 'marginTop';
return {
tablist: {
[borderProp]: '2px solid',
borderColor: 'inherit',
},
tabpanels: {
flex: 1,
minH: 0,
},
tabpanel: {
padding: 0,
},
tab: {
[borderProp]: '2px solid',
borderColor: 'transparent',
[marginProp]: '-2px',
justifyContent: 'flex-end',
_selected: {
[$fg.variable]: `colors.${c}.600`,
_dark: {
[$fg.variable]: `colors.${c}.300`,
},
borderColor: 'currentColor',
},
_active: {
[$bg.variable]: 'colors.gray.200',
_dark: {
[$bg.variable]: 'colors.whiteAlpha.300',
},
},
_disabled: {
_active: { bg: 'none' },
},
color: $fg.reference,
bg: $bg.reference,
},
root: {
display: 'flex',
flexDir: isVertical ? 'row' : 'column',
gap: 8,
minH: 0,
},
};
});
export const tabsTheme = defineMultiStyleConfig({
baseStyle: {
tablist: {
userSelect: 'none',
},
tabpanel: {
minHeight: 0,
overflow: 'auto',
maxHeight: '100%',
},
},
variants: {
'line-i': variantLineInvert,
},
});

View File

@ -96,13 +96,14 @@ export default defineConfig({
}, },
}, },
build: { build: {
minify: true,
rollupOptions: { rollupOptions: {
output: { output: {
manualChunks: { manualChunks: {
reacts: ['react', 'react-dom', 'react-dropzone', 'react-redux', '@reduxjs/toolkit'], core: ['react', 'react-dom'],
chakra: ['@chakra-ui/react', '@emotion/react', '@emotion/styled', 'framer-motion'], router: ['react-router'],
icons: ['react-icons', '@chakra-ui/icons'], store: ['react-redux', '@reduxjs/toolkit'],
utility: ['radash', 'nanoid', 'react-syntax-highlighter'], extras: ['react-dropzone', 'react-toastify'],
}, },
}, },
}, },