From 23c011d57e2ab70bcc23d40162f667072e449855 Mon Sep 17 00:00:00 2001 From: Thomas Low Date: Fri, 27 Sep 2024 13:22:33 +0200 Subject: [PATCH 01/11] Refactor detail view Javascript. Update OpenLayers to v6.14.1. Fix issue #5796. --- .../forms/dataeditor/GalleryPanel.java | 2 +- .../webapp/WEB-INF/resources/css/kitodo.css | 4 + .../WEB-INF/resources/js/metadata_editor.js | 4 +- .../webapp/WEB-INF/resources/js/ol_custom.js | 583 ++++++++++++------ .../webapp/WEB-INF/resources/js/resize.js | 7 +- .../metadataEditor/dialogs/pagination.xhtml | 2 +- .../includes/metadataEditor/gallery.xhtml | 6 +- .../metadataEditor/logicalStructure.xhtml | 4 +- .../metadataEditor/physicalStructure.xhtml | 4 +- pom.xml | 2 +- 10 files changed, 409 insertions(+), 209 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 3d09eb0bfb3..ada4f3655ee 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 @@ -859,7 +859,7 @@ private void selectMedia(String physicalDivisionOrder, String stripeIndex, Strin String scrollScripts = "scrollToSelectedTreeNode();scrollToSelectedPaginationRow();"; if (GalleryViewMode.PREVIEW.equals(galleryViewMode)) { PrimeFaces.current().executeScript( - "checkScrollPosition();initializeImage();metadataEditor.gallery.mediaView.update();" + scrollScripts); + "checkScrollPosition();metadataEditor.detailMap.update();metadataEditor.gallery.mediaView.update();" + scrollScripts); } else { PrimeFaces.current().executeScript(scrollScripts); } 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 a5e3a0e66e3..42a7d695a0c 100644 --- a/Kitodo/src/main/webapp/WEB-INF/resources/css/kitodo.css +++ b/Kitodo/src/main/webapp/WEB-INF/resources/css/kitodo.css @@ -3242,6 +3242,9 @@ Column content left: 16px; position: absolute; top: 16px; + display: flex; + flex-direction: column; + align-items: start; } #map .ol-overlaycontainer-stopevent > .ol-zoom.ol-unselectable.ol-control { @@ -3263,6 +3266,7 @@ Column content } #map .ol-control { + display: inline-block; background: transparent; padding: 0; position: static; 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 ccd10ae33c0..4cd4f57e078 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 @@ -14,7 +14,7 @@ /*eslint new-cap: ["error", { "capIsNewExceptionPattern": "^PF" }]*/ /*eslint complexity: ["error", 10]*/ -var metadataEditor = {}; +var metadataEditor = metadataEditor || {}; metadataEditor.metadataTree = { @@ -1147,7 +1147,7 @@ metadataEditor.shortcuts = { case "PREVIEW": initialize(); scrollToSelectedThumbnail(); - changeToMapView(); + metadataEditor.detailMap.update(); break; } } diff --git a/Kitodo/src/main/webapp/WEB-INF/resources/js/ol_custom.js b/Kitodo/src/main/webapp/WEB-INF/resources/js/ol_custom.js index 049d0f7da75..dee541b733c 100644 --- a/Kitodo/src/main/webapp/WEB-INF/resources/js/ol_custom.js +++ b/Kitodo/src/main/webapp/WEB-INF/resources/js/ol_custom.js @@ -11,244 +11,441 @@ /* globals ol */ // jshint unused:false -// Kitodo namespace -var kitodo = {}; -kitodo.map = null; - /** - * @param {Object=} options Custom control options for Kitodo in OpenLayers - * @extends {ol.control.Rotate} - * @constructor + * Abstract class describing a custom control button for the OpenLayers map. */ -kitodo.RotateLeftControl = function(options = {}) { - var buttonLeft = document.createElement('button'); - buttonLeft.innerHTML = ""; - buttonLeft.setAttribute("type", "button"); - buttonLeft.setAttribute("title", "Rotate left"); +class CustomControl extends ol.control.Control { + + /** + * Initializes a custom control button with various options. + * + * @param {object} options the custom control options (className, icon, title, other OpenLayer options) + */ + constructor(options) { + const className = options.className; + const icon = options.icon; + const title = options.title; + + const button = document.createElement('button'); + button.innerHTML = ""; + button.setAttribute("type", "button"); + button.setAttribute("title", title); + + const element = document.createElement('div'); + element.className = className + ' ol-unselectable ol-control ol-rotate'; + element.appendChild(button); + + super({ + element: element, + target: options.target + }); + + button.addEventListener('click', this.handleClick.bind(this), false); + } + + /** + * Abstract method that handles a click event on the button. + * + * @param {MouseEvent} event the click event + */ + handleClick(event) { + // not implemented + } +}; - var this_ = this; +/** + * Custom control that rotates the image 90 degrees to the left. + */ +class RotateLeftControl extends CustomControl { + + constructor(options) { + super(Object.assign(options || {}, { + className: "rotate-left", + icon: "fa-undo", + title: "Rotate left", + })); + } - var handleRotateLeft = function() { - var view = this_.getMap().getView(); + handleClick() { + const view = this.getMap().getView(); view.animate({ rotation: view.getRotation() - (90 * (Math.PI / 180)), duration: 100 }); - }; - - buttonLeft.addEventListener('click', handleRotateLeft, false); - - var elementLeft = document.createElement('div'); - elementLeft.className = 'rotate-left ol-unselectable ol-control ol-rotate'; - elementLeft.appendChild(buttonLeft); - - ol.control.Control.call(this, { - element: elementLeft, - target: options.target - }); + } }; /** - * @param {Object=} options Custom control options for Kitodo in OpenLayers - * @extends {ol.control.Rotate} - * @constructor + * Custom control that rotates the image 90 degrees to the right. */ -kitodo.RotateRightControl = function(options = {}) { - var buttonRight = document.createElement('button'); - buttonRight.innerHTML = ""; - buttonRight.setAttribute("type", "button"); - buttonRight.setAttribute("title", "Rotate right"); - - var this_ = this; +class RotateRightControl extends CustomControl { + + constructor(options) { + super(Object.assign(options || {}, { + className: "rotate-right", + icon: "fa-repeat", + title: "Rotate right", + })); + } - var handleRotateRight = function() { - var view = this_.getMap().getView(); + handleClick() { + const view = this.getMap().getView(); view.animate({ - rotation: view.getRotation() + (90 * (Math.PI / 180)), + rotation: view.getRotation() + (90 * (Math.PI / 180)), duration: 100 }); - }; - - buttonRight.addEventListener('click', handleRotateRight, false); - - var elementRight = document.createElement('div'); - elementRight.className = 'rotate-right ol-unselectable ol-control ol-rotate'; - elementRight.appendChild(buttonRight); - - ol.control.Control.call(this, { - element: elementRight, - target: options.target, - duration: 250 - }); + } }; -function resetNorth() { - if (kitodo.map) { - let view = kitodo.map.getView(); - view.animate({ +/** + * Custom control that rotates the image back to default. + */ +class RotateNorthControl extends CustomControl { + + constructor(options) { + super(Object.assign(options || {}, { + className: "rotate-north", + icon: "fa-compass", + title: "Reset orientation", + })); + } + + handleClick() { + this.getMap().getView().animate({ rotation: 0, - duration: 0 + duration: 100 }); } -} +}; /** - * @param {Object=} options Custom control options for Kitodo in OpenLayers - * @extends {ol.control.Rotate} - * @constructor + * Custom control that scales the image back to default. */ -kitodo.ResetNorthControl = function(options = {}) { - let buttonResetNorth = document.createElement("button"); - buttonResetNorth.innerHTML = ""; - buttonResetNorth.setAttribute("type", "button"); - buttonResetNorth.setAttribute("title", "Reset orientation"); - - buttonResetNorth.addEventListener("click", resetNorth, false); - - let elementResetNorth = document.createElement("div"); - elementResetNorth.className = "ol-rotate ol-unselectable ol-control"; /*ol-rotate-reset*/ - elementResetNorth.appendChild(buttonResetNorth); - - ol.control.Control.call(this, { - element: elementResetNorth, - target: options.target, - duration: 250 - }); +class ResetZoomControl extends CustomControl { + + /** The image dimensions as OpenLayers extent */ + #extent; + + /** + * Initialize a custom control button that scales the image back to its default position. + * + * @param {object} options containing the extent (Array) describing the image dimensions + */ + constructor(options) { + super(Object.assign(options, { + className: "reset-zoom", + icon: "fa-expand", + title: "Reset zoom", + })); + this.#extent = options.extent; + } + + handleClick() { + this.getMap().getView().fit(this.#extent, {}); + } }; -ol.inherits(kitodo.RotateLeftControl, ol.control.Rotate); -ol.inherits(kitodo.RotateRightControl, ol.control.Rotate); -ol.inherits(kitodo.ResetNorthControl, ol.control.Rotate); +/** + * Class managing OpenLayers detail map showing images. + */ +class KitodoDetailMap { + + /** + * Remember position, rotation and zoom level such that a new OpenLayers map can be + * initialized with the same position, rotation and zoom level when new images are selected. + */ + #last_view = { + center: null, + zoom: null, + rotation: null, + }; + + /** + * Image properties of the image that is currently shown in OpenLayers. Object with properties + * dimensions (width, height) and path (url). + */ + #image = { + dimensions: null, + path: null, + }; -function random(length) { - var text = ""; - var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + /** + * The OpenLayers maps instance + */ + #map = null; - for (var i = 0; i < length; i++) { - text += possible.charAt(Math.floor(Math.random() * possible.length)); + /** + * Initialize a new Kitodo detail map + */ + constructor() { + this.registerResizeEvent(); } - return text; -} + /** + * Debounces various event handlers to improve performance, e.g. when resizing. + * + * @param {function} func the function to be debounced + * @param {number} timeout the timeout in milliseconds + * + * @returns {function} the debounced function + */ + static makeDebounced(func, timeout = 100) { + let timer = null; + return function () { + clearTimeout(timer); + timer = setTimeout(func, timeout); + }; + } -function createProjection(extent) { - return new ol.proj.Projection({ - code: 'kitodo-image', - units: 'pixels', - extent: extent - }); -} + /** + * Generate a random string of [a-zA-Z0-9]. + * + * @param {number} length the length of the string to be generated + * @returns the string of random characters + */ + static randomUUID(length) { + const possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + + let text = ""; + for (let i = 0; i < length; i++) { + text += possible.charAt(Math.floor(Math.random() * possible.length)); + } + + return text; + } -function createSource(extent, imagePath, projection) { - return new ol.source.ImageStatic({ - url: imagePath, - projection: projection, - imageExtent: extent - }); -} + /** + * Create an OpenLayers projection given the image extent. + * + * @param {Array} extent the extent describing the image dimensions + * @returns {ol.proj.Projection} the OpenLayers projection + */ + createProjection(extent) { + return new ol.proj.Projection({ + code: 'kitodo-image', + units: 'pixels', + extent: extent + }); + } -function hideCanvas() { - let map = document.querySelector("#map canvas"); - let loadingIcon = document.querySelector("#map > .fa-spinner"); - if (map) { - map.style.opacity = 0; - loadingIcon.style.opacity = 1; + /** + * Create an OpenLayers source object for the image. + * + * @param {Array} extent the extent describing the image dimensions + * @param {string} path the path (url) of the image + * @param {ol.proj.Projection} projection the OpenLayers projection used to map the image to the canvas + * @returns {ol.source.ImageStatic} the OpenLayers image source object + */ + createSource(extent, path, projection) { + return new ol.source.ImageStatic({ + url: path, + projection: projection, + imageExtent: extent, + interpolate: true + }); } -} -function showCanvas() { - let map = document.querySelector("#map canvas"); - let loadingIcon = document.querySelector("#map > .fa-spinner"); - if (map) { - map.style.opacity = 1; - loadingIcon.style.opacity = 0; + /** + * Hide the map canvas (while the image is loading) + */ + hideCanvas() { + let canvas = document.querySelector("#map canvas"); + let loadingIcon = document.querySelector("#map > .fa-spinner"); + if (canvas) { + canvas.style.opacity = 0; + loadingIcon.style.opacity = 1; + } + } + + /** + * Show the map canvas (as soon as the image has finished loading) + */ + showCanvas() { + let canvas = document.querySelector("#map canvas"); + let loadingIcon = document.querySelector("#map > .fa-spinner"); + if (canvas) { + canvas.style.opacity = 1; + loadingIcon.style.opacity = 0; + } } -} -function initializeMap(imageDimensions, imagePath) { - // Map image coordinates to map coordinates to be able to use image extent in pixels. - let extent = [0, 0, imageDimensions[0], imageDimensions[1]]; - let projection = createProjection(extent); - - kitodo.map = new ol.Map({ - controls: ol.control.defaults({ - attributionOptions: { - collapsible: false - }, - zoomOptions: { - delta: 3 - }, - rotate: false - }).extend([ - new kitodo.RotateRightControl(), - new kitodo.RotateLeftControl(), - new kitodo.ResetNorthControl() - ]), - layers: [ - new ol.layer.Image({ - source: createSource(extent, imagePath, projection) - }) - ], - target: 'map', - view: new ol.View({ - projection: projection, - center: ol.extent.getCenter(extent), - zoomFactor: 1.1 - }) - }); - kitodo.map.getView().fit(extent, {}); - kitodo.map.on("rendercomplete", function () { - showCanvas(); - }); -} + /** + * Handler that is called as soon as the image was completely loaded + * @param {*} image the jQuery image dom element + */ + onImageLoad(image) { + this.#image = { + dimensions: [image.width(), image.height()], + path: image[0].src, + }; + this.initializeOpenLayersMap(); + } -function updateMap(imageDimensions, imagePath) { - // Map image coordinates to map coordinates to be able to use image extent in pixels. - let extent = [0, 0, imageDimensions[0], imageDimensions[1]]; - let projection = createProjection(extent); + /** + * Register the load event for the current image. + */ + registerImageLoadEvent() { + this.hideCanvas(); + let image = $("#imagePreviewForm\\:mediaPreviewGraphicImage"); + if (image.length > 0) { + image.on("load", this.onImageLoad.bind(this, image)); + image[0].src = image[0].src.replace(/&uuid=[a-z0-9]+/i, "") + "&uuid=" + KitodoDetailMap.randomUUID(8); + } + } - kitodo.map.getLayers().getArray()[0].setSource(createSource(extent, imagePath, projection)); - kitodo.map.getView().setCenter(ol.extent.getCenter(extent)); - kitodo.map.getView().getProjection().setExtent(extent); - kitodo.map.getView().fit(extent, {}); -} + /** + * Return extent array containg image dimensions. + * + * @param {Array} dimensions dimensions in pixel as [width, height] + * @returns {Array} the extent array + */ + createImageExtent(dimensions) { + return [0, 0, dimensions[0], dimensions[1]]; + } + + /** + * Creates the OpenLayers map object as soon as the image as been loaded. + */ + initializeOpenLayersMap() { + // Map image coordinates to map coordinates to be able to use image extent in pixels. + const extent = this.createImageExtent(this.#image.dimensions); + const projection = this.createProjection(extent); + + if (this.#map) { + // make last OpenLayers map forget canvas target + // (triggers OpenLayers cleanup code and allows garbage collection) + this.#map.setTarget(null); + } -function addListener(element) { - element.on("load", function () { - if (kitodo.map && $("#map .ol-viewport").length) { - updateMap([element.width(), element.height()], element[0].src); - } else { - initializeMap([element.width(), element.height()], element[0].src); + // initialize new OpenLayers map + this.#map = new ol.Map({ + controls: ol.control.defaults({ + attributionOptions: { + collapsible: false + }, + zoomOptions: { + delta: 3 // zoom delta when clicking zoom buttons + }, + rotate: false + }).extend([ + new RotateLeftControl(), + new RotateRightControl(), + new RotateNorthControl(), + new ResetZoomControl({extent: extent}) + ]), + interactions: ol.interaction.defaults({ + zoomDelta: 5, // zoom delta when using mouse wheel + zoomDuration: 100, + }), + layers: [ + new ol.layer.Image({ + source: this.createSource(extent, this.#image.path, projection) + }) + ], + target: 'map', + view: new ol.View({ + projection: projection, + center: this.unnormalizeCenter(this.#last_view.center, extent), + zoom: this.#last_view.zoom, + rotation: this.#last_view.rotation, + zoomFactor: 1.1, + extent: extent, + constrainOnlyCenter: true, + smoothExtentConstraint: true, + showFullExtent: true + }) + }); + if (this.#last_view.center == null) { + // fit image to current viewport unless previous zoom and center position is known + this.#map.getView().fit(extent, {}); } - }); -} + // register various events to make sure that previous view is remembered + this.#map.on("rendercomplete", KitodoDetailMap.makeDebounced(this.onRenderComplete.bind(this))); + this.#map.on("change", KitodoDetailMap.makeDebounced(this.saveCurrentView.bind(this))); + this.#map.on("postrender", KitodoDetailMap.makeDebounced(this.saveCurrentView.bind(this))); + } -function initializeImage() { - resetNorth(); - hideCanvas(); - let image = $("#imagePreviewForm\\:mediaPreviewGraphicImage"); - if (image.length > 0) { - addListener(image); - image[0].src = image[0].src.replace(/&uuid=[a-z0-9]+/i, "") + "&uuid=" + random(8); + /** + * Return unnormalized center coordinates in case previous center is known (not null). Otherwise + * center is calculated from the image extent containing image dimensions. + * + * @param {Array} center the normalized center coordinates [0..1, 0..1] + * @returns {Array} unnormalized center + */ + unnormalizeCenter(center) { + if (center !== null) { + return [ + center[0] * this.#image.dimensions[0], + center[1] * this.#image.dimensions[1], + ] + } + return ol.extent.getCenter(this.createImageExtent(this.#image.dimensions)); } -} -function changeToMapView() { - initializeImage(); - showCanvas(); - if (kitodo.map) { - kitodo.map.handleTargetChanged_(); + /** + * Normalizes the center coordinates from [0..width, 0..height] to [0..1, 0..1] such + * that images with different dimensions are visualized at the same relative position + * in the viewport. + * + * @param {Array} center the current center coordinates as reported by OpenLayers + * @returns {Array} the normalized center coordinates + */ + normalizeCenter(center) { + return [ + center[0] / this.#image.dimensions[0], + center[1] / this.#image.dimensions[1], + ]; + } + + /** + * Remembers current view properties (center, zoom rotation) such that the OpenLayers + * map can be initialized with the same parameters when selecting another image. + */ + saveCurrentView() { + this.#last_view = { + center: this.normalizeCenter(this.#map.getView().getCenter()), + zoom: this.#map.getView().getZoom(), + rotation: this.#map.getView().getRotation(), + }; + } + + /** + * Is called by OpenLayers whenever a canvas rendering has finished. Unless debounced, this + * event is triggered potentially at 60fps. + */ + onRenderComplete() { + this.showCanvas(); + this.saveCurrentView(); + } + + /** + * Registers the resize event for the meta data editor column, such that the image can be + * repositioned appropriately. + */ + registerResizeEvent() { + // reload map if container was resized + $('#thirdColumnWrapper').on('resize', KitodoDetailMap.makeDebounced(this.onResize.bind(this))); } -} -// reload map if container was resized -$('#thirdColumnWrapper').on('resize', function () { - if (kitodo.map) { - // FIXME: This causes lags. It should only be executed *once* after resize. - kitodo.map.updateSize(); + /** + * Is called when a resize event has happened. Unless debounced, this event is triggered potentially + * at 60fps. + */ + onResize() { + if (this.#map) { + this.#map.updateSize(); + } + } + + /** + * Reloads the image. Is called when the detail view is activated, or a new image was selected. + */ + update() { + this.registerImageLoadEvent(); } -}); +} + +// register detail map class with the metadataEditor namespace +var metadataEditor = metadataEditor || {}; +metadataEditor.detailMap = new KitodoDetailMap(); -$(document).ready(function () { - initializeImage(); -}); diff --git a/Kitodo/src/main/webapp/WEB-INF/resources/js/resize.js b/Kitodo/src/main/webapp/WEB-INF/resources/js/resize.js index 208bb40dcc1..747d6129d20 100644 --- a/Kitodo/src/main/webapp/WEB-INF/resources/js/resize.js +++ b/Kitodo/src/main/webapp/WEB-INF/resources/js/resize.js @@ -397,6 +397,7 @@ function toggleThirdColumn() { secondColumn.animate({width: wrapper.width() - firstColumn.width() - COLLAPSED_COL_WIDTH - 2 * SEPARATOR_WIDTH}); } } else { + metadataEditor.detailMap.update(); var neededWidth = thirdColumnWidth - COLLAPSED_COL_WIDTH - (secondColumn.width() - secondColumn.data('min-width')); if (secondColumn.hasClass(COLLAPSED)) { firstColumn.animate({width: wrapper.width() - COLLAPSED_COL_WIDTH - thirdColumnWidth - 2 * SEPARATOR_WIDTH}); @@ -522,16 +523,14 @@ function updateMetadataEditorView(showMetadataColumn) { } expandThirdColumn(); scrollToSelectedThumbnail(); - initializeImage(); + metadataEditor.detailMap.update(); metadataEditor.gallery.mediaView.update(); scrollToSelectedTreeNode(); scrollToSelectedPaginationRow(); } function resizeMap() { - if (kitodo.map) { - kitodo.map.updateSize(); - } + metadataEditor.detailMap.onResize(); } function saveLayout() { diff --git a/Kitodo/src/main/webapp/WEB-INF/templates/includes/metadataEditor/dialogs/pagination.xhtml b/Kitodo/src/main/webapp/WEB-INF/templates/includes/metadataEditor/dialogs/pagination.xhtml index 3b45179b4ac..aca83ac8064 100644 --- a/Kitodo/src/main/webapp/WEB-INF/templates/includes/metadataEditor/dialogs/pagination.xhtml +++ b/Kitodo/src/main/webapp/WEB-INF/templates/includes/metadataEditor/dialogs/pagination.xhtml @@ -47,7 +47,7 @@ showCheckbox="true"> diff --git a/Kitodo/src/main/webapp/WEB-INF/templates/includes/metadataEditor/gallery.xhtml b/Kitodo/src/main/webapp/WEB-INF/templates/includes/metadataEditor/gallery.xhtml index 963816c6116..51ae120ad9c 100644 --- a/Kitodo/src/main/webapp/WEB-INF/templates/includes/metadataEditor/gallery.xhtml +++ b/Kitodo/src/main/webapp/WEB-INF/templates/includes/metadataEditor/gallery.xhtml @@ -65,7 +65,7 @@ @@ -290,8 +290,8 @@ galleryWrapperPanel"/> - - + + diff --git a/Kitodo/src/main/webapp/WEB-INF/templates/includes/metadataEditor/logicalStructure.xhtml b/Kitodo/src/main/webapp/WEB-INF/templates/includes/metadataEditor/logicalStructure.xhtml index 4406dbc9572..4b2e5408122 100644 --- a/Kitodo/src/main/webapp/WEB-INF/templates/includes/metadataEditor/logicalStructure.xhtml +++ b/Kitodo/src/main/webapp/WEB-INF/templates/includes/metadataEditor/logicalStructure.xhtml @@ -72,7 +72,7 @@ listener="#{DataEditorForm.structurePanel.treeLogicalSelect}" oncomplete="scrollToSelectedThumbnail(); scrollToSelectedPaginationRow(); - changeToMapView(); + metadataEditor.detailMap.update(); metadataEditor.gallery.mediaView.update(); expandMetadata('logical-metadata-tab');" update="galleryHeadingWrapper @@ -87,7 +87,7 @@ onstart="$('#contextMenuLogicalTree .ui-menuitem').addClass('ui-state-disabled')" oncomplete="scrollToSelectedThumbnail(); scrollToSelectedPaginationRow(); - changeToMapView(); + metadataEditor.detailMap.update(); metadataEditor.gallery.mediaView.update(); PF('contextMenuLogicalTree').show(currentEvent)" update="@(.stripe) diff --git a/Kitodo/src/main/webapp/WEB-INF/templates/includes/metadataEditor/physicalStructure.xhtml b/Kitodo/src/main/webapp/WEB-INF/templates/includes/metadataEditor/physicalStructure.xhtml index fa03940d930..6998518509d 100644 --- a/Kitodo/src/main/webapp/WEB-INF/templates/includes/metadataEditor/physicalStructure.xhtml +++ b/Kitodo/src/main/webapp/WEB-INF/templates/includes/metadataEditor/physicalStructure.xhtml @@ -34,7 +34,7 @@ listener="#{DataEditorForm.structurePanel.treePhysicalSelect}" oncomplete="scrollToSelectedThumbnail(); scrollToSelectedPaginationRow(); - initializeImage(); + metadataEditor.detailMap.update(); metadataEditor.gallery.mediaView.update(); expandMetadata('physical-metadata-tab');" update="galleryHeadingWrapper @@ -44,7 +44,7 @@ div:nth-child(2) .thumbnail-container"; + private static final Double EPSILON = 0.001; + + private static int processId = -1; + + /** + * Prepare tests by inserting dummy processes into database and index for sub-folders of test metadata resources. + * @throws DAOException when saving of dummy or test processes fails. + * @throws DataException when retrieving test project for test processes fails. + * @throws IOException when copying test metadata or image files fails. + */ + @BeforeAll + public static void prepare() throws DAOException, DataException, IOException { + MockDatabase.insertFoldersForSecondProject(); + processId = MockDatabase.insertTestProcessIntoSecondProject(PROCESS_TITLE); + ProcessTestUtils.copyTestFiles(processId, TEST_RENAME_MEDIA_FILE); + } + + /** + * Tests whether the image preview is shown when a user clicks on the image preview button. + * @throws Exception when something fails + */ + @Test + public void imageVisibleTest() throws Exception { + login("kowal"); + Pages.getProcessesPage().goTo().editMetadata(PROCESS_TITLE); + + // check detail view is not yet visible + assertEquals(0, findElementsByCSS(OPEN_LAYERS_CANVAS_SELECTOR).size()); + + // open detail view + Pages.getMetadataEditorPage().openDetailView(); + + // check it is visible now + pollAssertTrue(() -> findElementsByCSS(OPEN_LAYERS_CANVAS_SELECTOR).get(0).isDisplayed()); + } + + /** + * Tests whether the zoom buttons of the image preview (zoom in, out, reset) work as intended. + * @throws Exception when something fails + */ + @Test + public void zoomLevelTest() throws Exception { + login("kowal"); + + // open detail view and wait for openlayers canvas + Pages.getProcessesPage().goTo().editMetadata(PROCESS_TITLE); + Pages.getMetadataEditorPage().openDetailView(); + pollAssertTrue(() -> findElementsByCSS(OPEN_LAYERS_CANVAS_SELECTOR).get(0).isDisplayed()); + + // remember initial zoom + Double initialZoom = getOpenLayersZoom(); + assertTrue(initialZoom > 0); + + // zoom in, and check zoom increases + findElementsByCSS(OPEN_LAYERS_ZOOM_IN_SELECTOR).get(0).click(); + pollAssertTrue(() -> !isOpenLayersAnimating()); + assertTrue(() -> getOpenLayersZoom() > initialZoom); + + // zoom out, and check zoom returns to initial zoom level + findElementsByCSS(OPEN_LAYERS_ZOOM_OUT_SELECTOR).get(0).click(); + pollAssertTrue(() -> !isOpenLayersAnimating()); + assertTrue(() -> Math.abs(getOpenLayersZoom() - initialZoom) < EPSILON); + + // zoom in, and reset zoom, check zoom returns to initial zoom level + findElementsByCSS(OPEN_LAYERS_ZOOM_IN_SELECTOR).get(0).click(); + pollAssertTrue(() -> !isOpenLayersAnimating()); + findElementsByCSS(OPEN_LAYERS_ZOOM_RESET_SELECTOR).get(0).click(); + pollAssertTrue(() -> !isOpenLayersAnimating()); + assertTrue(() -> Math.abs(getOpenLayersZoom() - initialZoom) < EPSILON); + } + + /** + * Tests whether the rotation buttons of the image preview (rotation left, right, north) work as intended. + * @throws Exception when something fails + */ + @Test + public void rotationTest() throws Exception { + login("kowal"); + + // open detail view and wait for openlayers canvas + Pages.getProcessesPage().goTo().editMetadata(PROCESS_TITLE); + Pages.getMetadataEditorPage().openDetailView(); + pollAssertTrue(() -> findElementsByCSS(OPEN_LAYERS_CANVAS_SELECTOR).get(0).isDisplayed()); + + // check initial rotation is zero + assertTrue(Math.abs(getOpenLayersRotation()) < EPSILON); + + // rotate left and check rotation is decreasing + findElementsByCSS(OPEN_LAYERS_ROTATE_LEFT_SELECTOR).get(0).click(); + pollAssertTrue(() -> !isOpenLayersAnimating()); + assertTrue(() -> getOpenLayersRotation() < 0.0); + + // rotate back, and check rotation returns to zero + findElementsByCSS(OPEN_LAYERS_ROTATE_RIGHT_SELECTOR).get(0).click(); + pollAssertTrue(() -> !isOpenLayersAnimating()); + assertTrue(() -> Math.abs(getOpenLayersRotation()) < EPSILON); + + // rotate left and reset to north, check rotation returns to zero + findElementsByCSS(OPEN_LAYERS_ROTATE_LEFT_SELECTOR).get(0).click(); + pollAssertTrue(() -> !isOpenLayersAnimating()); + findElementsByCSS(OPEN_LAYERS_ROTATE_NORTH_SELECTOR).get(0).click(); + pollAssertTrue(() -> !isOpenLayersAnimating()); + assertTrue(() -> Math.abs(getOpenLayersRotation()) < EPSILON); + } + + /** + * Tests that both zoom level and rotation persists when a user clicks on another image + * (which causes OpenLayers to be loaded again). + * @throws Exception when something fails + */ + @Test + public void viewPersistsImageChange() throws Exception { + login("kowal"); + + // open detail view and wait for openlayers canvas + Pages.getProcessesPage().goTo().editMetadata(PROCESS_TITLE); + Pages.getMetadataEditorPage().openDetailView(); + pollAssertTrue(() -> findElementsByCSS(OPEN_LAYERS_CANVAS_SELECTOR).get(0).isDisplayed()); + + // remember initial zoom, rotation + Double initialZoom = getOpenLayersZoom(); + Double initialRotation = getOpenLayersRotation(); + + // rotate left and zoom in + findElementsByCSS(OPEN_LAYERS_ROTATE_LEFT_SELECTOR).get(0).click(); + findElementsByCSS(OPEN_LAYERS_ZOOM_IN_SELECTOR).get(0).click(); + pollAssertTrue(() -> !isOpenLayersAnimating()); + + // remember changed zoom, rotation + Double changedZoom = getOpenLayersZoom(); + Double changedRotation = getOpenLayersRotation(); + + // verify zoom and rotation was applied + assertTrue(Math.abs(initialZoom - changedZoom) > 0); + assertTrue(Math.abs(initialRotation - changedRotation) > 0); + + // change to second image + findElementsByCSS(SECOND_THUMBNAIL_SELECTOR).get(0).click(); + + // wait until second image has been loaded + pollAssertTrue( + () -> "Bild 1, Seite -".equals( + findElementsByCSS(GALLERY_HEADING_WRAPPER_SELECTOR).get(0).getText().strip() + ) + ); + + // wait until OpenLayers canvas is available + pollAssertTrue(() -> findElementsByCSS(OPEN_LAYERS_CANVAS_SELECTOR).get(0).isDisplayed()); + + // check that rotation and zoom was correctly applied to next image (and is not reset) + assertTrue(Math.abs(getOpenLayersZoom() - changedZoom) < EPSILON); + assertTrue(Math.abs(getOpenLayersRotation() - changedRotation) < EPSILON); + } + + /** + * Close metadata editor and logout after every test. + * @throws Exception when page navigation fails + */ + @AfterEach + public void closeEditorAndLogout() throws Exception { + Pages.getMetadataEditorPage().closeEditor(); + Pages.getTopNavigation().logout(); + } + + /** + * Cleanup test environment by removing temporal dummy processes from database and index. + * @throws DAOException when dummy process cannot be removed from database + * @throws CustomResponseException when dummy process cannot be removed from index + * @throws DataException when dummy process cannot be removed from index + * @throws IOException when deleting test files fails. + */ + @AfterAll + public static void cleanup() throws DAOException, CustomResponseException, DataException, IOException { + ProcessService.deleteProcess(processId); + } + + private void login(String username) throws InstantiationException, IllegalAccessException, InterruptedException { + User metadataUser = ServiceManager.getUserService().getByLogin(username); + Pages.getLoginPage().goTo().performLogin(metadataUser); + } + + private List findElementsByCSS(String css) { + return Browser.getDriver().findElements(By.cssSelector(css)); + } + + private void pollAssertTrue(Callable conditionEvaluator) throws Exception { + await().ignoreExceptions().pollInterval(100, TimeUnit.MILLISECONDS).atMost(3, TimeUnit.SECONDS) + .until(conditionEvaluator); + } + + private Boolean isOpenLayersAnimating() { + return (Boolean)Browser.getDriver().executeScript("return metadataEditor.detailMap.getAnimating()"); + } + + private Double getOpenLayersZoom() { + Object result = Browser.getDriver().executeScript("return metadataEditor.detailMap.getZoom()"); + return ((Number)result).doubleValue(); + } + + private Double getOpenLayersRotation() { + Object result = Browser.getDriver().executeScript("return metadataEditor.detailMap.getRotation()"); + return ((Number)result).doubleValue(); + } + +} diff --git a/Kitodo/src/test/java/org/kitodo/selenium/testframework/pages/MetadataEditorPage.java b/Kitodo/src/test/java/org/kitodo/selenium/testframework/pages/MetadataEditorPage.java index e6284c17a9d..131830957e6 100644 --- a/Kitodo/src/test/java/org/kitodo/selenium/testframework/pages/MetadataEditorPage.java +++ b/Kitodo/src/test/java/org/kitodo/selenium/testframework/pages/MetadataEditorPage.java @@ -78,6 +78,9 @@ public class MetadataEditorPage extends Page { @FindBy(id = "renamingMediaResultForm:okSuccess") private WebElement okButtonRenameMediaFiles; + @FindBy(id = "imagePreviewForm:previewButton") + private WebElement imagePreviewButton; + public MetadataEditorPage() { super("metadataEditor.jsf"); } @@ -222,4 +225,12 @@ public long getNumberOfDisplayedStructureElements() { return Browser.getDriver().findElements(By.cssSelector(".ui-treenode")).stream().filter(WebElement::isDisplayed) .count(); } + + /** + * Open detail view by clicking on image preview button. + */ + public void openDetailView() { + imagePreviewButton.click(); + } + } From 54362a14a024ce4945e82fa2922ab0bd7833dad5 Mon Sep 17 00:00:00 2001 From: Thomas Low Date: Tue, 1 Oct 2024 12:37:55 +0200 Subject: [PATCH 05/11] Add missing semicolons. --- Kitodo/src/main/webapp/WEB-INF/resources/js/ol_custom.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Kitodo/src/main/webapp/WEB-INF/resources/js/ol_custom.js b/Kitodo/src/main/webapp/WEB-INF/resources/js/ol_custom.js index 369dca897c1..69e9a63eb73 100644 --- a/Kitodo/src/main/webapp/WEB-INF/resources/js/ol_custom.js +++ b/Kitodo/src/main/webapp/WEB-INF/resources/js/ol_custom.js @@ -434,7 +434,7 @@ class KitodoDetailMap { */ getZoom() { if (this.#map) { - return this.#map.getView().getZoom() + return this.#map.getView().getZoom(); } return -1; } @@ -456,7 +456,7 @@ class KitodoDetailMap { */ getRotation() { if (this.#map) { - return this.#map.getView().getRotation() + return this.#map.getView().getRotation(); } return 0; } From 709180bfba04e2dbedd068b3c49b1d740fb4e463 Mon Sep 17 00:00:00 2001 From: Thomas Low Date: Sun, 20 Oct 2024 11:17:07 +0000 Subject: [PATCH 06/11] Allow to open page in new window showing media detail preview. --- .../forms/dataeditor/ExternalView.java | 40 +++++++++ .../resources/messages/messages_de.properties | 1 + .../resources/messages/messages_en.properties | 1 + .../resources/messages/messages_es.properties | 2 + .../webapp/WEB-INF/resources/css/kitodo.css | 23 +++++ .../includes/metadataEditor/gallery.xhtml | 11 +++ .../metadataEditor/logicalStructure.xhtml | 11 +++ .../src/main/webapp/pages/externalView.xhtml | 90 +++++++++++++++++++ 8 files changed, 179 insertions(+) create mode 100644 Kitodo/src/main/java/org/kitodo/production/forms/dataeditor/ExternalView.java create mode 100644 Kitodo/src/main/webapp/pages/externalView.xhtml diff --git a/Kitodo/src/main/java/org/kitodo/production/forms/dataeditor/ExternalView.java b/Kitodo/src/main/java/org/kitodo/production/forms/dataeditor/ExternalView.java new file mode 100644 index 00000000000..559a3e82774 --- /dev/null +++ b/Kitodo/src/main/java/org/kitodo/production/forms/dataeditor/ExternalView.java @@ -0,0 +1,40 @@ +/* + * (c) Kitodo. Key to digital objects e. V. + * + * This file is part of the Kitodo project. + * + * It is licensed under GNU General Public License version 3 or later. + * + * For the full copyright and license information, please read the + * GPL3-License.txt file that was distributed with this source code. + */ + +package org.kitodo.production.forms.dataeditor; + +import java.util.Objects; + +import javax.enterprise.context.RequestScoped; +import javax.inject.Named; + +/** + * Bean that is used in externalView.xhtml. + */ +@Named("ExternalView") +@RequestScoped +public class ExternalView { + + /** + * Return the shortened ID of the media by removing leading zeros. + * + * @param id the long id of the media file as string + * @return the shortened id of the media file + */ + public static String convertToShortId(String id) { + if (Objects.nonNull(id)) { + return id.replaceFirst("^0+(?!$)", ""); + } else { + return "-"; + } + } + +} diff --git a/Kitodo/src/main/resources/messages/messages_de.properties b/Kitodo/src/main/resources/messages/messages_de.properties index 1dd2fbc31c8..5d933d7d451 100644 --- a/Kitodo/src/main/resources/messages/messages_de.properties +++ b/Kitodo/src/main/resources/messages/messages_de.properties @@ -1197,6 +1197,7 @@ validator={0}-Bilder validieren value=Wert video=Video view=Anzeigen +viewPageInNewWindow=Seite in neuem Browser-Fenster öffnen visible=Sichtbar volume=Band of=von diff --git a/Kitodo/src/main/resources/messages/messages_en.properties b/Kitodo/src/main/resources/messages/messages_en.properties index ffaabc80ab9..ee02599e3fa 100644 --- a/Kitodo/src/main/resources/messages/messages_en.properties +++ b/Kitodo/src/main/resources/messages/messages_en.properties @@ -1198,6 +1198,7 @@ validator=Validate {0} images value=Value video=Video view=View +viewPageInNewWindow=View page in new browser window visible=Visible volume=volume of=of diff --git a/Kitodo/src/main/resources/messages/messages_es.properties b/Kitodo/src/main/resources/messages/messages_es.properties index 32deac1a1b6..f222c67dd90 100644 --- a/Kitodo/src/main/resources/messages/messages_es.properties +++ b/Kitodo/src/main/resources/messages/messages_es.properties @@ -1198,6 +1198,8 @@ validator=Validar {0}-imágenes value=Valor video=Video view=Ver +# please check google translation below and remove comment if translation is acceptable +viewPageInNewWindow=Ver página en una nueva ventana del navegador visible=Visible volume=Banda of=Desde 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 0af10140564..576bd3a347c 100644 --- a/Kitodo/src/main/webapp/WEB-INF/resources/css/kitodo.css +++ b/Kitodo/src/main/webapp/WEB-INF/resources/css/kitodo.css @@ -3697,6 +3697,29 @@ kbd { padding: 0 var(--default-half-size); } +#externalViewTitle { + color: var(--pure-white); + line-height: 1.5em; +} + +#externalViewPanel { + margin: auto 16px; +} + +#externalViewPanel, #externalViewPanel_content { + padding: 0; + height: 100%; + overflow: hidden; +} + +#externalViewPanel #imagePreviewForm\:mediaDetailMediaContainer { + height: 100%; +} + +#externalViewPanel video { + max-height: 90%; +} + /*---------------------------------------------------------------------- Workflow Editor ----------------------------------------------------------------------*/ diff --git a/Kitodo/src/main/webapp/WEB-INF/templates/includes/metadataEditor/gallery.xhtml b/Kitodo/src/main/webapp/WEB-INF/templates/includes/metadataEditor/gallery.xhtml index 963816c6116..b8576f5b9ea 100644 --- a/Kitodo/src/main/webapp/WEB-INF/templates/includes/metadataEditor/gallery.xhtml +++ b/Kitodo/src/main/webapp/WEB-INF/templates/includes/metadataEditor/gallery.xhtml @@ -249,6 +249,17 @@ paginationForm:paginationWrapperPanel metadataAccordion:logicalMetadataWrapperPanel galleryWrapperPanel"/> + + + + + + + + + + + +
diff --git a/Kitodo/src/main/webapp/pages/externalView.xhtml b/Kitodo/src/main/webapp/pages/externalView.xhtml new file mode 100644 index 00000000000..2edabc9bc45 --- /dev/null +++ b/Kitodo/src/main/webapp/pages/externalView.xhtml @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + +

