feat: add bulk actions for file management in received files modal

- Implemented bulk selection functionality for files in the ReceivedFilesModal, allowing users to select multiple files for actions such as download, copy, and delete.
- Added UI elements for bulk actions, including a dropdown menu for selecting actions and confirmation dialogs for deletion.
- Enhanced user experience by providing feedback during bulk operations and clearing selections after successful actions.
- Localized new messages for bulk actions across multiple languages to ensure consistent user feedback.
This commit is contained in:
Daniel Luiz Alves
2025-06-23 17:11:08 -03:00
parent 68c565f265
commit b1cc9dbb21
16 changed files with 573 additions and 25 deletions

View File

@@ -1189,7 +1189,23 @@
"editError": "خطأ في تحديث الملف",
"previewNotAvailable": "المعاينة غير متوفرة",
"copyError": "خطأ نسخ الملف إلى ملفاتك",
"copySuccess": "تم نسخ الملف إلى ملفاتك بنجاح"
"copySuccess": "تم نسخ الملف إلى ملفاتك بنجاح",
"bulkActions": {
"selected": "{count, plural, =0 {لا ملفات محددة} =1 {ملف واحد محدد} =2 {ملفان محددان} other {# ملفات محددة}}",
"actions": "إجراءات",
"download": "تحميل المحدد",
"copyToMyFiles": "نسخ المحدد إلى ملفاتي",
"delete": "حذف المحدد"
},
"bulkCopyProgress": "جارٍ نسخ {count, plural, =1 {ملف واحد} =2 {ملفين} other {# ملفات}} إلى ملفاتك...",
"bulkCopySuccess": "{count, plural, =1 {تم نسخ ملف واحد إلى ملفاتك بنجاح} =2 {تم نسخ ملفين إلى ملفاتك بنجاح} other {تم نسخ # ملفات إلى ملفاتك بنجاح}}",
"bulkDeleteConfirmButton": "حذف {count, plural, =1 {الملف} =2 {الملفين} other {الملفات}}",
"bulkDeleteConfirmMessage": "هل أنت متأكد أنك تريد حذف {count, plural, =1 {هذا الملف} =2 {هذين الملفين} other {هذه الملفات الـ #}}؟ لا يمكن التراجع عن هذا الإجراء.",
"bulkDeleteConfirmTitle": "حذف الملفات المحددة",
"bulkDeleteProgress": "جارٍ حذف {count, plural, =1 {ملف واحد} =2 {ملفين} other {# ملفات}}...",
"bulkDeleteSuccess": "{count, plural, =1 {تم حذف ملف واحد بنجاح} =2 {تم حذف ملفين بنجاح} other {تم حذف # ملفات بنجاح}}",
"selectAll": "تحديد الكل",
"selectFile": "تحديد الملف {fileName}"
}
},
"form": {
@@ -1397,4 +1413,4 @@
}
}
}
}
}

View File

