Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
14 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 22 additions & 4 deletions PanTS-Demo/src/components/LiveWireOverlay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ type Props = {
anchorPointsCanvas: Array<[number, number]>; // dense — used for the fill path only
cornerPointsCanvas: Array<[number, number]>; // one per click — used for the dots
livePreviewPath: Array<[number, number]> | null;
// True once the cursor is within closing range of the first point. Used
// only to soften/highlight the closing anchor here — the actual
// "click here to close" nudge is a separate <CloseLoopHint> in
// VisualizationPage.tsx, positioned off the same corner.
nearClose?: boolean;
};


Expand All @@ -15,7 +20,7 @@ function pathToD(points: Array<[number, number]>, close: boolean): string {
}


function LiveWireOverlay({ anchorPointsCanvas, cornerPointsCanvas, livePreviewPath }: Props) {
function LiveWireOverlay({ anchorPointsCanvas, cornerPointsCanvas, livePreviewPath, nearClose }: Props) {
return (
<svg
className="vp-livewire-overlay"
Expand All @@ -39,9 +44,22 @@ function LiveWireOverlay({ anchorPointsCanvas, cornerPointsCanvas, livePreviewPa
opacity={0.6}
/>
)}
{cornerPointsCanvas.map((p, i) => (
<circle key={`corner-${i}`} cx={p[0]} cy={p[1]} r={4} fill={i === 0 ? "#f43f5e" : "#22d3ee"} stroke="#08090b" strokeWidth={1.5} />
))}
{cornerPointsCanvas.map((p, i) => {
const isClosingAnchor = i === 0;
return (
<circle
key={`corner-${i}`}
cx={p[0]}
cy={p[1]}
r={isClosingAnchor && nearClose ? 6 : 4}
fill={isClosingAnchor ? "#f43f5e" : "#22d3ee"}
fillOpacity={isClosingAnchor && nearClose ? 0.55 : 1}
stroke="#08090b"
strokeWidth={1.5}
style={isClosingAnchor ? { transition: "r 150ms ease, fill-opacity 150ms ease" } : undefined}
/>
);
})}
</svg>
);
}
Expand Down
134 changes: 76 additions & 58 deletions PanTS-Demo/src/components/segmentation/SegmentsPopup.css
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@

.segpop.is-open {
transform: translateX(0);
/* Faint blue-tinted left edge — same accent used by AnnotationToolbar's
ribbon border when open — so the two read as one coordinated
"annotation mode is on" surface, distinct from the plain-black main
toolbar and other panels. */
border-left-color: rgba(104, 172, 229, 0.3);
}

.segpop.is-closed {
Expand Down Expand Up @@ -148,6 +153,12 @@
background: rgba(104, 172, 229, 0.14);
}

.segpop__row.is-editing-target {
background: rgba(104, 172, 229, 0.1);
outline: 1px solid rgba(104, 172, 229, 0.35);
outline-offset: -1px;
}

.segpop__vis {
background: transparent;
border: none;
Expand Down Expand Up @@ -305,7 +316,9 @@

/* Shared active/targeting indicator — icon only, used identically in the
Existing-organ and Custom tabs so both read the same way and cost the
row as little width as possible, leaving more room for the name. */
row as little width as possible, leaving more room for the name. Now a
real button (see TargetBadge in SegmentsPopup.tsx): clicking it untargets
the class directly, so the icon itself is the "remove target" control. */
.segpop__target-badge {
display: flex;
align-items: center;
Expand All @@ -318,6 +331,19 @@
background: rgba(104, 172, 229, 0.16);
}

.segpop__target-badge--btn {
border: none;
cursor: pointer;
padding: 0;
transition: background 0.12s ease, color 0.12s ease, transform 0.12s ease;
}

.segpop__target-badge--btn:hover {
background: rgba(232, 93, 93, 0.22);
color: #E85D5D;
transform: scale(1.08);
}

.segpop__row.is-deleting {
opacity: 0;
transform: scale(0.97);
Expand Down Expand Up @@ -352,58 +378,64 @@
flex-shrink: 0;
}

.segpop__edit-btn:hover {
.segpop__edit-btn:hover,
.segpop__edit-btn.is-active {
color: #68ACE5;
}

/* .segpop__collapse (shared by both the editing row and the add-form below)
is the Collapse component's own wrapper — its `max-height` is driven
directly from JS (see the Collapse component in SegmentsPopup.tsx),
measured off the content's real scrollHeight rather than guessed via a
CSS grid-track trick. That's what fixes the old "stuck halfway then
disappears" bug: the previous grid-template-rows(0fr/1fr) approach
animated a grid track's size independently of the content's actual
height, so an error line appearing/disappearing (or any other height
change) could desync the two, and the close was finalized by a JS
setTimeout guessing the transition's duration rather than the
transition's own completion — a slow frame could unmount the row well
before the animation had actually finished, reading as a stuck-then-gone
snap. Only `max-height` needs a transition here; opacity/transform ride
along on the same one shared curve so the fade and the collapse read as
one continuous motion instead of two racing animations. */
.segpop__collapse {
overflow: hidden;
/* Floating add/edit form (see FormFlyout in SegmentsPopup.tsx). Portaled to
<body> and positioned via inline `fixed` coords computed off the trigger
button's own rect — it never participates in the row list's flex flow, so
opening it never shifts any existing row or icon. Open/close is driven by
this component's own JS timer rather than waiting on a transitionend, so
(unlike the old inline collapse) it can't get stuck: closing always
finishes and calls back, every time. */
.segpop__form-flyout {
position: relative;
z-index: 2000;
width: 260px;
max-width: calc(100vw - 16px);
display: flex;
flex-direction: column;
padding: 10px;
border-radius: 10px;
background: #16181d;
border: 1px solid rgba(255, 255, 255, 0.1);
box-shadow: 0 14px 34px rgba(0, 0, 0, 0.55);
transform-origin: top left;
transition: opacity 0.15s ease, transform 0.15s ease;
}

.segpop__form-flyout.is-open {
opacity: 1;
transform: none;
transform-origin: top center;
transition: max-height 0.28s cubic-bezier(0.4, 0, 0.2, 1),
opacity 0.2s cubic-bezier(0.4, 0, 0.2, 1),
transform 0.2s cubic-bezier(0.4, 0, 0.2, 1),
margin 0.28s cubic-bezier(0.4, 0, 0.2, 1);
transform: scale(1) translateY(0);
}

.segpop__collapse.is-closing {
.segpop__form-flyout.is-closing {
opacity: 0;
/* Subtler than a bouncier scale — a barely-there shrink reads as
"settling away" rather than a visible pop, which suits a form
dismiss better than the treatment used for e.g. a toast or modal. */
transform: scale(0.98);
margin: 0;
transform: scale(0.96) translateY(-4px);
pointer-events: none;
}

.segpop__collapse-inner {
min-height: 0;
/* Little pointer connecting the flyout back to whichever button opened it
(the "Add class" button, or a row's pencil icon) — left offset is set
inline, computed from that trigger's own center. */
.segpop__form-flyout-arrow {
position: absolute;
top: -6px;
width: 11px;
height: 11px;
background: #16181d;
border-left: 1px solid rgba(255, 255, 255, 0.1);
border-top: 1px solid rgba(255, 255, 255, 0.1);
transform: translateX(-50%) rotate(45deg);
}

.segpop__row--editing-inner {
.segpop__form-flyout-inner {
display: flex;
align-items: center;
gap: 6px;
flex-wrap: wrap;
padding: 8px;
border-radius: 8px;
background: rgba(255, 255, 255, 0.05);
flex-direction: column;
align-items: stretch;
gap: 8px;
position: relative;
}

Expand Down Expand Up @@ -515,28 +547,14 @@
margin-top: 4px;
}

.segpop__new:hover {
.segpop__new:hover,
.segpop__new.is-active {
color: #fff;
border-color: rgba(104, 172, 229, 0.45);
}

.segpop__add-form {
margin-top: 4px;
animation: segpop-row-in 0.2s ease both;
}

.segpop__add-form.is-closing {
margin-top: 0;
}

.segpop__add-form-inner {
display: flex;
flex-direction: column;
align-items: stretch;
gap: 8px;
padding: 8px;
border-radius: 8px;
background: rgba(255, 255, 255, 0.04);
.segpop__new.is-active {
background: rgba(104, 172, 229, 0.1);
}

/* Now rendered via the shared ApplyButton component (see SegmentsPopup.tsx)
Expand Down
Loading
Loading