fix(review): address code review findings — tool conflicts, dual inputs, pin animation
1. TEXT and ERASER tool clicks now suppressed during review mode via reviewMode flag on ToolContext (read from ref for live value) 2. Object-comment input hidden when draftPin is active — only one creation input visible at a time 3. PinOverlay entrance animation removed — focus styling (scale 1.15x, alpha changes) no longer fights with fade-in animation loop
This commit is contained in:
@@ -178,11 +178,7 @@ export class PinOverlay extends Container {
|
|||||||
|
|
||||||
this.addChild(container);
|
this.addChild(container);
|
||||||
|
|
||||||
// Entrance animation
|
// No entrance animation — focus styling must not be overwritten by animation.
|
||||||
container.alpha = 0;
|
|
||||||
container.scale.set(0.6);
|
|
||||||
this._animateEntrance(container);
|
|
||||||
|
|
||||||
return entry;
|
return entry;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -241,28 +237,6 @@ export class PinOverlay extends Container {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private _animateEntrance(pin: Container) {
|
|
||||||
const tick = () => {
|
|
||||||
if (pin.destroyed) return;
|
|
||||||
let done = true;
|
|
||||||
if (pin.alpha < 0.99) {
|
|
||||||
pin.alpha += (1 - pin.alpha) * 0.3;
|
|
||||||
done = false;
|
|
||||||
} else {
|
|
||||||
pin.alpha = 1;
|
|
||||||
}
|
|
||||||
const s = pin.scale.x;
|
|
||||||
if (s < 0.99) {
|
|
||||||
pin.scale.set(s + (1 - s) * 0.3);
|
|
||||||
done = false;
|
|
||||||
} else {
|
|
||||||
pin.scale.set(1);
|
|
||||||
}
|
|
||||||
if (!done) requestAnimationFrame(tick);
|
|
||||||
};
|
|
||||||
requestAnimationFrame(tick);
|
|
||||||
}
|
|
||||||
|
|
||||||
setGhostPin(pin: { objectId: string; pinX: number; pinY: number } | null) {
|
setGhostPin(pin: { objectId: string; pinX: number; pinY: number } | null) {
|
||||||
this._ghostPin = pin;
|
this._ghostPin = pin;
|
||||||
this._updateGhostPin();
|
this._updateGhostPin();
|
||||||
|
|||||||
@@ -49,6 +49,8 @@ export interface ToolContext {
|
|||||||
textEditor?: TextEditor;
|
textEditor?: TextEditor;
|
||||||
/** Switch back to select tool after placing text. */
|
/** Switch back to select tool after placing text. */
|
||||||
switchToSelect?: () => void;
|
switchToSelect?: () => void;
|
||||||
|
/** When true, destructive/creative tool clicks (TEXT, ERASER) are suppressed. */
|
||||||
|
reviewMode?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function activateTool(
|
export function activateTool(
|
||||||
@@ -82,6 +84,7 @@ export function activateTool(
|
|||||||
container.style.cursor = 'text';
|
container.style.cursor = 'text';
|
||||||
|
|
||||||
const onClick = (e: PointerEvent) => {
|
const onClick = (e: PointerEvent) => {
|
||||||
|
if (ctx.reviewMode) return; // Review mode suppresses text creation
|
||||||
const rect = container.getBoundingClientRect();
|
const rect = container.getBoundingClientRect();
|
||||||
const world = viewport.toWorld(e.clientX - rect.left, e.clientY - rect.top);
|
const world = viewport.toWorld(e.clientX - rect.left, e.clientY - rect.top);
|
||||||
|
|
||||||
@@ -154,6 +157,7 @@ export function activateTool(
|
|||||||
selection.transformBox.update([]);
|
selection.transformBox.update([]);
|
||||||
|
|
||||||
const onClick = (e: PointerEvent) => {
|
const onClick = (e: PointerEvent) => {
|
||||||
|
if (ctx.reviewMode) return; // Review mode suppresses eraser
|
||||||
const rect = container.getBoundingClientRect();
|
const rect = container.getBoundingClientRect();
|
||||||
const world = viewport.toWorld(e.clientX - rect.left, e.clientY - rect.top);
|
const world = viewport.toWorld(e.clientX - rect.left, e.clientY - rect.top);
|
||||||
const hit = selection._hitTest(world.x, world.y);
|
const hit = selection._hitTest(world.x, world.y);
|
||||||
|
|||||||
@@ -323,7 +323,7 @@ export default function FeedbackPanel({
|
|||||||
onFilterChange={setFilter}
|
onFilterChange={setFilter}
|
||||||
onSelectThread={updateExpandedThread}
|
onSelectThread={updateExpandedThread}
|
||||||
onCollapse={() => setCollapsed(true)}
|
onCollapse={() => setCollapsed(true)}
|
||||||
selectedObjectId={selectedObjectId}
|
selectedObjectId={draftPin ? null : selectedObjectId}
|
||||||
selectedObjectLabel={selectedLabel}
|
selectedObjectLabel={selectedLabel}
|
||||||
newCommentText={newCommentText}
|
newCommentText={newCommentText}
|
||||||
onNewCommentChange={setNewCommentText}
|
onNewCommentChange={setNewCommentText}
|
||||||
|
|||||||
@@ -102,6 +102,7 @@ export default function Editor({ isPublicView }: EditorProps) {
|
|||||||
const [showMmImport, setShowMmImport] = useState(false);
|
const [showMmImport, setShowMmImport] = useState(false);
|
||||||
const [showExport, setShowExport] = useState(false);
|
const [showExport, setShowExport] = useState(false);
|
||||||
const [reviewMode, setReviewMode] = useState(false);
|
const [reviewMode, setReviewMode] = useState(false);
|
||||||
|
const reviewModeRef = useRef(false);
|
||||||
const [focusedThreadId, setFocusedThreadId] = useState<string | null>(null);
|
const [focusedThreadId, setFocusedThreadId] = useState<string | null>(null);
|
||||||
// Pulse signal: threadId to open, or null to collapse detail view
|
// Pulse signal: threadId to open, or null to collapse detail view
|
||||||
const [expandRequest, setExpandRequest] = useState<{ threadId: string | null; seq: number } | null>(null);
|
const [expandRequest, setExpandRequest] = useState<{ threadId: string | null; seq: number } | null>(null);
|
||||||
@@ -201,6 +202,11 @@ export default function Editor({ isPublicView }: EditorProps) {
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [objectCount]);
|
}, [objectCount]);
|
||||||
|
|
||||||
|
// Keep reviewModeRef in sync for tool handlers to read
|
||||||
|
useEffect(() => {
|
||||||
|
reviewModeRef.current = reviewMode;
|
||||||
|
}, [reviewMode]);
|
||||||
|
|
||||||
// Pins visible only in review mode
|
// Pins visible only in review mode
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!pinOverlay) return;
|
if (!pinOverlay) return;
|
||||||
@@ -404,6 +410,8 @@ export default function Editor({ isPublicView }: EditorProps) {
|
|||||||
textEditor: textEditor ?? undefined,
|
textEditor: textEditor ?? undefined,
|
||||||
switchToSelect: () => setActiveTool(ToolType.SELECT),
|
switchToSelect: () => setActiveTool(ToolType.SELECT),
|
||||||
};
|
};
|
||||||
|
// reviewMode is read at click time via getter so it stays current
|
||||||
|
Object.defineProperty(ctx, 'reviewMode', { get: () => reviewModeRef.current });
|
||||||
toolCleanupRef.current = activateTool(ctx, activeTool, { color, strokeWidth, fontSize });
|
toolCleanupRef.current = activateTool(ctx, activeTool, { color, strokeWidth, fontSize });
|
||||||
}, [activeTool, color, strokeWidth, fontSize, onCanvasChange, textEditor]);
|
}, [activeTool, color, strokeWidth, fontSize, onCanvasChange, textEditor]);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user