Skip to content

feat: sync progress modal#179

Merged
roitium merged 4 commits into
devfrom
feat/sync-progress-modal
Jan 27, 2026
Merged

feat: sync progress modal#179
roitium merged 4 commits into
devfrom
feat/sync-progress-modal

Conversation

@roitium
Copy link
Copy Markdown
Collaborator

@roitium roitium commented Jan 27, 2026

Implements sync progress modal and reverts previous faulty commit.

Summary by CodeRabbit

  • 新特性
    • 收藏夹同步现在显示详细进度模态,展示各阶段与实时进度(初始化、获取元数据、计算差异、获取详情、保存等)。
    • 同步由模态负责展示与完成处理,触发同步时界面不再直接跳转或仅用简短提示。
    • 进度条与状态文本支持进行中、完成与错误展示;模态关闭后可根据情况跳转到本地歌单页面。

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jan 27, 2026

Caution

Review failed

The pull request is closed.

Walkthrough

此PR为收藏夹同步引入逐步进度回调与新的进度模态框:将原先在页面内执行的同步与即时通知/导航流程替换为打开 FavoriteSyncProgressModal,并通过 onProgress 回调实时报告同步阶段与进度。

Changes

内聚体 / 文件(s) 改动摘要
CHANGELOG 更新
CHANGELOG.md
新增未发布条目,记录收藏夹同步现在显示详细进度模态框
核心同步 Facade
src/lib/facades/sync.ts
新增 FavoriteSyncProgress 接口;syncFavoritesync 支持可选 onProgress 回调并在多个阶段发出进度事件
Mutation 钩子
src/hooks/mutations/db/playlist.ts
usePlaylistSync 的 mutation 接受并转发 onProgress?: (progress: FavoriteSyncProgress) => voidsyncFacade
远程收藏页调用点
src/app/playlist/remote/favorite/[id].tsx
移除直接使用 usePlaylistSync 的即时通知/导航逻辑;改为通过 openModal 打开 FavoriteSyncProgress 模态并传入 favoriteId
本地播放列表页同步
src/app/playlist/local/[id].tsx
对于 type 为 favorite 的播放列表,改为打开 SyncLocalToBilibili 模态并中止原先的同步流程;更新依赖数组包含 openModalid
模态注册与类型
src/components/ModalRegistry.tsx, src/types/navigation.ts
在 ModalRegistry 注册 FavoriteSyncProgressModal;在 ModalPropsMap 中添加 FavoriteSyncProgress: { favoriteId: number; shouldRedirectToLocalPlaylist?: boolean }
新模态框组件
src/components/modals/playlist/FavoriteSyncProgressModal.tsx
新增模态组件:挂载时发起同步(使用 usePlaylistSync + onProgress),维护并展示 FavoriteSyncProgress 状态、阶段与进度条;完成或出错后允许关闭并可选择重定向到本地播放列表

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant FavoriteDetail as "Favorite Detail\n(component)"
    participant ModalStore as "Modal Store"
    participant FavoriteSyncModal as "FavoriteSyncProgress\nModal"
    participant Mutation as "usePlaylistSync\nmutation"
    participant SyncFacade as "SyncFacade"

    User->>FavoriteDetail: 点击同步
    FavoriteDetail->>ModalStore: openModal('FavoriteSyncProgress', { favoriteId })
    ModalStore->>FavoriteSyncModal: 渲染并挂载
    FavoriteSyncModal->>Mutation: 调用 mutation({ remoteSyncId, type: 'favorite', onProgress })
    Mutation->>SyncFacade: sync(remoteSyncId, 'favorite', onProgress)

    SyncFacade->>FavoriteSyncModal: onProgress(stage: 'initializing', message)
    FavoriteSyncModal->>FavoriteSyncModal: 更新 UI(标题/消息)

    SyncFacade->>FavoriteSyncModal: onProgress(stage: 'fetching_metadata', current, total)
    FavoriteSyncModal->>FavoriteSyncModal: 更新进度条

    SyncFacade->>FavoriteSyncModal: onProgress(stage: 'calculating_diff' / 'fetching_details' / 'saving')
    FavoriteSyncModal->>FavoriteSyncModal: 更新阶段信息

    SyncFacade->>FavoriteSyncModal: onProgress(stage: 'completed', message) or onProgress(stage: 'error', message)
    User->>FavoriteSyncModal: 点击关闭
    FavoriteSyncModal->>ModalStore: closeModal() (可触发跳转到本地播放列表)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested labels

enhancement

Poem

🐰 在代码地里我蹦跶忙,
进度条像胡萝卜在发光,
阶段步步报来又飞扬,
收藏夹同步开了小窗,
我眨眨眼,咕噜跳着唱。

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
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.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed Pull request title accurately describes the main change: adding a sync progress modal feature that displays detailed progress during favorites synchronization.

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

✨ Finishing touches
  • 📝 Generate docstrings

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: 1

🤖 Fix all issues with AI agents
In `@src/app/playlist/remote/favorite/`[id].tsx:
- Around line 95-121: The modal can become unclosable when sync fails before
onProgress runs because syncProgress stays null; to fix, initialize syncProgress
when opening the modal (e.g., setSyncProgress to a starting object) and add
null-safe fallbacks in the syncFavorite callbacks: in the onSuccess handler
inside the syncFavorite call ensure you setSyncProgress to a completed state
even if prev is null, and in onError setSyncProgress to an error state with the
error message when prev is null; update the logic around
setIsSyncModalVisible/syncFavorite (the call that supplies remoteSyncId, type,
onProgress) so SyncProgressModal always receives a non-null progress object (or
make its dismissable/close control driven by this fallback) to guarantee the
modal can be dismissed.
🧹 Nitpick comments (1)
src/app/playlist/remote/favorite/[id].tsx (1)

