From 92ae82a17216ef9ed0d890bc43c9c32cac98ed36 Mon Sep 17 00:00:00 2001 From: Thomas Low Date: Fri, 22 Nov 2024 12:06:50 +0100 Subject: [PATCH] Merge branch 'detail-view-forward-backward-navigation-fixes-5930' into detail-view-forward-backward-navigation-fixes-5930-38x --- .../forms/dataeditor/GalleryPanel.java | 48 ++++++++++++ .../resources/messages/messages_de.properties | 4 + .../resources/messages/messages_en.properties | 4 + .../resources/messages/messages_es.properties | 8 ++ .../webapp/WEB-INF/resources/css/kitodo.css | 24 ++++++ .../WEB-INF/resources/js/metadata_editor.js | 30 ++++++++ .../partials/media-detail.xhtml | 32 +++++++- .../selenium/MetadataImagePreviewST.java | 73 +++++++++++++++++++ 8 files changed, 220 insertions(+), 3 deletions(-) diff --git a/Kitodo/src/main/java/org/kitodo/production/forms/dataeditor/GalleryPanel.java b/Kitodo/src/main/java/org/kitodo/production/forms/dataeditor/GalleryPanel.java index ada4f3655ee..f4280c0d7f2 100644 --- a/Kitodo/src/main/java/org/kitodo/production/forms/dataeditor/GalleryPanel.java +++ b/Kitodo/src/main/java/org/kitodo/production/forms/dataeditor/GalleryPanel.java @@ -1048,4 +1048,52 @@ public boolean hasMediaViewMimeTypePrefix(String mimeTypePrefix) { public MediaPartialsPanel getMediaPartialsPanel() { return mediaPartialsPanel; } + + /** + * Return true if the currently selected media (that is shown in the detail view) is the + * first media of all available media. + * @return boolean true if selected media is first media + */ + public boolean isSelectedMediaFirst() { + Pair lastSelection = getLastSelection(); + if (Objects.isNull(lastSelection)) { + return false; + } + + List medias = getMedias(); + if (medias.isEmpty()) { + return false; + } + + PhysicalDivision firstPhysicalDivision = medias.get(0).getView().getPhysicalDivision(); + if (Objects.isNull(firstPhysicalDivision)) { + return false; + } + + return firstPhysicalDivision.equals(lastSelection.getKey()); + } + + /** + * Return true if the currently selected media (that is shown in the detail view) is the + * last media of all available media. + * @return boolean true if selected media is last media + */ + public boolean isSelectedMediaLast() { + Pair lastSelection = getLastSelection(); + if (Objects.isNull(lastSelection)) { + return false; + } + + List medias = getMedias(); + if (medias.isEmpty()) { + return false; + } + + PhysicalDivision lastPhysicalDivision = medias.get(medias.size() - 1).getView().getPhysicalDivision(); + if (Objects.isNull(lastPhysicalDivision)) { + return false; + } + + return lastPhysicalDivision.equals(lastSelection.getKey()); + } } diff --git a/Kitodo/src/main/resources/messages/messages_de.properties b/Kitodo/src/main/resources/messages/messages_de.properties index 4fe1f4ab5a8..58f410606f9 100644 --- a/Kitodo/src/main/resources/messages/messages_de.properties +++ b/Kitodo/src/main/resources/messages/messages_de.properties @@ -334,6 +334,10 @@ dataEditor.layoutMenuSaveForTaskText=Spaltenaufteilung für Aufgabentyp speicher dataEditor.layoutSavedSuccessfullyTitle=Aktuelle Spaltenaufteilung erfolgreich gespeichert dataEditor.layoutSavedSuccessfullyDefaultText=Ihre Einstellungen wurden erfolgreich als Standard-Einstellungen gespeichert! Beim n\u00E4chsten \u00D6ffnen des Matadaten-Editors wird die Breite der drei Spalten (Strukturdaten, Metadaten und Galerie) entsprechend ihrer aktuellen Einstellung geladen. dataEditor.layoutSavedSuccessfullyForTaskText=Ihre Einstellungen wurden erfolgreich f\u00FCr alle zuk\u00FCnftigen Aufgaben des Typs "{0}" gespeichert! Beim n\u00E4chsten \u00D6ffnen des Matadaten-Editors f\u00FCr eine Aufgabe des gleichen Typs wird die Breite der drei Spalten (Strukturdaten, Metadaten und Galerie) entsprechend ihrer aktuellen Einstellung geladen. +dataEditor.navigateToPreviousElementMany=mehrere Elemente zur\u00FCckspringen +dataEditor.navigateToPreviousElementOne=zum vorherigen Element +dataEditor.navigateToNextElementMany=mehrere Elemente vorspringen +dataEditor.navigateToNextElementOne=zum n\u00E4chsten Element dataEditor.renamingMediaComplete=Das Umbenennen der Medien ist abgeschlossen dataEditor.renamingMediaError=Beim Umbenennen der Medien ist ein Fehler aufgetreten dataEditor.renamingMediaText={0} Mediendateien in {1} Ordnern wurden umbenannt. Bitte speichern Sie den Vorgang. Andernfalls werden die Dateiumbennungen beim Schlie\u00DFen des Metadateneditors verworfen. diff --git a/Kitodo/src/main/resources/messages/messages_en.properties b/Kitodo/src/main/resources/messages/messages_en.properties index e47ad13a226..c05ba95b74e 100644 --- a/Kitodo/src/main/resources/messages/messages_en.properties +++ b/Kitodo/src/main/resources/messages/messages_en.properties @@ -334,6 +334,10 @@ dataEditor.layoutMenuSaveForTaskText=Save task-specific layout dataEditor.layoutSavedSuccessfullyTitle=Current layout successfully saved dataEditor.layoutSavedSuccessfullyDefaultText=Your current editor column configuration has been successfully saved as default configuration. The next time you open the editor, the width of all three columns (structure data, metadata, gallery) will be loaded according to your current configuration. dataEditor.layoutSavedSuccessfullyForTaskText=Your current editor column configuration has been successfully saved as default configuration for the task type "{0}". The next time you open the editor for the same task type, the width of all three columns (structure data, metadata, gallery) will be loaded according to your current configuration. +dataEditor.navigateToPreviousElementMany=jump multiple elements back +dataEditor.navigateToPreviousElementOne=show previous element +dataEditor.navigateToNextElementMany=jump multiple elements forward +dataEditor.navigateToNextElementOne=show next element dataEditor.renamingMediaComplete=Finished renaming media dataEditor.renamingMediaError=An error occurred while renaming media files dataEditor.renamingMediaText={0} media files in {1} folders have been renamed. Please click the 'Save' button to persist changes to the filenames. Otherwise the renaming will be reverted upon closing the editor. diff --git a/Kitodo/src/main/resources/messages/messages_es.properties b/Kitodo/src/main/resources/messages/messages_es.properties index 132b65823be..c23d3b7d5bf 100644 --- a/Kitodo/src/main/resources/messages/messages_es.properties +++ b/Kitodo/src/main/resources/messages/messages_es.properties @@ -345,6 +345,14 @@ dataEditor.layoutSavedSuccessfullyTitle=La plantilla actual se guardó correctam dataEditor.layoutSavedSuccessfullyDefaultText=La configuración actual de la columna del editor se ha guardado correctamente como predeterminada. La próxima vez que abra el editor, el ancho de las tres columnas (datos de estructura, metadatos, galería) se cargará de acuerdo con su configuración actual. # please check google translation below and remove comment if translation is acceptable dataEditor.layoutSavedSuccessfullyForTaskText=La configuración actual de la columna del editor se ha guardado correctamente como predeterminada para el tipo de tarea "{0}". La próxima vez que abra el editor para el mismo tipo de tarea, el ancho de las tres columnas (datos de estructura, metadatos, galería) se cargará de acuerdo con su configuración actual. +# please check google translation below and remove comment if translation is acceptable +dataEditor.navigateToPreviousElementMany=saltar múltiples elementos hacia atrás +# please check google translation below and remove comment if translation is acceptable +dataEditor.navigateToPreviousElementOne=mostrar elemento anterior +# please check google translation below and remove comment if translation is acceptable +dataEditor.navigateToNextElementMany=saltar múltiples elementos hacia adelante +# please check google translation below and remove comment if translation is acceptable +dataEditor.navigateToNextElementOne=mostrar el siguiente elemento dataEditor.renamingMediaComplete=El cambio de nombre de los archivos multimedia ha finalizado dataEditor.renamingMediaError=Se produjo un error al cambiar el nombre de los archivos multimedia dataEditor.renamingMediaText=Se ha cambiado el nombre de {0} archivos multimedia en {1} carpeta. Por favor, haga clic en el botón 'Guardar' para mantener los cambios en los nombres de archivo. De lo contrario, el cambio de nombre se revertirá al cerrar el editor. diff --git a/Kitodo/src/main/webapp/WEB-INF/resources/css/kitodo.css b/Kitodo/src/main/webapp/WEB-INF/resources/css/kitodo.css index be9ec7189f7..d5ecb12f6da 100644 --- a/Kitodo/src/main/webapp/WEB-INF/resources/css/kitodo.css +++ b/Kitodo/src/main/webapp/WEB-INF/resources/css/kitodo.css @@ -3280,6 +3280,7 @@ div[id$='metadataTable'].ui-treetable tr.focusedRow { width: calc(100% - 100px); align-items: center; justify-content: left; + position: relative; } #imagePreviewForm\:mediaDetailMediaContainer { @@ -3441,6 +3442,29 @@ div[id$='metadataTable'].ui-treetable tr.focusedRow { background-color: var(--blue); } +#imagePreviewForm\:mediaDetailNavigationPanel { + position: absolute; + width: 100%; + bottom: 0; + text-align: center; + pointer-events: none; +} + +#imagePreviewForm\:mediaDetailNavigationPanel button { + margin: var(--default-full-size) var(--default-half-size); + pointer-events: all; + opacity: 0.0; + transition: opacity 0.1s linear; +} + +#imagePreviewForm:hover #imagePreviewForm\:mediaDetailNavigationPanel button { + opacity: 1.0; +} + +#imagePreviewForm\:mediaDetailNavigationPanel button:enabled:hover { + background: var(--blue); +} + #galleryWrapperPanel, #galleryWrapperPanel_content { height: 100%; diff --git a/Kitodo/src/main/webapp/WEB-INF/resources/js/metadata_editor.js b/Kitodo/src/main/webapp/WEB-INF/resources/js/metadata_editor.js index 4cd4f57e078..e35659a75ad 100644 --- a/Kitodo/src/main/webapp/WEB-INF/resources/js/metadata_editor.js +++ b/Kitodo/src/main/webapp/WEB-INF/resources/js/metadata_editor.js @@ -937,6 +937,36 @@ metadataEditor.pagination = { } }; +metadataEditor.detailView = { + + /** + * Select the previous or following media when clicking on the navigation buttons of the detail view. + * @param {int} delta position delta with respect to currently selected media + */ + navigate(delta) { + // find media currently shown in detail view + let currentTreeNodeId = $("#imagePreviewForm\\:mediaDetail").data("logicaltreenodeid"); + if (!currentTreeNodeId) { + return; + } + // find current media in list of thumbnails + let thumbnails = $("#imagePreviewForm .thumbnail-container"); + let currentThumbnail = $("#imagePreviewForm .thumbnail-container[data-logicaltreenodeid='" + currentTreeNodeId + "']"); + let index = thumbnails.index(currentThumbnail); + if (index < 0) { + return; + } + // add delta to find previous or next thumbnail + index = Math.min(thumbnails.length - 1, Math.max(0, index + delta)); + let nextThumbnail = thumbnails.eq(index); + let nextTreeNodeId = nextThumbnail.data("logicaltreenodeid"); + // update selection and trigger reload of detail view + metadataEditor.gallery.pages.handleSingleSelect(null, nextThumbnail, nextTreeNodeId); + metadataEditor.gallery.pages.handleSelectionUpdates(nextTreeNodeId); + scrollToPreviewThumbnail(nextThumbnail, $("#thumbnailStripeScrollableContent")); + } +}; + metadataEditor.contextMenu = { listen() { document.oncontextmenu = function(event) { diff --git a/Kitodo/src/main/webapp/WEB-INF/templates/includes/metadataEditor/partials/media-detail.xhtml b/Kitodo/src/main/webapp/WEB-INF/templates/includes/metadataEditor/partials/media-detail.xhtml index d3f619b955f..7a9ffb20edb 100644 --- a/Kitodo/src/main/webapp/WEB-INF/templates/includes/metadataEditor/partials/media-detail.xhtml +++ b/Kitodo/src/main/webapp/WEB-INF/templates/includes/metadataEditor/partials/media-detail.xhtml @@ -19,9 +19,12 @@ xmlns:ui="http://xmlns.jcp.org/jsf/facelets" xmlns:a="http://xmlns.jcp.org/jsf/passthrough"> - - + + + @@ -91,6 +94,29 @@ + + + + + + + diff --git a/Kitodo/src/test/java/org/kitodo/selenium/MetadataImagePreviewST.java b/Kitodo/src/test/java/org/kitodo/selenium/MetadataImagePreviewST.java index 23874081a00..d6e37eb9cd2 100644 --- a/Kitodo/src/test/java/org/kitodo/selenium/MetadataImagePreviewST.java +++ b/Kitodo/src/test/java/org/kitodo/selenium/MetadataImagePreviewST.java @@ -13,6 +13,7 @@ import static org.awaitility.Awaitility.await; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.IOException; @@ -37,6 +38,7 @@ import org.kitodo.test.utils.ProcessTestUtils; import org.openqa.selenium.By; import org.openqa.selenium.WebElement; +import org.openqa.selenium.interactions.Actions; /** * Tests the image preview panel (OpenLayers map) in the metadata editor. @@ -209,6 +211,77 @@ public void viewPersistsImageChange() throws Exception { assertTrue(Math.abs(getOpenLayersRotation() - changedRotation) < EPSILON); } + /** + * Test that navigation buttons select previous or following image, are disabled in case there is + * no previous or next image, and are hidden if the mouse is not inside the image preview panel. + */ + @Test + public void navigationButtonTest() throws Exception { + login("kowal"); + + // open metadata editor and detail view + Pages.getProcessesPage().goTo().editMetadata(PROCESS_TITLE); + Pages.getMetadataEditorPage().openDetailView(); + pollAssertTrue(() -> findElementsByCSS(OPEN_LAYERS_CANVAS_SELECTOR).get(0).isDisplayed()); + + // image is thumbnail 2, which is the very first image, such that left buttons are disabled + assertEquals("Bild 2, Seite -", findElementsByCSS(GALLERY_HEADING_WRAPPER_SELECTOR).get(0).getText().strip()); + WebElement leftMany = findElementsByCSS("#imagePreviewForm\\:navigateToPreviousElementMany").get(0); + WebElement rightMany = findElementsByCSS("#imagePreviewForm\\:navigateToNextElementMany").get(0); + + // check left buttons are disabled and right buttons are enabled + assertFalse(leftMany.isEnabled()); + assertTrue(rightMany.isEnabled()); + + // click on right-many button, which selects last image + rightMany.click(); + + // wait for image 3 to be shown + pollAssertTrue( + () -> "Bild 3, Seite -".equals( + findElementsByCSS(GALLERY_HEADING_WRAPPER_SELECTOR).get(0).getText().strip() + ) + ); + + // find buttons again because image preview is re-rendered + WebElement leftOne = findElementsByCSS("#imagePreviewForm\\:navigateToPreviousElementOne").get(0); + WebElement rightOne = findElementsByCSS("#imagePreviewForm\\:navigateToNextElementOne").get(0); + + // check left buttons are enabled and right buttons are disabled + assertTrue(leftOne.isEnabled()); + assertFalse(rightOne.isEnabled()); + + // click on left-one button, selecting image 1 (middle image of all 3 images) + leftOne.click(); + + // wait for image 1 to be shown + pollAssertTrue( + () -> "Bild 1, Seite -".equals( + findElementsByCSS(GALLERY_HEADING_WRAPPER_SELECTOR).get(0).getText().strip() + ) + ); + + // find buttons again because image preview is re-rendered + leftOne = findElementsByCSS("#imagePreviewForm\\:navigateToPreviousElementOne").get(0); + rightOne = findElementsByCSS("#imagePreviewForm\\:navigateToNextElementOne").get(0); + + // both left and right buttons are enabled + assertTrue(leftOne.isEnabled()); + assertTrue(rightOne.isEnabled()); + + // check that buttons are displayed (since mouse in hovering buttons due to prior click) + assertTrue(leftOne.isDisplayed()); + assertTrue(rightOne.isDisplayed()); + + // move mouse to main header menu + new Actions(Browser.getDriver()).moveToElement(findElementsByCSS("#menu").get(0)).perform(); + + // check buttons are hidden now + pollAssertTrue( + () -> !findElementsByCSS("#imagePreviewForm\\:navigateToPreviousElementOne").get(0).isDisplayed() + ); + } + /** * Close metadata editor and logout after every test. * @throws Exception when page navigation fails