From 7aa55b66f0823800b31a7fdab43bef1ecfbe2834 Mon Sep 17 00:00:00 2001 From: Hiren Kangad Date: Thu, 12 Mar 2026 19:33:28 +0530 Subject: [PATCH] refactor(review): extract shared anchor math into reviewAnchors.ts --- frontend/src/canvas/PinOverlay.ts | 20 ++++-------- frontend/src/canvas/reviewAnchors.ts | 46 ++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 14 deletions(-) create mode 100644 frontend/src/canvas/reviewAnchors.ts diff --git a/frontend/src/canvas/PinOverlay.ts b/frontend/src/canvas/PinOverlay.ts index 935d258..0bafe61 100644 --- a/frontend/src/canvas/PinOverlay.ts +++ b/frontend/src/canvas/PinOverlay.ts @@ -2,8 +2,8 @@ import { Container, Graphics, Text, TextStyle } from 'pixi.js'; import type { Viewport } from 'pixi-viewport'; import type { AnnotationStore } from '../stores/annotationStore'; import type { SceneManager } from './SceneManager'; -import { getItemWorldBounds } from './SceneManager'; import { getAuthorColorHex, getAuthorInitial } from '../utils/authorColors'; +import { getThreadAnchorWorld, getPointAnchorWorld } from './reviewAnchors'; const PIN_RADIUS = 11; const PIN_COLOR_RESOLVED = 0x555555; @@ -76,13 +76,7 @@ export class PinOverlay extends Container { } private _getAnchorWorld(thread: { object_id: string; anchor_type: string; pin_x: number | null; pin_y: number | null }): { x: number; y: number } | null { - const item = this._scene.items.get(thread.object_id); - if (!item) return null; - const b = getItemWorldBounds(item); - if (thread.anchor_type === 'point' && thread.pin_x != null && thread.pin_y != null) { - return { x: b.x + thread.pin_x * b.w, y: b.y + thread.pin_y * b.h }; - } - return { x: b.x + b.w * 0.9, y: b.y }; + return getThreadAnchorWorld(this._scene, thread); } /** @@ -257,12 +251,10 @@ export class PinOverlay extends Container { if (!this._ghostPin) return; - const item = this._scene.items.get(this._ghostPin.objectId); - if (!item) return; - - const b = getItemWorldBounds(item); - const wx = b.x + this._ghostPin.pinX * b.w; - const wy = b.y + this._ghostPin.pinY * b.h; + const pos = getPointAnchorWorld(this._scene, this._ghostPin.objectId, this._ghostPin.pinX, this._ghostPin.pinY); + if (!pos) return; + const wx = pos.x; + const wy = pos.y; const scale = 1 / this._viewport.scale.x; const r = PIN_RADIUS * scale; diff --git a/frontend/src/canvas/reviewAnchors.ts b/frontend/src/canvas/reviewAnchors.ts new file mode 100644 index 0000000..8bd674f --- /dev/null +++ b/frontend/src/canvas/reviewAnchors.ts @@ -0,0 +1,46 @@ +import type { SceneManager } from './SceneManager'; +import { getItemWorldBounds } from './SceneManager'; + +/** + * Compute the world-space position for a point-pinned anchor. + * Returns null if the target object no longer exists in the scene. + */ +export function getPointAnchorWorld( + scene: SceneManager, + objectId: string, + pinX: number, + pinY: number, +): { x: number; y: number } | null { + const item = scene.items.get(objectId); + if (!item) return null; + const b = getItemWorldBounds(item); + return { x: b.x + pinX * b.w, y: b.y + pinY * b.h }; +} + +/** + * Compute the world-space position for an object-level anchor. + * Default position: top-right corner (90% x, 0% y). + */ +export function getObjectAnchorWorld( + scene: SceneManager, + objectId: string, +): { x: number; y: number } | null { + const item = scene.items.get(objectId); + if (!item) return null; + const b = getItemWorldBounds(item); + return { x: b.x + b.w * 0.9, y: b.y }; +} + +/** + * Compute anchor world position for a thread (point or object anchor). + * This is the unified entry point — replaces PinOverlay._getAnchorWorld(). + */ +export function getThreadAnchorWorld( + scene: SceneManager, + thread: { object_id: string; anchor_type: string; pin_x: number | null; pin_y: number | null }, +): { x: number; y: number } | null { + if (thread.anchor_type === 'point' && thread.pin_x != null && thread.pin_y != null) { + return getPointAnchorWorld(scene, thread.object_id, thread.pin_x, thread.pin_y); + } + return getObjectAnchorWorld(scene, thread.object_id); +}