fix multi-profile

This commit is contained in:
2026-04-16 01:14:57 -07:00
parent 2d66eaef02
commit c7e7531206
8 changed files with 224 additions and 6 deletions

View File

@@ -14,6 +14,7 @@ const AngleMeasureOverlay = lazy(() => import('./AngleMeasureOverlay'));
const ThresholdHistogram = lazy(() => import('./ThresholdHistogram'));
const RadialProfileOverlay = lazy(() => import('./RadialProfileOverlay'));
const StraightenPathOverlay = lazy(() => import('./StraightenPathOverlay'));
const MultiProfileOverlay = lazy(() => import('./MultiProfileOverlay'));
import TextNoteNode from './TextNoteNode';
@@ -1198,6 +1199,7 @@ function CustomNode({ id, data }: { id: string; data: NodeData }) {
|| data.overlay.kind === 'threshold_histogram'
|| data.overlay.kind === 'radial_profile'
|| data.overlay.kind === 'straighten_path'
|| data.overlay.kind === 'multi_profile'
);
const hidePreviewForInteractiveMask = data.overlay?.kind === 'mask_paint' || data.overlay?.kind === 'markup';
const overlayTitle = data.overlay?.section_title
@@ -1217,6 +1219,8 @@ function CustomNode({ id, data }: { id: string; data: NodeData }) {
? 'Radial Profile'
: data.overlay?.kind === 'straighten_path'
? 'Path'
: data.overlay?.kind === 'multi_profile'
? 'Preview'
: 'Cross Section');
const headerMeta = (() => {
if (data.className === 'Folder') {
@@ -1572,6 +1576,15 @@ function CustomNode({ id, data }: { id: string; data: NodeData }) {
nodeId={id}
onWidgetChange={ctx!.onWidgetChange}
/>
) : data.overlay!.kind === 'multi_profile' ? (
<MultiProfileOverlay
image={data.overlay!.image ?? ''}
row={(data.overlay!.row ?? 0) as number}
direction={(data.overlay!.direction ?? 'horizontal') as 'horizontal' | 'vertical'}
maxIndex={(data.overlay!.max_index ?? 0) as number}
nodeId={id}
onWidgetChange={ctx!.onWidgetChange}
/>
) : data.overlay!.kind === 'angle_measure' ? (
<AngleMeasureOverlay
image={data.overlay!.image ?? ''}

View File

@@ -0,0 +1,89 @@
import React, { useRef, useState, useCallback, useEffect } from 'react';
import { clamp, pointerToFraction } from './overlayUtils';
export const CAPTURE_SELECTOR = '.multiprofile-overlay';
interface MultiProfileOverlayProps {
image: string;
row: number;
direction: 'horizontal' | 'vertical';
maxIndex: number;
nodeId: string;
onWidgetChange: (nodeId: string, name: string, value: unknown) => void;
}
export default function MultiProfileOverlay({
image, row, direction, maxIndex,
nodeId, onWidgetChange,
}: MultiProfileOverlayProps) {
const containerRef = useRef<HTMLDivElement>(null);
const [draftRow, setDraftRow] = useState<number | null>(null);
const draggingRef = useRef(false);
const pendingCommitRef = useRef<number | null>(null);
useEffect(() => {
if (pendingCommitRef.current !== null && row === pendingCommitRef.current) {
pendingCommitRef.current = null;
setDraftRow(null);
}
}, [row]);
const displayRow = draftRow ?? row;
const fractionFromEvent = useCallback((e: React.PointerEvent<Element>): number => {
if (!containerRef.current) return 0;
const { fx, fy } = pointerToFraction(e, containerRef.current);
return direction === 'horizontal' ? fy : fx;
}, [direction]);
const fractionToIndex = useCallback((frac: number): number => {
return clamp(Math.round(frac * maxIndex), 0, maxIndex);
}, [maxIndex]);
const onPointerDown = useCallback((e: React.PointerEvent<Element>) => {
e.stopPropagation();
e.preventDefault();
(e.currentTarget as HTMLElement).setPointerCapture(e.pointerId);
draggingRef.current = true;
setDraftRow(fractionToIndex(fractionFromEvent(e)));
}, [fractionFromEvent, fractionToIndex]);
const onPointerMove = useCallback((e: React.PointerEvent<Element>) => {
if (!draggingRef.current) return;
setDraftRow(fractionToIndex(fractionFromEvent(e)));
}, [fractionFromEvent, fractionToIndex]);
const onPointerUp = useCallback(() => {
if (draggingRef.current && draftRow !== null) {
pendingCommitRef.current = draftRow;
onWidgetChange(nodeId, 'row', draftRow);
}
draggingRef.current = false;
}, [draftRow, nodeId, onWidgetChange]);
const fracPos = maxIndex > 0 ? displayRow / maxIndex : 0;
const linePct = clamp(fracPos * 100, 0, 100);
const lineStyle: React.CSSProperties = direction === 'horizontal'
? { left: 0, right: 0, top: `${linePct}%`, height: 0 }
: { top: 0, bottom: 0, left: `${linePct}%`, width: 0 };
const cursorClass = direction === 'horizontal' ? 'cursor-row' : 'cursor-col';
return (
<div
ref={containerRef}
className={`nodrag nowheel multiprofile-overlay ${cursorClass}`}
onPointerDown={onPointerDown}
onPointerMove={onPointerMove}
onPointerUp={onPointerUp}
onLostPointerCapture={onPointerUp}
>
<img src={image} alt="A blended with B" draggable={false} className="multiprofile-image" />
<div className={`multiprofile-line multiprofile-line-${direction}`} style={lineStyle} />
<div className="multiprofile-readout">
{direction === 'horizontal' ? 'row' : 'col'} {displayRow}
</div>
</div>
);
}

View File

@@ -1736,6 +1736,43 @@ html, body, #root {
transform: translate(-50%, -50%) scale(1.2);
}
/* ── Multi Profile overlay ────────────────────────────────────────── */
.multiprofile-overlay {
position: relative;
user-select: none;
touch-action: none;
overflow: hidden;
}
.multiprofile-overlay.cursor-row { cursor: row-resize; }
.multiprofile-overlay.cursor-col { cursor: col-resize; }
.multiprofile-image {
width: 100%;
display: block;
}
.multiprofile-line {
position: absolute;
pointer-events: none;
}
.multiprofile-line-horizontal {
border-top: 1.5px solid var(--marker);
box-shadow: 0 0 4px var(--marker-shadow);
}
.multiprofile-line-vertical {
border-left: 1.5px solid var(--marker);
box-shadow: 0 0 4px var(--marker-shadow);
}
.multiprofile-readout {
position: absolute;
top: 4px;
left: 4px;
font-size: 10px;
background: rgba(0, 0, 0, 0.55);
color: #fff;
padding: 2px 5px;
border-radius: 3px;
pointer-events: none;
}
.angle-overlay {
--angle-line-color: #ff9800;
--angle-arc-color: rgb(255, 166, 77);
@@ -1992,7 +2029,8 @@ html, body, #root {
.is-panning .mask-paint-overlay,
.is-panning .markup-overlay,
.is-panning .radial-overlay,
.is-panning .straighten-overlay {
.is-panning .straighten-overlay,
.is-panning .multiprofile-overlay {
pointer-events: none;
}

View File

@@ -79,6 +79,9 @@ export interface OverlayData {
thickness?: number;
xres?: number;
yres?: number;
row?: number;
direction?: 'horizontal' | 'vertical';
max_index?: number;
section_title?: string;
line?: number[];
shape?: string;

View File

@@ -13,6 +13,7 @@ export const OVERLAY_CAPTURE_SELECTORS = [
'.angle-overlay', // AngleMeasureOverlay
'.radial-overlay', // RadialProfileOverlay
'.straighten-overlay', // StraightenPathOverlay
'.multiprofile-overlay', // MultiProfileOverlay
];
function encodeBase64(bytes: Uint8Array) {