+ + + + + + +

+
+ + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+
+
+ + + + +
+ +
\ No newline at end of file From d12b2b3df362f14ea2771298959a4dbb5152f1b8 Mon Sep 17 00:00:00 2001 From: Thomas Low Date: Sun, 20 Oct 2024 11:43:28 +0000 Subject: [PATCH 07/11] Do not show menu option to open new window if preview media is not available. --- .../WEB-INF/templates/includes/metadataEditor/gallery.xhtml | 4 +++- .../templates/includes/metadataEditor/logicalStructure.xhtml | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Kitodo/src/main/webapp/WEB-INF/templates/includes/metadataEditor/gallery.xhtml b/Kitodo/src/main/webapp/WEB-INF/templates/includes/metadataEditor/gallery.xhtml index b8576f5b9ea..67fe8f13071 100644 --- a/Kitodo/src/main/webapp/WEB-INF/templates/includes/metadataEditor/gallery.xhtml +++ b/Kitodo/src/main/webapp/WEB-INF/templates/includes/metadataEditor/gallery.xhtml @@ -250,7 +250,9 @@ metadataAccordion:logicalMetadataWrapperPanel galleryWrapperPanel"/> Date: Sun, 20 Oct 2024 20:14:57 +0000 Subject: [PATCH 08/11] Add selenium test verifying that page can be opened in new browser window. --- .../java/org/kitodo/selenium/MetadataST.java | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/Kitodo/src/test/java/org/kitodo/selenium/MetadataST.java b/Kitodo/src/test/java/org/kitodo/selenium/MetadataST.java index 031fc461cb4..de0a30bc3d5 100644 --- a/Kitodo/src/test/java/org/kitodo/selenium/MetadataST.java +++ b/Kitodo/src/test/java/org/kitodo/selenium/MetadataST.java @@ -353,6 +353,60 @@ public void showPhysicalPageNumberBelowThumbnailTest() throws Exception { assertFalse(Browser.getDriver().findElements(By.cssSelector(".thumbnail-banner")).isEmpty()); } + /** + * Verifies that an image can be openend in a separate window by clicking on the corresponding + * context menu item of the first logical tree node. + */ + @Test + public void openPageInSeparateWindowTest() throws Exception { + login("kowal"); + + // remember current window handle + String firstWindowHandle = Browser.getDriver().getWindowHandle(); + + // open the metadata editor + Pages.getProcessesPage().goTo().editMetadata(MockDatabase.MEDIA_RENAMING_TEST_PROCESS_TITLE); + + // wait until structure tree is shown + await().ignoreExceptions().pollDelay(100, TimeUnit.MILLISECONDS).atMost(5, TimeUnit.SECONDS) + .until(() -> Browser.getDriver().findElement(By.id("logicalTree")).isDisplayed()); + + // right click on first tree node representing image 2 + WebElement firstTreeNode = Browser.getDriver().findElement( + By.cssSelector("#logicalTree\\:0_0 .ui-treenode-content") + ); + new Actions(Browser.getDriver()).contextClick(firstTreeNode).build().perform(); + + // wait until menu is visible + await().ignoreExceptions().pollDelay(100, TimeUnit.MILLISECONDS).atMost(5, TimeUnit.SECONDS) + .until(() -> Browser.getDriver().findElement(By.id("contextMenuLogicalTree")).isDisplayed()); + + // click second menu entry to open new tab + Browser.getDriver().findElement(By.cssSelector( + "#contextMenuLogicalTree .ui-menuitem:nth-child(2) .ui-menuitem-link" + )).click(); + + // find handle of new tab window + String newWindowHandle = Browser.getDriver().getWindowHandles().stream() + .filter((h) -> !h.equals(firstWindowHandle)).findFirst().get(); + + // switch to new window + Browser.getDriver().switchTo().window(newWindowHandle); + + // wait until preview image is found + await().ignoreExceptions().pollDelay(100, TimeUnit.MILLISECONDS).atMost(5, TimeUnit.SECONDS) + .until(() -> !Browser.getDriver().findElements(By.id("imagePreviewForm:mediaPreviewGraphicImage")).isEmpty()); + + // check that title contains image number + assertEquals("Bild 2", Browser.getDriver().findElement(By.id("externalViewTitle")).getText()); + + // close tab + Browser.getDriver().close(); + + // switch back to previous window + Browser.getDriver().switchTo().window(firstWindowHandle); + } + /** * Close metadata editor and logout after every test. * @throws Exception when page navigation fails From dca883e3f1cf51c0ebab523cb8b57e2b09428a19 Mon Sep 17 00:00:00 2001 From: Thomas Low Date: Tue, 5 Nov 2024 14:01:00 +0000 Subject: [PATCH 09/11] Update references to openlayers 6.14.1 after merging with #6236. --- Kitodo/src/main/webapp/pages/externalView.xhtml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Kitodo/src/main/webapp/pages/externalView.xhtml b/Kitodo/src/main/webapp/pages/externalView.xhtml index 2edabc9bc45..9150bfde4b9 100644 --- a/Kitodo/src/main/webapp/pages/externalView.xhtml +++ b/Kitodo/src/main/webapp/pages/externalView.xhtml @@ -82,9 +82,15 @@ - - + + + + $(document).ready(function () { + // load openlayers and show image + metadataEditor.detailMap.update(); + }); + \ No newline at end of file From a70fa11b7c20647949fe73a682b541a6ffc9422a Mon Sep 17 00:00:00 2001 From: Thomas Low Date: Tue, 5 Nov 2024 14:07:39 +0000 Subject: [PATCH 10/11] Move context menu for opening new window to second position. --- .../includes/metadataEditor/gallery.xhtml | 26 +++++++++---------- .../metadataEditor/logicalStructure.xhtml | 26 +++++++++---------- 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/Kitodo/src/main/webapp/WEB-INF/templates/includes/metadataEditor/gallery.xhtml b/Kitodo/src/main/webapp/WEB-INF/templates/includes/metadataEditor/gallery.xhtml index dfdb9f11a1f..60086fd5895 100644 --- a/Kitodo/src/main/webapp/WEB-INF/templates/includes/metadataEditor/gallery.xhtml +++ b/Kitodo/src/main/webapp/WEB-INF/templates/includes/metadataEditor/gallery.xhtml @@ -201,6 +201,19 @@ update="dialogAddDocStrucTypeDialog"> + + + + + + - - - - - - + + + + + + - - - - - -
From 28292df5320e0b76be017992b00f99e6d8f80a9f Mon Sep 17 00:00:00 2001 From: Thomas Low Date: Tue, 5 Nov 2024 14:08:20 +0000 Subject: [PATCH 11/11] Add check for map canvas to detect potential openlayer problems in separate window. --- Kitodo/src/test/java/org/kitodo/selenium/MetadataST.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Kitodo/src/test/java/org/kitodo/selenium/MetadataST.java b/Kitodo/src/test/java/org/kitodo/selenium/MetadataST.java index 58be2c6906a..de1499ada37 100644 --- a/Kitodo/src/test/java/org/kitodo/selenium/MetadataST.java +++ b/Kitodo/src/test/java/org/kitodo/selenium/MetadataST.java @@ -450,6 +450,9 @@ public void openPageInSeparateWindowTest() throws Exception { // check that title contains image number assertEquals("Bild 2", Browser.getDriver().findElement(By.id("externalViewTitle")).getText()); + // check that canvas is visible + assertTrue(Browser.getDriver().findElement(By.cssSelector("#map canvas")).isDisplayed()); + // close tab Browser.getDriver().close();