From 94224421a365d973c515901ec2b23d80e52fe131 Mon Sep 17 00:00:00 2001 From: eafonyang Date: Fri, 3 Jul 2026 11:11:10 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E9=A6=96=E9=A1=B5=E5=9B=BE?= =?UTF-8?q?=E6=A0=87=EF=BC=8C=E7=94=A8=20lucide-react=20=E4=BB=A3=E6=9B=BF?= =?UTF-8?q?=E4=BA=86=E8=93=9D=E7=89=99=E6=A8=A1=E5=9D=97=E7=9A=84=E5=9B=BE?= =?UTF-8?q?=E6=A0=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/public/images/player/music.png | Bin 7075 -> 0 bytes client/public/images/player/pause.png | Bin 673 -> 0 bytes client/public/images/player/play.png | Bin 1143 -> 0 bytes client/public/images/player/skip-back.png | Bin 1166 -> 0 bytes client/public/images/player/skip-forward.png | Bin 1168 -> 0 bytes client/src/pages/EQPage.tsx | 46 +++++-- client/src/pages/Home.tsx | 34 +++-- .../components/PeqOverwriteConfirmDialog.tsx | 127 ++++++++++-------- .../eq/components/PeqPresetManageDialog.tsx | 60 +++++++-- package.json | 2 +- pnpm-lock.yaml | 18 +-- 11 files changed, 183 insertions(+), 104 deletions(-) delete mode 100644 client/public/images/player/music.png delete mode 100644 client/public/images/player/pause.png delete mode 100644 client/public/images/player/play.png delete mode 100644 client/public/images/player/skip-back.png delete mode 100644 client/public/images/player/skip-forward.png diff --git a/client/public/images/player/music.png b/client/public/images/player/music.png deleted file mode 100644 index f88fe9e16541db70f89c0aa915a9f1728a691a42..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7075 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 z 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 S@+0Hohi%iI3gcKQ`{Dn{xR>$( diff --git a/client/public/images/player/skip-forward.png b/client/public/images/player/skip-forward.png deleted file mode 100644 index 823bcbb5a462f23907c9a7000916d27fae2c85b5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1168 zcmb7^`%hbS7{=f4x2JGuDU=outZPPB%0;b$ftq3_Em*Kzq->IL>Tnc-kjc!-McmBN zoU+IPXO6gKlbdugB%mqdCU}Wt$FW%jq%#;sHgzox!zB`nnM*Sf?EZv3zr3IKdGh4_ z?d>Wrds!kzP&q4{UuuS{t?Mv zNail~?{E${-4y!G3fFugb{rBH{IPGKOfHbLgfAJ`qz<96nlr?UnOvpAOo69myT%Gi zkDqNo_GphmC}w7qI~7{E)wI1l0jplWWdYlgJO-VZxvAfd*TPK8_AwgV8^Lf)d$xw_ zP_Spt@)P#udR&7-UdL8<2zd*4C49mXb44QtPj!#5ymv0ci$~_SrfB#_fBIP(F6bSO z33$pL`n3ktZG=wo_=kD_RJ*YIqd-H7SS4q@g_M!vTnRKxwS@k(p~c0msq@0jWY&^Q z@yC@0i4~^oAtjG{oPiA$Wvq*Fow35f9LEde@M%-1wGNrqLiZMNC-2Yd6lV4X8dPFi zJZk}pCpni&%M2=WzoTJR@4%D5-3>-9V*R;5nqKT9V_ZcvjOI8}Yta{L!QO7+jrKsp zA&TD@v1ctxr6Z8JLN$nHih0S=IV&>SVbyS%9n=^ooja`2oxTL}PFF~2> zzSxNLj#W)1l${t}zu%_#FS@7TG7NNq`&h%O<_ zW|$d|Fc-qn{aCXf#wH`oMtC4Ysj2V`EhsI9*TS3(qgFww2{<(_H3C;Bkq*Fp6(&+( zmKG2inC${5$&)LPNAhMhZ^M~=I^Kw-?~$1dm}(_AdcmZ@t!g-E6|}10=PrnpF?(o% z(ggKtB0CkbWJnRmSgnF=6->!do0N%mqoW$=?S#Ei#^+aHFKIli=0&)>SI5h7xs$ZB zP*EeXpMi>GTqR}RccUFSGjF99k;WYU>eos9boU*mA6B%ha5_U z0}Ya)6mX~EdnDYZ1)q^H7Uzs7n44C?vK$`BoyVf!En4uZ3~tGs;dC?XOMy$q{wVNF zM&!%kq14$;Fb=oVPB1$rRW!0iH>wC^)5awhL!wuaSO}dj5#Pw6cH+^mcuX-Tc41T+ UG|!ZzTz^z8Dkw8u&bPJy4>Mwg=l}o! diff --git a/client/src/pages/EQPage.tsx b/client/src/pages/EQPage.tsx index 899727f..20e7594 100644 --- a/client/src/pages/EQPage.tsx +++ b/client/src/pages/EQPage.tsx @@ -11,7 +11,7 @@ 7. Total gain card: 总增益 value + AUTO toggle + slider ============================================================ */ import { useDevice } from "@/contexts/DeviceContext"; -import { ChevronLeft, ChevronDown, Minus, Plus, Edit3, Headphones, Share2 } from "lucide-react"; +import { ChevronLeft, ChevronDown, Trash2, Copy, Edit3, Headphones, Share2, FilePenLine } from "lucide-react"; import { useLocation } from "wouter"; import { useState, useMemo, useEffect, useRef, useCallback } from "react"; import { cn } from "@/lib/utils"; @@ -161,6 +161,10 @@ export default function EQPage() { onConfirm: () => void | Promise; onDismiss: () => void; } | null>(null); + const [deleteHeadphoneDialog, setDeleteHeadphoneDialog] = useState<{ + name: string; + idx: number; + } | null>(null); const { currentRawCurve, setCurrentRawCurve, @@ -550,18 +554,25 @@ export default function EQPage() { setSelectedBandByMode({ A: 0, B: 0 }); }; - const handleDeleteHeadphone = async () => { + const handleDeleteHeadphone = () => { const target = peqItems[headphoneIdx]; if (!target?.name) return; + setDeleteHeadphoneDialog({ name: target.name, idx: headphoneIdx }); + }; - const confirmed = window.confirm(eqInterp(eqUi.deleteConfirm, { name: target.name })); - if (!confirmed) return; + const confirmDeleteHeadphone = async () => { + const target = deleteHeadphoneDialog; + if (!target) return; + setDeleteHeadphoneDialog(null); try { if (isDemoMode || !api) { - const nextItems = peqItems.filter((_, idx) => idx !== headphoneIdx); + const nextItems = peqItems.filter((_, idx) => idx !== target.idx); delete peqPresetCacheRef.current[target.name]; - applyPeqStateToUI({ peq: nextItems, peqSelect: Math.max(0, headphoneIdx - 1) }); + applyPeqStateToUI({ + peq: nextItems, + peqSelect: Math.max(0, target.idx - 1), + }); toast.success(eqUi.toastDeleted); return; } @@ -1432,14 +1443,14 @@ export default function EQPage() { style={{ background: "rgba(44,44,46,0.8)", border: "1px solid rgba(255,255,255,0.1)" }} onClick={openPresetManageDialog} > - + @@ -1822,6 +1833,23 @@ export default function EQPage() { }} /> + { + void confirmDeleteHeadphone(); + }} + onCancel={() => setDeleteHeadphoneDialog(null)} + /> + updateSetting({ bt_next: 0 })} className="w-10 h-10 rounded-full flex items-center justify-center transition-all active:scale-95" style={{ background: "rgba(0,255,246,0.15)", border: "1px solid rgba(0,255,246,0.3)" }}> - 上一首 + @@ -856,36 +854,36 @@ export default function Home() {
} + icon={} label={homeText.input ?? "输入源"} value={inputLabel} onClick={() => setLocation("/io")} /> } + icon={} label={homeText.output ?? "输出端口"} value={outputLabel} onClick={() => setLocation("/io")} /> } + icon={} label={homeText.audioSet ?? "音频设置"} onClick={() => setLocation("/audio")} /> } + icon={} label={homeText.systemSet ?? "系统设置"} onClick={() => setLocation("/system")} /> } + icon={} label={homeText.vu ?? "VU表"} value={vuLabel} thumbnail={`${import.meta.env.BASE_URL}images/vu/vu${(ds.vu ?? 0) + 1}.png`} onClick={() => setLocation("/vu")} /> } + icon={} label={homeText.backToLegacy ?? "返回旧版"} onClick={() => { const target = ip.trim(); diff --git a/client/src/pages/eq/components/PeqOverwriteConfirmDialog.tsx b/client/src/pages/eq/components/PeqOverwriteConfirmDialog.tsx index 5a74c3e..d37bcd1 100644 --- a/client/src/pages/eq/components/PeqOverwriteConfirmDialog.tsx +++ b/client/src/pages/eq/components/PeqOverwriteConfirmDialog.tsx @@ -1,27 +1,24 @@ import { useRef } from "react"; -import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"; -import { - AlertDialog, - AlertDialogAction, - AlertDialogCancel, - AlertDialogDescription, - AlertDialogFooter, - AlertDialogHeader, - AlertDialogOverlay, - AlertDialogPortal, - AlertDialogTitle, -} from "@/components/ui/alert-dialog"; import { cn } from "@/lib/utils"; /** Above BrandDrawer (z-110) and ShareDialog (z-120). */ const EQ_OVERWRITE_DIALOG_Z = "z-[130]"; +const PANEL_STYLE = { + background: + "linear-gradient(180deg, rgba(34,36,40,0.98) 0%, rgba(24,26,30,0.98) 100%)", + border: "1px solid rgba(255,255,255,0.12)", + boxShadow: "0 18px 48px rgba(0,0,0,0.55)", +} as const; + export function PeqOverwriteConfirmDialog({ open, title, description, cancelLabel, confirmLabel, + variant = "default", + zClassName = EQ_OVERWRITE_DIALOG_Z, onConfirm, onCancel, }: { @@ -30,56 +27,76 @@ export function PeqOverwriteConfirmDialog({ description: string; cancelLabel: string; confirmLabel: string; + variant?: "default" | "destructive"; + /** Override stacking order when nested inside another dialog. */ + zClassName?: string; onConfirm: () => void; onCancel: () => void; }) { const actionTakenRef = useRef(false); + if (!open) return null; + + const handleDismiss = () => { + if (!actionTakenRef.current) onCancel(); + actionTakenRef.current = false; + }; + return ( - { - if (!next) { - if (!actionTakenRef.current) onCancel(); - actionTakenRef.current = false; - } - }} +
- - - e.stopPropagation()} + role="alertdialog" + aria-modal="true" + aria-labelledby="peq-confirm-title" + aria-describedby="peq-confirm-desc" + > +

- - {title} - - {description} - - - - e.preventDefault()} - onClick={onCancel} - > - {cancelLabel} - - e.preventDefault()} - onClick={() => { - actionTakenRef.current = true; - onConfirm(); - }} - > - {confirmLabel} - - - - - + {title} +