@@ -1189,7 +1189,23 @@
"editError": "Fehler beim Aktualisieren der Datei",
"previewNotAvailable": "Vorschau nicht verfügbar",
"copyError": "Fehler beim Kopieren der Datei in Ihre Dateien",
"copySuccess": "Datei erfolgreich in Ihre Dateien kopiert"
"copySuccess": "Datei erfolgreich in Ihre Dateien kopiert",
"bulkActions": {
"selected": "{count, plural, =1 {1 Datei ausgewählt} other {# Dateien ausgewählt}}",
"actions": "Aktionen",
"download": "Ausgewählte herunterladen",
"copyToMyFiles": "Ausgewählte in meine Dateien kopieren",
"delete": "Ausgewählte löschen"
},
"bulkCopyProgress": "{count, plural, =1 {1 Datei wird} other {# Dateien werden}} in Ihre Dateien kopiert...",
"bulkCopySuccess": "{count, plural, =1 {1 Datei wurde erfolgreich in Ihre Dateien kopiert} other {# Dateien wurden erfolgreich in Ihre Dateien kopiert}}",
"bulkDeleteConfirmButton": "{count, plural, =1 {Datei löschen} other {Dateien löschen}}",
"bulkDeleteConfirmMessage": "Sind Sie sicher, dass Sie {count, plural, =1 {diese Datei} other {diese # Dateien}} löschen möchten? Diese Aktion kann nicht rückgängig gemacht werden.",
"bulkDeleteConfirmTitle": "Ausgewählte Dateien löschen",
"bulkDeleteProgress": "{count, plural, =1 {1 Datei wird} other {# Dateien werden}} gelöscht...",
"bulkDeleteSuccess": "{count, plural, =1 {1 Datei wurde erfolgreich gelöscht} other {# Dateien wurden erfolgreich gelöscht}}",
"selectAll": "Alle auswählen",
"selectFile": "Datei {fileName} auswählen"
}
},
"form": {

View File

@@ -1189,7 +1189,23 @@
"editError": "Error updating file",
"previewNotAvailable": "Preview not available",
"copySuccess": "File copied to your files successfully",
"copyError": "Error copying file to your files"
"copyError": "Error copying file to your files",
"bulkCopySuccess": "{count, plural, =1 {1 file copied to your files successfully} other {# files copied to your files successfully}}",
"bulkDeleteSuccess": "{count, plural, =1 {1 file deleted successfully} other {# files deleted successfully}}",
"bulkCopyProgress": "Copying {count, plural, =1 {1 file} other {# files}} to your files...",
"bulkDeleteProgress": "Deleting {count, plural, =1 {1 file} other {# files}}...",
"bulkDeleteConfirmTitle": "Delete Selected Files",
"bulkDeleteConfirmMessage": "Are you sure you want to delete {count, plural, =1 {this file} other {these # files}}? This action cannot be undone.",
"bulkDeleteConfirmButton": "Delete {count, plural, =1 {File} other {Files}}",
"bulkActions": {
"selected": "{count, plural, =1 {1 file selected} other {# files selected}}",
"actions": "Actions",
"download": "Download Selected",
"copyToMyFiles": "Copy Selected to My Files",
"delete": "Delete Selected"
},
"selectAll": "Select all",
"selectFile": "Select file {fileName}"
}
},
"form": {

View File

@@ -1189,7 +1189,23 @@
"editError": "Error al actualizar archivo",
"previewNotAvailable": "Vista previa no disponible",
"copyError": "Error de copiar el archivo a sus archivos",
"copySuccess": "Archivo copiado en sus archivos correctamente"
"copySuccess": "Archivo copiado en sus archivos correctamente",
"bulkActions": {
"selected": "{count, plural, =1 {1 archivo seleccionado} other {# archivos seleccionados}}",
"actions": "Acciones",
"download": "Descargar Seleccionados",
"copyToMyFiles": "Copiar Seleccionados a Mis Archivos",
"delete": "Eliminar Seleccionados"
},
"bulkCopyProgress": "Copiando {count, plural, =1 {1 archivo} other {# archivos}} a tus archivos...",
"bulkCopySuccess": "{count, plural, =1 {1 archivo copiado a tus archivos correctamente} other {# archivos copiados a tus archivos correctamente}}",
"bulkDeleteConfirmButton": "Eliminar {count, plural, =1 {Archivo} other {Archivos}}",
"bulkDeleteConfirmMessage": "¿Estás seguro de que quieres eliminar {count, plural, =1 {este archivo} other {estos # archivos}}? Esta acción no se puede deshacer.",
"bulkDeleteConfirmTitle": "Eliminar Archivos Seleccionados",
"bulkDeleteProgress": "Eliminando {count, plural, =1 {1 archivo} other {# archivos}}...",
"bulkDeleteSuccess": "{count, plural, =1 {1 archivo eliminado correctamente} other {# archivos eliminados correctamente}}",
"selectAll": "Seleccionar todo",
"selectFile": "Seleccionar archivo {fileName}"
}
},
"form": {

View File

@@ -1189,7 +1189,23 @@
"editError": "Erreur lors de la mise à jour du fichier",
"previewNotAvailable": "Aperçu non disponible",
"copyError": "Erreur de copie du fichier dans vos fichiers",
"copySuccess": "Fichier copié dans vos fichiers avec succès"
"copySuccess": "Fichier copié dans vos fichiers avec succès",
"bulkActions": {
"selected": "{count, plural, =1 {1 fichier sélectionné} other {# fichiers sélectionnés}}",
"actions": "Actes",
"download": "Télécharger la sélection",
"copyToMyFiles": "Copier la sélection dans mes fichiers",
"delete": "Supprimer la sélection"
},
"bulkCopyProgress": "Copie de {count, plural, =1 {1 fichier} other {# fichiers}} dans vos fichiers...",
"bulkCopySuccess": "{count, plural, =1 {1 fichier copié dans vos fichiers avec succès} other {# fichiers copiés dans vos fichiers avec succès}}",
"bulkDeleteConfirmButton": "Supprimer {count, plural, =1 {le fichier} other {les fichiers}}",
"bulkDeleteConfirmMessage": "Êtes-vous sûr de vouloir supprimer {count, plural, =1 {ce fichier} other {ces # fichiers}} ? Cette action est irréversible.",
"bulkDeleteConfirmTitle": "Supprimer les fichiers sélectionnés",
"bulkDeleteProgress": "Suppression de {count, plural, =1 {1 fichier} other {# fichiers}}...",
"bulkDeleteSuccess": "{count, plural, =1 {1 fichier supprimé avec succès} other {# fichiers supprimés avec succès}}",
"selectAll": "Tout sélectionner",
"selectFile": "Sélectionner le fichier {fileName}"
}
},
"form": {

View File

@@ -1189,7 +1189,23 @@
"editError": "फ़ाइल अपडेट करने में त्रुटि",
"previewNotAvailable": "पूर्वावलोकन उपलब्ध नहीं है",
"copyError": "अपनी फ़ाइलों में फ़ाइल की नकल करने में त्रुटि",
"copySuccess": "आपकी फ़ाइलों को सफलतापूर्वक कॉपी की गई फ़ाइल"
"copySuccess": "आपकी फ़ाइलों को सफलतापूर्वक कॉपी की गई फ़ाइल",
"bulkActions": {
"selected": "{count, plural, =1 {1 फ़ाइल चयनित} other {# फ़ाइलें चयनित}}",
"actions": "कार्रवाइयां",
"download": "चयनित डाउनलोड करें",
"copyToMyFiles": "चयनित को मेरी फ़ाइलों में कॉपी करें",
"delete": "चयनित हटाएं"
},
"bulkCopyProgress": "{count, plural, =1 {1 फ़ाइल} other {# फ़ाइलें}} आपकी फ़ाइलों में कॉपी की जा रही हैं...",
"bulkCopySuccess": "{count, plural, =1 {1 फ़ाइल सफलतापूर्वक आपकी फ़ाइलों में कॉपी की गई} other {# फ़ाइलें सफलतापूर्वक आपकी फ़ाइलों में कॉपी की गईं}}",
"bulkDeleteConfirmButton": "{count, plural, =1 {फ़ाइल} other {फ़ाइलें}} हटाएं",
"bulkDeleteConfirmMessage": "क्या आप वाकई {count, plural, =1 {इस फ़ाइल} other {इन # फ़ाइलों}} को हटाना चाहते हैं? यह क्रिया पूर्ववत नहीं की जा सकती।",
"bulkDeleteConfirmTitle": "चयनित फ़ाइलें हटाएं",
"bulkDeleteProgress": "{count, plural, =1 {1 फ़ाइल} other {# फ़ाइलें}} हटाई जा रही हैं...",
"bulkDeleteSuccess": "{count, plural, =1 {1 फ़ाइल सफलतापूर्वक हटाई गई} other {# फ़ाइलें सफलतापूर्वक हटाई गईं}}",
"selectAll": "सभी चुनें",
"selectFile": "फ़ाइल {fileName} चुनें"
}
},
"form": {
@@ -1397,4 +1413,4 @@
}
}
}
}
}

