diff --git a/src/components/layout/aux-panel-file-tree-tab.tsx b/src/components/layout/aux-panel-file-tree-tab.tsx index 0dfff45..6fb3835 100644 --- a/src/components/layout/aux-panel-file-tree-tab.tsx +++ b/src/components/layout/aux-panel-file-tree-tab.tsx @@ -489,7 +489,7 @@ function RenderNode({ return ( - + - + {folder?.path && ( - + - + - + - + - + - + - + - + { + const label = displayName ?? dirPath + setConfirm({ + open: true, + title: t("confirm.rollbackTitle"), + description: t("confirm.rollbackDirDescription", { dir: label }), + variant: "destructive", + action: () => { + void (async () => { + if (!folderPath) return + try { + await gitRollbackFile(folderPath, dirPath) + toast.success(t("toasts.dirRolledBack"), { + description: label, + }) + if (diffFileRef.current && files.includes(diffFileRef.current)) { + setDiffFile(null) + setDiffOriginal("") + setDiffModified("") + } + setSelected((prev) => { + const next = new Set(prev) + files.forEach((f) => next.delete(f)) + return next + }) + void loadStatus() + } catch (err) { + toast.error(t("toasts.rollbackFailed"), { + description: String(err), + }) + } + })() + }, + }) + }, + [folderPath, loadStatus, t] + ) + + const handleDeleteDir = useCallback( + (dirPath: string, files: string[], displayName?: string) => { + const label = displayName ?? dirPath + setConfirm({ + open: true, + title: t("confirm.deleteTitle"), + description: t("confirm.deleteDirDescription", { dir: label }), + variant: "destructive", + action: () => { + void (async () => { + if (!folderPath) return + try { + await deleteFileTreeEntry(folderPath, dirPath) + toast.success(t("toasts.dirDeleted"), { + description: label, + }) + if (diffFileRef.current && files.includes(diffFileRef.current)) { + setDiffFile(null) + setDiffOriginal("") + setDiffModified("") + } + setSelected((prev) => { + const next = new Set(prev) + files.forEach((f) => next.delete(f)) + return next + }) + void loadStatus() + } catch (err) { + toast.error(t("toasts.deleteFailed"), { + description: String(err), + }) + } + })() + }, + }) + }, + [folderPath, loadStatus, t] + ) + + const handleAddDirToVcs = useCallback( + async (dirPath: string, files: string[], displayName?: string) => { + if (!folderPath) return + const label = displayName ?? dirPath + try { + await gitAddFiles(folderPath, files) + toast.success(t("toasts.addedToVcs"), { description: label }) + void loadStatus() + } catch (err) { + toast.error(t("toasts.addToVcsFailed"), { description: String(err) }) + } + }, + [folderPath, loadStatus, t] + ) + const closeConfirm = useCallback(() => { setConfirm(CONFIRM_INITIAL) }, []) @@ -607,14 +706,36 @@ export function CommitWorkspace({ const renderTrackedNode = useCallback( function renderNode(node: TreeNode): React.ReactNode { if (node.kind === "dir") { + const dirFiles = collectFilePaths(node) + const hasNonDeleted = node.children.some( + (child) => + child.kind === "file" && + child.entry.status !== " D" && + child.entry.status !== "D" + ) return ( - - {node.children.map(renderNode)} - + + + + {node.children.map(renderNode)} + + + + {hasNonDeleted && ( + handleRollbackDir(node.path, dirFiles)} + > + {t("actions.rollback")} + + )} + handleDeleteDir(node.path, dirFiles)} + > + {tCommon("delete")} + + + ) } @@ -686,7 +807,9 @@ export function CommitWorkspace({ toggleFile, handleViewDiff, handleRollbackFile, + handleRollbackDir, handleDeleteFile, + handleDeleteDir, t, tCommon, ] @@ -695,14 +818,31 @@ export function CommitWorkspace({ const renderUntrackedNode = useCallback( function renderNode(node: TreeNode): React.ReactNode { if (node.kind === "dir") { + const dirFiles = collectFilePaths(node) return ( - - {node.children.map(renderNode)} - + + + + {node.children.map(renderNode)} + + + + { + void handleAddDirToVcs(node.path, dirFiles) + }} + > + {t("actions.addToVcs")} + + + handleDeleteDir(node.path, dirFiles)} + > + {tCommon("delete")} + + + ) } @@ -768,7 +908,9 @@ export function CommitWorkspace({ toggleFile, handleViewDiff, handleAddToVcs, + handleAddDirToVcs, handleDeleteFile, + handleDeleteDir, t, tCommon, ] @@ -879,9 +1021,37 @@ export function CommitWorkspace({ selectedPath={diffFile ?? undefined} onSelect={handleSelectPath} > - - {trackedTree.map(renderTrackedNode)} - + + + + {trackedTree.map(renderTrackedNode)} + + + + + handleRollbackDir( + ".", + trackedFiles, + folderName + ) + } + > + {t("actions.rollback")} + + + handleDeleteDir(".", trackedFiles, folderName) + } + > + {tCommon("delete")} + + + )} @@ -933,9 +1103,42 @@ export function CommitWorkspace({ selectedPath={diffFile ?? undefined} onSelect={handleSelectPath} > - - {untrackedTree.map(renderUntrackedNode)} - + + + + {untrackedTree.map(renderUntrackedNode)} + + + + { + void handleAddDirToVcs( + ".", + untrackedFiles, + folderName + ) + }} + > + {t("actions.addToVcs")} + + + + handleDeleteDir( + ".", + untrackedFiles, + folderName + ) + } + > + {tCommon("delete")} + + + )} diff --git a/src/i18n/messages/ar.json b/src/i18n/messages/ar.json index 3686ce2..02bfc56 100644 --- a/src/i18n/messages/ar.json +++ b/src/i18n/messages/ar.json @@ -895,13 +895,17 @@ "fileDeleted": "تم حذف الملف", "deleteFailed": "فشل الحذف", "fileRolledBack": "تم التراجع عن الملف", - "rollbackFailed": "فشل التراجع" + "rollbackFailed": "فشل التراجع", + "dirRolledBack": "تم استعادة المجلد", + "dirDeleted": "تم حذف المجلد" }, "confirm": { "deleteTitle": "تأكيد الحذف", "deleteDescription": "حذف الملف \"{file}\"؟ لا يمكن التراجع عن هذا الإجراء.", "rollbackTitle": "تأكيد التراجع", - "rollbackDescription": "التراجع عن الملف \"{file}\" إلى HEAD؟ ستفقد التغييرات غير المحفوظة." + "rollbackDescription": "التراجع عن الملف \"{file}\" إلى HEAD؟ ستفقد التغييرات غير المحفوظة.", + "rollbackDirDescription": "هل تريد استعادة المجلد \"{dir}\" إلى HEAD؟ ستفقد التغييرات غير المحفوظة.", + "deleteDirDescription": "هل تريد حذف المجلد \"{dir}\"؟ لا يمكن التراجع عن هذا الإجراء." }, "actions": { "select": "تحديد", diff --git a/src/i18n/messages/de.json b/src/i18n/messages/de.json index 67d188a..d23f2cd 100644 --- a/src/i18n/messages/de.json +++ b/src/i18n/messages/de.json @@ -895,13 +895,17 @@ "fileDeleted": "Datei gelöscht", "deleteFailed": "Löschen fehlgeschlagen", "fileRolledBack": "Datei zurückgesetzt", - "rollbackFailed": "Rollback fehlgeschlagen" + "rollbackFailed": "Rollback fehlgeschlagen", + "dirRolledBack": "Verzeichnis zurückgesetzt", + "dirDeleted": "Verzeichnis gelöscht" }, "confirm": { "deleteTitle": "Löschen bestätigen", "deleteDescription": "Datei \"{file}\" löschen? Diese Aktion kann nicht rückgängig gemacht werden.", "rollbackTitle": "Rollback bestätigen", - "rollbackDescription": "Datei \"{file}\" auf HEAD zurücksetzen? Ungespeicherte Änderungen gehen verloren." + "rollbackDescription": "Datei \"{file}\" auf HEAD zurücksetzen? Ungespeicherte Änderungen gehen verloren.", + "rollbackDirDescription": "Verzeichnis \"{dir}\" auf HEAD zurücksetzen? Nicht gespeicherte Änderungen gehen verloren.", + "deleteDirDescription": "Verzeichnis \"{dir}\" löschen? Diese Aktion kann nicht rückgängig gemacht werden." }, "actions": { "select": "Auswählen", diff --git a/src/i18n/messages/en.json b/src/i18n/messages/en.json index 0e0d350..42e821e 100644 --- a/src/i18n/messages/en.json +++ b/src/i18n/messages/en.json @@ -895,13 +895,17 @@ "fileDeleted": "File deleted", "deleteFailed": "Delete failed", "fileRolledBack": "File rolled back", - "rollbackFailed": "Rollback failed" + "rollbackFailed": "Rollback failed", + "dirRolledBack": "Directory rolled back", + "dirDeleted": "Directory deleted" }, "confirm": { "deleteTitle": "Confirm deletion", "deleteDescription": "Delete file \"{file}\"? This action cannot be undone.", "rollbackTitle": "Confirm rollback", - "rollbackDescription": "Rollback file \"{file}\" to HEAD? Unsaved changes will be lost." + "rollbackDescription": "Rollback file \"{file}\" to HEAD? Unsaved changes will be lost.", + "rollbackDirDescription": "Rollback directory \"{dir}\" to HEAD? Unsaved changes will be lost.", + "deleteDirDescription": "Delete directory \"{dir}\"? This action cannot be undone." }, "actions": { "select": "Select", diff --git a/src/i18n/messages/es.json b/src/i18n/messages/es.json index 9f5f100..cca383e 100644 --- a/src/i18n/messages/es.json +++ b/src/i18n/messages/es.json @@ -895,13 +895,17 @@ "fileDeleted": "Archivo eliminado", "deleteFailed": "Error al eliminar", "fileRolledBack": "Archivo revertido", - "rollbackFailed": "Error al revertir" + "rollbackFailed": "Error al revertir", + "dirRolledBack": "Directorio revertido", + "dirDeleted": "Directorio eliminado" }, "confirm": { "deleteTitle": "Confirmar eliminación", "deleteDescription": "¿Eliminar el archivo \"{file}\"? Esta acción no se puede deshacer.", "rollbackTitle": "Confirmar reversión", - "rollbackDescription": "¿Revertir el archivo \"{file}\" a HEAD? Se perderán los cambios sin guardar." + "rollbackDescription": "¿Revertir el archivo \"{file}\" a HEAD? Se perderán los cambios sin guardar.", + "rollbackDirDescription": "¿Revertir el directorio \"{dir}\" a HEAD? Los cambios no guardados se perderán.", + "deleteDirDescription": "¿Eliminar el directorio \"{dir}\"? Esta acción no se puede deshacer." }, "actions": { "select": "Seleccionar", diff --git a/src/i18n/messages/fr.json b/src/i18n/messages/fr.json index 2c03c3b..0ab828e 100644 --- a/src/i18n/messages/fr.json +++ b/src/i18n/messages/fr.json @@ -895,13 +895,17 @@ "fileDeleted": "Fichier supprimé", "deleteFailed": "Échec de la suppression", "fileRolledBack": "Fichier restauré", - "rollbackFailed": "Échec du rollback" + "rollbackFailed": "Échec du rollback", + "dirRolledBack": "Répertoire restauré", + "dirDeleted": "Répertoire supprimé" }, "confirm": { "deleteTitle": "Confirmer la suppression", "deleteDescription": "Supprimer le fichier \"{file}\" ? Cette action est irréversible.", "rollbackTitle": "Confirmer le rollback", - "rollbackDescription": "Restaurer le fichier \"{file}\" vers HEAD ? Les modifications non enregistrées seront perdues." + "rollbackDescription": "Restaurer le fichier \"{file}\" vers HEAD ? Les modifications non enregistrées seront perdues.", + "rollbackDirDescription": "Restaurer le répertoire \"{dir}\" vers HEAD ? Les modifications non enregistrées seront perdues.", + "deleteDirDescription": "Supprimer le répertoire \"{dir}\" ? Cette action est irréversible." }, "actions": { "select": "Sélectionner", diff --git a/src/i18n/messages/ja.json b/src/i18n/messages/ja.json index 0ae4340..8bce0d7 100644 --- a/src/i18n/messages/ja.json +++ b/src/i18n/messages/ja.json @@ -895,13 +895,17 @@ "fileDeleted": "ファイルを削除しました", "deleteFailed": "削除に失敗しました", "fileRolledBack": "ファイルをロールバックしました", - "rollbackFailed": "ロールバックに失敗しました" + "rollbackFailed": "ロールバックに失敗しました", + "dirRolledBack": "ディレクトリをロールバックしました", + "dirDeleted": "ディレクトリを削除しました" }, "confirm": { "deleteTitle": "削除の確認", "deleteDescription": "ファイル \"{file}\" を削除しますか?この操作は元に戻せません。", "rollbackTitle": "ロールバックの確認", - "rollbackDescription": "ファイル \"{file}\" を HEAD にロールバックしますか?未保存の変更は失われます。" + "rollbackDescription": "ファイル \"{file}\" を HEAD にロールバックしますか?未保存の変更は失われます。", + "rollbackDirDescription": "ディレクトリ「{dir}」をHEADにロールバックしますか?未保存の変更は失われます。", + "deleteDirDescription": "ディレクトリ「{dir}」を削除しますか?この操作は元に戻せません。" }, "actions": { "select": "選択", diff --git a/src/i18n/messages/ko.json b/src/i18n/messages/ko.json index dbed1cf..a4577a3 100644 --- a/src/i18n/messages/ko.json +++ b/src/i18n/messages/ko.json @@ -895,13 +895,17 @@ "fileDeleted": "파일이 삭제되었습니다", "deleteFailed": "삭제에 실패했습니다", "fileRolledBack": "파일이 롤백되었습니다", - "rollbackFailed": "롤백에 실패했습니다" + "rollbackFailed": "롤백에 실패했습니다", + "dirRolledBack": "디렉토리가 롤백되었습니다", + "dirDeleted": "디렉토리가 삭제되었습니다" }, "confirm": { "deleteTitle": "삭제 확인", "deleteDescription": "파일 \"{file}\"을(를) 삭제할까요? 이 작업은 되돌릴 수 없습니다.", "rollbackTitle": "롤백 확인", - "rollbackDescription": "파일 \"{file}\"을(를) HEAD로 롤백할까요? 저장되지 않은 변경 사항은 사라집니다." + "rollbackDescription": "파일 \"{file}\"을(를) HEAD로 롤백할까요? 저장되지 않은 변경 사항은 사라집니다.", + "rollbackDirDescription": "디렉토리 \"{dir}\"를 HEAD로 롤백하시겠습니까? 저장되지 않은 변경 사항이 손실됩니다.", + "deleteDirDescription": "디렉토리 \"{dir}\"를 삭제하시겠습니까? 이 작업은 되돌릴 수 없습니다." }, "actions": { "select": "선택", diff --git a/src/i18n/messages/pt.json b/src/i18n/messages/pt.json index 3104955..5e5d51d 100644 --- a/src/i18n/messages/pt.json +++ b/src/i18n/messages/pt.json @@ -895,13 +895,17 @@ "fileDeleted": "Arquivo excluído", "deleteFailed": "Falha ao excluir", "fileRolledBack": "Arquivo revertido", - "rollbackFailed": "Falha no rollback" + "rollbackFailed": "Falha no rollback", + "dirRolledBack": "Diretório revertido", + "dirDeleted": "Diretório excluído" }, "confirm": { "deleteTitle": "Confirmar exclusão", "deleteDescription": "Excluir o arquivo \"{file}\"? Esta ação não pode ser desfeita.", "rollbackTitle": "Confirmar rollback", - "rollbackDescription": "Reverter o arquivo \"{file}\" para o HEAD? Alterações não salvas serão perdidas." + "rollbackDescription": "Reverter o arquivo \"{file}\" para o HEAD? Alterações não salvas serão perdidas.", + "rollbackDirDescription": "Reverter o diretório \"{dir}\" para HEAD? Alterações não salvas serão perdidas.", + "deleteDirDescription": "Excluir o diretório \"{dir}\"? Esta ação não pode ser desfeita." }, "actions": { "select": "Selecionar", diff --git a/src/i18n/messages/zh-CN.json b/src/i18n/messages/zh-CN.json index 5cbfb64..cf4a7a2 100644 --- a/src/i18n/messages/zh-CN.json +++ b/src/i18n/messages/zh-CN.json @@ -895,13 +895,17 @@ "fileDeleted": "文件已删除", "deleteFailed": "删除失败", "fileRolledBack": "文件已回滚", - "rollbackFailed": "回滚失败" + "rollbackFailed": "回滚失败", + "dirRolledBack": "目录已回滚", + "dirDeleted": "目录已删除" }, "confirm": { "deleteTitle": "确认删除", "deleteDescription": "确定要删除文件「{file}」吗?此操作不可恢复。", "rollbackTitle": "确认回滚", - "rollbackDescription": "确定要回滚文件「{file}」到 HEAD 版本吗?未保存的修改将丢失。" + "rollbackDescription": "确定要回滚文件「{file}」到 HEAD 版本吗?未保存的修改将丢失。", + "rollbackDirDescription": "确定要回滚目录「{dir}」到 HEAD 版本吗?未保存的修改将丢失。", + "deleteDirDescription": "确定要删除目录「{dir}」吗?此操作不可恢复。" }, "actions": { "select": "选择", diff --git a/src/i18n/messages/zh-TW.json b/src/i18n/messages/zh-TW.json index ce9871c..55ec5a5 100644 --- a/src/i18n/messages/zh-TW.json +++ b/src/i18n/messages/zh-TW.json @@ -895,13 +895,17 @@ "fileDeleted": "檔案已刪除", "deleteFailed": "刪除失敗", "fileRolledBack": "檔案已回滾", - "rollbackFailed": "回滾失敗" + "rollbackFailed": "回滾失敗", + "dirRolledBack": "目錄已回滾", + "dirDeleted": "目錄已刪除" }, "confirm": { "deleteTitle": "確認刪除", "deleteDescription": "確定要刪除檔案「{file}」嗎?此操作無法復原。", "rollbackTitle": "確認回滾", - "rollbackDescription": "確定要回滾檔案「{file}」到 HEAD 版本嗎?未儲存修改將遺失。" + "rollbackDescription": "確定要回滾檔案「{file}」到 HEAD 版本嗎?未儲存修改將遺失。", + "rollbackDirDescription": "確定要回滾目錄「{dir}」到 HEAD 版本嗎?未儲存的修改將遺失。", + "deleteDirDescription": "確定要刪除目錄「{dir}」嗎?此操作不可恢復。" }, "actions": { "select": "選擇",