feat(annotations): Figma-style UI overhaul

- Redesign PinOverlay: teardrop pins with author initials, colors, numbers
- Split FeedbackPanel into modular components:
  - feedback/FeedbackPanel.tsx (orchestrator)
  - feedback/ThreadList.tsx (list view with filters)
  - feedback/ThreadListItem.tsx (individual thread row)
  - feedback/ThreadDetail.tsx (expanded thread with comments)
  - feedback/CommentItem.tsx (single comment)
  - feedback/CommentInput.tsx (reusable input with Enter/Shift+Enter)
  - feedback/feedbackStyles.ts (shared design tokens)
- Add status colors: red (open), green (resolved)
- Add relative timestamps (2h ago, 3d, etc.)
- Wire pin clicks to expand threads in panel
- Pins follow media transforms in real-time
- Add delete thread with inline confirmation
- Register review mode shortcut (. key)
- Better empty states and filter bar (Open/Resolved/All/Mine)
This commit is contained in:
Hiren Kangad
2026-03-11 01:08:05 +05:30
parent f24414e25c
commit ed8424d9a1
15 changed files with 1228 additions and 419 deletions
+28 -8
View File
@@ -26,7 +26,7 @@ import MattermostImport from '../components/MattermostImport';
import Minimap from '../components/Minimap';
import UploadPanel from '../components/UploadPanel';
import ExportDialog from '../components/ExportDialog';
import FeedbackPanel from '../components/FeedbackPanel';
import FeedbackPanel from '../components/feedback/FeedbackPanel';
import { UploadManager } from '../stores/uploadManager';
import { InboxZone } from '../canvas/InboxZone';
import { getItemWorldBounds } from '../canvas/SceneManager';
@@ -92,6 +92,7 @@ export default function Editor({ isPublicView }: EditorProps) {
const [showMmImport, setShowMmImport] = useState(false);
const [showExport, setShowExport] = useState(false);
const [reviewMode, setReviewMode] = useState(false);
const [focusedThreadId, setFocusedThreadId] = useState<string | null>(null);
const [focusMode, setFocusMode] = useState(false);
const [layerList, setLayerList] = useState<any[]>([]);
const [selectedLayerIds, setSelectedLayerIds] = useState<string[]>([]);
@@ -175,12 +176,27 @@ export default function Editor({ isPublicView }: EditorProps) {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [objectCount]);
// Toggle pin overlay visibility with review mode
// Toggle pin overlay visibility with review mode + wire pin clicks
useEffect(() => {
if (pinOverlay) {
pinOverlay.visible = reviewMode;
if (reviewMode) pinOverlay.refresh();
}
if (!pinOverlay) return;
pinOverlay.visible = reviewMode;
if (reviewMode) pinOverlay.refresh();
// Pin click → expand thread in panel
const onClick = (e: any) => {
const vp = canvasRef.current?.getViewport();
if (!vp) return;
const worldPos = vp.toWorld(e.global);
const threadId = pinOverlay.getThreadIdAtPoint(worldPos.x, worldPos.y);
if (threadId) {
setFocusedThreadId(threadId);
// Reset so it can be triggered again for the same pin
requestAnimationFrame(() => setFocusedThreadId(null));
}
};
pinOverlay.eventMode = 'static';
pinOverlay.on('pointerdown', onClick);
return () => { pinOverlay.off('pointerdown', onClick); };
}, [reviewMode, pinOverlay]);
// Tool activation
@@ -264,7 +280,7 @@ export default function Editor({ isPublicView }: EditorProps) {
onCanvasChange, showToast, refreshLayers,
handleGroup, handleUngroup,
setActiveTool, setCanUndo, setCanRedo, setZoom,
setShowGrid, setShowHelp, setFocusMode,
setShowGrid, setShowHelp, setFocusMode, setReviewMode,
});
// Context menu
@@ -369,7 +385,10 @@ export default function Editor({ isPublicView }: EditorProps) {
contentBounds: { x: cMinX, y: cMinY, w: cMaxX - cMinX, h: cMaxY - cMinY },
});
}
}, []);
// Refresh annotation pins so they follow transforms
if (pinOverlay?.visible) pinOverlay.refresh();
}, [pinOverlay]);
// Listen to viewport moved event for overlay updates (throttled)
// Depends on objectCount so it re-runs after canvas init (viewport becomes available)
@@ -678,6 +697,7 @@ export default function Editor({ isPublicView }: EditorProps) {
token={localStorage.getItem('refboard_token') || ''}
canvasObjects={canvasObjectMap}
onError={(msg) => showToast(msg)}
expandThreadId={focusedThreadId}
onJumpToObject={(objectId) => {
const scene = canvasRef.current?.getScene();
const vp = canvasRef.current?.getViewport();