View File

@@ -1189,7 +1189,23 @@
"editError": "Errore durante l'aggiornamento del file",
"previewNotAvailable": "Anteprima non disponibile",
"copyError": "Errore di copia del file sui tuoi file",
"copySuccess": "File copiato sui tuoi file correttamente"
"copySuccess": "File copiato sui tuoi file correttamente",
"bulkActions": {
"selected": "{count, plural, =1 {1 file selezionato} other {# file selezionati}}",
"actions": "Azioni",
"download": "Scarica Selezionati",
"copyToMyFiles": "Copia Selezionati sui Miei File",
"delete": "Elimina Selezionati"
},
"bulkCopyProgress": "Copia di {count, plural, =1 {1 file} other {# file}} sui tuoi file in corso...",
"bulkCopySuccess": "{count, plural, =1 {1 file copiato sui tuoi file con successo} other {# file copiati sui tuoi file con successo}}",
"bulkDeleteConfirmButton": "Elimina {count, plural, =1 {File} other {File}}",
"bulkDeleteConfirmMessage": "Sei sicuro di voler eliminare {count, plural, =1 {questo file} other {questi # file}}? Questa azione non può essere annullata.",
"bulkDeleteConfirmTitle": "Elimina File Selezionati",
"bulkDeleteProgress": "Eliminazione di {count, plural, =1 {1 file} other {# file}} in corso...",
"bulkDeleteSuccess": "{count, plural, =1 {1 file eliminato con successo} other {# file eliminati con successo}}",
"selectAll": "Seleziona tutto",
"selectFile": "Seleziona file {fileName}"
}
},
"form": {

View File

@@ -1189,7 +1189,23 @@
"editError": "ファイルの更新に失敗しました",
"previewNotAvailable": "プレビューは利用できません",
"copyError": "ファイルにファイルをコピーするエラー",
"copySuccess": "ファイルに正常にコピーされたファイル"
"copySuccess": "ファイルに正常にコピーされたファイル",
"bulkActions": {
"selected": "{count, plural, =1 {1ファイルを選択} other {#ファイルを選択}}",
"actions": "アクション",
"download": "選択したファイルをダウンロード",
"copyToMyFiles": "選択したファイルを私のファイルにコピー",
"delete": "選択したファイルを削除"
},
"bulkCopyProgress": "{count, plural, =1 {1ファイル} other {#ファイル}}を私のファイルにコピー中...",
"bulkCopySuccess": "{count, plural, =1 {1ファイル} other {#ファイル}}を私のファイルに正常にコピーしました",
"bulkDeleteConfirmButton": "{count, plural, =1 {ファイル} other {ファイル}}を削除",
"bulkDeleteConfirmMessage": "{count, plural, =1 {このファイル} other {これらの#ファイル}}を削除してもよろしいですか?この操作は取り消せません。",
"bulkDeleteConfirmTitle": "選択したファイルを削除",
"bulkDeleteProgress": "{count, plural, =1 {1ファイル} other {#ファイル}}を削除中...",
"bulkDeleteSuccess": "{count, plural, =1 {1ファイル} other {#ファイル}}を正常に削除しました",
"selectAll": "すべて選択",
"selectFile": "ファイル{fileName}を選択"
}
},
"form": {
@@ -1397,4 +1413,4 @@
}
}
}
}
}

