's'
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { Copy, Layers, Loader2, Package, Terminal } from "lucide-react";
|
||||
import { Copy, ExternalLink, Layers, Loader2, Package, RefreshCw, Terminal } from "lucide-react";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
|
||||
type DeviceRow = { serial: string; model?: string; state: string };
|
||||
@@ -84,9 +84,14 @@ export function LiveAndroidWorkstation({
|
||||
const [openUrl, setOpenUrl] = useState("https://");
|
||||
const [adbInputText, setAdbInputText] = useState("");
|
||||
const [mirrorMode, setMirrorMode] = useState<"host" | "webusb">("host");
|
||||
const [mirrorReloadKey, setMirrorReloadKey] = useState(0);
|
||||
const [logcatLive, setLogcatLive] = useState(false);
|
||||
const [pending, setPending] = useState<string | null>(null);
|
||||
|
||||
const PANDA_WEB_SCRCPY = "https://pandatestgrid.github.io/panda-web-scrcpy/";
|
||||
const LINK_SCRCPY = "https://github.com/Genymobile/scrcpy";
|
||||
const LINK_WS_SCRCPY = "https://github.com/NetrisTV/ws-scrcpy";
|
||||
const LINK_PANDA_GH = "https://github.com/PandaTestGrid/panda-web-scrcpy";
|
||||
|
||||
const lock = !!(busy || pending);
|
||||
|
||||
@@ -157,26 +162,36 @@ export function LiveAndroidWorkstation({
|
||||
}
|
||||
};
|
||||
|
||||
const refreshLogcat = async () => {
|
||||
if (!androidSerial) {
|
||||
notify(t("请选择设备", "Pick a device"));
|
||||
return;
|
||||
}
|
||||
const n = Math.min(3000, Math.max(1, parseInt(logcatLines, 10) || 200));
|
||||
setPending("logcat");
|
||||
setLogcatErr(null);
|
||||
try {
|
||||
const data = await fetchJson<{ text?: string }>(
|
||||
`/android/logcat?serial=${encodeURIComponent(androidSerial)}&lines=${n}`,
|
||||
);
|
||||
setLogcatText(data.text ?? "");
|
||||
} catch (e) {
|
||||
setLogcatText("");
|
||||
setLogcatErr((e as Error).message);
|
||||
} finally {
|
||||
setPending(null);
|
||||
}
|
||||
};
|
||||
const refreshLogcat = useCallback(
|
||||
async (opts?: { silent?: boolean }) => {
|
||||
if (!androidSerial) {
|
||||
return;
|
||||
}
|
||||
const n = Math.min(3000, Math.max(1, parseInt(logcatLines, 10) || 200));
|
||||
const silent = !!opts?.silent;
|
||||
if (!silent) setPending("logcat");
|
||||
setLogcatErr(null);
|
||||
try {
|
||||
const data = await fetchJson<{ text?: string }>(
|
||||
`/android/logcat?serial=${encodeURIComponent(androidSerial)}&lines=${n}`,
|
||||
);
|
||||
setLogcatText(data.text ?? "");
|
||||
} catch (e) {
|
||||
setLogcatText("");
|
||||
setLogcatErr((e as Error).message);
|
||||
} finally {
|
||||
if (!silent) setPending(null);
|
||||
}
|
||||
},
|
||||
[androidSerial, fetchJson, logcatLines],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!logcatLive || !androidSerial || !adbAvailable) return;
|
||||
const id = window.setInterval(() => void refreshLogcat({ silent: true }), 3000);
|
||||
void refreshLogcat({ silent: true });
|
||||
return () => clearInterval(id);
|
||||
}, [logcatLive, androidSerial, adbAvailable, refreshLogcat]);
|
||||
|
||||
const loadUiNodes = async () => {
|
||||
if (!androidSerial) {
|
||||
@@ -260,10 +275,10 @@ export function LiveAndroidWorkstation({
|
||||
))}
|
||||
</div>
|
||||
{adbAvailable && devices.length === 0 ? (
|
||||
<p className="mt-4 rounded-lg border border-amber-500/20 bg-amber-500/5 p-3 text-xs text-amber-200/90">
|
||||
<p className="mt-4 rounded-lg border border-amber-500/20 bg-amber-500/5 p-3 text-xs leading-relaxed text-amber-200/90">
|
||||
{t(
|
||||
"未检测到已授权设备:请 USB 连接并开启「USB 调试」,手机上允许本计算机调试。",
|
||||
"No authorized device: enable USB debugging and authorize this computer.",
|
||||
"未检测到已授权设备:双头 USB 接板子时请开「USB 调试」并授权;Redroid / 容器安卓可先 `adb connect IP:5555` 再刷新本页。",
|
||||
"No device: enable USB debugging for dual-USB to the board; for Redroid use adb connect IP:5555 then refresh.",
|
||||
)}
|
||||
</p>
|
||||
) : null}
|
||||
@@ -293,22 +308,40 @@ export function LiveAndroidWorkstation({
|
||||
>
|
||||
WebUSB · Panda
|
||||
</button>
|
||||
<a
|
||||
href="https://github.com/PandaTestGrid/panda-web-scrcpy"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="ml-auto self-center text-[11px] text-zinc-500 underline-offset-2 hover:text-violet-300 hover:underline"
|
||||
>
|
||||
GitHub
|
||||
</a>
|
||||
<div className="ml-auto flex flex-wrap items-center gap-2 self-center">
|
||||
<a
|
||||
href={LINK_SCRCPY}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="inline-flex items-center gap-0.5 text-[10px] text-zinc-500 hover:text-emerald-300"
|
||||
>
|
||||
scrcpy <ExternalLink className="h-3 w-3" />
|
||||
</a>
|
||||
<a
|
||||
href={LINK_WS_SCRCPY}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="inline-flex items-center gap-0.5 text-[10px] text-zinc-500 hover:text-violet-300"
|
||||
>
|
||||
ws-scrcpy <ExternalLink className="h-3 w-3" />
|
||||
</a>
|
||||
<a
|
||||
href={LINK_PANDA_GH}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="inline-flex items-center gap-0.5 text-[10px] text-zinc-500 hover:text-cyan-300"
|
||||
>
|
||||
panda-web-scrcpy <ExternalLink className="h-3 w-3" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{mirrorMode === "host" ? (
|
||||
<div>
|
||||
<div className="space-y-2 border-b border-white/5 px-4 py-3">
|
||||
<p className="text-[11px] text-zinc-400">
|
||||
<p className="text-[11px] leading-relaxed text-zinc-400">
|
||||
{t(
|
||||
"手机接在 live 板子 USB 时使用本页:先在 ws-scrcpy 里选设备并连接;黑屏多为未连 WebSocket / 屏幕休眠——可先 Wake 再刷新或新窗口打开面板。",
|
||||
"USB to the board: pick device in ws-scrcpy; black screen often means WS down or screen off—Wake, refresh, or open panel in a new tab.",
|
||||
"板载 USB:投屏协议来自开源 scrcpy(见上方仓库链接);浏览器内常用 ws-scrcpy。黑屏时先 Wake,再点「重载投屏」或新窗口打开面板。",
|
||||
"Board USB: scrcpy protocol; browser uses ws-scrcpy. Black screen: Wake, reload mirror, or open panel in new tab.",
|
||||
)}
|
||||
</p>
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
@@ -323,19 +356,30 @@ export function LiveAndroidWorkstation({
|
||||
</button>
|
||||
) : null}
|
||||
{scrcpyUrl ? (
|
||||
<a
|
||||
href={scrcpyUrl}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="rounded-lg border border-white/15 bg-black/30 px-3 py-1.5 text-[11px] text-violet-300"
|
||||
>
|
||||
{t("新窗口打开 :5000", "Open :5000 tab")}
|
||||
</a>
|
||||
<>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setMirrorReloadKey((k) => k + 1)}
|
||||
className="inline-flex items-center gap-1 rounded-lg border border-white/15 bg-black/30 px-3 py-1.5 text-[11px] text-zinc-200"
|
||||
>
|
||||
<RefreshCw className="h-3.5 w-3.5" />
|
||||
{t("重载投屏", "Reload mirror")}
|
||||
</button>
|
||||
<a
|
||||
href={scrcpyUrl}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="rounded-lg border border-white/15 bg-black/30 px-3 py-1.5 text-[11px] text-violet-300"
|
||||
>
|
||||
{t("新窗口打开", "New tab")}
|
||||
</a>
|
||||
</>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
{scrcpyUrl ? (
|
||||
<iframe
|
||||
key={`ws-scrcpy-${mirrorReloadKey}`}
|
||||
title="ws-scrcpy"
|
||||
src={scrcpyUrl}
|
||||
className="h-[min(72vh,640px)] w-full bg-black"
|
||||
@@ -349,15 +393,15 @@ export function LiveAndroidWorkstation({
|
||||
<div>
|
||||
<p className="border-b border-white/5 px-4 py-3 text-[11px] leading-relaxed text-zinc-400">
|
||||
{t(
|
||||
"「Panda / WebUSB」在浏览器里直连手机:手机必须插在您正在用浏览器看页面的这台电脑的 USB 上(走 WebUSB,不是板子上的 ADB)。官方演示见 ",
|
||||
"Panda uses WebUSB: phone must plug into the PC running this browser, not the board. Demo: ",
|
||||
"Panda / WebUSB:手机必须插在您正在打开本页的电脑 USB 上(与板载 ADB 无关)。与官方 scrcpy 协议生态同源,适合本地调试。仓库:",
|
||||
"Panda WebUSB: phone plugs into the PC running this browser. Same scrcpy-protocol family. Repo: ",
|
||||
)}
|
||||
<a className="text-cyan-300 hover:underline" href={PANDA_WEB_SCRCPY} target="_blank" rel="noreferrer">
|
||||
panda-web-scrcpy
|
||||
<a className="text-cyan-300 hover:underline" href={LINK_PANDA_GH} target="_blank" rel="noreferrer">
|
||||
PandaTestGrid/panda-web-scrcpy
|
||||
</a>
|
||||
。
|
||||
</p>
|
||||
<iframe
|
||||
key={`panda-${mirrorReloadKey}`}
|
||||
title="panda-web-scrcpy"
|
||||
src={PANDA_WEB_SCRCPY}
|
||||
className="h-[min(72vh,640px)] w-full bg-black"
|
||||
@@ -680,10 +724,25 @@ export function LiveAndroidWorkstation({
|
||||
value={logcatLines}
|
||||
onChange={(e) => setLogcatLines(e.target.value)}
|
||||
/>
|
||||
<label className="flex cursor-pointer items-center gap-2 text-[11px] text-zinc-400">
|
||||
<input
|
||||
type="checkbox"
|
||||
className="rounded border-white/20 bg-black/40"
|
||||
checked={logcatLive}
|
||||
onChange={(e) => setLogcatLive(e.target.checked)}
|
||||
/>
|
||||
{t("每 3 秒自动刷新", "Auto every 3s")}
|
||||
</label>
|
||||
<button
|
||||
type="button"
|
||||
disabled={lock}
|
||||
onClick={() => void refreshLogcat()}
|
||||
onClick={() => {
|
||||
if (!androidSerial) {
|
||||
notify(t("请选择设备", "Pick a device"));
|
||||
return;
|
||||
}
|
||||
void refreshLogcat();
|
||||
}}
|
||||
className="rounded-xl bg-cyan-500/20 px-4 py-2 text-sm text-cyan-100 ring-1 ring-cyan-500/30"
|
||||
>
|
||||
{t("拉取日志", "Pull logs")}
|
||||
|
||||
Reference in New Issue
Block a user