feat(editor): duplicate annotations
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import Block from "@uiw/react-color-block";
|
||||
import {
|
||||
AlignCenter,
|
||||
Copy,
|
||||
AlignLeft,
|
||||
AlignRight,
|
||||
Bold,
|
||||
@@ -40,6 +41,7 @@ interface AnnotationSettingsPanelProps {
|
||||
onTypeChange: (type: AnnotationType) => void;
|
||||
onStyleChange: (style: Partial<AnnotationRegion["style"]>) => void;
|
||||
onFigureDataChange?: (figureData: FigureData) => void;
|
||||
onDuplicate?: () => void;
|
||||
onDelete: () => void;
|
||||
}
|
||||
|
||||
@@ -62,6 +64,7 @@ export function AnnotationSettingsPanel({
|
||||
onTypeChange,
|
||||
onStyleChange,
|
||||
onFigureDataChange,
|
||||
onDuplicate,
|
||||
onDelete,
|
||||
}: AnnotationSettingsPanelProps) {
|
||||
const t = useScopedT("settings");
|
||||
@@ -597,15 +600,28 @@ export function AnnotationSettingsPanel({
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
|
||||
<Button
|
||||
onClick={onDelete}
|
||||
variant="destructive"
|
||||
size="sm"
|
||||
className="w-full gap-2 bg-red-500/10 text-red-400 border border-red-500/20 hover:bg-red-500/20 hover:border-red-500/30 transition-all mt-4"
|
||||
>
|
||||
<Trash2 className="w-4 h-4" />
|
||||
{t("annotation.deleteAnnotation")}
|
||||
</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" />
|
||||
Duplicate
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
onClick={onDelete}
|
||||
variant="destructive"
|
||||
size="sm"
|
||||
className="w-full gap-2 bg-red-500/10 text-red-400 border border-red-500/20 hover:bg-red-500/20 hover:border-red-500/30 transition-all"
|
||||
>
|
||||
<Trash2 className="w-4 h-4" />
|
||||
{t("annotation.deleteAnnotation")}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="mt-6 p-3 bg-white/5 rounded-lg border border-white/5">
|
||||
<div className="flex items-center gap-2 mb-2 text-slate-300">
|
||||
|
||||
@@ -140,6 +140,7 @@ interface SettingsPanelProps {
|
||||
onAnnotationTypeChange?: (id: string, type: AnnotationType) => void;
|
||||
onAnnotationStyleChange?: (id: string, style: Partial<AnnotationRegion["style"]>) => void;
|
||||
onAnnotationFigureDataChange?: (id: string, figureData: FigureData) => void;
|
||||
onAnnotationDuplicate?: (id: string) => void;
|
||||
onAnnotationDelete?: (id: string) => void;
|
||||
selectedSpeedId?: string | null;
|
||||
selectedSpeedValue?: PlaybackSpeed | null;
|
||||
@@ -213,6 +214,7 @@ export function SettingsPanel({
|
||||
onAnnotationTypeChange,
|
||||
onAnnotationStyleChange,
|
||||
onAnnotationFigureDataChange,
|
||||
onAnnotationDuplicate,
|
||||
onAnnotationDelete,
|
||||
selectedSpeedId,
|
||||
selectedSpeedValue,
|
||||
@@ -466,6 +468,7 @@ export function SettingsPanel({
|
||||
? (figureData) => onAnnotationFigureDataChange(selectedAnnotation.id, figureData)
|
||||
: undefined
|
||||
}
|
||||
onDuplicate={onAnnotationDuplicate ? () => onAnnotationDuplicate(selectedAnnotation.id) : undefined}
|
||||
onDelete={() => onAnnotationDelete(selectedAnnotation.id)}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -831,6 +831,33 @@ export default function VideoEditor() {
|
||||
[pushState],
|
||||
);
|
||||
|
||||
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],
|
||||
);
|
||||
|
||||
const handleAnnotationDelete = useCallback(
|
||||
(id: string) => {
|
||||
pushState((prev) => ({
|
||||
@@ -1680,6 +1707,7 @@ export default function VideoEditor() {
|
||||
onAnnotationTypeChange={handleAnnotationTypeChange}
|
||||
onAnnotationStyleChange={handleAnnotationStyleChange}
|
||||
onAnnotationFigureDataChange={handleAnnotationFigureDataChange}
|
||||
onAnnotationDuplicate={handleAnnotationDuplicate}
|
||||
onAnnotationDelete={handleAnnotationDelete}
|
||||
selectedSpeedId={selectedSpeedId}
|
||||
selectedSpeedValue={
|
||||
|
||||
Reference in New Issue
Block a user