View File

@@ -1189,7 +1189,23 @@
"editError": "파일 업데이트 오류",
"previewNotAvailable": "미리보기 불가",
"copyError": "파일에 파일을 복사합니다",
"copySuccess": "파일을 파일에 성공적으로 복사했습니다"
"copySuccess": "파일을 파일에 성공적으로 복사했습니다",
"bulkActions": {
"selected": "{count, plural, =1 {1개의 파일 선택됨} other {#개의 파일 선택됨}}",
"actions": "작업",
"download": "선택한 항목 다운로드",
"copyToMyFiles": "선택한 항목을 내 파일로 복사",
"delete": "선택한 항목 삭제"
},
"bulkCopyProgress": "{count, plural, =1 {1개의 파일} other {#개의 파일}}을(를) 내 파일로 복사하는 중...",
"bulkCopySuccess": "{count, plural, =1 {1개의 파일이} other {#개의 파일이}} 내 파일로 성공적으로 복사됨",
"bulkDeleteConfirmButton": "{count, plural, =1 {파일} other {파일}} 삭제",
"bulkDeleteConfirmMessage": "{count, plural, =1 {이 파일을} other {이 #개의 파일을}} 삭제하시겠습니까? 이 작업은 취소할 수 없습니다.",
"bulkDeleteConfirmTitle": "선택한 파일 삭제",
"bulkDeleteProgress": "{count, plural, =1 {1개의 파일} other {#개의 파일}} 삭제 중...",
"bulkDeleteSuccess": "{count, plural, =1 {1개의 파일이} other {#개의 파일이}} 성공적으로 삭제됨",
"selectAll": "모두 선택",
"selectFile": "{fileName} 파일 선택"
}
},
"form": {
@@ -1397,4 +1413,4 @@
}
}
}
}
}

View File

@@ -1189,7 +1189,23 @@
"editError": "Fout bij bijwerken bestand",
"previewNotAvailable": "Voorvertoning niet beschikbaar",
"copyError": "Fout bij het kopiëren van bestand naar uw bestanden",
"copySuccess": "Bestand gekopieerd naar uw bestanden succesvol"
"copySuccess": "Bestand gekopieerd naar uw bestanden succesvol",
"bulkActions": {
"selected": "{count, plural, =1 {1 bestand geselecteerd} other {# bestanden geselecteerd}}",
"actions": "Acties",
"download": "Geselecteerde Downloaden",
"copyToMyFiles": "Geselecteerde Kopiëren naar Mijn Bestanden",
"delete": "Geselecteerde Verwijderen"
},
"bulkCopyProgress": "{count, plural, =1 {1 bestand} other {# bestanden}} kopiëren naar uw bestanden...",
"bulkCopySuccess": "{count, plural, =1 {1 bestand succesvol gekopieerd naar uw bestanden} other {# bestanden succesvol gekopieerd naar uw bestanden}}",
"bulkDeleteConfirmButton": "{count, plural, =1 {Bestand} other {Bestanden}} Verwijderen",
"bulkDeleteConfirmMessage": "Weet u zeker dat u {count, plural, =1 {dit bestand} other {deze # bestanden}} wilt verwijderen? Deze actie kan niet ongedaan worden gemaakt.",
"bulkDeleteConfirmTitle": "Geselecteerde Bestanden Verwijderen",
"bulkDeleteProgress": "{count, plural, =1 {1 bestand} other {# bestanden}} verwijderen...",
"bulkDeleteSuccess": "{count, plural, =1 {1 bestand succesvol verwijderd} other {# bestanden succesvol verwijderd}}",
"selectAll": "Alles selecteren",
"selectFile": "Selecteer bestand {fileName}"
}
},
"form": {
@@ -1397,4 +1413,4 @@
}
}
}
}
}

View File

@@ -1189,7 +1189,23 @@
"editError": "Błąd aktualizacji pliku",
"previewNotAvailable": "Podgląd niedostępny",
"copyError": "Plik kopiowania błędów do plików",
"copySuccess": "Plik skopiowany do plików pomyślnie"
"copySuccess": "Plik skopiowany do plików pomyślnie",
"bulkActions": {
"selected": "{count, plural, =1 {1 plik wybrany} other {# plików wybranych}}",
"actions": "Akcje",
"download": "Pobierz wybrane",
"copyToMyFiles": "Skopiuj wybrane do Moich plików",
"delete": "Usuń wybrane"
},
"bulkCopyProgress": "Kopiowanie {count, plural, =1 {1 pliku} other {# plików}} do twoich plików...",
"bulkCopySuccess": "{count, plural, =1 {1 plik skopiowany pomyślnie do twoich plików} other {# plików skopiowanych pomyślnie do twoich plików}}",
"bulkDeleteConfirmButton": "Usuń {count, plural, =1 {plik} other {pliki}}",
"bulkDeleteConfirmMessage": "Czy na pewno chcesz usunąć {count, plural, =1 {ten plik} other {te # pliki}}? Tej akcji nie można cofnąć.",
"bulkDeleteConfirmTitle": "Usuń wybrane pliki",
"bulkDeleteProgress": "Usuwanie {count, plural, =1 {1 pliku} other {# plików}}...",
"bulkDeleteSuccess": "{count, plural, =1 {1 plik usunięty pomyślnie} other {# plików usuniętych pomyślnie}}",
"selectAll": "Zaznacz wszystko",
"selectFile": "Wybierz plik {fileName}"
}
},
"form": {
@@ -1397,4 +1413,4 @@
}
}
}
}
}

