mirror of
https://git.unlock-music.dev/um/um-react.git
synced 2025-05-23 16:27:41 +08:00
adjust dark mode and layout
All checks were successful
Build and Deploy / build (push) Successful in 2m2s
All checks were successful
Build and Deploy / build (push) Successful in 2m2s
This commit is contained in:
parent
6cb1f9f87f
commit
3ab73d8369
@ -48,6 +48,11 @@ export function AdbInstructionTemplate({ dir, file, platform }: AdbInstructionTe
|
||||
<SyntaxHighlighter language={language} style={hljsStyleGitHub}>
|
||||
{command}
|
||||
</SyntaxHighlighter>
|
||||
<br />※ 安卓模拟器可能需要额外操作,如
|
||||
<ExtLink className="text-nowrap" href="https://g.126.fm/04jewvw">
|
||||
网易 MuMu 模拟器
|
||||
</ExtLink>
|
||||
需要提前使用 <code>adb connect ...</code> 指令连接模拟器。详细请参考官方说明文档并调整上述脚本。
|
||||
</li>
|
||||
<li>
|
||||
提交当前目录下的 <code>{file}</code> 文件。
|
||||
|
@ -8,7 +8,7 @@ export interface HeaderProps {
|
||||
|
||||
export function Header3({ children, className, id }: HeaderProps) {
|
||||
return (
|
||||
<h3 id={id} className={`text-2xl pt-3 pb-1 font-bold border-b border-base-300 text-neutral-800 ${className}`}>
|
||||
<h3 id={id} className={`text-2xl pt-3 pb-1 font-bold border-b border-base-300 ${className}`}>
|
||||
{children}
|
||||
</h3>
|
||||
);
|
||||
@ -16,7 +16,7 @@ export function Header3({ children, className, id }: HeaderProps) {
|
||||
|
||||
export function Header4({ children, className, id }: HeaderProps) {
|
||||
return (
|
||||
<h4 id={id} className={`text-xl pt-3 pb-1 font-semibold text-neutral-800 ${className}`}>
|
||||
<h4 id={id} className={`text-xl pt-3 pb-1 font-semibold ${className}`}>
|
||||
{children}
|
||||
</h4>
|
||||
);
|
||||
@ -24,7 +24,7 @@ export function Header4({ children, className, id }: HeaderProps) {
|
||||
|
||||
export function Header5({ children, className, id }: HeaderProps) {
|
||||
return (
|
||||
<h5 id={id} className={`text-lg pt-3 pb-1 font-semibold text-neutral-800 ${className}`}>
|
||||
<h5 id={id} className={`text-lg pt-3 pb-1 font-semibold ${className}`}>
|
||||
{children}
|
||||
</h5>
|
||||
);
|
||||
|
@ -10,7 +10,7 @@ export function KugouFAQ() {
|
||||
<p>
|
||||
酷狗现在对部分用户推送了 <code>kgg</code> 加密格式(安卓、Windows 客户端)。
|
||||
</p>
|
||||
<p>根据平台不同,你需要提取密钥数据库。</p>
|
||||
<p className="my-4">根据平台不同,你需要提取密钥数据库。</p>
|
||||
|
||||
<div className="p-2 @container">
|
||||
<div className="alert alert-warning">
|
||||
|
@ -11,7 +11,7 @@ export function KuwoFAQ() {
|
||||
<>
|
||||
<Header4>解锁失败</Header4>
|
||||
<SegmentTryOfficialPlayer />
|
||||
<p>
|
||||
<p className="my-4">
|
||||
日前,仅<HiWord>手机客户端</HiWord>下载的
|
||||
<VQuote>
|
||||
<strong>至臻全景声</strong>
|
||||
@ -22,10 +22,10 @@ export function KuwoFAQ() {
|
||||
</VQuote>
|
||||
音质的音乐文件采用新版加密。
|
||||
</p>
|
||||
<p>其他音质目前不需要提取密钥。</p>
|
||||
<p>PC平台暂未推出使用新版加密的音质。</p>
|
||||
<p className="my-4">其他音质目前不需要提取密钥。</p>
|
||||
<p className="my-4">PC平台暂未推出使用新版加密的音质。</p>
|
||||
|
||||
<div className="alert alert-warning">
|
||||
<div className="alert alert-warning mb-4">
|
||||
<RiErrorWarningLine className="text-2xl" />
|
||||
<div>
|
||||
<p>安卓用户提取密钥需要 root 权限,或注入文件提供器。</p>
|
||||
|
@ -50,8 +50,8 @@ export function OtherFAQ() {
|
||||
对安卓设备获取 root 特权通常会破坏系统的完整性并导致部分功能无法使用。
|
||||
例如部分厂商的安卓设备会在解锁后丧失保修资格,或导致无法使用 NFC 移动支付等限制。
|
||||
</p>
|
||||
<p>如果希望不破坏系统完整性,你可以考虑在电脑上使用安卓模拟器。</p>
|
||||
<p>
|
||||
<p className="my-2">如果希望不破坏系统完整性,你可以考虑在电脑上使用安卓模拟器。</p>
|
||||
<p className="my-2">
|
||||
很多安卓模拟器都提供了 root 特权支持,可以很方便的启用,例如
|
||||
<VQuote>
|
||||
<ExtLink href="https://mumu.163.com/">网易 MuMu 模拟器(安卓 12,推荐)</ExtLink>
|
||||
|
@ -7,8 +7,8 @@ export function QQMusicFAQ() {
|
||||
<>
|
||||
<Header4>解锁失败</Header4>
|
||||
<SegmentTryOfficialPlayer />
|
||||
<p>重复下载同一首的歌曲不重复扣下载配额,但是同一首歌的两个版本会重复扣下载配额,请仔细分辨。</p>
|
||||
<p className="mb-2">
|
||||
<p className="my-4">重复下载同一首的歌曲不重复扣下载配额,但是同一首歌的两个版本会重复扣下载配额,请仔细分辨。</p>
|
||||
<p className="mb-4">
|
||||
部分平台获取的加密文件未包含密钥。选择你<strong>下载文件时</strong>使用的客户端来查看说明。
|
||||
</p>
|
||||
|
||||
|
@ -16,7 +16,7 @@ export function SegmentKeyImportInstructions({
|
||||
}: SegmentKeyImportInstructionsProps) {
|
||||
return (
|
||||
<>
|
||||
<p>导入密钥可以参考下面的步骤:</p>
|
||||
<p className="mt-2">导入密钥可以参考下面的步骤:</p>
|
||||
<ol className="list-decimal pl-5">
|
||||
<li>
|
||||
<SegmentTopNavSettings />
|
||||
@ -28,7 +28,7 @@ export function SegmentKeyImportInstructions({
|
||||
<SegmentAddKeyDropdown />
|
||||
</li>
|
||||
<li>
|
||||
<p>{keyInstructionText}</p>
|
||||
<p className="mb-2">{keyInstructionText}</p>
|
||||
{clientInstructions}
|
||||
</li>
|
||||
</ol>
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { RiErrorWarningLine } from 'react-icons/ri';
|
||||
|
||||
export function SegmentTryOfficialPlayer() {
|
||||
export function SegmentTryOfficialPlayer({ className = '' }: { className?: string }) {
|
||||
return (
|
||||
<div className="alert alert-warning">
|
||||
<div className={`alert alert-warning ${className}`}>
|
||||
<RiErrorWarningLine className="text-2xl" />
|
||||
<p>尝试用下载音乐的设备播放一次看看,如果官方客户端都无法播放,那解锁肯定会失败哦。</p>
|
||||
</div>
|
||||
|
@ -11,31 +11,23 @@ interface FileRowProps {
|
||||
export function FileRow({ id, file }: FileRowProps) {
|
||||
const dispatch = useAppDispatch();
|
||||
const isDecrypted = file.state === ProcessState.COMPLETE;
|
||||
const metadata = file.metadata;
|
||||
|
||||
const nameWithoutExt = file.fileName.replace(/\.[a-z\d]{3,6}$/, '');
|
||||
const decryptedName = nameWithoutExt + '.' + file.ext;
|
||||
const decryptedName = file.cleanName + '.' + file.ext;
|
||||
|
||||
return (
|
||||
<div className="card bg-base-100 shadow-sm w-full md:w-[30%] " data-testid="file-row">
|
||||
<div className="card-body items-center text-center px-2">
|
||||
<h2
|
||||
className="card-title overflow-hidden text-ellipsis block max-w-full whitespace-nowrap"
|
||||
data-testid="audio-meta-song-name"
|
||||
>
|
||||
{metadata?.name ?? nameWithoutExt}
|
||||
<h2 className="card-title max-w-full whitespace-nowrap flex gap-0" data-testid="audio-meta-song-name">
|
||||
<span className="grow overflow-hidden text-ellipsis" title={decryptedName}>
|
||||
{file.cleanName}
|
||||
</span>
|
||||
{isDecrypted && file.ext && <div className="ml-2 badge badge-accent">{file.ext}</div>}
|
||||
</h2>
|
||||
|
||||
<div className="w-full grow">
|
||||
{file.state === ProcessState.ERROR && <FileError error={file.errorMessage} code={file.errorCode} />}
|
||||
{isDecrypted && (
|
||||
<audio
|
||||
className="max-w-[100%]"
|
||||
aria-disabled={!file.decrypted}
|
||||
controls
|
||||
autoPlay={false}
|
||||
src={file.decrypted}
|
||||
/>
|
||||
<audio className="w-full" aria-disabled={!file.decrypted} controls autoPlay={false} src={file.decrypted} />
|
||||
)}
|
||||
</div>
|
||||
|
||||
|
@ -14,5 +14,5 @@ test('should be able to render a list of 3 items', () => {
|
||||
});
|
||||
|
||||
expect(screen.getAllByTestId('file-row')).toHaveLength(3);
|
||||
expect(screen.getByText('Für Alice')).toBeInTheDocument();
|
||||
expect(screen.getByText('ready')).toBeInTheDocument();
|
||||
});
|
||||
|
@ -3,17 +3,14 @@ import { untouchedFile } from './__fixture__/file-list';
|
||||
import { FileRow } from '../FileRow';
|
||||
import { completedFile } from './__fixture__/file-list';
|
||||
|
||||
test('should render no metadata when unavailable', () => {
|
||||
test('should render basic title (ready)', () => {
|
||||
renderWithProviders(<FileRow id="file://ready" file={untouchedFile} />);
|
||||
|
||||
expect(screen.getAllByTestId('file-row')).toHaveLength(1);
|
||||
expect(screen.getByTestId('audio-meta-song-name')).toHaveTextContent('ready');
|
||||
expect(screen.queryByTestId('audio-meta-album-name')).toBeFalsy();
|
||||
expect(screen.queryByTestId('audio-meta-song-artist')).toBeFalsy();
|
||||
expect(screen.queryByTestId('audio-meta-album-artist')).toBeFalsy();
|
||||
});
|
||||
|
||||
test('should render metadata when file has been processed', () => {
|
||||
test('should render basic title (done)', () => {
|
||||
renderWithProviders(<FileRow id="file://done" file={completedFile} />);
|
||||
|
||||
expect(screen.getAllByTestId('file-row')).toHaveLength(1);
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { DecryptedAudioFile, ProcessState } from '../../fileListingSlice';
|
||||
|
||||
export const untouchedFile: DecryptedAudioFile = {
|
||||
cleanName: 'ready',
|
||||
fileName: 'ready.bin',
|
||||
raw: 'blob://localhost/file-a',
|
||||
decrypted: '',
|
||||
@ -13,6 +14,7 @@ export const untouchedFile: DecryptedAudioFile = {
|
||||
|
||||
export const completedFile: DecryptedAudioFile = {
|
||||
fileName: 'hello-b.bin',
|
||||
cleanName: 'hello-b',
|
||||
raw: 'blob://localhost/file-b',
|
||||
decrypted: 'blob://localhost/file-b-decrypted',
|
||||
ext: 'flac',
|
||||
@ -30,6 +32,7 @@ export const completedFile: DecryptedAudioFile = {
|
||||
|
||||
export const fileWithError: DecryptedAudioFile = {
|
||||
fileName: 'hello-c.bin',
|
||||
cleanName: 'hello-c',
|
||||
raw: 'blob://localhost/file-c',
|
||||
decrypted: 'blob://localhost/file-c-decrypted',
|
||||
ext: 'flac',
|
||||
|
@ -5,9 +5,11 @@ import type { RootState } from '~/store';
|
||||
import { DECRYPTION_WORKER_ACTION_NAME, type DecryptionResult } from '~/decrypt-worker/constants';
|
||||
import type {
|
||||
DecryptCommandOptions,
|
||||
FetchMusicExNamePayload, ParseKugouHeaderPayload, ParseKugouHeaderResponse,
|
||||
FetchMusicExNamePayload,
|
||||
ParseKugouHeaderPayload,
|
||||
ParseKugouHeaderResponse,
|
||||
ParseKuwoHeaderPayload,
|
||||
ParseKuwoHeaderResponse
|
||||
ParseKuwoHeaderResponse,
|
||||
} from '~/decrypt-worker/types';
|
||||
import { decryptionQueue, workerClientBus } from '~/decrypt-worker/client';
|
||||
import { DecryptErrorType } from '~/decrypt-worker/util/DecryptError';
|
||||
@ -15,8 +17,9 @@ import {
|
||||
selectKugouKey,
|
||||
selectKWMv2Key,
|
||||
selectQMCv2KeyByFileName,
|
||||
selectQtfmAndroidKey
|
||||
selectQtfmAndroidKey,
|
||||
} from '../settings/settingsSelector';
|
||||
import { cleanFilename } from '~/util/cleanFilename';
|
||||
|
||||
export enum ProcessState {
|
||||
QUEUED = 'QUEUED',
|
||||
@ -40,6 +43,7 @@ export interface AudioMetadata {
|
||||
|
||||
export interface DecryptedAudioFile {
|
||||
fileName: string;
|
||||
cleanName: string;
|
||||
raw: string; // blob uri
|
||||
ext: string;
|
||||
decrypted: string; // blob uri
|
||||
@ -106,6 +110,7 @@ export const fileListingSlice = createSlice({
|
||||
addNewFile: (state, { payload }: PayloadAction<{ id: string; fileName: string; blobURI: string }>) => {
|
||||
state.files[payload.id] = {
|
||||
fileName: payload.fileName,
|
||||
cleanName: cleanFilename(payload.fileName),
|
||||
raw: payload.blobURI,
|
||||
decrypted: '',
|
||||
ext: '',
|
||||
|
@ -20,7 +20,7 @@ export function ResponsiveNav({
|
||||
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>
|
||||
<aside className={`bg-base-300 md:p-4 md:block ${navigationClassName}`}>{navigation}</aside>
|
||||
|
||||
{/* Main content */}
|
||||
<div className={`p-4 grow ${contentClassName}`}>{children}</div>
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { FC, Fragment } from 'react';
|
||||
import { ComponentType, Fragment } from 'react';
|
||||
import { Header3 } from '~/components/HelpText/Headers';
|
||||
import { KuwoFAQ } from '~/faq/KuwoFAQ';
|
||||
import { OtherFAQ } from '~/faq/OtherFAQ';
|
||||
@ -8,7 +8,7 @@ import { KugouFAQ } from '~/faq/KugouFAQ.tsx';
|
||||
type FAQEntry = {
|
||||
id: string;
|
||||
title: string;
|
||||
Help: FC;
|
||||
Help: ComponentType;
|
||||
};
|
||||
|
||||
const faqEntries: FAQEntry[] = [
|
||||
@ -20,10 +20,10 @@ const faqEntries: FAQEntry[] = [
|
||||
|
||||
export function FaqTab() {
|
||||
return (
|
||||
<section className="container pb-10">
|
||||
<section className="container pb-10 px-4">
|
||||
<h2 className="text-3xl font-bold text-center">常见问题解答</h2>
|
||||
<Header3>答疑目录</Header3>
|
||||
<ul className="list-disc list-inside">
|
||||
<ul className="list-disc pl-6">
|
||||
{faqEntries.map(({ id, title }) => (
|
||||
<li key={id}>
|
||||
<a className="link link-info no-underline" href={`#faq-${id}`}>
|
||||
|
6
src/util/cleanFilename.ts
Normal file
6
src/util/cleanFilename.ts
Normal file
@ -0,0 +1,6 @@
|
||||
export function cleanFilename(filename: string): string {
|
||||
return filename
|
||||
.replace(/\.[a-z\d]{3,6}$/, '') // Remove file extension
|
||||
.replace(/\.(mgg|kgg|mflac)\d*$/, '') // Remove extra mgg/kgg/mflac extension
|
||||
.replace(/\s?\[mq[a-z\d]*\]\s*$/, ''); // Remove " [mqms*]" suffix
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user