fix(editor): via GetWidth layer fix + console tab/copy restore
- itemToJson: pass PADSTACK::ALL_LAYERS for vias — the layerless virtual PCB_VIA::GetWidth() is an assert trap since the padstack refactor, and the collab baseline/snapshot serializer hit it once per via per snapshot (big-board load = assert storm). Values were already correct; wire format unchanged (applyChanged's layerless SetWidth writes the same slot). - console: closed state is a content-width bottom-left tab again (version badge + app bottom edge visible); opened footer panel unchanged. - console: partial-selection copy works — wx's window-level keydown handler preventDefaults Ctrl/Cmd+C, so a capture-phase guard stops propagation to wx when the selection lives in the console; canvas mousedown collapses stale log selections so they can't steal the editor's own Ctrl+C. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HPtPBVLKQzaXTkirYgcVay
This commit is contained in:
parent
42479b313c
commit
a0bbffe5c0
2 changed files with 76 additions and 25 deletions
|
|
@ -999,6 +999,7 @@ export function WasmTool({
|
|||
const [status, setStatus] = React.useState("Loading tool…");
|
||||
const [logs, setLogs] = React.useState<string[]>([]);
|
||||
const [showLog, setShowLog] = React.useState(false);
|
||||
const consolePanelRef = React.useRef<HTMLDivElement>(null);
|
||||
const [oomExhausted, setOomExhausted] = React.useState(false);
|
||||
// Terminal failure, rendered INDEPENDENTLY of `ready`. The boot overlay only
|
||||
// exists while `!ready`, so anything that killed the runtime after the editor
|
||||
|
|
@ -1096,6 +1097,36 @@ export function WasmTool({
|
|||
// exposes the style bridge, mounts the floating panel.
|
||||
const [tunerMod, setTunerMod] = React.useState<TunerModule | null>(null);
|
||||
|
||||
// wx's window-level keydown handler forwards Ctrl/Cmd+C to the wasm app and
|
||||
// preventDefaults it, so the browser's native "copy selection" never runs —
|
||||
// log text could be selected but not copied. When the selection lives in the
|
||||
// console panel, intercept the chord in the CAPTURE phase (ahead of wx's
|
||||
// bubble-phase listener) and stop propagation; the default copy still fires.
|
||||
// A canvas mousedown would normally collapse a selection, but wx
|
||||
// preventDefaults that too — mirror it, or a stale log selection would keep
|
||||
// stealing the editor's own Ctrl+C.
|
||||
React.useEffect(() => {
|
||||
const selectionInConsole = () => {
|
||||
const sel = window.getSelection();
|
||||
if (!sel || sel.isCollapsed || !sel.anchorNode) return false;
|
||||
return consolePanelRef.current?.contains(sel.anchorNode) ?? false;
|
||||
};
|
||||
const onKeyDown = (e: KeyboardEvent) => {
|
||||
if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "c" && selectionInConsole())
|
||||
e.stopPropagation();
|
||||
};
|
||||
const onPointerDown = (e: PointerEvent) => {
|
||||
if (e.target instanceof HTMLCanvasElement && selectionInConsole())
|
||||
window.getSelection()?.removeAllRanges();
|
||||
};
|
||||
window.addEventListener("keydown", onKeyDown, true);
|
||||
window.addEventListener("pointerdown", onPointerDown, true);
|
||||
return () => {
|
||||
window.removeEventListener("keydown", onKeyDown, true);
|
||||
window.removeEventListener("pointerdown", onPointerDown, true);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const append = React.useCallback((msg: string) => {
|
||||
// Mirror into the React-independent fatal-screen ring: if React ever
|
||||
// unmounts itself on a crash, the DOM floor still has the full log.
|
||||
|
|
@ -2481,33 +2512,47 @@ export function WasmTool({
|
|||
must never end up underneath the thing reporting the failure. Forced
|
||||
visible on a fatal even with chrome hidden, for the same reason. */}
|
||||
{(!effectiveChromeHidden || fatal) && (
|
||||
<div className="absolute bottom-0 left-0 right-0 z-40">
|
||||
<div className="flex items-center bg-black/70">
|
||||
/* Closed: a content-width tab pinned bottom-left (no right-0), so the
|
||||
version badge and the app's bottom edge stay visible/clickable.
|
||||
Open: the full-width footer panel. */
|
||||
<div
|
||||
ref={consolePanelRef}
|
||||
className={
|
||||
showLog ? "absolute bottom-0 left-0 right-0 z-40" : "absolute bottom-0 left-0 z-40"
|
||||
}
|
||||
>
|
||||
{showLog ? (
|
||||
<>
|
||||
<div className="flex items-center bg-black/70">
|
||||
<button
|
||||
className="flex items-center gap-1 px-3 py-1 font-mono text-xs text-white"
|
||||
onClick={() => setShowLog(false)}
|
||||
>
|
||||
<ChevronDown size={14} /> console ({logs.length})
|
||||
</button>
|
||||
<button
|
||||
className="ml-auto px-3 py-1 font-mono text-xs text-white/70 hover:text-white"
|
||||
onClick={() => {
|
||||
void navigator.clipboard.writeText(logs.join("\n")).then(
|
||||
() => append("[console] copied to clipboard"),
|
||||
() => append("[console] clipboard copy failed"),
|
||||
);
|
||||
}}
|
||||
>
|
||||
copy
|
||||
</button>
|
||||
</div>
|
||||
<pre className="max-h-64 select-text cursor-text overflow-auto bg-black/85 p-3 font-mono text-[11px] leading-tight text-green-300">
|
||||
{logs.join("\n")}
|
||||
</pre>
|
||||
</>
|
||||
) : (
|
||||
<button
|
||||
className="flex items-center gap-1 px-3 py-1 font-mono text-xs text-white"
|
||||
onClick={() => setShowLog((s) => !s)}
|
||||
className="flex items-center gap-1 bg-black/70 px-3 py-1 font-mono text-xs text-white"
|
||||
onClick={() => setShowLog(true)}
|
||||
>
|
||||
{showLog ? <ChevronDown size={14} /> : <ChevronUp size={14} />} console
|
||||
({logs.length})
|
||||
<ChevronUp size={14} /> console ({logs.length})
|
||||
</button>
|
||||
{showLog && (
|
||||
<button
|
||||
className="ml-auto px-3 py-1 font-mono text-xs text-white/70 hover:text-white"
|
||||
onClick={() => {
|
||||
void navigator.clipboard.writeText(logs.join("\n")).then(
|
||||
() => append("[console] copied to clipboard"),
|
||||
() => append("[console] clipboard copy failed"),
|
||||
);
|
||||
}}
|
||||
>
|
||||
copy
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
{showLog && (
|
||||
<pre className="max-h-64 overflow-auto bg-black/85 p-3 font-mono text-[11px] leading-tight text-green-300">
|
||||
{logs.join("\n")}
|
||||
</pre>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
|
|
|||
Loading…
Reference in a new issue