commits
tags
import { useEffect, useRef, useState } from "react";
import { ApiError, api, type InspectRow, type Step } from "../lib/api";
import type { T } from "../lib/i18n";
import { uid } from "../lib/ids";
interface Props {
open: boolean;
t: T;
onClose: () => void;
onCreateStep: (step: Step) => void;
}
export function InspectDialog({ open, t, onClose, onCreateStep }: Props) {
const ref = useRef<HTMLDialogElement>(null);
const [windows, setWindows] = useState<string[]>([]);
const [title, setTitle] = useState("");
const [rows, setRows] = useState<InspectRow[]>([]);
const [err, setErr] = useState<string | null>(null);
const [busy, setBusy] = useState(false);
useEffect(() => {
const d = ref.current;
if (!d) return;
if (open && !d.open) {
d.showModal();
setErr(null);
setRows([]);
api
.desktopWindows()
.then((w) => {
setWindows(w);
if (w.length) setTitle(w[0]);
})
.catch((e) => setErr(e instanceof ApiError && e.status === 501 ? t("inspect_unavailable") : String(e)));
}
if (!open && d.open) d.close();
}, [open, t]);
async function load() {
if (!title) return;
setBusy(true);
setErr(null);
try {
setRows(await api.inspectWindow(title));
} catch (e) {
setErr(e instanceof ApiError ? String(e.detail) : String(e));
} finally {
setBusy(false);
}
}
function selectorFor(row: InspectRow): { by: string; value: string } {
if (row.auto_id) return { by: "auto_id", value: row.auto_id };
if (row.name) return { by: "name", value: row.name };
if (row.class_name) return { by: "class_name", value: row.class_name };
return { by: "name", value: "" };
}
function makeStep(row: InspectRow, type: "desktop_click" | "desktop_type") {
const sel = selectorFor(row);
const step: Step = { type, id: uid(), by: sel.by, value: sel.value };
if (row.control_type) step.control_type = row.control_type;
if (type === "desktop_type") step.text = "";
onCreateStep(step);
}
return (
<dialog ref={ref} onClose={onClose} className="inspect-dialog">
<h2>{t("inspect_window")}</h2>
<p className="hint">{t("inspect_hint")}</p>
<div className="inspect-controls">
<select value={title} onChange={(e) => setTitle(e.target.value)}>
{windows.length === 0 && <option value="">{t("inspect_none")}</option>}
{windows.map((w) => (
<option key={w} value={w}>
{w}
</option>
))}
</select>
<button onClick={load} disabled={busy || !title}>
{busy ? "…" : t("inspect_load")}
</button>
<span className="spacer" />
<button type="button" className="secondary" onClick={onClose}>
{t("cancel")}
</button>
</div>
{err && <p className="assist-msg error">{err}</p>}
{rows.length > 0 && (
<div className="inspect-table">
{rows.map((row, i) => {
const sel = selectorFor(row);
return (
<div className="inspect-row" key={i} style={{ paddingLeft: 8 + row.depth * 14 }}>
<code className="inspect-ct">{row.control_type || "?"}</code>
<span className="inspect-name">{row.name || <em>—</em>}</span>
{row.auto_id && <code className="inspect-aid">#{row.auto_id}</code>}
<span className="spacer" />
<button
className="secondary tiny"
title={`${sel.by}=${sel.value}`}
onClick={() => navigator.clipboard?.writeText(`${sel.by}=${sel.value}`)}
>
{t("inspect_copy")}
</button>
<button className="secondary tiny" onClick={() => makeStep(row, "desktop_click")}>
{t("inspect_make_click")}
</button>
<button className="secondary tiny" onClick={() => makeStep(row, "desktop_type")}>
{t("inspect_make_type")}
</button>
</div>
);
})}
</div>
)}
</dialog>
);
}