View File

@@ -1189,7 +1189,23 @@
"editError": "Erro ao atualizar arquivo",
"previewNotAvailable": "Visualização não disponível",
"copySuccess": "Arquivo copiado para seus arquivos com sucesso",
"copyError": "Erro ao copiar arquivo para seus arquivos"
"copyError": "Erro ao copiar arquivo para seus arquivos",
"bulkActions": {
"selected": "{count, plural, =1 {1 arquivo selecionado} other {# arquivos selecionados}}",
"actions": "Ações",
"download": "Baixar Selecionados",
"copyToMyFiles": "Copiar Selecionados para Meus Arquivos",
"delete": "Excluir Selecionados"
},
"bulkCopyProgress": "Copiando {count, plural, =1 {1 arquivo} other {# arquivos}} para seus arquivos...",
"bulkCopySuccess": "{count, plural, =1 {1 arquivo copiado para seus arquivos com sucesso} other {# arquivos copiados para seus arquivos com sucesso}}",
"bulkDeleteConfirmButton": "Excluir {count, plural, =1 {Arquivo} other {Arquivos}}",
"bulkDeleteConfirmMessage": "Tem certeza que deseja excluir {count, plural, =1 {este arquivo} other {estes # arquivos}}? Esta ação não pode ser desfeita.",
"bulkDeleteConfirmTitle": "Excluir Arquivos Selecionados",
"bulkDeleteProgress": "Excluindo {count, plural, =1 {1 arquivo} other {# arquivos}}...",
"bulkDeleteSuccess": "{count, plural, =1 {1 arquivo excluído com sucesso} other {# arquivos excluídos com sucesso}}",
"selectAll": "Selecionar todos",
"selectFile": "Selecionar arquivo {fileName}"
}
},
"form": {

View File

@@ -1189,7 +1189,23 @@
"editError": "Ошибка при обновлении файла",
"previewNotAvailable": "Предпросмотр недоступен",
"copyError": "Ошибка копирования файла в ваши файлы",
"copySuccess": "Файл успешно скопирован в ваши файлы"
"copySuccess": "Файл успешно скопирован в ваши файлы",
"bulkActions": {
"selected": "{count, plural, =1 {1 файл выбран} other {# файлов выбрано}}",
"actions": "Действия",
"download": "Скачать выбранные",
"copyToMyFiles": "Копировать выбранные в мои файлы",
"delete": "Удалить выбранные"
},
"bulkCopyProgress": "Копирование {count, plural, =1 {1 файла} other {# файлов}} в ваши файлы...",
"bulkCopySuccess": "{count, plural, =1 {1 файл успешно скопирован в ваши файлы} other {# файлов успешно скопировано в ваши файлы}}",
"bulkDeleteConfirmButton": "Удалить {count, plural, =1 {файл} other {файлы}}",
"bulkDeleteConfirmMessage": "Вы уверены, что хотите удалить {count, plural, =1 {этот файл} other {эти # файлов}}? Это действие нельзя отменить.",
"bulkDeleteConfirmTitle": "Удалить выбранные файлы",
"bulkDeleteProgress": "Удаление {count, plural, =1 {1 файла} other {# файлов}}...",
"bulkDeleteSuccess": "{count, plural, =1 {1 файл успешно удален} other {# файлов успешно удалено}}",
"selectAll": "Выбрать все",
"selectFile": "Выбрать файл {fileName}"
}
},
"form": {
@@ -1397,4 +1413,4 @@
}
}
}
}
}

View File

@@ -1189,7 +1189,23 @@
"editError": "Dosya güncellenirken hata oluştu",
"previewNotAvailable": "Önizleme mevcut değil",
"copyError": "Dosyalarınıza dosyayı kopyalama hatası",
"copySuccess": "Dosyalarınıza başarılı bir şekilde kopyalanan dosya"
"copySuccess": "Dosyalarınıza başarılı bir şekilde kopyalanan dosya",
"bulkActions": {
"selected": "{count, plural, =1 {1 dosya seçildi} other {# dosya seçildi}}",
"actions": "İşlemler",
"download": "Seçilenleri İndir",
"copyToMyFiles": "Seçilenleri Dosyalarıma Kopyala",
"delete": "Seçilenleri Sil"
},
"bulkCopyProgress": "Dosyalarınıza {count, plural, =1 {1 dosya} other {# dosya}} kopyalanıyor...",
"bulkCopySuccess": "{count, plural, =1 {1 dosya dosyalarınıza başarıyla kopyalandı} other {# dosya dosyalarınıza başarıyla kopyalandı}}",
"bulkDeleteConfirmButton": "{count, plural, =1 {Dosyayı} other {Dosyaları}} Sil",
"bulkDeleteConfirmMessage": "{count, plural, =1 {Bu dosyayı} other {Bu # dosyayı}} silmek istediğinizden emin misiniz? Bu işlem geri alınamaz.",
"bulkDeleteConfirmTitle": "Seçili Dosyaları Sil",
"bulkDeleteProgress": "{count, plural, =1 {1 dosya} other {# dosya}} siliniyor...",
"bulkDeleteSuccess": "{count, plural, =1 {1 dosya başarıyla silindi} other {# dosya başarıyla silindi}}",
"selectAll": "Tümünü seç",
"selectFile": "{fileName} dosyasını seç"
}
},
"form": {
@@ -1397,4 +1413,4 @@
}
}
}
}
}

