Skip to content

feat(editor): duplicate annotations#342

Open
kuishou68 wants to merge 1 commit intosiddharthvaddem:mainfrom
kuishou68:cocoon/feature-duplicate-annotation
Open

feat(editor): duplicate annotations#342
kuishou68 wants to merge 1 commit intosiddharthvaddem:mainfrom
kuishou68:cocoon/feature-duplicate-annotation

Conversation

@kuishou68
Copy link
Copy Markdown
Contributor

@kuishou68 kuishou68 commented Apr 5, 2026

Summary\n- add a duplicate action for selected annotations in the settings panel\n- preserve annotation content, style, timing, and type when duplicating\n- assign a fresh annotation id/z-index and slightly offset the duplicate so it is easy to distinguish\n\nCloses #167

Summary by CodeRabbit

  • New Features
    • Added annotation duplication feature. A "Duplicate" button now appears alongside the delete button when an annotation is selected, enabling users to quickly create copies of existing annotations with adjusted positioning.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 5, 2026

📝 Walkthrough

Walkthrough

This pull request adds annotation duplication functionality to the video editor. Users can now duplicate annotations via a new button in the annotation settings panel, preserving styling and position while offsetting the duplicate by a small amount.

Changes

Cohort / File(s) Summary
Annotation Duplication Feature
src/components/video-editor/AnnotationSettingsPanel.tsx, src/components/video-editor/SettingsPanel.tsx, src/components/video-editor/VideoEditor.tsx
Added onDuplicate callback prop to AnnotationSettingsPanel with a new duplicate button; propagated onAnnotationDuplicate callback through SettingsPanel; implemented handleAnnotationDuplicate in VideoEditor to clone annotations with unique IDs, incremented z-index, offset position (+4px x/y), and deep-copied size/style/figureData.

Sequence Diagram

sequenceDiagram
    actor User
    participant ASP as AnnotationSettingsPanel
    participant SP as SettingsPanel
    participant VE as VideoEditor
    participant State as annotationRegions

    User->>ASP: Click Duplicate Button
    ASP->>SP: onDuplicate()
    SP->>VE: onAnnotationDuplicate(annotationId)
    VE->>VE: Clone annotation<br/>Generate new ID<br/>Offset position (+4, +4)<br/>Increment z-index<br/>Deep copy properties
    VE->>State: pushState(new annotation)
    VE->>VE: Update selection<br/>to new annotation ID
    VE-->>ASP: UI updates
    ASP-->>User: Duplicate annotation displayed
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 A hop, a click, a copy made,
With styles and grace, the clone is laid.
Four pixels shifted, standing tall,
The duplicate annotation answers the call!
One annotation becomes not one but two,
Happy hopping—the feature's through! 🎬

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive The description covers the key objectives and includes a linked issue reference, but deviates from the provided template structure with missing sections like Type of Change, Screenshots/Video, Testing, and Checklist. Follow the repository PR template more closely by adding Type of Change checkbox, Screenshots/Video section, Testing instructions, and completing the Checklist.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat(editor): duplicate annotations' directly and clearly describes the main feature being added, matching the primary change across all three modified files.
Linked Issues check ✅ Passed The implementation fully addresses issue #167 by adding annotation duplication that preserves style, content, type, assigns fresh IDs and z-index, and offsets position for easy distinction.
Out of Scope Changes check ✅ Passed All changes in the three modified files are directly scoped to implementing the annotation duplication feature with no extraneous modifications.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/components/video-editor/AnnotationSettingsPanel.tsx`:
- Around line 603-613: The "Duplicate" button in AnnotationSettingsPanel is
hardcoded and must be localized; replace the literal "Duplicate" string inside
the Button (the element with onClick={() => onDuplicate?.()} and className...)
with the component's i18n call (the same translation helper used elsewhere in
this file, e.g., t('...') or useTranslations('...')) using the appropriate
translation key (e.g., "duplicate" or "annotation.duplicate"), and add that key
to the project's locale files so the label is translated in non-English locales.

In `@src/components/video-editor/VideoEditor.tsx`:
- Around line 834-859: handleAnnotationDuplicate currently increments
nextAnnotationIdRef and nextAnnotationZIndexRef and sets selection even when the
source region isn't found, causing wasted IDs and invalid selection; modify the
flow so you first use pushState to find the source region and only
generate/consume the new duplicateId and duplicateZIndex after confirming the
source exists (or abort without mutating refs), returning the previous state
unchanged if source is missing, and only call
setSelectedAnnotationId(duplicateId) / setSelectedZoomId(null) /
setSelectedTrimId(null) when a duplicate was actually created; you can either
move the id/zIndex increments inside the pushState callback after the source
lookup or detect source absence and skip both state change and selection updates
to avoid no-op history entries and invalid selection.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f645b4f1-d924-463b-9d88-8ba84ead134b

📥 Commits

Reviewing files that changed from the base of the PR and between da16872 and 5426b62.

📒 Files selected for processing (3)
  • src/components/video-editor/AnnotationSettingsPanel.tsx
  • src/components/video-editor/SettingsPanel.tsx
  • src/components/video-editor/VideoEditor.tsx

Comment on lines +603 to +613
<div className="mt-4 grid grid-cols-2 gap-2">
<Button
onClick={() => onDuplicate?.()}
variant="outline"
size="sm"
disabled={!onDuplicate}
className="w-full gap-2 bg-white/5 text-slate-200 border border-white/10 hover:bg-white/10 hover:border-white/20 transition-all"
>
<Copy className="w-4 h-4" />
Duplicate
</Button>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Localize the new Duplicate button label.

Line 612 hardcodes "Duplicate" while the panel otherwise uses translation keys. This introduces a localization gap in non-English locales.

Suggested fix
 					<Button
 						onClick={() => onDuplicate?.()}
 						variant="outline"
 						size="sm"
 						disabled={!onDuplicate}
 						className="w-full gap-2 bg-white/5 text-slate-200 border border-white/10 hover:bg-white/10 hover:border-white/20 transition-all"
 					>
 						<Copy className="w-4 h-4" />
-						Duplicate
+						{t("annotation.duplicateAnnotation")}
 					</Button>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<div className="mt-4 grid grid-cols-2 gap-2">
<Button
onClick={() => onDuplicate?.()}
variant="outline"
size="sm"
disabled={!onDuplicate}
className="w-full gap-2 bg-white/5 text-slate-200 border border-white/10 hover:bg-white/10 hover:border-white/20 transition-all"
>
<Copy className="w-4 h-4" />
Duplicate
</Button>
<div className="mt-4 grid grid-cols-2 gap-2">
<Button
onClick={() => onDuplicate?.()}
variant="outline"
size="sm"
disabled={!onDuplicate}
className="w-full gap-2 bg-white/5 text-slate-200 border border-white/10 hover:bg-white/10 hover:border-white/20 transition-all"
>
<Copy className="w-4 h-4" />
{t("annotation.duplicateAnnotation")}
</Button>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/video-editor/AnnotationSettingsPanel.tsx` around lines 603 -
613, The "Duplicate" button in AnnotationSettingsPanel is hardcoded and must be
localized; replace the literal "Duplicate" string inside the Button (the element
with onClick={() => onDuplicate?.()} and className...) with the component's i18n
call (the same translation helper used elsewhere in this file, e.g., t('...') or
useTranslations('...')) using the appropriate translation key (e.g., "duplicate"
or "annotation.duplicate"), and add that key to the project's locale files so
the label is translated in non-English locales.

