Refactor ConnectScreen to use logo image instead of SVG, enhance EQPage with batch editing functionality for PEQ settings, and implement power off confirmation dialog in Home component. Update localization files for new power off features.
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 66 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 817 B |
@@ -24,22 +24,12 @@ export default function ConnectScreen() {
|
||||
return (
|
||||
<div className="min-h-screen bg-black flex flex-col items-center justify-center px-6">
|
||||
{/* Logo / Brand */}
|
||||
<div className="mb-12 flex flex-col items-center">
|
||||
<div className="w-20 h-20 rounded-[24px] flex items-center justify-center mb-5"
|
||||
style={{
|
||||
background: "rgba(0,255,246,0.08)",
|
||||
border: "1px solid rgba(0,255,246,0.2)",
|
||||
boxShadow: "0 0 40px rgba(0,255,246,0.12)",
|
||||
}}>
|
||||
<svg width="40" height="40" viewBox="0 0 40 40" fill="none">
|
||||
<circle cx="20" cy="20" r="14" stroke="#00FFF6" strokeWidth="2" opacity="0.4" />
|
||||
<circle cx="20" cy="20" r="8" stroke="#00FFF6" strokeWidth="2" />
|
||||
<circle cx="20" cy="20" r="3" fill="#00FFF6" />
|
||||
<line x1="20" y1="6" x2="20" y2="2" stroke="#00FFF6" strokeWidth="2" strokeLinecap="round" />
|
||||
<line x1="34" y1="20" x2="38" y2="20" stroke="#00FFF6" strokeWidth="2" strokeLinecap="round" opacity="0.5" />
|
||||
<line x1="2" y1="20" x2="6" y2="20" stroke="#00FFF6" strokeWidth="2" strokeLinecap="round" opacity="0.5" />
|
||||
</svg>
|
||||
</div>
|
||||
<div className="mb-10 flex flex-col items-center">
|
||||
<img
|
||||
src={`${import.meta.env.BASE_URL}logo.png`}
|
||||
alt="Luxsin"
|
||||
className="mb-4 h-auto w-20 object-contain"
|
||||
/>
|
||||
<h1 className="text-[30px] font-bold text-white tracking-tight">Luxsin X8</h1>
|
||||
<p className="text-[14px] text-white/35 mt-1 tracking-wide">Controller</p>
|
||||
</div>
|
||||
|
||||
@@ -262,6 +262,8 @@ export class LuxsinAPI {
|
||||
setXlrPolarity(reverse: boolean) { return this.setSetting({ xlr: reverse ? 1 : 0 }); }
|
||||
setDacArc(earc: boolean) { return this.setSetting({ dacArc: earc ? 1 : 0 }); }
|
||||
setBootSound(on: boolean) { return this.setSetting({ bootSound: on ? 1 : 0 }); }
|
||||
/** Device power off — `GET .../dev/info.cgi?action=setting&power=0` */
|
||||
powerOff() { return this.setSetting({ power: 0 }); }
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
|
||||
@@ -1,6 +1,16 @@
|
||||
{
|
||||
"home": {
|
||||
"audioSet": "Audio setting","power": "power", "input": "input", "output": "output", "vu":"VU meter"
|
||||
"audioSet": "Audio setting",
|
||||
"power": "Power",
|
||||
"input": "Input",
|
||||
"output": "Output",
|
||||
"vu": "VU meter",
|
||||
"powerOffConfirmTitle": "Turn off the device?",
|
||||
"powerOffConfirmDesc": "The device will shut down and disconnect from this app. Do you want to continue?",
|
||||
"powerOffConfirmOk": "Turn off",
|
||||
"powerOffConfirmCancel": "Cancel",
|
||||
"powerOffToastDemo": "Demo mode: disconnected from the device.",
|
||||
"powerOffToastSent": "Shutdown command sent."
|
||||
},
|
||||
"source": {
|
||||
"label": "Input/Output",
|
||||
|
||||
@@ -1,5 +1,17 @@
|
||||
{
|
||||
"home": { "audioSet": "音訊設定","power": "電源", "input": "輸入源", "output": "輸出端口", "vu":"VU表"},
|
||||
"home": {
|
||||
"audioSet": "音訊設定",
|
||||
"power": "電源",
|
||||
"input": "輸入源",
|
||||
"output": "輸出端口",
|
||||
"vu": "VU表",
|
||||
"powerOffConfirmTitle": "確認關閉電源?",
|
||||
"powerOffConfirmDesc": "裝置將關機並中斷與本應用程式的連線,是否繼續?",
|
||||
"powerOffConfirmOk": "關閉電源",
|
||||
"powerOffConfirmCancel": "取消",
|
||||
"powerOffToastDemo": "演示模式:已中斷與裝置的連線",
|
||||
"powerOffToastSent": "已傳送關機指令"
|
||||
},
|
||||
"source": {
|
||||
"label": "輸入/輸出",
|
||||
"input": { "coaxial": "同軸", "optical": "光纖", "bluetooth": "藍牙", "rca": "類比RCA" },
|
||||
|
||||
@@ -1,5 +1,17 @@
|
||||
{
|
||||
"home": { "audioSet": "音频设置", "power": "电源", "input": "输入源", "output": "输出端口", "vu":"VU表" },
|
||||
"home": {
|
||||
"audioSet": "音频设置",
|
||||
"power": "电源",
|
||||
"input": "输入源",
|
||||
"output": "输出端口",
|
||||
"vu": "VU表",
|
||||
"powerOffConfirmTitle": "确认关闭电源?",
|
||||
"powerOffConfirmDesc": "设备将关机并断开与本应用的连接,是否继续?",
|
||||
"powerOffConfirmOk": "关闭电源",
|
||||
"powerOffConfirmCancel": "取消",
|
||||
"powerOffToastDemo": "演示模式:已断开与设备的连接",
|
||||
"powerOffToastSent": "已发送关机指令"
|
||||
},
|
||||
"source": {
|
||||
"label": "输入输出",
|
||||
"input": { "coaxial": "同轴", "optical": "光钎", "bluetooth": "蓝牙", "rca": "模拟RCA" },
|
||||
|
||||
+182
-8
@@ -458,6 +458,8 @@ export default function EQPage() {
|
||||
const [addPresetMode, setAddPresetMode] = useState<"copy" | "flat">("copy");
|
||||
const [copyPresetName, setCopyPresetName] = useState("");
|
||||
const [flatPresetName, setFlatPresetName] = useState("");
|
||||
const [isBatchEditDialogOpen, setIsBatchEditDialogOpen] = useState(false);
|
||||
const [batchEditText, setBatchEditText] = useState("");
|
||||
type BrandDrawerTab = "brands" | "models" | "target";
|
||||
const [isBrandDrawerOpen, setIsBrandDrawerOpen] = useState(false);
|
||||
const [brandDrawerTab, setBrandDrawerTab] = useState<BrandDrawerTab>("brands");
|
||||
@@ -624,6 +626,124 @@ export default function EQPage() {
|
||||
});
|
||||
};
|
||||
|
||||
const toCompactNumber = (value: number) => Number(value.toFixed(3)).toString();
|
||||
|
||||
const buildBatchEditText = useCallback(() => {
|
||||
const preamp = Number(currentPeq?.preamp ?? 0);
|
||||
const source = bands.length > 0 ? bands : DEFAULT_BANDS;
|
||||
const lines = [`Preamp:${toCompactNumber(preamp)}dB`];
|
||||
for (let i = 0; i < 10; i++) {
|
||||
const band = source[i] ?? DEFAULT_BANDS[i] ?? DEFAULT_BANDS[0];
|
||||
lines.push(
|
||||
`Filter ${i + 1}: ${band.enabled ? "ON" : "OFF"} ${normalizeFilterType(band.type)} Fc ${toCompactNumber(band.freq)} Hz Gain ${toCompactNumber(band.gain)} dB Q ${toCompactNumber(band.q)}`
|
||||
);
|
||||
}
|
||||
return lines.join("\n");
|
||||
}, [bands, currentPeq?.preamp]);
|
||||
|
||||
const openBatchEditDialog = () => {
|
||||
setBatchEditText(buildBatchEditText());
|
||||
setIsBatchEditDialogOpen(true);
|
||||
};
|
||||
|
||||
const parseBatchEditText = (raw: string) => {
|
||||
const lines = raw
|
||||
.split(/\r?\n/)
|
||||
.map((line) => line.trim())
|
||||
.filter(Boolean);
|
||||
if (lines.length < 11) {
|
||||
throw new Error("格式错误:至少需要 11 行(Preamp + 10 个滤波器)");
|
||||
}
|
||||
|
||||
const preampMatch = lines[0].match(/^Preamp\s*:\s*([+-]?\d+(?:\.\d+)?)\s*dB$/i);
|
||||
if (!preampMatch) {
|
||||
throw new Error("格式错误:第 1 行应为 Preamp:-5.96dB");
|
||||
}
|
||||
const preamp = Number(preampMatch[1]);
|
||||
if (!Number.isFinite(preamp)) {
|
||||
throw new Error("格式错误:Preamp 数值无效");
|
||||
}
|
||||
|
||||
const parsedFilters = new Array<{
|
||||
enabled: boolean;
|
||||
type: string;
|
||||
freq: number;
|
||||
gain: number;
|
||||
q: number;
|
||||
}>(10);
|
||||
|
||||
for (let i = 1; i <= 10; i++) {
|
||||
const line = lines[i];
|
||||
const match = line.match(
|
||||
/^Filter\s+(\d+)\s*:\s*(ON|OFF)\s+([A-Za-z]+)\s+Fc\s+([+-]?\d+(?:\.\d+)?)\s+Hz\s+Gain\s+([+-]?\d+(?:\.\d+)?)\s+dB\s+Q\s+([+-]?\d+(?:\.\d+)?)$/i
|
||||
);
|
||||
if (!match) {
|
||||
throw new Error(`格式错误:第 ${i + 1} 行不符合 Filter 格式`);
|
||||
}
|
||||
|
||||
const filterNo = Number(match[1]);
|
||||
if (filterNo < 1 || filterNo > 10) {
|
||||
throw new Error(`格式错误:Filter 编号 ${filterNo} 超出范围`);
|
||||
}
|
||||
|
||||
const type = normalizeFilterType(match[3].toUpperCase());
|
||||
if (!FILTER_TYPES.includes(type)) {
|
||||
throw new Error(`格式错误:Filter ${filterNo} 的类型无效(${match[3]})`);
|
||||
}
|
||||
|
||||
const freq = Number(match[4]);
|
||||
const gain = Number(match[5]);
|
||||
const q = Number(match[6]);
|
||||
if (![freq, gain, q].every(Number.isFinite)) {
|
||||
throw new Error(`格式错误:Filter ${filterNo} 的数值无效`);
|
||||
}
|
||||
|
||||
parsedFilters[filterNo - 1] = {
|
||||
enabled: match[2].toUpperCase() === "ON",
|
||||
type,
|
||||
freq: Number(freq.toFixed(2)),
|
||||
gain: Number(gain.toFixed(2)),
|
||||
q: Number(q.toFixed(2)),
|
||||
};
|
||||
}
|
||||
|
||||
if (parsedFilters.some((item) => !item)) {
|
||||
throw new Error("格式错误:Filter 1-10 必须全部提供");
|
||||
}
|
||||
|
||||
return {
|
||||
preamp: Number(preamp.toFixed(2)),
|
||||
filters: parsedFilters as typeof bands,
|
||||
};
|
||||
};
|
||||
|
||||
const handleSaveBatchEdit = () => {
|
||||
try {
|
||||
const parsed = parseBatchEditText(batchEditText);
|
||||
let updatedPeq: { name: string; autoPre?: number; preamp?: number; canDel?: number } | undefined;
|
||||
setPeqItems((prev) =>
|
||||
prev.map((item, i) => {
|
||||
if (i !== headphoneIdx) return item;
|
||||
const merged = {
|
||||
...item,
|
||||
autoPre: 0,
|
||||
preamp: parsed.preamp,
|
||||
filters: parsed.filters.map(bandToPeqFilter),
|
||||
};
|
||||
updatedPeq = merged;
|
||||
return merged;
|
||||
})
|
||||
);
|
||||
setBands(parsed.filters);
|
||||
schedulePeqSync(updatedPeq, parsed.filters, 0);
|
||||
setSelectedBand(0);
|
||||
setIsBatchEditDialogOpen(false);
|
||||
toast.success("批量编辑已应用");
|
||||
} catch (error) {
|
||||
toast.error(error instanceof Error ? error.message : "批量编辑格式错误");
|
||||
}
|
||||
};
|
||||
|
||||
const applyPeqStateToUI = (
|
||||
peqState: { peq?: Array<{ name: string; filters?: any[] | string }>; peqSelect?: number },
|
||||
) => {
|
||||
@@ -1069,7 +1189,7 @@ export default function EQPage() {
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<button
|
||||
className="ios-list-group flex flex-col items-center justify-center py-3 gap-2 active:opacity-70 transition-opacity"
|
||||
onClick={() => toast.info("批量编辑功能即将推出")}>
|
||||
onClick={openBatchEditDialog}>
|
||||
<div className="w-10 h-10 rounded-[12px] flex items-center justify-center"
|
||||
style={{ background: "rgba(0,255,246,0.12)", border: "1px solid rgba(0,255,246,0.2)" }}>
|
||||
<Edit3 size={20} style={{ color: "#00FFF6" }} />
|
||||
@@ -1394,6 +1514,56 @@ export default function EQPage() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isBatchEditDialogOpen && (
|
||||
<div className="fixed inset-0 z-[121] flex items-center justify-center bg-black/65 px-4">
|
||||
<div
|
||||
className="w-full max-w-[760px] rounded-[14px] p-5"
|
||||
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)",
|
||||
}}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h3 className="text-[28px] text-white/90 font-semibold">批量编辑</h3>
|
||||
<button
|
||||
type="button"
|
||||
className="text-white/45 hover:text-white/75 transition-colors"
|
||||
onClick={() => setIsBatchEditDialogOpen(false)}
|
||||
>
|
||||
<X size={20} />
|
||||
</button>
|
||||
</div>
|
||||
<p className="mb-3 text-[13px] text-white/55">
|
||||
共 11 行:第 1 行 Preamp,第 2-11 行 Filter 1-10。保存后将覆盖当前耳机参数。
|
||||
</p>
|
||||
<textarea
|
||||
value={batchEditText}
|
||||
onChange={(e) => setBatchEditText(e.target.value)}
|
||||
spellCheck={false}
|
||||
className="h-[360px] w-full resize-none overflow-auto rounded-[10px] border border-white/12 bg-[#15171b] p-3 font-mono text-[13px] leading-6 text-white/90 outline-none focus:border-[#00FFF6]/55"
|
||||
/>
|
||||
<div className="mt-5 flex items-center justify-center gap-16">
|
||||
<button
|
||||
type="button"
|
||||
className="px-5 py-2 rounded-full text-[24px] text-white/80 bg-[#3f4349] hover:bg-[#4a4f56] transition-colors"
|
||||
onClick={() => setIsBatchEditDialogOpen(false)}
|
||||
>
|
||||
取消
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="px-5 py-2 rounded-full text-[24px] text-black bg-[#00FFF6] hover:brightness-95 transition-all"
|
||||
onClick={handleSaveBatchEdit}
|
||||
>
|
||||
保存
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isBrandDrawerOpen && (
|
||||
<div className="fixed inset-0 z-[110] flex flex-col justify-end">
|
||||
<button
|
||||
@@ -1403,7 +1573,7 @@ export default function EQPage() {
|
||||
onClick={() => setIsBrandDrawerOpen(false)}
|
||||
/>
|
||||
<div
|
||||
className="relative z-10 flex max-h-[88vh] flex-col rounded-t-[18px] bg-white shadow-[0_-8px_32px_rgba(0,0,0,0.35)]"
|
||||
className="relative z-10 flex h-[58dvh] flex-shrink-0 flex-col overflow-hidden rounded-t-[18px] bg-white shadow-[0_-8px_32px_rgba(0,0,0,0.35)]"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<div className="flex shrink-0 items-center justify-center pt-2 pb-1">
|
||||
@@ -1428,9 +1598,9 @@ export default function EQPage() {
|
||||
})}
|
||||
</div>
|
||||
|
||||
<div className="flex min-h-0 flex-1 flex-col bg-white">
|
||||
<div className="flex min-h-0 flex-1 flex-col overflow-hidden bg-white">
|
||||
{brandDrawerTab === "brands" && (
|
||||
<>
|
||||
<div className="flex min-h-0 flex-1 flex-col overflow-hidden">
|
||||
<div className="shrink-0 border-b border-black/8 px-3 py-2.5">
|
||||
<div className="flex items-center gap-2 rounded-[10px] border border-black/12 bg-[#f5f5f7] px-3 py-2">
|
||||
<Search size={18} className="shrink-0 text-black/35" />
|
||||
@@ -1443,7 +1613,7 @@ export default function EQPage() {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="min-h-0 flex-1 overflow-y-auto">
|
||||
<div className="min-h-0 flex-1 overflow-y-auto overflow-x-hidden overscroll-contain touch-pan-y">
|
||||
{brandSearchQuery.trim() && catalogSearchLoading && (
|
||||
<div className="py-10 text-center text-[14px] text-black/45">搜索中…</div>
|
||||
)}
|
||||
@@ -1511,10 +1681,11 @@ export default function EQPage() {
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
</div>
|
||||
)}
|
||||
{brandDrawerTab === "models" && (
|
||||
<div className="min-h-0 flex-1 overflow-y-auto">
|
||||
<div className="flex min-h-0 flex-1 flex-col overflow-hidden">
|
||||
<div className="min-h-0 flex-1 overflow-y-auto overflow-x-hidden overscroll-contain touch-pan-y">
|
||||
<div className="sticky top-0 z-10 border-b border-black/8 bg-white px-4 py-2.5 text-[13px] text-black/55">
|
||||
品牌:<span className="font-semibold text-black/80">{selectedCatalogBrand || "—"}</span>
|
||||
{selectedCatalogModelFromSearch && (
|
||||
@@ -1553,9 +1724,11 @@ export default function EQPage() {
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{brandDrawerTab === "target" && (
|
||||
<div className="min-h-0 flex-1 overflow-y-auto">
|
||||
<div className="flex min-h-0 flex-1 flex-col overflow-hidden">
|
||||
<div className="min-h-0 flex-1 overflow-y-auto overflow-x-hidden overscroll-contain touch-pan-y">
|
||||
<div className="sticky top-0 z-10 border-b border-black/8 bg-white px-4 py-2.5 text-[13px] text-black/55 space-y-0.5">
|
||||
<div>品牌:<span className="font-semibold text-black/80">{selectedCatalogBrand || "—"}</span></div>
|
||||
<div>型号:<span className="font-semibold text-black/80">{selectedCatalogModelName || "—"}</span></div>
|
||||
@@ -1715,6 +1888,7 @@ export default function EQPage() {
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -12,6 +12,16 @@
|
||||
import { useDevice } from "@/contexts/DeviceContext";
|
||||
import ConnectScreen from "@/components/ConnectScreen";
|
||||
import BottomNav from "@/components/BottomNav";
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
AlertDialogCancel,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
} from "@/components/ui/alert-dialog";
|
||||
import { INPUT_LABELS, OUTPUT_LABELS } from "@/lib/luxsinApi";
|
||||
import { cn } from "@/lib/utils";
|
||||
import {
|
||||
@@ -27,12 +37,14 @@ import {
|
||||
Bluetooth,
|
||||
} from "lucide-react";
|
||||
import { useLocation } from "wouter";
|
||||
import { useRef, useState, useEffect, useCallback } from "react";
|
||||
import { useRef, useState, useEffect, useCallback, useMemo } from "react";
|
||||
import { toast } from "sonner";
|
||||
import localeZh from "@/locales/data-zh.json";
|
||||
import localeZhHK from "@/locales/data-zh-HK.json";
|
||||
import localeEn from "@/locales/data-en.json";
|
||||
|
||||
type HomeLocale = NonNullable<(typeof localeZh)["home"]>;
|
||||
|
||||
// ── Volume in dB (0-200 → -100dB to 0dB) ──
|
||||
function volToDB(v: number) {
|
||||
if (v === 0) return "-∞";
|
||||
@@ -86,8 +98,17 @@ function ListRow({
|
||||
}
|
||||
|
||||
export default function Home() {
|
||||
const { isConnected, deviceState, setVolume, updateSetting } = useDevice();
|
||||
const { isConnected, deviceState, setVolume, updateSetting, api, disconnect, isDemoMode } = useDevice();
|
||||
const [, setLocation] = useLocation();
|
||||
const powerActionRef = useRef(false);
|
||||
const [powerConfirmOpen, setPowerConfirmOpen] = useState(false);
|
||||
|
||||
const homeText = useMemo((): HomeLocale => {
|
||||
if (!deviceState) return {} as HomeLocale;
|
||||
const loc =
|
||||
deviceState.language === 0 ? localeEn : deviceState.language === 1 ? localeZhHK : localeZh;
|
||||
return (loc.home ?? {}) as HomeLocale;
|
||||
}, [deviceState]);
|
||||
|
||||
const [localVol, setLocalVol] = useState(deviceState?.volume ?? 100);
|
||||
const isDragging = useRef(false);
|
||||
@@ -112,11 +133,32 @@ export default function Home() {
|
||||
setVolume(v);
|
||||
}, [setVolume]);
|
||||
|
||||
const handlePowerOff = useCallback(async () => {
|
||||
if (powerActionRef.current) return;
|
||||
powerActionRef.current = true;
|
||||
try {
|
||||
if (isDemoMode) {
|
||||
toast.info(homeText.powerOffToastDemo ?? "演示模式:已断开与设备的连接");
|
||||
disconnect();
|
||||
return;
|
||||
}
|
||||
if (api) {
|
||||
try {
|
||||
await api.powerOff();
|
||||
} catch {
|
||||
// 设备可能已掉线或未返回响应,仍断开本地会话
|
||||
}
|
||||
}
|
||||
disconnect();
|
||||
toast.success(homeText.powerOffToastSent ?? "已发送关机指令");
|
||||
} finally {
|
||||
powerActionRef.current = false;
|
||||
}
|
||||
}, [api, disconnect, isDemoMode, homeText]);
|
||||
|
||||
if (!isConnected) return <ConnectScreen />;
|
||||
|
||||
const ds = deviceState!;
|
||||
const localeData = ds.language === 0 ? localeEn : ds.language === 1 ? localeZhHK : localeZh;
|
||||
const homeText = localeData.home ?? {};
|
||||
const inputLabel = INPUT_LABELS[ds.input] ?? "USB-C";
|
||||
const outputLabel = OUTPUT_LABELS[ds.output] ?? "Headset";
|
||||
const effectOn = ds.audio_enable === 1;
|
||||
@@ -381,8 +423,9 @@ export default function Home() {
|
||||
{/* Power */}
|
||||
<div className="flex flex-col items-center gap-2">
|
||||
<button
|
||||
type="button"
|
||||
className="icon-circle-btn"
|
||||
onClick={() => toast.info("远程待机功能即将推出")}
|
||||
onClick={() => setPowerConfirmOpen(true)}
|
||||
>
|
||||
<Power size={22} className="text-white/60" />
|
||||
</button>
|
||||
@@ -460,6 +503,31 @@ export default function Home() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<AlertDialog open={powerConfirmOpen} onOpenChange={setPowerConfirmOpen}>
|
||||
<AlertDialogContent className="border-white/10 bg-zinc-900 text-white sm:max-w-md">
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle className="text-white">
|
||||
{homeText.powerOffConfirmTitle ?? "确认关闭电源?"}
|
||||
</AlertDialogTitle>
|
||||
<AlertDialogDescription className="text-white/60">
|
||||
{homeText.powerOffConfirmDesc ??
|
||||
"设备将关机并断开与本应用的连接,是否继续?"}
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel className="border-white/20 bg-transparent text-white hover:bg-white/10">
|
||||
{homeText.powerOffConfirmCancel ?? "取消"}
|
||||
</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
className="bg-red-600 text-white hover:bg-red-700 focus-visible:ring-red-600"
|
||||
onClick={() => void handlePowerOff()}
|
||||
>
|
||||
{homeText.powerOffConfirmOk ?? "关闭电源"}
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
|
||||
{/* Bottom nav */}
|
||||
<BottomNav />
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user