View File

@@ -1189,7 +1189,23 @@
"editError": "更新文件时出错",
"previewNotAvailable": "预览不可用",
"copyError": "错误将文件复制到您的文件",
"copySuccess": "文件已成功复制到您的文件"
"copySuccess": "文件已成功复制到您的文件",
"bulkActions": {
"selected": "{count, plural, =1 {已选择1个文件} other {已选择#个文件}}",
"actions": "操作",
"download": "下载所选",
"copyToMyFiles": "复制所选到我的文件",
"delete": "删除所选"
},
"bulkCopyProgress": "正在将{count, plural, =1 {1个文件} other {#个文件}}复制到您的文件...",
"bulkCopySuccess": "{count, plural, =1 {1个文件已成功复制到您的文件} other {#个文件已成功复制到您的文件}}",
"bulkDeleteConfirmButton": "删除{count, plural, =1 {文件} other {文件}}",
"bulkDeleteConfirmMessage": "您确定要删除{count, plural, =1 {这个文件} other {这些#个文件}}吗?此操作无法撤消。",
"bulkDeleteConfirmTitle": "删除所选文件",
"bulkDeleteProgress": "正在删除{count, plural, =1 {1个文件} other {#个文件}}...",
"bulkDeleteSuccess": "{count, plural, =1 {1个文件已成功删除} other {#个文件已成功删除}}",
"selectAll": "全选",
"selectFile": "选择文件 {fileName}"
}
},
"form": {
@@ -1397,4 +1413,4 @@
}
}
}
}
}

View File