Comment on lines +834 to +859
const handleAnnotationDuplicate = useCallback(
(id: string) => {
const duplicateId = `annotation-${nextAnnotationIdRef.current++}`;
const duplicateZIndex = nextAnnotationZIndexRef.current++;
pushState((prev) => {
const source = prev.annotationRegions.find((region) => region.id === id);
if (!source) return {};

const duplicate: AnnotationRegion = {
...source,
id: duplicateId,
zIndex: duplicateZIndex,
position: { x: source.position.x + 4, y: source.position.y + 4 },
size: { ...source.size },
style: { ...source.style },
figureData: source.figureData ? { ...source.figureData } : undefined,
};

return { annotationRegions: [...prev.annotationRegions, duplicate] };
});
setSelectedAnnotationId(duplicateId);
setSelectedZoomId(null);
setSelectedTrimId(null);
},
[pushState],
);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Guard duplicate flow when the source annotation is missing.

At Line 840, returning {} means no annotation is duplicated, but Line 854 still selects duplicateId, and Line 836-837 already consumed ID/z-index values. This can create a no-op history checkpoint and transient invalid selection.

Suggested fix
 const handleAnnotationDuplicate = useCallback(
 		(id: string) => {
+			const source = annotationRegions.find((region) => region.id === id);
+			if (!source) return;
+
 			const duplicateId = `annotation-${nextAnnotationIdRef.current++}`;
 			const duplicateZIndex = nextAnnotationZIndexRef.current++;
 			pushState((prev) => {
-				const source = prev.annotationRegions.find((region) => region.id === id);
-				if (!source) return {};
-
 				const duplicate: AnnotationRegion = {
 					...source,
 					id: duplicateId,
 					zIndex: duplicateZIndex,
 					position: { x: source.position.x + 4, y: source.position.y + 4 },
 					size: { ...source.size },
 					style: { ...source.style },
 					figureData: source.figureData ? { ...source.figureData } : undefined,
 				};

 				return { annotationRegions: [...prev.annotationRegions, duplicate] };
 			});
 			setSelectedAnnotationId(duplicateId);
 			setSelectedZoomId(null);
 			setSelectedTrimId(null);
 		},
-		[pushState],
+		[annotationRegions, pushState],
 	);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/video-editor/VideoEditor.tsx` around lines 834 - 859,
handleAnnotationDuplicate currently increments nextAnnotationIdRef and
nextAnnotationZIndexRef and sets selection even when the source region isn't
found, causing wasted IDs and invalid selection; modify the flow so you first
use pushState to find the source region and only generate/consume the new
duplicateId and duplicateZIndex after confirming the source exists (or abort
without mutating refs), returning the previous state unchanged if source is
missing, and only call setSelectedAnnotationId(duplicateId) /
setSelectedZoomId(null) / setSelectedTrimId(null) when a duplicate was actually
created; you can either move the id/zIndex increments inside the pushState
callback after the source lookup or detect source absence and skip both state
change and selection updates to avoid no-op history entries and invalid
selection.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant