From 4f68109421e5ad6e3a0f9bd5e2daaa3fba3f9e73 Mon Sep 17 00:00:00 2001 From: David Montero Crespo Date: Tue, 4 Aug 2026 02:43:53 +0200 Subject: [PATCH] feat(ui): pins always on in the inspector, above the chat, and traceable Three things from using it: - The pin map is why the dialog gets opened, so hiding it behind a checkbox meant every visit started with a click. Pins are always drawn now and the toggle is gone (with its translation key). - The dialog sat at z-index 1300 and the pro agent chat panel docks over the canvas at 8000, so on a wide layout the dialog opened UNDERNEATH the chat - invisible, on the very area the chat covers. It now sits at 8500: above the chat, still below the component picker. - On a 32-pin board a name in the gutter could not be matched to a pad by eye. Hovering (or focusing) either the label or the dot now lights the whole run - label, leader line and pad together. --- .../simulator/PartInspectorDialog.css | 41 +++++----- .../simulator/PartInspectorDialog.tsx | 81 ++++++++++--------- frontend/src/i18n/locales/de/common2.json | 3 +- frontend/src/i18n/locales/en/common2.json | 3 +- frontend/src/i18n/locales/es/common2.json | 3 +- frontend/src/i18n/locales/fr/common2.json | 3 +- frontend/src/i18n/locales/it/common2.json | 3 +- frontend/src/i18n/locales/ja/common2.json | 3 +- frontend/src/i18n/locales/pt-br/common2.json | 3 +- frontend/src/i18n/locales/ru/common2.json | 3 +- frontend/src/i18n/locales/zh-cn/common2.json | 3 +- 11 files changed, 74 insertions(+), 75 deletions(-) diff --git a/frontend/src/components/simulator/PartInspectorDialog.css b/frontend/src/components/simulator/PartInspectorDialog.css index f45a5988..163b22c9 100644 --- a/frontend/src/components/simulator/PartInspectorDialog.css +++ b/frontend/src/components/simulator/PartInspectorDialog.css @@ -15,9 +15,11 @@ display: flex; flex-direction: column; box-shadow: 0 6px 28px rgba(0, 0, 0, 0.55); - /* Above every canvas overlay (wire menus sit at ~1001); below the picker - modal stack (9000+), which is never open at the same time anyway. */ - z-index: 1300; + /* Above every canvas overlay (wire menus ~1001) AND above the pro agent + chat panel (8000/8001), which docks over the canvas — the dialog opens + from a right-click on the very thing the chat covers, so it has to win. + Still below the component picker (9000+), which is never open with it. */ + z-index: 8500; pointer-events: all; color: #fff; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif; @@ -200,6 +202,14 @@ stroke: #777; stroke-width: 1; stroke-dasharray: 2 2; + transition: stroke 0.1s; +} + +/* Tracing a name back to its pad: the whole run lights at once. */ +.pid-pin-leader--lit line { + stroke: #4dd0e1; + stroke-width: 1.6; + stroke-dasharray: none; } .pid-pin-dot { @@ -219,9 +229,11 @@ cursor: default; } -.pid-pin-dot:hover:not(:disabled) { - transform: scale(1.5); +.pid-pin-dot:hover:not(:disabled), +.pid-pin-dot--lit { + transform: scale(1.6); background: #4dd0e1; + box-shadow: 0 0 6px rgba(77, 208, 225, 0.9); } /* Signal tinting — the same data the wire classifier reads. */ @@ -252,8 +264,10 @@ cursor: default; } -.pid-pin-label:hover:not(:disabled) { +.pid-pin-label:hover:not(:disabled), +.pid-pin-label--lit { color: #4dd0e1; + font-weight: 700; } .pid-pin-label--left { @@ -276,19 +290,10 @@ transform: translate(-50%, 0); } -.pid-show-pins { - display: flex; - align-items: center; - gap: 6px; - font-size: 12px; - color: #ccc; - cursor: pointer; - user-select: none; -} - -.pid-wire-hint { - color: #4dd0e1; +.pid-pin-hint { font-size: 11px; + color: #999; + text-align: center; } /* ── Actions ─────────────────────────────────────────────────────────── */ diff --git a/frontend/src/components/simulator/PartInspectorDialog.tsx b/frontend/src/components/simulator/PartInspectorDialog.tsx index 859634fb..5af0c2b8 100644 --- a/frontend/src/components/simulator/PartInspectorDialog.tsx +++ b/frontend/src/components/simulator/PartInspectorDialog.tsx @@ -8,10 +8,11 @@ * Datasheet tabs on the right. * * The preview is the REAL web component, mounted and scaled — not a drawing. - * With "Show pins" enabled (off by default), pin markers are laid over it at - * their true positions and labels flank the art on the side each pin lives on - * (pinInspectorLayout). Everything is driven by the element's own pinInfo and - * measured size, so any part present or future works with zero per-part data. + * Pin markers are laid over it at their true positions and their labels flank + * the art on the side each pin lives on (pinInspectorLayout); hovering a name + * lights its leader line and its pad so the two can be traced to each other. + * Everything is driven by the element's own pinInfo and measured size, so any + * part present or future works with zero per-part data. * * Clicking a pin keeps the exact contract of the old dialog: onPinSelect -> * the canvas starts (or finishes) a wire through handlePinClick, with its @@ -99,12 +100,9 @@ interface PartInspectorDialogProps { previewAttributes?: Record; } -const PREVIEW_ART = 150; // collapsed preview box (the part's own art) /** - * Pin-map box. Smaller than the collapsed preview on purpose: in this mode the - * part's art is HIDDEN (it ate the dialog for no information) and only the pin - * dots, their labels and a faint outline of the body are drawn, so the spatial - * arrangement still reads while the panel stays compact. + * The art box the part is scaled into. The pin gutters are added around it, so + * the dialog's left column ends up a little wider than this. */ const PINS_ART = 190; @@ -131,10 +129,9 @@ export const PartInspectorDialog: React.FC = ({ const previewHostRef = useRef(null); const [dialogPos, setDialogPos] = useState<{ x: number; y: number } | null>(null); const [tab, setTab] = useState<'properties' | 'datasheet'>('properties'); - // Pins are HIDDEN by default (explicit product decision): most opens are - // for a property tweak or a delete, and the quiet preview keeps the dialog - // compact until pins are actually wanted. - const [showPins, setShowPins] = useState(false); + // The pin the pointer is on — its leader line and dot light up together, so + // a name in the gutter can be traced to the pad it belongs to. + const [hoverPin, setHoverPin] = useState(null); const [confirmingDelete, setConfirmingDelete] = useState(false); const [layout, setLayout] = useState(null); const [previewFailed, setPreviewFailed] = useState(false); @@ -247,7 +244,7 @@ export const PartInspectorDialog: React.FC = ({ return; } const pins: InspectorPinInput[] = (el.pinInfo && el.pinInfo.length ? el.pinInfo : pinInfo) ?? []; - const art = showPins ? PINS_ART : PREVIEW_ART; + const art = PINS_ART; const result = layoutInspectorPins(pins, { width: w, height: h }, { maxArtWidth: art, maxArtHeight: art, @@ -270,7 +267,7 @@ export const PartInspectorDialog: React.FC = ({ // (a colour edit, an LED flip) changes the digest and repaints; a bare // re-render does not. // eslint-disable-next-line react-hooks/exhaustive-deps - }, [componentMetadata, propsKey, pinsKey, showPins, previewTagName]); + }, [componentMetadata, propsKey, pinsKey, previewTagName]); // ── Position: viewport coords, clamped, portaled to body ───────────────── // Re-run on every SIZE change, not just on the inputs we can name. The @@ -391,12 +388,12 @@ export const PartInspectorDialog: React.FC = ({ {/* Left column: live preview, pin toggle, actions. */}
= ({
{previewFailed && !svgThumb &&
—
} {previewFailed && svgThumb && ( @@ -416,17 +409,29 @@ export const PartInspectorDialog: React.FC = ({ )} {/* Pin markers + labels, at their true positions. */} - {showPins && - layout && + {layout && layout.pins.map((p) => { const ox = layout.padLeft; const oy = layout.padTop; const clickable = Boolean(onPinSelect); const activate = () => onPinSelect?.(componentId, p.name); + const lit = hoverPin === p.name; + // Hovering EITHER the label or the dot lights the whole run: + // label, leader and pad. On a 32-pin board that is the only + // way to be sure which pad a gutter name belongs to. + const trace = { + onMouseEnter: () => setHoverPin(p.name), + onMouseLeave: () => setHoverPin((c) => (c === p.name ? null : c)), + onFocus: () => setHoverPin(p.name), + onBlur: () => setHoverPin((c) => (c === p.name ? null : c)), + }; return ( {p.needsLeader && ( -
{pinInfo.length > 0 && ( - +
+ {wireInProgress + ? t('editor.componentProps.tapToConnect') + : onPinSelect + ? t('editor.componentProps.tapToWire') + : t('editor.componentProps.pinRoles')} +
)}
diff --git a/frontend/src/i18n/locales/de/common2.json b/frontend/src/i18n/locales/de/common2.json index dc71e3b9..ad590e75 100644 --- a/frontend/src/i18n/locales/de/common2.json +++ b/frontend/src/i18n/locales/de/common2.json @@ -391,8 +391,7 @@ }, "inspector": { "propertiesTab": "Eigenschaften", - "datasheetTab": "Datenblatt", - "showPins": "Pins anzeigen" + "datasheetTab": "Datenblatt" } }, "about": { diff --git a/frontend/src/i18n/locales/en/common2.json b/frontend/src/i18n/locales/en/common2.json index 26e5507f..d14744f4 100644 --- a/frontend/src/i18n/locales/en/common2.json +++ b/frontend/src/i18n/locales/en/common2.json @@ -393,8 +393,7 @@ }, "inspector": { "propertiesTab": "Properties", - "datasheetTab": "Datasheet", - "showPins": "Show pins" + "datasheetTab": "Datasheet" } }, "about": { diff --git a/frontend/src/i18n/locales/es/common2.json b/frontend/src/i18n/locales/es/common2.json index 6af6202c..275b7cae 100644 --- a/frontend/src/i18n/locales/es/common2.json +++ b/frontend/src/i18n/locales/es/common2.json @@ -393,8 +393,7 @@ }, "inspector": { "propertiesTab": "Propiedades", - "datasheetTab": "Ficha técnica", - "showPins": "Ver pines" + "datasheetTab": "Ficha técnica" } }, "about": { diff --git a/frontend/src/i18n/locales/fr/common2.json b/frontend/src/i18n/locales/fr/common2.json index f0f4fb17..4ac9958c 100644 --- a/frontend/src/i18n/locales/fr/common2.json +++ b/frontend/src/i18n/locales/fr/common2.json @@ -391,8 +391,7 @@ }, "inspector": { "propertiesTab": "Propriétés", - "datasheetTab": "Fiche technique", - "showPins": "Afficher les broches" + "datasheetTab": "Fiche technique" } }, "about": { diff --git a/frontend/src/i18n/locales/it/common2.json b/frontend/src/i18n/locales/it/common2.json index 15ec3cf2..735ccdf8 100644 --- a/frontend/src/i18n/locales/it/common2.json +++ b/frontend/src/i18n/locales/it/common2.json @@ -391,8 +391,7 @@ }, "inspector": { "propertiesTab": "Proprietà", - "datasheetTab": "Scheda tecnica", - "showPins": "Mostra i pin" + "datasheetTab": "Scheda tecnica" } }, "about": { diff --git a/frontend/src/i18n/locales/ja/common2.json b/frontend/src/i18n/locales/ja/common2.json index bd3ac1da..d71d819b 100644 --- a/frontend/src/i18n/locales/ja/common2.json +++ b/frontend/src/i18n/locales/ja/common2.json @@ -391,8 +391,7 @@ }, "inspector": { "propertiesTab": "プロパティ", - "datasheetTab": "データシート", - "showPins": "ピンを表示" + "datasheetTab": "データシート" } }, "about": { diff --git a/frontend/src/i18n/locales/pt-br/common2.json b/frontend/src/i18n/locales/pt-br/common2.json index dec9d4bf..8c7a51fb 100644 --- a/frontend/src/i18n/locales/pt-br/common2.json +++ b/frontend/src/i18n/locales/pt-br/common2.json @@ -391,8 +391,7 @@ }, "inspector": { "propertiesTab": "Propriedades", - "datasheetTab": "Ficha técnica", - "showPins": "Mostrar pinos" + "datasheetTab": "Ficha técnica" } }, "about": { diff --git a/frontend/src/i18n/locales/ru/common2.json b/frontend/src/i18n/locales/ru/common2.json index 9ad90910..c020ce94 100644 --- a/frontend/src/i18n/locales/ru/common2.json +++ b/frontend/src/i18n/locales/ru/common2.json @@ -391,8 +391,7 @@ }, "inspector": { "propertiesTab": "Свойства", - "datasheetTab": "Спецификация", - "showPins": "Показать выводы" + "datasheetTab": "Спецификация" } }, "about": { diff --git a/frontend/src/i18n/locales/zh-cn/common2.json b/frontend/src/i18n/locales/zh-cn/common2.json index b13c0761..9ff52270 100644 --- a/frontend/src/i18n/locales/zh-cn/common2.json +++ b/frontend/src/i18n/locales/zh-cn/common2.json @@ -391,8 +391,7 @@ }, "inspector": { "propertiesTab": "属性", - "datasheetTab": "数据手册", - "showPins": "显示引脚" + "datasheetTab": "数据手册" } }, "about": {