@@ -3,6 +3,7 @@
import { useEffect, useRef, useState } from "react";
import {
IconCheck,
IconChevronDown,
IconClipboardCopy,
IconDownload,
IconEdit,
@@ -19,7 +20,21 @@ import { toast } from "sonner";
import { Avatar, AvatarFallback } from "@/components/ui/avatar";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog";
import { Checkbox } from "@/components/ui/checkbox";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { Input } from "@/components/ui/input";
import { ScrollArea } from "@/components/ui/scroll-area";
import { Separator } from "@/components/ui/separator";
@@ -262,6 +277,7 @@ interface FileRowProps {
inputRef: React.RefObject<HTMLInputElement | null>;
hoveredFile: HoverState | null;
copyingFile: string | null;
isSelected: boolean;
onStartEdit: (fileId: string, field: string, currentValue: string) => void;
onSaveEdit: () => void;
onCancelEdit: () => void;
@@ -272,6 +288,7 @@ interface FileRowProps {
onDownload: (file: ReverseShareFile) => void;
onDelete: (file: ReverseShareFile) => void;
onCopy: (file: ReverseShareFile) => void;
onSelectFile: (fileId: string, checked: boolean) => void;
}
function FileRow({
@@ -281,6 +298,7 @@ function FileRow({
inputRef,
hoveredFile,
copyingFile,
isSelected,
onStartEdit,
onSaveEdit,
onCancelEdit,
@@ -291,12 +309,20 @@ function FileRow({
onDownload,
onDelete,
onCopy,
onSelectFile,
}: FileRowProps) {
const t = useTranslations();
const { icon: FileIcon, color } = getFileIcon(file.name);
return (
<TableRow key={file.id}>
<TableCell>
<Checkbox
checked={isSelected}
onCheckedChange={(checked: boolean) => onSelectFile(file.id, checked)}
aria-label={t("reverseShares.modals.receivedFiles.selectFile", { fileName: file.name })}
/>
</TableCell>
<TableCell>
<div className="flex items-center gap-3">
<FileIcon className={`h-8 w-8 ${color} flex-shrink-0`} />
@@ -425,9 +451,19 @@ export function ReceivedFilesModal({
const [previewFile, setPreviewFile] = useState<ReverseShareFile | null>(null);
const [hoveredFile, setHoveredFile] = useState<HoverState | null>(null);
const [copyingFile, setCopyingFile] = useState<string | null>(null);
const [selectedFiles, setSelectedFiles] = useState<Set<string>>(new Set());
const [bulkCopying, setBulkCopying] = useState(false);
const [bulkDeleting, setBulkDeleting] = useState(false);
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
const [filesToDeleteBulk, setFilesToDeleteBulk] = useState<ReverseShareFile[]>([]);
const { editingFile, editValue, setEditValue, inputRef, startEdit, cancelEdit } = useFileEdit();
// Clear selections when files change
useEffect(() => {
setSelectedFiles(new Set());
}, [reverseShare?.files]);
const getTotalSize = () => {
if (!reverseShare?.files) return "0 B";
const totalBytes = reverseShare.files.reduce((acc, file) => acc + parseInt(file.size), 0);
@@ -548,6 +584,176 @@ export function ReceivedFilesModal({
const files = reverseShare.files || [];
const handleSelectAll = (checked: boolean) => {
if (checked) {
setSelectedFiles(new Set(files.map((file) => file.id)));
} else {
setSelectedFiles(new Set());
}
};
const handleSelectFile = (fileId: string, checked: boolean) => {
const newSelected = new Set(selectedFiles);
if (checked) {
newSelected.add(fileId);
} else {
newSelected.delete(fileId);
}
setSelectedFiles(newSelected);
};
const getSelectedFileObjects = () => {
return files.filter((file) => selectedFiles.has(file.id));
};
const isAllSelected = files.length > 0 && selectedFiles.size === files.length;
const handleBulkDownload = async () => {
const selectedFileObjects = getSelectedFileObjects();
if (selectedFileObjects.length === 0) return;
try {
toast.promise(
(async () => {
const JSZip = (await import("jszip")).default;
const zip = new JSZip();
const downloadPromises = selectedFileObjects.map(async (file) => {
try {
const response = await downloadReverseShareFile(file.id);
const downloadUrl = response.data.url;
const fileResponse = await fetch(downloadUrl);
if (!fileResponse.ok) {
throw new Error(`Failed to download ${file.name}`);
}
const blob = await fileResponse.blob();
zip.file(file.name, blob);
} catch (error) {
console.error(`Error downloading file ${file.name}:`, error);
throw error;
}
});
await Promise.all(downloadPromises);
const zipBlob = await zip.generateAsync({ type: "blob" });
const zipName = `${reverseShare.name || "received_files"}_files.zip`;
const url = URL.createObjectURL(zipBlob);
const a = document.createElement("a");
a.href = url;
a.download = zipName;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
// Clear selections after successful download
setSelectedFiles(new Set());
})(),
{
loading: t("shareManager.creatingZip"),
success: t("shareManager.zipDownloadSuccess"),
error: t("shareManager.zipDownloadError"),
}
);
} catch (error) {
console.error("Error creating ZIP:", error);
}
};
const handleBulkCopyToMyFiles = async () => {
const selectedFileObjects = getSelectedFileObjects();
if (selectedFileObjects.length === 0) return;
toast.promise(
(async () => {
setBulkCopying(true);
try {
const copyPromises = selectedFileObjects.map(async (file) => {
try {
await copyReverseShareFileToUserFiles(file.id);
} catch (error: any) {
console.error(`Error copying file ${file.name}:`, error);
throw new Error(`Failed to copy ${file.name}: ${error.response?.data?.error || error.message}`);
}
});
await Promise.all(copyPromises);
// Clear selections after successful copy
setSelectedFiles(new Set());
} finally {
setBulkCopying(false);
}
})(),
{
loading: t("reverseShares.modals.receivedFiles.bulkCopyProgress", { count: selectedFileObjects.length }),
success: t("reverseShares.modals.receivedFiles.bulkCopySuccess", { count: selectedFileObjects.length }),
error: (error: any) => {
if (error.message.includes("File size exceeds") || error.message.includes("Insufficient storage")) {
return error.message;
} else {
return t("reverseShares.modals.receivedFiles.copyError");
}
},
}
);
};
const handleBulkDelete = () => {
const selectedFileObjects = getSelectedFileObjects();
if (selectedFileObjects.length === 0) return;
setFilesToDeleteBulk(selectedFileObjects);
setShowDeleteConfirm(true);
};
const confirmBulkDelete = async () => {
if (filesToDeleteBulk.length === 0) return;
setShowDeleteConfirm(false);
toast.promise(
(async () => {
setBulkDeleting(true);
try {
const deletePromises = filesToDeleteBulk.map(async (file) => {
try {
await deleteReverseShareFile(file.id);
} catch (error) {
console.error(`Error deleting file ${file.name}:`, error);
throw new Error(`Failed to delete ${file.name}`);
}
});
await Promise.all(deletePromises);
// Clear selections and refresh data
setSelectedFiles(new Set());
setFilesToDeleteBulk([]);
if (onRefresh) {
await onRefresh();
}
if (refreshReverseShare) {
await refreshReverseShare(reverseShare.id);
}
} finally {
setBulkDeleting(false);
}
})(),
{
loading: t("reverseShares.modals.receivedFiles.bulkDeleteProgress", { count: filesToDeleteBulk.length }),
success: t("reverseShares.modals.receivedFiles.bulkDeleteSuccess", { count: filesToDeleteBulk.length }),
error: "Error deleting selected files",
}
);
};
const showBulkActions = selectedFiles.size > 0;
return (
<>
<Dialog open={isOpen} onOpenChange={onClose}>
@@ -574,6 +780,59 @@ export function ReceivedFilesModal({
<Separator />
{showBulkActions && (
<div className="flex items-center justify-between p-4 bg-muted/30 border rounded-lg">
<div className="flex items-center gap-3">
<span className="text-sm font-medium text-foreground">
{t("reverseShares.modals.receivedFiles.bulkActions.selected", { count: selectedFiles.size })}
</span>
</div>
<div className="flex items-center gap-2">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="default" size="sm" className="gap-2">
{t("reverseShares.modals.receivedFiles.bulkActions.actions")}
<IconChevronDown className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-[200px]">
<DropdownMenuItem className="cursor-pointer py-2" onClick={handleBulkDownload}>
<IconDownload className="h-4 w-4" />
{t("reverseShares.modals.receivedFiles.bulkActions.download")}
</DropdownMenuItem>
<DropdownMenuItem
className="cursor-pointer py-2"
onClick={handleBulkCopyToMyFiles}
disabled={bulkCopying}
>
{bulkCopying ? (
<div className="animate-spin rounded-full h-4 w-4 border-2 border-blue-600 border-t-transparent"></div>
) : (
<IconClipboardCopy className="h-4 w-4" />
)}
{t("reverseShares.modals.receivedFiles.bulkActions.copyToMyFiles")}
</DropdownMenuItem>
<DropdownMenuItem
className="cursor-pointer py-2 text-destructive focus:text-destructive"
onClick={handleBulkDelete}
disabled={bulkDeleting}
>
{bulkDeleting ? (
<div className="animate-spin rounded-full h-4 w-4 border-2 border-red-600 border-t-transparent"></div>
) : (
<IconTrash className="h-4 w-4" />
)}
{t("reverseShares.modals.receivedFiles.bulkActions.delete")}
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
<Button variant="outline" size="sm" onClick={() => setSelectedFiles(new Set())}>
{t("common.cancel")}
</Button>
</div>
</div>
)}
{files.length === 0 ? (
<div className="flex flex-col items-center justify-center flex-1 gap-4 py-12">
<div className="w-16 h-16 bg-muted rounded-full flex items-center justify-center">
@@ -591,6 +850,13 @@ export function ReceivedFilesModal({
<Table>
<TableHeader>
<TableRow>
<TableHead className="w-[50px]">
<Checkbox
checked={isAllSelected}
onCheckedChange={handleSelectAll}
aria-label={t("reverseShares.modals.receivedFiles.selectAll")}
/>
</TableHead>
<TableHead>{t("reverseShares.modals.receivedFiles.columns.file")}</TableHead>
<TableHead>{t("reverseShares.modals.receivedFiles.columns.size")}</TableHead>
<TableHead>{t("reverseShares.modals.receivedFiles.columns.sender")}</TableHead>
@@ -610,6 +876,7 @@ export function ReceivedFilesModal({
inputRef={inputRef}
hoveredFile={hoveredFile}
copyingFile={copyingFile}
isSelected={selectedFiles.has(file.id)}
onStartEdit={startEdit}
onSaveEdit={saveEdit}
onCancelEdit={cancelEdit}
@@ -620,6 +887,7 @@ export function ReceivedFilesModal({
onDownload={handleDownload}
onDelete={handleDeleteFile}
onCopy={handleCopyFile}
onSelectFile={handleSelectFile}
/>
))}
</TableBody>
@@ -630,6 +898,46 @@ export function ReceivedFilesModal({
</DialogContent>
</Dialog>
{/* Delete Confirmation Modal */}
<Dialog open={showDeleteConfirm} onOpenChange={setShowDeleteConfirm}>
<DialogContent className="sm:max-w-md">
<DialogHeader>
<DialogTitle>{t("reverseShares.modals.receivedFiles.bulkDeleteConfirmTitle")}</DialogTitle>
<DialogDescription>
{t("reverseShares.modals.receivedFiles.bulkDeleteConfirmMessage", { count: filesToDeleteBulk.length })}
</DialogDescription>
</DialogHeader>
<div className="max-h-48 overflow-y-auto border rounded-lg p-2">
<div className="space-y-1">
{filesToDeleteBulk.map((file) => {
const { icon: FileIcon, color } = getFileIcon(file.name);
return (
<div key={file.id} className="flex items-center gap-2 p-2 bg-muted/20 rounded text-sm">
<FileIcon className={`h-4 w-4 ${color} flex-shrink-0`} />
<span className="truncate" title={file.name}>
{file.name}
</span>
</div>
);
})}
</div>
</div>
<DialogFooter className="flex gap-2">
<Button variant="outline" onClick={() => setShowDeleteConfirm(false)}>
{t("common.cancel")}
</Button>
<Button variant="destructive" onClick={confirmBulkDelete} disabled={bulkDeleting}>
{bulkDeleting ? (
<div className="animate-spin rounded-full h-4 w-4 border-2 border-white border-t-transparent mr-2" />
) : null}
{t("reverseShares.modals.receivedFiles.bulkDeleteConfirmButton", { count: filesToDeleteBulk.length })}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
{previewFile && (
<ReverseShareFilePreviewModal
isOpen={!!previewFile}