+

+ {description} +

+ +
+ + +
+
+
); } diff --git a/client/src/pages/eq/components/PeqPresetManageDialog.tsx b/client/src/pages/eq/components/PeqPresetManageDialog.tsx index e43b6d6..f2d52cc 100644 --- a/client/src/pages/eq/components/PeqPresetManageDialog.tsx +++ b/client/src/pages/eq/components/PeqPresetManageDialog.tsx @@ -1,4 +1,4 @@ -import { useMemo, useState } from "react"; +import { useEffect, useMemo, useState } from "react"; import { X, Trash2, Pencil, Check, Loader2 } from "lucide-react"; import { cn } from "@/lib/utils"; import { toast } from "sonner"; @@ -6,6 +6,8 @@ import type { PeqEqUi } from "../types"; import { PEQ_PRESET_NAME_MAX_LENGTH } from "../eqConstants"; import { clampPeqPresetName, isPeqPresetNameTooLong } from "../peqPresetName"; import { PeqPresetNameLengthHint } from "./PeqPresetNameLengthHint"; +import { PeqOverwriteConfirmDialog } from "./PeqOverwriteConfirmDialog"; +import { eqInterp } from "../eqFormatters"; /* ── Custom checkbox ── */ function Checkbox({ @@ -96,6 +98,9 @@ export function PeqPresetManageDialog({ const [deleting, setDeleting] = useState(false); const [renaming, setRenaming] = useState(false); + const [deleteConfirmIdxs, setDeleteConfirmIdxs] = useState( + null, + ); const checkedIdxs = useMemo( () => @@ -107,6 +112,10 @@ export function PeqPresetManageDialog({ const allChecked = items.length > 0 && checkedIdxs.length === items.length; + useEffect(() => { + if (!open) setDeleteConfirmIdxs(null); + }, [open]); + if (!open) return null; const toggleAll = () => { @@ -164,27 +173,27 @@ export function PeqPresetManageDialog({ } }; - const confirmAndDelete = async (idxs: number[]) => { + const requestDelete = (idxs: number[]) => { const uniq = Array.from(new Set(idxs)).filter( i => i >= 0 && i < items.length ); if (uniq.length === 0) return; + setDeleteConfirmIdxs(uniq); + }; - const names = uniq.map(i => items[i].name); + const executeDelete = async () => { + const idxs = deleteConfirmIdxs; + if (!idxs) return; + setDeleteConfirmIdxs(null); - // Confirmation dialog - const confirmMsg = - names.length === 1 - ? eqUi.deletePresetConfirm.replace("{{name}}", names[0]) - : eqUi.batchDeleteConfirm.replace("{{count}}", String(names.length)); - if (!window.confirm(confirmMsg)) return; + const names = idxs.map(i => items[i].name); setDeleting(true); try { const ok = await onDeletePresets(names); if (ok) { setChecked({}); - if (editingIdx !== null && uniq.includes(editingIdx)) { + if (editingIdx !== null && idxs.includes(editingIdx)) { setEditingIdx(null); setEditingName(""); } @@ -194,7 +203,19 @@ export function PeqPresetManageDialog({ } }; + const deleteConfirmDescription = + deleteConfirmIdxs === null + ? "" + : deleteConfirmIdxs.length === 1 + ? eqInterp(eqUi.deletePresetConfirm, { + name: items[deleteConfirmIdxs[0]]?.name ?? "", + }) + : eqInterp(eqUi.batchDeleteConfirm, { + count: deleteConfirmIdxs.length, + }); + return ( + <>
void confirmAndDelete(checkedIdxs)} + onClick={() => requestDelete(checkedIdxs)} > {deleting && } {eqUi.batchDelete} @@ -344,7 +365,7 @@ export function PeqPresetManageDialog({ border: "1px solid rgba(239,68,68,0.2)", }} title={eqUi.deletePreset} - onClick={() => void confirmAndDelete([idx])} + onClick={() => requestDelete([idx])} > @@ -357,5 +378,20 @@ export function PeqPresetManageDialog({
+ + { + void executeDelete(); + }} + onCancel={() => setDeleteConfirmIdxs(null)} + /> + ); } diff --git a/package.json b/package.json index 02ba7e0..e904bc1 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,7 @@ "embla-carousel-react": "^8.6.0", "framer-motion": "^12.23.22", "input-otp": "^1.4.2", - "lucide-react": "^0.453.0", + "lucide-react": "^0.575.0", "nanoid": "^5.1.5", "next-themes": "^0.4.6", "react": "^19.2.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9d37d17..8b20055 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -117,8 +117,8 @@ importers: specifier: ^1.4.2 version: 1.4.2(react-dom@19.2.7(react@19.2.7))(react@19.2.7) lucide-react: - specifier: ^0.453.0 - version: 0.453.0(react@19.2.7) + specifier: ^0.575.0 + version: 0.575.0(react@19.2.7) nanoid: specifier: ^5.1.5 version: 5.1.11 @@ -3028,16 +3028,16 @@ packages: lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} - lucide-react@0.453.0: - resolution: {integrity: sha512-kL+RGZCcJi9BvJtzg2kshO192Ddy9hv3ij+cPrVPWSRzgCWCVazoQJxOjAwgK53NomL07HB7GPHW120FimjNhQ==} - peerDependencies: - react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc - lucide-react@0.542.0: resolution: {integrity: sha512-w3hD8/SQB7+lzU2r4VdFyzzOzKnUjTZIF/MQJGSSvni7Llewni4vuViRppfRAa2guOsY5k4jZyxw/i9DQHv+dw==} peerDependencies: react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 + lucide-react@0.575.0: + resolution: {integrity: sha512-VuXgKZrk0uiDlWjGGXmKV6MSk9Yy4l10qgVvzGn2AWBx1Ylt0iBexKOAoA6I7JO3m+M9oeovJd3yYENfkUbOeg==} + peerDependencies: + react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 + magic-string@0.30.19: resolution: {integrity: sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==} @@ -6657,11 +6657,11 @@ snapshots: dependencies: yallist: 3.1.1 - lucide-react@0.453.0(react@19.2.7): + lucide-react@0.542.0(react@19.2.7): dependencies: react: 19.2.7 - lucide-react@0.542.0(react@19.2.7): + lucide-react@0.575.0(react@19.2.7): dependencies: react: 19.2.7