239-261: 建议收敛大段注释逻辑。
这里的注释体量较大且包含已弃用逻辑,建议移到 issue/ADR 或精简为一两行关键说明,避免后续维护噪音。

Comment on lines +95 to +121
setIsSyncModalVisible(true)
syncFavorite(
{
remoteSyncId: Number(id),
type: 'favorite',
toastId,
onProgress: setSyncProgress,
},
{
onSuccess: (id) => {
setSyncProgress((prev) =>
prev ? { ...prev, stage: 'completed', message: '同步完成' } : null,
)
// 等待用户点击关闭或自动关闭逻辑
// 这里我们不自动跳转,让用户看到结果
if (!id) return
router.replace({
pathname: '/playlist/local/[id]',
params: { id: String(id) },
})
// 如果需要自动跳转,可以在 onClose 里处理,或者这里延迟处理
},
onError: (error) => {
setSyncProgress((prev) =>
prev
? {
...prev,
stage: 'error',
message: `同步失败: ${error.message}`,
}
: null,
)
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 | 🟠 Major

同步任务提前失败时,Modal 可能无法关闭。
当同步在 onProgress 触发前就失败(如“任务已在进行中”)时,syncProgress 仍为 nullSyncProgressModal 处于不可关闭状态(dismissable=false 且无关闭按钮)。建议在打开 Modal 时初始化进度,或在 onSuccess/onError 中对 prev 为空的情况兜底。

🔧 建议修复(兜底初始化 + 完成/失败时强制赋值)
-		setIsSyncModalVisible(true)
+		setSyncProgress({ stage: 'initializing', message: '准备中...' })
+		setIsSyncModalVisible(true)
 		syncFavorite(
 			{
 				remoteSyncId: Number(id),
 				type: 'favorite',
 				onProgress: setSyncProgress,
 			},
 			{
 				onSuccess: (id) => {
-					setSyncProgress((prev) =>
-						prev ? { ...prev, stage: 'completed', message: '同步完成' } : null,
-					)
+					setSyncProgress((prev) => ({
+						...(prev ?? {}),
+						stage: 'completed',
+						message: '同步完成',
+					}))
 					// 等待用户点击关闭或自动关闭逻辑
 					// 这里我们不自动跳转,让用户看到结果
 					if (!id) return
 					// 如果需要自动跳转,可以在 onClose 里处理,或者这里延迟处理
 				},
 				onError: (error) => {
-					setSyncProgress((prev) =>
-						prev
-							? {
-									...prev,
-									stage: 'error',
-									message: `同步失败: ${error.message}`,
-								}
-							: null,
-					)
+					setSyncProgress((prev) => ({
+						...(prev ?? {}),
+						stage: 'error',
+						message: `同步失败: ${error.message}`,
+					}))
 				},
 			},
 		)
🤖 Prompt for AI Agents
In `@src/app/playlist/remote/favorite/`[id].tsx around lines 95 - 121, The modal
can become unclosable when sync fails before onProgress runs because
syncProgress stays null; to fix, initialize syncProgress when opening the modal
(e.g., setSyncProgress to a starting object) and add null-safe fallbacks in the
syncFavorite callbacks: in the onSuccess handler inside the syncFavorite call
ensure you setSyncProgress to a completed state even if prev is null, and in
onError setSyncProgress to an error state with the error message when prev is
null; update the logic around setIsSyncModalVisible/syncFavorite (the call that
supplies remoteSyncId, type, onProgress) so SyncProgressModal always receives a
non-null progress object (or make its dismissable/close control driven by this
fallback) to guarantee the modal can be dismissed.

@safedep
Copy link
Copy Markdown

safedep Bot commented Jan 27, 2026

SafeDep Report Summary

Green Malicious Packages Badge Green Vulnerable Packages Badge Green Risky License Badge

No dependency changes detected. Nothing to scan.

This report is generated by SafeDep Github App

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: 1

🤖 Fix all issues with AI agents
In `@src/components/modals/playlist/FavoriteSyncProgressModal.tsx`:
- Around line 64-68: The title currently only checks for progress?.stage ===
'completed' and otherwise shows "正在同步收藏夹", so when progress?.stage === 'error'
the title is inconsistent with the error message; update the Dialog.Title
rendering in FavoriteSyncProgressModal to handle the 'error' stage explicitly
(e.g., show "同步失败" or include the error brief) when progress?.stage === 'error',
keep "同步完成" for 'completed', and "正在同步收藏夹" for the in-progress states by
branching on progress?.stage in the Dialog.Title.
🧹 Nitpick comments (1)
src/components/modals/playlist/FavoriteSyncProgressModal.tsx (1)

19-46: React StrictMode 下可能触发两次同步请求。

在 React 18+ 的 StrictMode 开发模式下,useEffect 会执行两次。虽然 syncFacade 有防重入保护(syncingIds),但第二次调用会返回错误,导致 modal 显示 "同步失败" 的错误信息,影响开发体验。

考虑添加 cleanup 或使用 ref 来防止重复调用:

♻️ 建议的修改
+	const hasSyncStarted = useRef(false)
+
 	// Auto-start sync on mount
 	useEffect(() => {
+		if (hasSyncStarted.current) return
+		hasSyncStarted.current = true
+
 		syncFavorite(
 			{
 				remoteSyncId: favoriteId,
 				type: 'favorite',
 				onProgress: setProgress,
 			},
 			// ...
 		)
 	}, [favoriteId, syncFavorite])

Comment thread src/components/modals/playlist/FavoriteSyncProgressModal.tsx
@roitium roitium merged commit 5f6cd50 into dev Jan 27, 2026
2 of 3 checks passed
@roitium roitium deleted the feat/sync-progress-modal branch January 27, 2026 12:26
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