From 61e750646c13f9ed8fa312e192d99b1c1e57bef7 Mon Sep 17 00:00:00 2001 From: Omar Ahmed Hassan <98468609+omar-ahmed42@users.noreply.github.com> Date: Thu, 28 Nov 2024 16:25:13 +0200 Subject: [PATCH] Feature: Undo Redo options multi tool #2297 (#2348) * Implement Command class for Command Pattern Created a base `Command` class to implement the **Command Pattern**. This class provides a skeletal implementation for `execute`, `undo`, and `redo` methods. **Note:** This class is intended to be subclassed and not instantiated directly. * Add undo/redo stacks and operations * Use rotate element command to perform execute/undo/redo operations * Handle commands executed through events - Add "command-execution" event listener to execute commands that are not invoked from the same class while adding the command to the undo stack and clearing the redo stack. * Add and use rotate all command to rotate/redo/undo all elements * Use command pattern to delete pages * Use command pattern for page selection * Use command pattern to move pages up and down * Use command pattern to remove selected pages * Use command pattern to perform the splitting operation * Add undo/redo functionality with filename input exclusion - Implement undo (Ctrl+Z) and redo (Ctrl+Y) functionality. - Prevent undo/redo actions when the filename input field is focused. - Ensures proper handling of undo/redo actions without interfering with text editing. * Introduce UndoManager for managing undo/redo operations - Encapsulate undo/redo stacks and operations within UndoManager. - Simplify handling of undo/redo functionality through a dedicated manager. * Call execute on splitAllCommand - Fix a bug that caused split all functionality to not work as execute() wasn't called on splitAllCommand * Add undo/redo buttons to multi tool - Add undo/redo buttons to multi tool - Dispatch an event upon state change (such as changes in the undo/redo stacks) to update the UI accordingly. * Add undo/redo to translations * Replace hard-coded "Undo"/"Redo" with translation keys in multi tool --------- Co-authored-by: Anthony Stirling <77850077+Frooodle@users.noreply.github.com> --- src/main/resources/messages_ar_AR.properties | 2 + src/main/resources/messages_az_AZ.properties | 2 + src/main/resources/messages_bg_BG.properties | 2 + src/main/resources/messages_ca_CA.properties | 2 + src/main/resources/messages_cs_CZ.properties | 2 + src/main/resources/messages_da_DK.properties | 2 + src/main/resources/messages_de_DE.properties | 2 + src/main/resources/messages_el_GR.properties | 2 + src/main/resources/messages_en_GB.properties | 2 + src/main/resources/messages_en_US.properties | 2 + src/main/resources/messages_es_ES.properties | 2 + src/main/resources/messages_eu_ES.properties | 2 + src/main/resources/messages_fr_FR.properties | 2 + src/main/resources/messages_ga_IE.properties | 2 + src/main/resources/messages_hi_IN.properties | 2 + src/main/resources/messages_hr_HR.properties | 2 + src/main/resources/messages_hu_HU.properties | 2 + src/main/resources/messages_id_ID.properties | 2 + src/main/resources/messages_it_IT.properties | 2 + src/main/resources/messages_ja_JP.properties | 2 + src/main/resources/messages_ko_KR.properties | 2 + src/main/resources/messages_nl_NL.properties | 2 + src/main/resources/messages_no_NB.properties | 2 + src/main/resources/messages_pl_PL.properties | 2 + src/main/resources/messages_pt_BR.properties | 2 + src/main/resources/messages_pt_PT.properties | 2 + src/main/resources/messages_ro_RO.properties | 2 + src/main/resources/messages_ru_RU.properties | 2 + src/main/resources/messages_sk_SK.properties | 2 + .../resources/messages_sr_LATN_RS.properties | 2 + src/main/resources/messages_sv_SE.properties | 2 + src/main/resources/messages_th_TH.properties | 2 + src/main/resources/messages_tr_TR.properties | 2 + src/main/resources/messages_uk_UA.properties | 2 + src/main/resources/messages_vi_VN.properties | 2 + src/main/resources/messages_zh_CN.properties | 2 + src/main/resources/messages_zh_TW.properties | 2 + .../static/js/multitool/PdfActionsManager.js | 76 ++++---- .../static/js/multitool/PdfContainer.js | 165 +++++++++--------- .../static/js/multitool/UndoManager.js | 65 +++++++ .../static/js/multitool/commands/command.js | 5 + .../js/multitool/commands/delete-page.js | 74 ++++++++ .../static/js/multitool/commands/move-page.js | 133 ++++++++++++++ .../static/js/multitool/commands/remove.js | 101 +++++++++++ .../static/js/multitool/commands/rotate.js | 74 ++++++++ .../static/js/multitool/commands/select.js | 59 +++++++ .../static/js/multitool/commands/split.js | 101 +++++++++++ src/main/resources/templates/multi-tool.html | 45 ++++- 48 files changed, 848 insertions(+), 124 deletions(-) create mode 100644 src/main/resources/static/js/multitool/UndoManager.js create mode 100644 src/main/resources/static/js/multitool/commands/command.js create mode 100644 src/main/resources/static/js/multitool/commands/delete-page.js create mode 100644 src/main/resources/static/js/multitool/commands/move-page.js create mode 100644 src/main/resources/static/js/multitool/commands/remove.js create mode 100644 src/main/resources/static/js/multitool/commands/rotate.js create mode 100644 src/main/resources/static/js/multitool/commands/select.js create mode 100644 src/main/resources/static/js/multitool/commands/split.js diff --git a/src/main/resources/messages_ar_AR.properties b/src/main/resources/messages_ar_AR.properties index aff70807e..1202616bf 100644 --- a/src/main/resources/messages_ar_AR.properties +++ b/src/main/resources/messages_ar_AR.properties @@ -958,6 +958,8 @@ multiTool.moveLeft=تحريك إلى اليسار multiTool.moveRight=تحريك إلى اليمين multiTool.delete=حذف multiTool.dragDropMessage=الصفحات المحددة +multiTool.undo=تراجع +multiTool.redo=إعادة إجراء #multiTool-advert multiTool-advert.message=هذه الميزة متوفرة في صفحة الأدوات المتعددة لدينا. اطلع عليها للحصول على واجهة مستخدم محسّنة لكل صفحة وميزات إضافية! diff --git a/src/main/resources/messages_az_AZ.properties b/src/main/resources/messages_az_AZ.properties index 572659baf..aabd13723 100644 --- a/src/main/resources/messages_az_AZ.properties +++ b/src/main/resources/messages_az_AZ.properties @@ -958,6 +958,8 @@ multiTool.moveLeft=Move Left multiTool.moveRight=Move Right multiTool.delete=Delete multiTool.dragDropMessage=Seçilmiş Səhifə(lər) +multiTool.undo=Undo +multiTool.redo=Redo #multiTool-advert multiTool-advert.message=Bu xüsusiyyət bizim multi-alət səhifəmizdə də mövcuddur. Əlavə xüsusiyyətlər və səhifə-səhifə interfeys üçün sınaqdan keçirin! diff --git a/src/main/resources/messages_bg_BG.properties b/src/main/resources/messages_bg_BG.properties index 9e2d727b6..f1e5fc3bd 100644 --- a/src/main/resources/messages_bg_BG.properties +++ b/src/main/resources/messages_bg_BG.properties @@ -958,6 +958,8 @@ multiTool.moveLeft=Move Left multiTool.moveRight=Move Right multiTool.delete=Delete multiTool.dragDropMessage=Page(s) Selected +multiTool.undo=Undo +multiTool.redo=Redo #multiTool-advert multiTool-advert.message=This feature is also available in our multi-tool page. Check it out for enhanced page-by-page UI and additional features! diff --git a/src/main/resources/messages_ca_CA.properties b/src/main/resources/messages_ca_CA.properties index 38ae93a9f..aa860cb78 100644 --- a/src/main/resources/messages_ca_CA.properties +++ b/src/main/resources/messages_ca_CA.properties @@ -958,6 +958,8 @@ multiTool.moveLeft=Move Left multiTool.moveRight=Move Right multiTool.delete=Delete multiTool.dragDropMessage=Page(s) Selected +multiTool.undo=Undo +multiTool.redo=Redo #multiTool-advert multiTool-advert.message=This feature is also available in our multi-tool page. Check it out for enhanced page-by-page UI and additional features! diff --git a/src/main/resources/messages_cs_CZ.properties b/src/main/resources/messages_cs_CZ.properties index 10c5ce88b..3c50d3875 100644 --- a/src/main/resources/messages_cs_CZ.properties +++ b/src/main/resources/messages_cs_CZ.properties @@ -958,6 +958,8 @@ multiTool.moveLeft=Move Left multiTool.moveRight=Move Right multiTool.delete=Delete multiTool.dragDropMessage=Page(s) Selected +multiTool.undo=Undo +multiTool.redo=Redo #multiTool-advert multiTool-advert.message=This feature is also available in our multi-tool page. Check it out for enhanced page-by-page UI and additional features! diff --git a/src/main/resources/messages_da_DK.properties b/src/main/resources/messages_da_DK.properties index 86e4b3ccb..ddaf3c607 100644 --- a/src/main/resources/messages_da_DK.properties +++ b/src/main/resources/messages_da_DK.properties @@ -958,6 +958,8 @@ multiTool.moveLeft=Move Left multiTool.moveRight=Move Right multiTool.delete=Delete multiTool.dragDropMessage=Page(s) Selected +multiTool.undo=Undo +multiTool.redo=Redo #multiTool-advert multiTool-advert.message=This feature is also available in our multi-tool page. Check it out for enhanced page-by-page UI and additional features! diff --git a/src/main/resources/messages_de_DE.properties b/src/main/resources/messages_de_DE.properties index 1c9609c55..087b7be7b 100644 --- a/src/main/resources/messages_de_DE.properties +++ b/src/main/resources/messages_de_DE.properties @@ -958,6 +958,8 @@ multiTool.moveLeft=Move Left multiTool.moveRight=Move Right multiTool.delete=Delete multiTool.dragDropMessage=Page(s) Selected +multiTool.undo=Undo +multiTool.redo=Redo #multiTool-advert multiTool-advert.message=Diese Funktion ist auch auf unserer PDF-Multitool-Seite verfügbar. Probieren Sie sie aus, denn sie bietet eine verbesserte Benutzeroberfläche und zusätzliche Funktionen! diff --git a/src/main/resources/messages_el_GR.properties b/src/main/resources/messages_el_GR.properties index b6be4be12..757203774 100644 --- a/src/main/resources/messages_el_GR.properties +++ b/src/main/resources/messages_el_GR.properties @@ -958,6 +958,8 @@ multiTool.moveLeft=Move Left multiTool.moveRight=Move Right multiTool.delete=Delete multiTool.dragDropMessage=Page(s) Selected +multiTool.undo=Undo +multiTool.redo=Redo #multiTool-advert multiTool-advert.message=This feature is also available in our multi-tool page. Check it out for enhanced page-by-page UI and additional features! diff --git a/src/main/resources/messages_en_GB.properties b/src/main/resources/messages_en_GB.properties index 8a471ade7..e2f74fbc9 100644 --- a/src/main/resources/messages_en_GB.properties +++ b/src/main/resources/messages_en_GB.properties @@ -958,6 +958,8 @@ multiTool.moveLeft=Move Left multiTool.moveRight=Move Right multiTool.delete=Delete multiTool.dragDropMessage=Page(s) Selected +multiTool.undo=Undo +multiTool.redo=Redo #multiTool-advert multiTool-advert.message=This feature is also available in our multi-tool page. Check it out for enhanced page-by-page UI and additional features! diff --git a/src/main/resources/messages_en_US.properties b/src/main/resources/messages_en_US.properties index cd43508ce..cde361d99 100644 --- a/src/main/resources/messages_en_US.properties +++ b/src/main/resources/messages_en_US.properties @@ -958,6 +958,8 @@ multiTool.moveLeft=Move Left multiTool.moveRight=Move Right multiTool.delete=Delete multiTool.dragDropMessage=Page(s) Selected +multiTool.undo=Undo +multiTool.redo=Redo #multiTool-advert multiTool-advert.message=This feature is also available in our multi-tool page. Check it out for enhanced page-by-page UI and additional features! diff --git a/src/main/resources/messages_es_ES.properties b/src/main/resources/messages_es_ES.properties index 6869b4f45..db712d420 100644 --- a/src/main/resources/messages_es_ES.properties +++ b/src/main/resources/messages_es_ES.properties @@ -958,6 +958,8 @@ multiTool.moveLeft=Move Left multiTool.moveRight=Move Right multiTool.delete=Delete multiTool.dragDropMessage=Page(s) Selected +multiTool.undo=Undo +multiTool.redo=Redo #multiTool-advert multiTool-advert.message=This feature is also available in our multi-tool page. Check it out for enhanced page-by-page UI and additional features! diff --git a/src/main/resources/messages_eu_ES.properties b/src/main/resources/messages_eu_ES.properties index 6022513c8..0daedb195 100644 --- a/src/main/resources/messages_eu_ES.properties +++ b/src/main/resources/messages_eu_ES.properties @@ -958,6 +958,8 @@ multiTool.moveLeft=Move Left multiTool.moveRight=Move Right multiTool.delete=Delete multiTool.dragDropMessage=Page(s) Selected +multiTool.undo=Undo +multiTool.redo=Redo #multiTool-advert multiTool-advert.message=This feature is also available in our multi-tool page. Check it out for enhanced page-by-page UI and additional features! diff --git a/src/main/resources/messages_fr_FR.properties b/src/main/resources/messages_fr_FR.properties index 9b902ae05..7ac4a8a9a 100644 --- a/src/main/resources/messages_fr_FR.properties +++ b/src/main/resources/messages_fr_FR.properties @@ -958,6 +958,8 @@ multiTool.moveLeft=Déplacer vers la gauche multiTool.moveRight=Déplacer vers la droite multiTool.delete=Supprimer multiTool.dragDropMessage=Page(s) sélectionnées +multiTool.undo=Undo +multiTool.redo=Redo #multiTool-advert multiTool-advert.message=Cette fonctionnalité est aussi disponible dans la page de l'outil multifonction. Allez-y pour une interface page par page améliorée et des fonctionnalités additionnelles ! diff --git a/src/main/resources/messages_ga_IE.properties b/src/main/resources/messages_ga_IE.properties index c1d46c038..8a95af6da 100644 --- a/src/main/resources/messages_ga_IE.properties +++ b/src/main/resources/messages_ga_IE.properties @@ -958,6 +958,8 @@ multiTool.moveLeft=Move Left multiTool.moveRight=Move Right multiTool.delete=Delete multiTool.dragDropMessage=Page(s) Selected +multiTool.undo=Undo +multiTool.redo=Redo #multiTool-advert multiTool-advert.message=This feature is also available in our multi-tool page. Check it out for enhanced page-by-page UI and additional features! diff --git a/src/main/resources/messages_hi_IN.properties b/src/main/resources/messages_hi_IN.properties index 490d8bf9c..1c61b7ec9 100644 --- a/src/main/resources/messages_hi_IN.properties +++ b/src/main/resources/messages_hi_IN.properties @@ -958,6 +958,8 @@ multiTool.moveLeft=Move Left multiTool.moveRight=Move Right multiTool.delete=Delete multiTool.dragDropMessage=Page(s) Selected +multiTool.undo=Undo +multiTool.redo=Redo #multiTool-advert multiTool-advert.message=This feature is also available in our multi-tool page. Check it out for enhanced page-by-page UI and additional features! diff --git a/src/main/resources/messages_hr_HR.properties b/src/main/resources/messages_hr_HR.properties index 1e76ba926..efc41e4f6 100644 --- a/src/main/resources/messages_hr_HR.properties +++ b/src/main/resources/messages_hr_HR.properties @@ -958,6 +958,8 @@ multiTool.moveLeft=Move Left multiTool.moveRight=Move Right multiTool.delete=Delete multiTool.dragDropMessage=Page(s) Selected +multiTool.undo=Undo +multiTool.redo=Redo #multiTool-advert multiTool-advert.message=This feature is also available in our multi-tool page. Check it out for enhanced page-by-page UI and additional features! diff --git a/src/main/resources/messages_hu_HU.properties b/src/main/resources/messages_hu_HU.properties index 4bef02014..841c71fdb 100644 --- a/src/main/resources/messages_hu_HU.properties +++ b/src/main/resources/messages_hu_HU.properties @@ -958,6 +958,8 @@ multiTool.moveLeft=Move Left multiTool.moveRight=Move Right multiTool.delete=Delete multiTool.dragDropMessage=Page(s) Selected +multiTool.undo=Undo +multiTool.redo=Redo #multiTool-advert multiTool-advert.message=This feature is also available in our multi-tool page. Check it out for enhanced page-by-page UI and additional features! diff --git a/src/main/resources/messages_id_ID.properties b/src/main/resources/messages_id_ID.properties index b04de8947..fdd3816b7 100644 --- a/src/main/resources/messages_id_ID.properties +++ b/src/main/resources/messages_id_ID.properties @@ -958,6 +958,8 @@ multiTool.moveLeft=Move Left multiTool.moveRight=Move Right multiTool.delete=Delete multiTool.dragDropMessage=Page(s) Selected +multiTool.undo=Undo +multiTool.redo=Redo #multiTool-advert multiTool-advert.message=This feature is also available in our multi-tool page. Check it out for enhanced page-by-page UI and additional features! diff --git a/src/main/resources/messages_it_IT.properties b/src/main/resources/messages_it_IT.properties index a34a050b1..1b2a55fc4 100644 --- a/src/main/resources/messages_it_IT.properties +++ b/src/main/resources/messages_it_IT.properties @@ -958,6 +958,8 @@ multiTool.moveLeft=Sposta a sinistra multiTool.moveRight=Sposta a destra multiTool.delete=Elimina multiTool.dragDropMessage=Pagina(e) selezionata(e) +multiTool.undo=Undo +multiTool.redo=Redo #multiTool-advert multiTool-advert.message=Questa funzione è disponibile anche nella nostra pagina multi-strumento. Scoprila per un'interfaccia utente pagina per pagina migliorata e funzionalità aggiuntive! diff --git a/src/main/resources/messages_ja_JP.properties b/src/main/resources/messages_ja_JP.properties index 5574521c0..e893029b7 100644 --- a/src/main/resources/messages_ja_JP.properties +++ b/src/main/resources/messages_ja_JP.properties @@ -958,6 +958,8 @@ multiTool.moveLeft=Move Left multiTool.moveRight=Move Right multiTool.delete=Delete multiTool.dragDropMessage=Page(s) Selected +multiTool.undo=Undo +multiTool.redo=Redo #multiTool-advert multiTool-advert.message=This feature is also available in our multi-tool page. Check it out for enhanced page-by-page UI and additional features! diff --git a/src/main/resources/messages_ko_KR.properties b/src/main/resources/messages_ko_KR.properties index 27db7dd6f..aa77af639 100644 --- a/src/main/resources/messages_ko_KR.properties +++ b/src/main/resources/messages_ko_KR.properties @@ -958,6 +958,8 @@ multiTool.moveLeft=Move Left multiTool.moveRight=Move Right multiTool.delete=Delete multiTool.dragDropMessage=Page(s) Selected +multiTool.undo=Undo +multiTool.redo=Redo #multiTool-advert multiTool-advert.message=This feature is also available in our multi-tool page. Check it out for enhanced page-by-page UI and additional features! diff --git a/src/main/resources/messages_nl_NL.properties b/src/main/resources/messages_nl_NL.properties index b6f0ef0e5..23ac465ab 100644 --- a/src/main/resources/messages_nl_NL.properties +++ b/src/main/resources/messages_nl_NL.properties @@ -958,6 +958,8 @@ multiTool.moveLeft=Move Left multiTool.moveRight=Move Right multiTool.delete=Delete multiTool.dragDropMessage=Page(s) Selected +multiTool.undo=Undo +multiTool.redo=Redo #multiTool-advert multiTool-advert.message=This feature is also available in our multi-tool page. Check it out for enhanced page-by-page UI and additional features! diff --git a/src/main/resources/messages_no_NB.properties b/src/main/resources/messages_no_NB.properties index 0a4461cc6..8d1220e6d 100644 --- a/src/main/resources/messages_no_NB.properties +++ b/src/main/resources/messages_no_NB.properties @@ -958,6 +958,8 @@ multiTool.moveLeft=Move Left multiTool.moveRight=Move Right multiTool.delete=Delete multiTool.dragDropMessage=Page(s) Selected +multiTool.undo=Undo +multiTool.redo=Redo #multiTool-advert multiTool-advert.message=This feature is also available in our multi-tool page. Check it out for enhanced page-by-page UI and additional features! diff --git a/src/main/resources/messages_pl_PL.properties b/src/main/resources/messages_pl_PL.properties index ffc2e33a9..02469e1f3 100755 --- a/src/main/resources/messages_pl_PL.properties +++ b/src/main/resources/messages_pl_PL.properties @@ -958,6 +958,8 @@ multiTool.moveLeft=Move Left multiTool.moveRight=Move Right multiTool.delete=Delete multiTool.dragDropMessage=Page(s) Selected +multiTool.undo=Undo +multiTool.redo=Redo #multiTool-advert multiTool-advert.message=This feature is also available in our multi-tool page. Check it out for enhanced page-by-page UI and additional features! diff --git a/src/main/resources/messages_pt_BR.properties b/src/main/resources/messages_pt_BR.properties index aba81922f..b7af46b15 100644 --- a/src/main/resources/messages_pt_BR.properties +++ b/src/main/resources/messages_pt_BR.properties @@ -958,6 +958,8 @@ multiTool.moveLeft=Move Left multiTool.moveRight=Move Right multiTool.delete=Delete multiTool.dragDropMessage=Page(s) Selected +multiTool.undo=Undo +multiTool.redo=Redo #multiTool-advert multiTool-advert.message=This feature is also available in our multi-tool page. Check it out for enhanced page-by-page UI and additional features! diff --git a/src/main/resources/messages_pt_PT.properties b/src/main/resources/messages_pt_PT.properties index 0f3830c23..f5f2137b9 100644 --- a/src/main/resources/messages_pt_PT.properties +++ b/src/main/resources/messages_pt_PT.properties @@ -958,6 +958,8 @@ multiTool.moveLeft=Move Left multiTool.moveRight=Move Right multiTool.delete=Delete multiTool.dragDropMessage=Page(s) Selected +multiTool.undo=Undo +multiTool.redo=Redo #multiTool-advert multiTool-advert.message=This feature is also available in our multi-tool page. Check it out for enhanced page-by-page UI and additional features! diff --git a/src/main/resources/messages_ro_RO.properties b/src/main/resources/messages_ro_RO.properties index d289ff961..6be97127a 100644 --- a/src/main/resources/messages_ro_RO.properties +++ b/src/main/resources/messages_ro_RO.properties @@ -958,6 +958,8 @@ multiTool.moveLeft=Move Left multiTool.moveRight=Move Right multiTool.delete=Delete multiTool.dragDropMessage=Page(s) Selected +multiTool.undo=Undo +multiTool.redo=Redo #multiTool-advert multiTool-advert.message=This feature is also available in our multi-tool page. Check it out for enhanced page-by-page UI and additional features! diff --git a/src/main/resources/messages_ru_RU.properties b/src/main/resources/messages_ru_RU.properties index 35aefdd03..1908496b2 100644 --- a/src/main/resources/messages_ru_RU.properties +++ b/src/main/resources/messages_ru_RU.properties @@ -958,6 +958,8 @@ multiTool.moveLeft=Move Left multiTool.moveRight=Move Right multiTool.delete=Delete multiTool.dragDropMessage=Page(s) Selected +multiTool.undo=Undo +multiTool.redo=Redo #multiTool-advert multiTool-advert.message=This feature is also available in our multi-tool page. Check it out for enhanced page-by-page UI and additional features! diff --git a/src/main/resources/messages_sk_SK.properties b/src/main/resources/messages_sk_SK.properties index 0c78dad13..57bb598ac 100644 --- a/src/main/resources/messages_sk_SK.properties +++ b/src/main/resources/messages_sk_SK.properties @@ -958,6 +958,8 @@ multiTool.moveLeft=Move Left multiTool.moveRight=Move Right multiTool.delete=Delete multiTool.dragDropMessage=Page(s) Selected +multiTool.undo=Undo +multiTool.redo=Redo #multiTool-advert multiTool-advert.message=This feature is also available in our multi-tool page. Check it out for enhanced page-by-page UI and additional features! diff --git a/src/main/resources/messages_sr_LATN_RS.properties b/src/main/resources/messages_sr_LATN_RS.properties index 9ac101ad8..34b737d1a 100644 --- a/src/main/resources/messages_sr_LATN_RS.properties +++ b/src/main/resources/messages_sr_LATN_RS.properties @@ -958,6 +958,8 @@ multiTool.moveLeft=Move Left multiTool.moveRight=Move Right multiTool.delete=Delete multiTool.dragDropMessage=Page(s) Selected +multiTool.undo=Undo +multiTool.redo=Redo #multiTool-advert multiTool-advert.message=This feature is also available in our multi-tool page. Check it out for enhanced page-by-page UI and additional features! diff --git a/src/main/resources/messages_sv_SE.properties b/src/main/resources/messages_sv_SE.properties index c2a7de326..98ccaaa10 100644 --- a/src/main/resources/messages_sv_SE.properties +++ b/src/main/resources/messages_sv_SE.properties @@ -958,6 +958,8 @@ multiTool.moveLeft=Move Left multiTool.moveRight=Move Right multiTool.delete=Delete multiTool.dragDropMessage=Page(s) Selected +multiTool.undo=Undo +multiTool.redo=Redo #multiTool-advert multiTool-advert.message=This feature is also available in our multi-tool page. Check it out for enhanced page-by-page UI and additional features! diff --git a/src/main/resources/messages_th_TH.properties b/src/main/resources/messages_th_TH.properties index 1b58842bc..08e0281bf 100644 --- a/src/main/resources/messages_th_TH.properties +++ b/src/main/resources/messages_th_TH.properties @@ -958,6 +958,8 @@ multiTool.moveLeft=Move Left multiTool.moveRight=Move Right multiTool.delete=Delete multiTool.dragDropMessage=Page(s) Selected +multiTool.undo=Undo +multiTool.redo=Redo #multiTool-advert multiTool-advert.message=This feature is also available in our multi-tool page. Check it out for enhanced page-by-page UI and additional features! diff --git a/src/main/resources/messages_tr_TR.properties b/src/main/resources/messages_tr_TR.properties index 79130da2c..e71165368 100644 --- a/src/main/resources/messages_tr_TR.properties +++ b/src/main/resources/messages_tr_TR.properties @@ -958,6 +958,8 @@ multiTool.moveLeft=Move Left multiTool.moveRight=Move Right multiTool.delete=Delete multiTool.dragDropMessage=Page(s) Selected +multiTool.undo=Undo +multiTool.redo=Redo #multiTool-advert multiTool-advert.message=This feature is also available in our multi-tool page. Check it out for enhanced page-by-page UI and additional features! diff --git a/src/main/resources/messages_uk_UA.properties b/src/main/resources/messages_uk_UA.properties index 4b9cbd26c..1c2b60cf7 100644 --- a/src/main/resources/messages_uk_UA.properties +++ b/src/main/resources/messages_uk_UA.properties @@ -958,6 +958,8 @@ multiTool.moveLeft=Move Left multiTool.moveRight=Move Right multiTool.delete=Delete multiTool.dragDropMessage=Page(s) Selected +multiTool.undo=Undo +multiTool.redo=Redo #multiTool-advert multiTool-advert.message=This feature is also available in our multi-tool page. Check it out for enhanced page-by-page UI and additional features! diff --git a/src/main/resources/messages_vi_VN.properties b/src/main/resources/messages_vi_VN.properties index 14e78b028..bfb7d9c7b 100644 --- a/src/main/resources/messages_vi_VN.properties +++ b/src/main/resources/messages_vi_VN.properties @@ -958,6 +958,8 @@ multiTool.moveLeft=Move Left multiTool.moveRight=Move Right multiTool.delete=Delete multiTool.dragDropMessage=Page(s) Selected +multiTool.undo=Undo +multiTool.redo=Redo #multiTool-advert multiTool-advert.message=This feature is also available in our multi-tool page. Check it out for enhanced page-by-page UI and additional features! diff --git a/src/main/resources/messages_zh_CN.properties b/src/main/resources/messages_zh_CN.properties index 12f2b69b1..dfec712bf 100644 --- a/src/main/resources/messages_zh_CN.properties +++ b/src/main/resources/messages_zh_CN.properties @@ -958,6 +958,8 @@ multiTool.moveLeft=Move Left multiTool.moveRight=Move Right multiTool.delete=Delete multiTool.dragDropMessage=Page(s) Selected +multiTool.undo=Undo +multiTool.redo=Redo #multiTool-advert multiTool-advert.message=This feature is also available in our multi-tool page. Check it out for enhanced page-by-page UI and additional features! diff --git a/src/main/resources/messages_zh_TW.properties b/src/main/resources/messages_zh_TW.properties index fd3529a77..c1b3929ad 100644 --- a/src/main/resources/messages_zh_TW.properties +++ b/src/main/resources/messages_zh_TW.properties @@ -958,6 +958,8 @@ multiTool.moveLeft=Move Left multiTool.moveRight=Move Right multiTool.delete=Delete multiTool.dragDropMessage=Page(s) Selected +multiTool.undo=Undo +multiTool.redo=Redo #multiTool-advert multiTool-advert.message=This feature is also available in our multi-tool page. Check it out for enhanced page-by-page UI and additional features! diff --git a/src/main/resources/static/js/multitool/PdfActionsManager.js b/src/main/resources/static/js/multitool/PdfActionsManager.js index b83beb5eb..17386fd79 100644 --- a/src/main/resources/static/js/multitool/PdfActionsManager.js +++ b/src/main/resources/static/js/multitool/PdfActionsManager.js @@ -1,12 +1,20 @@ +import { DeletePageCommand } from "./commands/delete-page.js"; +import { SelectPageCommand } from "./commands/select.js"; +import { SplitFileCommand } from "./commands/split.js"; +import { UndoManager } from "./UndoManager.js"; + class PdfActionsManager { pageDirection; pagesContainer; static selectedPages = []; // Static property shared across all instances + undoManager; - constructor(id) { + constructor(id, undoManager) { this.pagesContainer = document.getElementById(id); this.pageDirection = document.documentElement.getAttribute("dir"); + this.undoManager = undoManager || new UndoManager(); + var styleElement = document.createElement("link"); styleElement.rel = "stylesheet"; styleElement.href = "css/pdfActions.css"; @@ -27,7 +35,8 @@ class PdfActionsManager { const sibling = imgContainer.previousSibling; if (sibling) { - this.movePageTo(imgContainer, sibling, true); + let movePageCommand = this.movePageTo(imgContainer, sibling, true, true); + this._pushUndoClearRedo(movePageCommand); } } @@ -35,7 +44,12 @@ class PdfActionsManager { var imgContainer = this.getPageContainer(e.target); const sibling = imgContainer.nextSibling; if (sibling) { - this.movePageTo(imgContainer, sibling.nextSibling, true); + let movePageCommand = this.movePageTo( + imgContainer, + sibling.nextSibling, + true + ); + this._pushUndoClearRedo(movePageCommand); } } @@ -43,30 +57,27 @@ class PdfActionsManager { var imgContainer = this.getPageContainer(e.target); const img = imgContainer.querySelector("img"); - this.rotateElement(img, -90); + let rotateCommand = this.rotateElement(img, -90); + this._pushUndoClearRedo(rotateCommand); } rotateCWButtonCallback(e) { var imgContainer = this.getPageContainer(e.target); const img = imgContainer.querySelector("img"); - this.rotateElement(img, 90); + let rotateCommand = this.rotateElement(img, 90); + this._pushUndoClearRedo(rotateCommand); } deletePageButtonCallback(e) { - var imgContainer = this.getPageContainer(e.target); - this.pagesContainer.removeChild(imgContainer); - if (this.pagesContainer.childElementCount === 0) { - const filenameInput = document.getElementById("filename-input"); - const filenameParagraph = document.getElementById("filename"); - const downloadBtn = document.getElementById("export-button"); + let imgContainer = this.getPageContainer(e.target); + let deletePageCommand = new DeletePageCommand( + imgContainer, + this.pagesContainer + ); + deletePageCommand.execute(); - filenameInput.disabled = true; - filenameInput.value = ""; - filenameParagraph.innerText = ""; - - downloadBtn.disabled = true; - } + this._pushUndoClearRedo(deletePageCommand); } insertFileButtonCallback(e) { @@ -81,7 +92,15 @@ class PdfActionsManager { splitFileButtonCallback(e) { var imgContainer = this.getPageContainer(e.target); - imgContainer.classList.toggle("split-before"); + + let splitFileCommand = new SplitFileCommand(imgContainer, "split-before"); + splitFileCommand.execute(); + + this._pushUndoClearRedo(splitFileCommand); + } + + _pushUndoClearRedo(command) { + this.undoManager.pushUndoClearRedo(command); } setActions({ movePageTo, addFiles, rotateElement }) { @@ -159,25 +178,10 @@ class PdfActionsManager { selectCheckbox.onchange = () => { const pageNumber = Array.from(div.parentNode.children).indexOf(div) + 1; - if (selectCheckbox.checked) { - //adds to array of selected pages - window.selectedPages.push(pageNumber); - } else { - //remove page from selected pages array - const index = window.selectedPages.indexOf(pageNumber); - if (index !== -1) { - window.selectedPages.splice(index, 1); - } - } + let selectPageCommand = new SelectPageCommand(pageNumber, selectCheckbox); + selectPageCommand.execute(); - if (window.selectedPages.length > 0 && !window.selectPage) { - window.toggleSelectPageVisibility(); - } - if (window.selectedPages.length == 0 && window.selectPage) { - window.toggleSelectPageVisibility(); - } - - window.updateSelectedPagesDisplay(); + this._pushUndoClearRedo(selectPageCommand); }; const insertFileButtonContainer = document.createElement("div"); diff --git a/src/main/resources/static/js/multitool/PdfContainer.js b/src/main/resources/static/js/multitool/PdfContainer.js index 1fa836c31..b5721dfac 100644 --- a/src/main/resources/static/js/multitool/PdfContainer.js +++ b/src/main/resources/static/js/multitool/PdfContainer.js @@ -1,11 +1,18 @@ +import { MovePageUpCommand, MovePageDownCommand } from "./commands/move-page.js"; +import { RemoveSelectedCommand } from "./commands/remove.js"; +import { RotateAllCommand, RotateElementCommand } from "./commands/rotate.js"; +import { SplitAllCommand } from "./commands/split.js"; +import { UndoManager } from "./UndoManager.js"; + class PdfContainer { fileName; pagesContainer; pagesContainerWrapper; pdfAdapters; downloadLink; + undoManager; - constructor(id, wrapperId, pdfAdapters) { + constructor(id, wrapperId, pdfAdapters, undoManager) { this.pagesContainer = document.getElementById(id); this.pagesContainerWrapper = document.getElementById(wrapperId); this.downloadLink = null; @@ -31,6 +38,8 @@ class PdfContainer { this.removeAllElements = this.removeAllElements.bind(this); this.resetPages = this.resetPages.bind(this); + this.undoManager = undoManager || new UndoManager(); + this.pdfAdapters = pdfAdapters; this.pdfAdapters.forEach((adapter) => { @@ -58,6 +67,33 @@ class PdfContainer { window.removeAllElements = this.removeAllElements; window.resetPages = this.resetPages; + let undoBtn = document.getElementById('undo-btn'); + let redoBtn = document.getElementById('redo-btn'); + + document.addEventListener('undo-manager-update', (e) => { + let canUndo = e.detail.canUndo; + let canRedo = e.detail.canRedo; + + undoBtn.disabled = !canUndo; + redoBtn.disabled = !canRedo; + }) + + window.undo = () => { + if (undoManager.canUndo()) undoManager.undo(); + else { + undoBtn.disabled = !undoManager.canUndo(); + redoBtn.disabled = !undoManager.canRedo(); + } + } + + window.redo = () => { + if (undoManager.canRedo()) undoManager.redo(); + else { + undoBtn.disabled = !undoManager.canUndo(); + redoBtn.disabled = !undoManager.canRedo(); + } + } + const filenameInput = document.getElementById("filename-input"); const downloadBtn = document.getElementById("export-button"); @@ -68,32 +104,28 @@ class PdfContainer { downloadBtn.disabled = true; } - movePageTo(startElement, endElement, scrollTo = false) { - const childArray = Array.from(this.pagesContainer.childNodes); - const startIndex = childArray.indexOf(startElement); - const endIndex = childArray.indexOf(endElement); - - // Check & remove page number elements here too if they exist because Firefox doesn't fire the relevant event on page move. - const pageNumberElement = startElement.querySelector(".page-number"); - if (pageNumberElement) { - startElement.removeChild(pageNumberElement); - } - - this.pagesContainer.removeChild(startElement); - if (!endElement) { - this.pagesContainer.append(startElement); + movePageTo(startElement, endElement, scrollTo = false, moveUp = false) { + let movePageCommand; + if (moveUp) { + movePageCommand = new MovePageUpCommand( + startElement, + endElement, + this.pagesContainer, + this.pagesContainerWrapper, + scrollTo + ); } else { - this.pagesContainer.insertBefore(startElement, endElement); + movePageCommand = new MovePageDownCommand( + startElement, + endElement, + this.pagesContainer, + this.pagesContainerWrapper, + scrollTo + ); } - if (scrollTo) { - const { width } = startElement.getBoundingClientRect(); - const vector = endIndex !== -1 && startIndex > endIndex ? 0 - width : width; - - this.pagesContainerWrapper.scroll({ - left: this.pagesContainerWrapper.scrollLeft + vector, - }); - } + movePageCommand.execute(); + return movePageCommand; } addFiles(nextSiblingElement, blank = false) { @@ -177,15 +209,10 @@ class PdfContainer { rotateElement(element, deg) { - var lastTransform = element.style.rotate; - if (!lastTransform) { - lastTransform = "0"; - } - const lastAngle = parseInt(lastTransform.replace(/[^\d-]/g, "")); - const newAngle = lastAngle + deg; - - element.style.rotate = newAngle + "deg"; + let rotateCommand = new RotateElementCommand(element, deg); + rotateCommand.execute(); + return rotateCommand; } async addPdfFile(renderer, pdfDocument, nextSiblingElement) { @@ -280,6 +307,7 @@ class PdfContainer { } rotateAll(deg) { + let elementsToRotate = []; for (let i = 0; i < this.pagesContainer.childNodes.length; i++) { const child = this.pagesContainer.children[i]; if (!child) continue; @@ -291,8 +319,13 @@ class PdfContainer { const img = child.querySelector("img"); if (!img) continue; - this.rotateElement(img, deg); + elementsToRotate.push(img); } + + let rotateAllCommand = new RotateAllCommand(elementsToRotate, deg); + rotateAllCommand.execute(); + + this.undoManager.pushUndoClearRedo(rotateAllCommand); } removeAllElements(){ @@ -307,34 +340,13 @@ class PdfContainer { deleteSelected() { window.selectedPages.sort((a, b) => a - b); - let deletions = 0; + let removeSelectedCommand = new RemoveSelectedCommand( + this.pagesContainer, + window.selectedPages, + this.updatePageNumbersAndCheckboxes + ); - window.selectedPages.forEach((pageIndex) => { - const adjustedIndex = pageIndex - 1 - deletions; - const child = this.pagesContainer.children[adjustedIndex]; - if (child) { - this.pagesContainer.removeChild(child); - deletions++; - } - }); - - if (this.pagesContainer.childElementCount === 0) { - const filenameInput = document.getElementById("filename-input"); - const filenameParagraph = document.getElementById("filename"); - const downloadBtn = document.getElementById("export-button"); - - if (filenameInput) - filenameInput.disabled = true; - filenameInput.value = ""; - if (filenameParagraph) - filenameParagraph.innerText = ""; - - downloadBtn.disabled = true; - } - - window.selectedPages = []; - this.updatePageNumbersAndCheckboxes(); - document.dispatchEvent(new Event("selectedPagesUpdated")); + this.undoManager.pushUndoClearRedo(removeSelectedCommand); } toggleSelectAll() { @@ -502,34 +514,17 @@ class PdfContainer { splitAll() { const allPages = this.pagesContainer.querySelectorAll(".page-container"); + let splitAllCommand = new SplitAllCommand( + allPages, + window.selectPage, + window.selectedPages, + "split-before" + ); + splitAllCommand.execute(); - if (!window.selectPage) { - const hasSplit = this.pagesContainer.querySelectorAll(".split-before").length > 0; - if (hasSplit) { - allPages.forEach(page => { - page.classList.remove("split-before"); - }); - } else { - allPages.forEach(page => { - page.classList.add("split-before"); - }); - } - return; - } - - allPages.forEach((page, index) => { - const pageIndex = index; - if (window.selectPage && !window.selectedPages.includes(pageIndex)) return; - - if (page.classList.contains("split-before")) { - page.classList.remove("split-before"); - } else { - page.classList.add("split-before"); - } - }); + this.undoManager.pushUndoClearRedo(splitAllCommand); } - async splitPDF(baseDocBytes, splitters) { const baseDocument = await PDFLib.PDFDocument.load(baseDocBytes); const pageNum = baseDocument.getPages().length; diff --git a/src/main/resources/static/js/multitool/UndoManager.js b/src/main/resources/static/js/multitool/UndoManager.js new file mode 100644 index 000000000..39202f3f6 --- /dev/null +++ b/src/main/resources/static/js/multitool/UndoManager.js @@ -0,0 +1,65 @@ +export class UndoManager { + _undoStack; + _redoStack; + + constructor() { + this._undoStack = []; + this._redoStack = []; + } + + pushUndo(command) { + this._undoStack.push(command); + this._dispatchStateChange(); + } + + pushRedo(command) { + this._redoStack.push(command); + this._dispatchStateChange(); + } + + pushUndoClearRedo(command) { + this._undoStack.push(command); + this._redoStack = []; + this._dispatchStateChange(); + } + + undo() { + if (!this.canUndo()) return; + + let cmd = this._undoStack.pop(); + cmd.undo(); + + this._redoStack.push(cmd); + this._dispatchStateChange(); + } + + canUndo() { + return this._undoStack && this._undoStack.length > 0; + } + + redo() { + if (!this.canRedo()) return; + + let cmd = this._redoStack.pop(); + cmd.redo(); + + this._undoStack.push(cmd); + this._dispatchStateChange(); + } + + canRedo() { + return this._redoStack && this._redoStack.length > 0; + } + + _dispatchStateChange() { + document.dispatchEvent( + new CustomEvent("undo-manager-update", { + bubbles: true, + detail: { + canUndo: this.canUndo(), + canRedo: this.canRedo(), + }, + }) + ); + } +} diff --git a/src/main/resources/static/js/multitool/commands/command.js b/src/main/resources/static/js/multitool/commands/command.js new file mode 100644 index 000000000..a4ae5fdf0 --- /dev/null +++ b/src/main/resources/static/js/multitool/commands/command.js @@ -0,0 +1,5 @@ +export class Command { + execute() {} + undo() {} + redo() {} +} diff --git a/src/main/resources/static/js/multitool/commands/delete-page.js b/src/main/resources/static/js/multitool/commands/delete-page.js new file mode 100644 index 000000000..f9a00e7a9 --- /dev/null +++ b/src/main/resources/static/js/multitool/commands/delete-page.js @@ -0,0 +1,74 @@ +import { Command } from "./command.js"; + +export class DeletePageCommand extends Command { + constructor(element, pagesContainer) { + super(); + + this.element = element; + this.pagesContainer = pagesContainer; + + this.filenameInputValue = document.getElementById("filename-input").value; + + const filenameParagraph = document.getElementById("filename"); + this.filenameParagraphText = filenameParagraph ? filenameParagraph.innerText : ""; + } + + execute() { + this.nextSibling = this.element.nextSibling; + + this.pagesContainer.removeChild(this.element); + if (this.pagesContainer.childElementCount === 0) { + const filenameInput = document.getElementById("filename-input"); + const filenameParagraph = document.getElementById("filename"); + const downloadBtn = document.getElementById("export-button"); + + filenameInput.disabled = true; + filenameInput.value = ""; + filenameParagraph.innerText = ""; + + downloadBtn.disabled = true; + } + } + + undo() { + let node = this.nextSibling; + if (node) this.pagesContainer.insertBefore(this.element, node); + else this.pagesContainer.appendChild(this.element); + + const pageNumberElement = this.element.querySelector(".page-number"); + if (pageNumberElement) { + this.element.removeChild(pageNumberElement); + } + + const filenameInput = document.getElementById("filename-input"); + const filenameParagraph = document.getElementById("filename"); + const downloadBtn = document.getElementById("export-button"); + + filenameInput.disabled = false; + filenameInput.value = this.filenameInputValue; + if (this.filenameParagraph) + filenameParagraph.innerText = this.filenameParagraphText; + + downloadBtn.disabled = false; + } + + redo() { + const pageNumberElement = this.element.querySelector(".page-number"); + if (pageNumberElement) { + this.element.removeChild(pageNumberElement); + } + + this.pagesContainer.removeChild(this.element); + if (this.pagesContainer.childElementCount === 0) { + const filenameInput = document.getElementById("filename-input"); + const filenameParagraph = document.getElementById("filename"); + const downloadBtn = document.getElementById("export-button"); + + filenameInput.disabled = true; + filenameInput.value = ""; + filenameParagraph.innerText = ""; + + downloadBtn.disabled = true; + } + } +} diff --git a/src/main/resources/static/js/multitool/commands/move-page.js b/src/main/resources/static/js/multitool/commands/move-page.js new file mode 100644 index 000000000..6f7b185ea --- /dev/null +++ b/src/main/resources/static/js/multitool/commands/move-page.js @@ -0,0 +1,133 @@ +import { Command } from "./command.js"; + +export class AbstractMovePageCommand extends Command { + constructor( + startElement, + endElement, + pagesContainer, + pagesContainerWrapper, + scrollTo = false + ) { + super(); + + this.pagesContainer = pagesContainer; + const childArray = Array.from(this.pagesContainer.childNodes); + + this.startIndex = childArray.indexOf(startElement); + this.endIndex = childArray.indexOf(endElement); + + this.startElement = startElement; + this.endElement = endElement; + + this.scrollTo = scrollTo; + this.pagesContainerWrapper = pagesContainerWrapper; + } + + execute() { + // Check & remove page number elements here too if they exist because Firefox doesn't fire the relevant event on page move. + const pageNumberElement = this.startElement.querySelector(".page-number"); + if (pageNumberElement) { + this.startElement.removeChild(pageNumberElement); + } + + this.pagesContainer.removeChild(this.startElement); + if (!this.endElement) { + this.pagesContainer.append(this.startElement); + } else { + this.pagesContainer.insertBefore(this.startElement, this.endElement); + } + + if (this.scrollTo) { + const { width } = this.startElement.getBoundingClientRect(); + const vector = + this.endIndex !== -1 && this.startIndex > this.endIndex + ? 0 - width + : width; + + this.pagesContainerWrapper.scroll({ + left: this.pagesContainerWrapper.scrollLeft + vector, + }); + } + } + + undo() { + // Requires overriding in child classes + + } + + redo() { + this.execute(); + } +} + +export class MovePageUpCommand extends AbstractMovePageCommand { + constructor( + startElement, + endElement, + pagesContainer, + pagesContainerWrapper, + scrollTo = false + ) { + super(startElement, endElement, pagesContainer, pagesContainerWrapper, scrollTo); + } + + undo() { + if (this.endElement) { + this.pagesContainer.removeChild(this.endElement); + this.startElement.insertAdjacentElement("beforebegin", this.endElement); + } + + if (this.scrollTo) { + const { width } = this.startElement.getBoundingClientRect(); + const vector = + this.endIndex === -1 || this.startIndex <= this.endIndex + ? 0 - width + : width; + + this.pagesContainerWrapper.scroll({ + left: this.pagesContainerWrapper.scrollLeft - vector, + }); + } + } + + redo() { + this.execute(); + } +} + +export class MovePageDownCommand extends AbstractMovePageCommand { + constructor( + startElement, + endElement, + pagesContainer, + pagesContainerWrapper, + scrollTo = false + ) { + super(startElement, endElement, pagesContainer, pagesContainerWrapper, scrollTo); + } + + undo() { + let previousElement = this.startElement.previousSibling; + + if (this.startElement) { + this.pagesContainer.removeChild(this.startElement); + previousElement.insertAdjacentElement("beforebegin", this.startElement); + } + + if (this.scrollTo) { + const { width } = this.startElement.getBoundingClientRect(); + const vector = + this.endIndex === -1 || this.startIndex <= this.endIndex + ? 0 - width + : width; + + this.pagesContainerWrapper.scroll({ + left: this.pagesContainerWrapper.scrollLeft - vector, + }); + } + } + + redo() { + this.execute(); + } +} diff --git a/src/main/resources/static/js/multitool/commands/remove.js b/src/main/resources/static/js/multitool/commands/remove.js new file mode 100644 index 000000000..0c2dc14e1 --- /dev/null +++ b/src/main/resources/static/js/multitool/commands/remove.js @@ -0,0 +1,101 @@ +import { Command } from "./command.js"; + +export class RemoveSelectedCommand extends Command { + constructor(pagesContainer, selectedPages, updatePageNumbersAndCheckboxes) { + super(); + this.pagesContainer = pagesContainer; + this.selectedPages = selectedPages; + + this.deletedChildren = []; + + if (updatePageNumbersAndCheckboxes) { + this.updatePageNumbersAndCheckboxes = updatePageNumbersAndCheckboxes; + } else { + const pageDivs = document.querySelectorAll(".pdf-actions_container"); + + pageDivs.forEach((div, index) => { + const pageNumber = index + 1; + const checkbox = div.querySelector(".pdf-actions_checkbox"); + checkbox.id = `selectPageCheckbox-${pageNumber}`; + checkbox.setAttribute("data-page-number", pageNumber); + checkbox.checked = window.selectedPages.includes(pageNumber); + }); + } + + const filenameInput = document.getElementById("filename-input"); + const filenameParagraph = document.getElementById("filename"); + + this.originalFilenameInputValue = filenameInput ? filenameInput.value : ""; + if (filenameParagraph) + this.originalFilenameParagraphText = filenameParagraph.innerText; + } + + execute() { + let deletions = 0; + + this.selectedPages.forEach((pageIndex) => { + const adjustedIndex = pageIndex - 1 - deletions; + const child = this.pagesContainer.children[adjustedIndex]; + if (child) { + this.pagesContainer.removeChild(child); + deletions++; + + this.deletedChildren.push({ + idx: adjustedIndex, + childNode: child, + }); + } + }); + + if (this.pagesContainer.childElementCount === 0) { + const filenameInput = document.getElementById("filename-input"); + const filenameParagraph = document.getElementById("filename"); + const downloadBtn = document.getElementById("export-button"); + + if (filenameInput) filenameInput.disabled = true; + filenameInput.value = ""; + if (filenameParagraph) filenameParagraph.innerText = ""; + + downloadBtn.disabled = true; + } + + window.selectedPages = []; + this.updatePageNumbersAndCheckboxes(); + document.dispatchEvent(new Event("selectedPagesUpdated")); + } + + undo() { + while (this.deletedChildren.length > 0) { + let deletedChild = this.deletedChildren.pop(); + if (this.pagesContainer.children.length <= deletedChild.idx) + this.pagesContainer.appendChild(deletedChild.childNode); + else { + this.pagesContainer.insertBefore( + deletedChild.childNode, + this.pagesContainer.children[deletedChild.idx] + ); + } + } + + if (this.pagesContainer.childElementCount > 0) { + const filenameInput = document.getElementById("filename-input"); + const filenameParagraph = document.getElementById("filename"); + const downloadBtn = document.getElementById("export-button"); + + if (filenameInput) filenameInput.disabled = false; + filenameInput.value = this.originalFilenameInputValue; + if (filenameParagraph) + filenameParagraph.innerText = this.originalFilenameParagraphText; + + downloadBtn.disabled = false; + } + + window.selectedPages = this.selectedPages; + this.updatePageNumbersAndCheckboxes(); + document.dispatchEvent(new Event("selectedPagesUpdated")); + } + + redo() { + this.execute(); + } +} diff --git a/src/main/resources/static/js/multitool/commands/rotate.js b/src/main/resources/static/js/multitool/commands/rotate.js new file mode 100644 index 000000000..6fb08cb94 --- /dev/null +++ b/src/main/resources/static/js/multitool/commands/rotate.js @@ -0,0 +1,74 @@ +import { Command } from "./command.js"; + +export class RotateElementCommand extends Command { + constructor(element, degree) { + super(); + this.element = element; + this.degree = degree; + } + + execute() { + let lastTransform = this.element.style.rotate; + if (!lastTransform) { + lastTransform = "0"; + } + const lastAngle = parseInt(lastTransform.replace(/[^\d-]/g, "")); + const newAngle = lastAngle + parseInt(this.degree); + + this.element.style.rotate = newAngle + "deg"; + } + + undo() { + let lastTransform = this.element.style.rotate; + if (!lastTransform) { + lastTransform = "0"; + } + + const currentAngle = parseInt(lastTransform.replace(/[^\d-]/g, "")); + const undoAngle = currentAngle + -parseInt(this.degree); + + this.element.style.rotate = undoAngle + "deg"; + } + + redo() { + this.execute(); + } +} + +export class RotateAllCommand extends Command { + constructor(elements, degree) { + super(); + this.elements = elements; + this.degree = degree; + } + + execute() { + for (let element of this.elements) { + let lastTransform = element.style.rotate; + if (!lastTransform) { + lastTransform = "0"; + } + const lastAngle = parseInt(lastTransform.replace(/[^\d-]/g, "")); + const newAngle = lastAngle + this.degree; + + element.style.rotate = newAngle + "deg"; + } + } + + undo() { + for (let element of this.elements) { + let lastTransform = element.style.rotate; + if (!lastTransform) { + lastTransform = "0"; + } + const currentAngle = parseInt(lastTransform.replace(/[^\d-]/g, "")); + const undoAngle = currentAngle + -this.degree; + + element.style.rotate = undoAngle + "deg"; + } + } + + redo() { + this.execute(); + } +} diff --git a/src/main/resources/static/js/multitool/commands/select.js b/src/main/resources/static/js/multitool/commands/select.js new file mode 100644 index 000000000..b76a25ca1 --- /dev/null +++ b/src/main/resources/static/js/multitool/commands/select.js @@ -0,0 +1,59 @@ +import { Command } from "./command.js"; + +export class SelectPageCommand extends Command { + constructor(pageNumber, checkbox) { + super(); + this.pageNumber = pageNumber; + this.selectCheckbox = checkbox; + } + + execute() { + if (this.selectCheckbox.checked) { + //adds to array of selected pages + window.selectedPages.push(this.pageNumber); + } else { + //remove page from selected pages array + const index = window.selectedPages.indexOf(this.pageNumber); + if (index !== -1) { + window.selectedPages.splice(index, 1); + } + } + + if (window.selectedPages.length > 0 && !window.selectPage) { + window.toggleSelectPageVisibility(); + } + if (window.selectedPages.length == 0 && window.selectPage) { + window.toggleSelectPageVisibility(); + } + + window.updateSelectedPagesDisplay(); + } + + undo() { + this.selectCheckbox.checked = !this.selectCheckbox.checked; + if (this.selectCheckbox.checked) { + //adds to array of selected pages + window.selectedPages.push(this.pageNumber); + } else { + //remove page from selected pages array + const index = window.selectedPages.indexOf(this.pageNumber); + if (index !== -1) { + window.selectedPages.splice(index, 1); + } + } + + if (window.selectedPages.length > 0 && !window.selectPage) { + window.toggleSelectPageVisibility(); + } + if (window.selectedPages.length == 0 && window.selectPage) { + window.toggleSelectPageVisibility(); + } + + window.updateSelectedPagesDisplay(); + } + + redo() { + this.selectCheckbox.checked = !this.selectCheckbox.checked; + this.execute(); + } +} diff --git a/src/main/resources/static/js/multitool/commands/split.js b/src/main/resources/static/js/multitool/commands/split.js new file mode 100644 index 000000000..3cbc32cde --- /dev/null +++ b/src/main/resources/static/js/multitool/commands/split.js @@ -0,0 +1,101 @@ +import { Command } from "./command.js"; + +export class SplitFileCommand extends Command { + constructor(element, splitClass) { + super(); + this.element = element; + this.splitClass = splitClass; + } + + execute() { + this.element.classList.toggle(this.splitClass); + } + + undo() { + this.element.classList.toggle(this.splitClass); + } + + redo() { + this.execute(); + } +} + +export class SplitAllCommand extends Command { + constructor(elements, isSelectedInWindow, selectedPages, splitClass) { + super(); + this.elements = elements; + this.isSelectedInWindow = isSelectedInWindow; + this.selectedPages = selectedPages; + this.splitClass = splitClass; + } + + execute() { + if (!this.isSelectedInWindow) { + const hasSplit = this._hasSplit(this.elements, this.splitClass); + if (hasSplit) { + this.elements.forEach((page) => { + page.classList.remove(this.splitClass); + }); + } else { + this.elements.forEach((page) => { + page.classList.add(this.splitClass); + }); + } + return; + } + + this.elements.forEach((page, index) => { + const pageIndex = index; + if (this.isSelectedInWindow && !this.selectedPages.includes(pageIndex)) + return; + + if (page.classList.contains(this.splitClass)) { + page.classList.remove(this.splitClass); + } else { + page.classList.add(this.splitClass); + } + }); + } + + _hasSplit() { + if (!this.elements || this.elements.length == 0) return false; + + for (const node of this.elements) { + if (node.classList.contains(this.splitClass)) return true; + } + + return false; + } + + undo() { + if (!this.isSelectedInWindow) { + const hasSplit = this._hasSplit(this.elements, this.splitClass); + if (hasSplit) { + this.elements.forEach((page) => { + page.classList.remove(this.splitClass); + }); + } else { + this.elements.forEach((page) => { + page.classList.add(this.splitClass); + }); + } + return; + } + + this.elements.forEach((page, index) => { + const pageIndex = index; + if (this.isSelectedInWindow && !this.selectedPages.includes(pageIndex)) + return; + + if (page.classList.contains(this.splitClass)) { + page.classList.remove(this.splitClass); + } else { + page.classList.add(this.splitClass); + } + }); + } + + redo() { + this.execute(); + } +} diff --git a/src/main/resources/templates/multi-tool.html b/src/main/resources/templates/multi-tool.html index 2a47a1e92..14e5a85ec 100644 --- a/src/main/resources/templates/multi-tool.html +++ b/src/main/resources/templates/multi-tool.html @@ -57,6 +57,27 @@ + + + + +