Skip to content

feat(timeline): copy / cut / paste clips (⌘C / ⌘X / ⌘V) (#94)#105

Open
cuic19053-hue wants to merge 3 commits into
appergb:mainfrom
cuic19053-hue:feat-94-clipboard-copy-paste
Open

feat(timeline): copy / cut / paste clips (⌘C / ⌘X / ⌘V) (#94)#105
cuic19053-hue wants to merge 3 commits into
appergb:mainfrom
cuic19053-hue:feat-94-clipboard-copy-paste

Conversation

@cuic19053-hue

Copy link
Copy Markdown
Contributor

Summary

Adds the standard copy / cut / paste clipboard shortcuts (⌘C / ⌘X / ⌘V) that were completely missing. Closes #94.

Changes

  • web/src/store/clipboardStore.ts (new) — Zustand store holding deep snapshots of the selected clips plus the source first-frame, so a paste can re-place the group relative to the current playhead. UI-only, never persisted.
  • web/src/store/editActions.tscopyClips / cutClips / pasteClipsAtPlayhead:
    • copy: snapshot selected clips + their track index + min startFrame.
    • cut: copy then deleteSelectedClips.
    • paste: offset each clip's startFrame by activeFrame - sourceFirstFrame, clear addLinkedAudio so the paste stands alone (mirrors upstream pasteClipsAtPlayhead link re-reflection), and select the new clips. Clips whose source track no longer exists are skipped.
    • addClips now returns the EditResult so paste can read affectedClipIds.
  • web/src/hooks/useKeyboardShortcuts.ts — wire ⌘C / ⌘X / ⌘V inside the existing if (mod) block. No conflict with the unmodified C/V tool switches (those are in the separate no-mod branch).
  • web/src/i18n/dict.ts — 4 new keys (zh-CN + en): edit.copy / edit.cut / edit.paste / edit.clipboardEmpty.

Verification

  • pnpm tsc --noEmit
  • pnpm build ✅ (1640 modules, 5.81s)

User flow

  1. Select one or more clips on the timeline.
  2. ⌘C (copy) or ⌘X (cut).
  3. Move the playhead to the target position.
  4. ⌘V (paste) — the clips re-appear at the playhead, offset from their original group start. The pasted clips are auto-selected so the user can immediately move/trim them.

Notes

  • linkGroupId re-reflection: paste clears addLinkedAudio so the new clips don't re-link to the originals. The backend assigns fresh link groups on demand.
  • Track placement preserved: a clip copied from track 2 pastes back onto track 2 (upstream behavior). If track 2 was deleted in the meantime, the clip is silently skipped.
  • No Rust changes: reuses the existing addClips / removeClips commands.

@appergb appergb left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

@cuic19053-hue 自动审核结论:请修改(REQUEST_CHANGES)。核心逻辑架构正确,但当前 CONFLICTING 无法合并:

阻塞项:

  1. 冲突,需 rebase:分支落后 main 39 个 commit,与 PR#102 在 web/src/store/editActions.ts import 段(第 12-16 行)真实三方冲突。请 rebase 到最新 main,保留两组 import(main 的 trimToPlayheadEdits/FrameRangeReq/KeyframePayloadReq/KeyframeProperty + 本 PR 的 useClipboardStore/Clip)后强推。

合并前请一并修(非阻塞但影响 1:1):
2. 粘贴视频时 addLinkedAudio 恒为 false → 复制带链接音轨的视频会丢音轨;上游 pasteClipsAtPlayhead 会做 linkGroup 重映(issue #94 亦点名此方案)。
3. 空剪贴板粘贴静默无反馈:已定义 i18n 键 edit.clipboardEmpty 但未接 toast。

rebase 后请自验 tsc + vitest 再提交。

Adds the standard clipboard shortcuts that were completely missing. Only
⌘C/⌘X/⌘V were absent — the unmodified C/V already switch tools (razor /
pointer), and the mod-prefixed branch had no handlers.

Frontend only:
- clipboardStore: new Zustand store holding deep snapshots of the selected
  clips plus the source first-frame, so a paste can re-place the group
  relative to the current playhead. UI-only, never persisted.
- editActions: copyClips / cutClips / pasteClipsAtPlayhead.
  - copy: snapshot selected clips + their track index + min startFrame.
  - cut: copy then deleteSelectedClips.
  - paste: offset each clip's startFrame by `activeFrame - sourceFirstFrame`,
    clear addLinkedAudio so the paste stands alone (mirrors upstream
    `pasteClipsAtPlayhead` link re-reflection), and select the new clips.
    Clips whose source track no longer exists are skipped.
- useKeyboardShortcuts: wire ⌘C / ⌘X / ⌘V inside the existing `if (mod)`
  block — no conflict with the unmodified C/V tool switches.
- i18n: 4 new keys (zh-CN + en) for copy / cut / paste / clipboardEmpty.

Closes appergb#94.
…board toast

Address review feedback on PR appergb#105:

1. Rebased onto latest main (resolved import conflict: kept both trimToPlayheadEdits and useClipboardStore).

2. copyClips now expands link groups: if a selected clip has a linkGroupId, all linked companions are included in the clipboard (mirrors upstream copyClips), so a paste reproduces the video+audio pair.

3. pasteClipsAtPlayhead now re-establishes link groups after addClips: clips that shared a linkGroupId in the clipboard are re-linked via linkClips, preserving video+audio linkage.

4. Empty-clipboard paste now shows a toast (edit.clipboardEmpty) instead of silently doing nothing. Added toast mechanism to uiStore + Toast component in App.tsx.
@cuic19053-hue cuic19053-hue force-pushed the feat-94-clipboard-copy-paste branch from fa27d06 to 3192a2e Compare June 23, 2026 14:03
@cuic19053-hue

Copy link
Copy Markdown
Contributor Author

Rebased onto latest main and addressed all review feedback. Ready for re-review.

Changes:

  1. Conflict resolved: rebased onto latest main (45 commits ahead), kept both \ rimToPlayheadEdits\ and \useClipboardStore\ imports.
  2. linkGroup re-mapping: \copyClips\ now expands link groups (if a selected clip has a \linkGroupId, all linked companions are included in the clipboard). \pasteClipsAtPlayhead\ re-establishes link groups after \�ddClips\ via \linkClips, preserving video+audio linkage.
  3. Empty-clipboard toast: ⌘V with an empty clipboard now shows a toast (\edit.clipboardEmpty) instead of silently doing nothing. Added a minimal toast mechanism to \uiStore\ + \Toast\ component in \App.tsx.

\pnpm run build\ (tsc -b + vite build) passes.

@appergb appergb left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

@cuic19053-hue 复审 ✅ 你的修复方向正确,已逐一回应上轮反馈:

  • 空剪贴板提示:uiStore 加 pushToast/clearToast + App.tsx Toast 组件,粘贴空时 pushToast(t("edit.clipboardEmpty")),死键已消费。✔
  • linkGroup 重映射:copyClips 展开链接伴随、pasteClipsAtPlayheadsourceLinkGroups 对齐 res.affectedClipIdsnewGroupMaplinkClips 重链,addLinkedAudio:false 避免重复音轨——粘贴 A/V 链接对会成对保留。✔ 贴近上游 pasteClipsAtPlayhead。

⚠️ 唯一剩余阻塞:本 PR 现再次 CONFLICTING——你 14:03 rebase 后 main 又前进(#119 关键帧已合并,同样改了 editActions.ts 并新增 keyframe 函数 + dict.ts 新增键)。请再 rebase 一次到最新 main,在 editActions.ts/dict.ts 保留两边(你的 clipboard 函数 + #119 的 keyframe 函数/键)。

💡 一处建议(非阻塞,请确认):linkGroup 重映依赖 res.affectedClipIds[i] 与输入 entries[i] 同序对齐。若 addClips/后端不保证返回顺序与输入一致,重链可能配错对。建议确认 addClips 保序,或改用更稳的映射(如按返回里携带的 source 标识匹配)。

rebase + CI 双绿后我会做最终审核 + 合并。👍

@appergb appergb left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

✅ 批准(Sonnet 4.6 复审 + 主控自查)。前两轮的阻塞项已在当前 diff 中切实修复:

  1. 链接组重映射已修:copyClips 把链接伴随 clip 一并纳入剪贴板;pasteClipsAtPlayheadsourceLinkGroups[]affectedClipIds[] 按序对齐、对每组 linkClips 重建链接,addLinkedAudio:false 避免重复音轨——语义等价上游 EditorViewModel+Clipboard.swift 的 group remap。
  2. 空剪贴板反馈已补:⌘V 路径在剪贴板为空时 pushToast(t("edit.clipboardEmpty"));uiStore 加 toast/pushToast/clearToast + App.tsx Toast 组件,链路完整。
  3. rebase/冲突已解:已合 origin/main,mergeable:MERGEABLE,CI 双绿(Rust + Web)。

改动文件均在剪辑/UI 层(editActions/clipboardStore/uiStore/useKeyboardShortcuts/App/dict),不碰 #91 媒体重写区;对 TimelinePlaybackLayer.tsx 仅删一个尾随空格,无逻辑影响。粘贴偏移用 sourceTrackIndex + (activeFrame - sourceFirstFrame) 与上游相对偏移路径不同但语义等价,属可接受的 1:1 复刻。

已具备合并条件(待 owner 决定 --admin 合并)。

@cuic19053-hue

Copy link
Copy Markdown
Contributor Author

@appergb 本 PR 已 APPROVED + CLEAN + CI 双绿(Rust ✅ Web ✅),可合并。贡献者账号无 MergePullRequest 权限,烦请 owner 执行 merge(squash),谢谢!

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.

[timeline] 复制/剪切/粘贴(⌘C/⌘X/⌘V)缺失

2 participants