From b47c9b176e7aecfefac2db824f5b408694e75857 Mon Sep 17 00:00:00 2001 From: NicoTuxx Date: Wed, 3 Dec 2025 13:58:21 +0100 Subject: [PATCH] feat(editor): add timeout modal for file loading issues and enhance error handling --- client/src/locales/en-US.json | 19 +- client/src/locales/fr-FR.json | 19 +- client/src/services/cryptpad.ts | 37 +-- .../files/handler/editor/EditorIssueModal.vue | 174 +++++++++++++++ .../views/files/handler/editor/FileEditor.vue | 211 +++++++++++------- .../src/views/files/handler/editor/types.ts | 28 +++ 6 files changed, 360 insertions(+), 128 deletions(-) create mode 100644 client/src/views/files/handler/editor/EditorIssueModal.vue diff --git a/client/src/locales/en-US.json b/client/src/locales/en-US.json index a61636af80c..426d22699fd 100644 --- a/client/src/locales/en-US.json +++ b/client/src/locales/en-US.json @@ -2514,13 +2514,10 @@ "impossibleToOpen": "Cannot edit this file", "genericError": "Failed to open the file", "unsupportedFileType": "File not supported by Parsec", - "editionNotAvailable": "Document editing is unavailable", - "tooLongToOpen": "The file is taking too long to open" + "editionNotAvailable": "Document editing is unavailable" }, "informationEditDownload": "If you want to edit this file, you can download it and open it locally on your device.", "informationPreviewDownload": "If you want to preview this file, you can download it and open it locally on your device.", - "tooLongToOpenOnWeb": "You can try downloading it and opening with default app (right click on file > 'Download').", - "tooLongToOpenOnDesktop": "You can try opening it with default app (right click on file > 'Open with default app').", "unknownFileExtension": "This file type extension could not be identified. Please check that it's correctly written (.txt, .pdf, .doc, etc.).", "noContentFileType": "Could not open this file in the Parsec editor as its type could not be identified.", "noFolderPreview": "Cannot preview folders.", @@ -2579,9 +2576,12 @@ "globalTitle": "Cannot open file", "errors": { "titles": { - "networkOffline": "Network connection lost" + "networkOffline": "Network connection lost", + "tooLongToOpen": "The file seems to be taking too long to open" }, "networkOfflineMessage": "Your network connection has been lost. Please check your internet connection and try again. Any unsaved changes may be lost.", + "tooLongToOpenOnWeb": "You can try downloading it and opening with default app (right click on file > 'Download').", + "tooLongToOpenOnDesktop": "You can try opening it with default app (right click on file > 'Open with default app').", "contentInfoMissing": "The file content information is empty or missing.", "fileExtensionMissing": "The file type (extension) is missing or empty.", "fileNameMissing": "The file name is empty or missing.", @@ -2591,12 +2591,15 @@ "advices": { "title": "Some tips to help resolve this issue", "advice1": "Check that the file can be opened from an external application.", - "advice2": "Re-import the file (check versions).", - "advice3": "If the file used to open properly, try restoring a previous version (through file history)" + "advice2": "If the file used to open properly, try restoring a previous version (through file history)" }, "actions": { "backToFiles": "Back to files", - "retry": "Reload page" + "retry": "Reload page", + "openWithDefaultApp": "Open with default app", + "downloadFile": "Download file", + "wait": "Wait", + "close": "Close" }, "saving": { "unsaved": "Changes unsaved", diff --git a/client/src/locales/fr-FR.json b/client/src/locales/fr-FR.json index 93aac3a0aed..642903c15b2 100644 --- a/client/src/locales/fr-FR.json +++ b/client/src/locales/fr-FR.json @@ -2515,13 +2515,10 @@ "impossibleToOpen": "Impossible d'éditer ce fichier", "genericError": "Impossible d'ouvrir ce fichier", "unsupportedFileType": "Type de fichier non pris en charge par Parsec", - "editionNotAvailable": "L'édition de documents n'est pas disponible", - "tooLongToOpen": "Le fichier met trop de temps à s'ouvrir" + "editionNotAvailable": "L'édition de documents n'est pas disponible" }, "informationEditDownload": "Si vous souhaitez modifier ce fichier, vous pouvez le télécharger et l'ouvrir localement sur votre appareil.", "informationPreviewDownload": "Si vous souhaitez prévisualiser ce fichier, vous pouvez le faire dans Parsec ou le télécharger pour l'ouvrir localement sur votre appareil.", - "tooLongToOpenOnWeb": "Vous pouvez essayer de le télécharger et de l'ouvrir avec l'application par défaut (clic droit sur le fichier > 'Télécharger').", - "tooLongToOpenOnDesktop": "Vous pouvez essayer de l'ouvrir avec l'application par défaut (clic droit sur le fichier > 'Ouvrir avec l'app par défaut').", "unknownFileExtension": "L'extension de ce fichier n'a pas pu être reconnue. Veuillez vérifier qu'elle est correctement écrite (.txt, .pdf, .doc, etc.).", "noContentFileType": "Impossible d'ouvrir ce fichier, son type n'ayant pas pu être reconnu dans l'éditeur de Parsec.", "noFolderPreview": "Impossible d'afficher l'aperçu d'un dossier.", @@ -2580,9 +2577,12 @@ "globalTitle": "Impossible d'ouvrir le fichier", "errors": { "titles": { - "networkOffline": "Connexion réseau perdue" + "networkOffline": "Connexion réseau perdue", + "tooLongToOpen": "Le fichier semble mettre trop de temps à s'ouvrir" }, "networkOfflineMessage": "Votre connexion réseau a été perdue. Veuillez vérifier votre connexion internet et réessayer. Les modifications non enregistrées peuvent être perdues.", + "tooLongToOpenOnWeb": "Vous pouvez essayer de le télécharger et de l'ouvrir avec l'application par défaut (clic droit sur le fichier > 'Télécharger').", + "tooLongToOpenOnDesktop": "Vous pouvez essayer de l'ouvrir avec l'application par défaut (clic droit sur le fichier > 'Ouvrir avec l'app par défaut').", "contentInfoMissing": "Les informations sur le contenu du fichier sont vides ou manquantes.", "fileExtensionMissing": "Le type de fichier (extension) est manquant ou vide.", "fileNameMissing": "Le nom du fichier est vide ou manquant.", @@ -2592,12 +2592,15 @@ "advices": { "title": "Quelques conseils pour résoudre ce problème", "advice1": "Vérifier que le fichier s'ouvre correctement depuis un logiciel externe.", - "advice2": "Ré-importer le fichier (vérifier les versions).", - "advice3": "Si le fichier s'ouvrait avant, essayer de restaurer une version antérieure (via l'historique du fichier)" + "advice2": "Si le fichier s'ouvrait avant, essayer de restaurer une version antérieure (via l'historique du fichier)" }, "actions": { "backToFiles": "Retour aux fichiers", - "retry": "Recharger la page" + "retry": "Recharger la page", + "openWithDefaultApp": "Ouvrir avec l'app par défaut", + "downloadFile": "Télécharger le fichier", + "wait": "Attendre", + "close": "Fermer" }, "saving": { "unsaved": "Modifications non sauvegardées", diff --git a/client/src/services/cryptpad.ts b/client/src/services/cryptpad.ts index 3d673143c08..36d09a467eb 100644 --- a/client/src/services/cryptpad.ts +++ b/client/src/services/cryptpad.ts @@ -173,44 +173,11 @@ export class Cryptpad { throw new CryptpadOpenError(CryptpadErrorCode.DocumentTypeNotEnabled, config.documentType); } - // Set up a loading timeout (30 seconds) to detect stuck/corrupted files - const LOADING_TIMEOUT_MS = 30000; - let loadingTimeoutId: any = undefined; - - // Create a promise that will be resolved when CryptPad successfully loads - let resolveLoading!: () => void; - const loadingComplete = new Promise((resolve) => { - resolveLoading = resolve; - }); - - const timeoutPromise = new Promise((_, reject) => { - loadingTimeoutId = window.setTimeout(() => { - const message = 'CryptPad loading timeout - the file may be corrupted, too large, or the server is not responding.'; - window.electronAPI.log('warn', message); - reject(new CryptpadOpenError(CryptpadErrorCode.LoadingTimeout, config.documentType, message)); - }, LOADING_TIMEOUT_MS); - }); - // Wrap the original onReady callback to signal successful loading - const originalOnReady = config.events.onReady; - config.events.onReady = () => { - resolveLoading(); - if (originalOnReady) originalOnReady(); - }; - // Store config globally so CryptPad customization can access the onError callback (window as any).cryptpadConfig = config; - try { - // Start CryptPad API (doesn't wait for loading to complete) - void (window as any).CryptPadAPI(this.containerElement.id, { ...config }); - - // Race between successful loading (onReady callback) and timeout - await Promise.race([loadingComplete, timeoutPromise]); - } finally { - window.clearTimeout(loadingTimeoutId); - // Clean up global config reference - delete (window as any).cryptpadConfig; - } + // Start CryptPad API (doesn't wait for loading to complete) + void (window as any).CryptPadAPI(this.containerElement.id, { ...config }); } } diff --git a/client/src/views/files/handler/editor/EditorIssueModal.vue b/client/src/views/files/handler/editor/EditorIssueModal.vue new file mode 100644 index 00000000000..8a2a9db7e98 --- /dev/null +++ b/client/src/views/files/handler/editor/EditorIssueModal.vue @@ -0,0 +1,174 @@ + + + + + + + diff --git a/client/src/views/files/handler/editor/FileEditor.vue b/client/src/views/files/handler/editor/FileEditor.vue index b4d565c6115..b222388866a 100644 --- a/client/src/views/files/handler/editor/FileEditor.vue +++ b/client/src/views/files/handler/editor/FileEditor.vue @@ -21,7 +21,7 @@ class="error-content-buttons__item button-default" @click="routerGoBack()" > - {{ $msTranslate('fileEditors.actions.backToFiles') }} + {{ $msTranslate(EditorButtonAction.BackToFiles) }} @@ -43,13 +43,6 @@ /> {{ $msTranslate('fileEditors.advices.advice2') }} - - - {{ $msTranslate('fileEditors.advices.advice3') }} - @@ -57,7 +50,7 @@