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
|
|
@ -276,7 +276,13 @@ json itemToJson( BOARD_ITEM* aItem )
|
||||||
j["sy"] = tr->GetStart().y;
|
j["sy"] = tr->GetStart().y;
|
||||||
j["ex"] = tr->GetEnd().x;
|
j["ex"] = tr->GetEnd().x;
|
||||||
j["ey"] = tr->GetEnd().y;
|
j["ey"] = tr->GetEnd().y;
|
||||||
j["width"] = tr->GetWidth();
|
|
||||||
|
// The layerless PCB_VIA::GetWidth() is an assert trap (padstack refactor); pass the
|
||||||
|
// whole-stack slot instead. applyChanged's layerless SetWidth writes the same slot.
|
||||||
|
if( aItem->Type() == PCB_VIA_T )
|
||||||
|
j["width"] = static_cast<PCB_VIA*>( aItem )->GetWidth( PADSTACK::ALL_LAYERS );
|
||||||
|
else
|
||||||
|
j["width"] = tr->GetWidth();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Vias and zones reconstruct NATIVELY on `added` (the s-expr clipboard blob's `(kicad_pcb …)`
|
// Vias and zones reconstruct NATIVELY on `added` (the s-expr clipboard blob's `(kicad_pcb …)`
|
||||||
|
|
|
||||||
|
|
@ -999,6 +999,7 @@ export function WasmTool({
|
||||||
const [status, setStatus] = React.useState("Loading tool…");
|
const [status, setStatus] = React.useState("Loading tool…");
|
||||||
const [logs, setLogs] = React.useState<string[]>([]);
|
const [logs, setLogs] = React.useState<string[]>([]);
|
||||||
const [showLog, setShowLog] = React.useState(false);
|
const [showLog, setShowLog] = React.useState(false);
|
||||||
|
const consolePanelRef = React.useRef<HTMLDivElement>(null);
|
||||||
const [oomExhausted, setOomExhausted] = React.useState(false);
|
const [oomExhausted, setOomExhausted] = React.useState(false);
|
||||||
// Terminal failure, rendered INDEPENDENTLY of `ready`. The boot overlay only
|
// Terminal failure, rendered INDEPENDENTLY of `ready`. The boot overlay only
|
||||||
// exists while `!ready`, so anything that killed the runtime after the editor
|
// 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.
|
// exposes the style bridge, mounts the floating panel.
|
||||||
const [tunerMod, setTunerMod] = React.useState<TunerModule | null>(null);
|
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) => {
|
const append = React.useCallback((msg: string) => {
|
||||||
// Mirror into the React-independent fatal-screen ring: if React ever
|
// Mirror into the React-independent fatal-screen ring: if React ever
|
||||||
// unmounts itself on a crash, the DOM floor still has the full log.
|
// 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
|
must never end up underneath the thing reporting the failure. Forced
|
||||||
visible on a fatal even with chrome hidden, for the same reason. */}
|
visible on a fatal even with chrome hidden, for the same reason. */}
|
||||||
{(!effectiveChromeHidden || fatal) && (
|
{(!effectiveChromeHidden || fatal) && (
|
||||||
<div className="absolute bottom-0 left-0 right-0 z-40">
|
/* Closed: a content-width tab pinned bottom-left (no right-0), so the
|
||||||
<div className="flex items-center bg-black/70">
|
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
|
<button
|
||||||
className="flex items-center gap-1 px-3 py-1 font-mono text-xs text-white"
|
className="flex items-center gap-1 bg-black/70 px-3 py-1 font-mono text-xs text-white"
|
||||||
onClick={() => setShowLog((s) => !s)}
|
onClick={() => setShowLog(true)}
|
||||||
>
|
>
|
||||||
{showLog ? <ChevronDown size={14} /> : <ChevronUp size={14} />} console
|
<ChevronUp size={14} /> console ({logs.length})
|
||||||
({logs.length})
|
|
||||||
</button>
|
</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>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue