Skip to content

Image component integration - #219

Open
hizclick wants to merge 81 commits into
developfrom
image-component-integration
Open

Image component integration#219
hizclick wants to merge 81 commits into
developfrom
image-component-integration

Conversation

@hizclick

@hizclick hizclick commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Integration of the edirom-image-viewer web component

Description, Context and related Issue

This PR integrates the new OpenSeadragon-based edirom-image-viewer web component into the Edirom Online Frontend and rewires the existing ExtJS image window to drive it. It replaces the previous tightly-coupled OpenSeadragon usage with a reusable, attribute-driven custom element (rendered in its own Shadow DOM), so the facsimile viewer can be developed and reused independently of the ExtJS application.

The result is a clean split of responsibilities: the component owns all viewer rendering (tiles, pagination, and every overlay — measures, measure labels, movements, annotation badges, and their tooltips), while ExtJS only provides data and the surrounding window UI. The host communicates with the component purely through attributes and custom events — no direct OpenSeadragon API calls remain in the ExtJS layer.

What this PR delivers:

  • New web component (resources/js/edirom-image-viewer/): a self-contained edirom-image-viewer.js custom element built on OpenSeadragon, with IIIF manifest / tile-source loading, sequence & collection modes, zoom/pan/rotation, configurable native controls, and a fully attribute-driven API plus custom events. Includes component metadata (README.md, LICENSE, CITATION.cff, CODE_OF_CONDUCT.md). The component is included as a git submodule.
  • ExtJS integration (OpenSeaDragonViewer.js): the ExtJS image viewer now instantiates the custom element and communicates with it purely through attributes/events instead of direct OpenSeadragon API calls — keeping all viewer state (page, zoom, rotation, controls) in sync with the surrounding Edirom UI (page spinner, top bar, source views).
  • Unified zones-data overlay model: every overlay type — annotations, measures, measure labels, and movements (mdivs) — is now pushed through one zones-data map keyed by a namespaced zone key (measure:<key>, mdiv:<key>, annotation:<…>). Each zone entry carries its type, optional page, rectangle (ulx/uly/lrx/lry), CSS classes (containerClass/innerClass), an optional label, a group (so badges on the same region stack), a preloaded tooltip HTML, a click fn, and generic filters tokens. Each sub-map (measures / mdivs / annotations) is merged on its own push, so re-pushing one type never drops the others. This replaces the former separate measures-data / mdivs-data / annotations-data / measure-numbers-data attributes.
  • Universal zone navigation attribute: navigation to any zone (a bar, a movement, or a generic region) goes through the single semantic zone attribute, set to the namespaced key with an optional |<nonce> token so repeated jumps to the same key re-fire. Same-page jumps fit the region directly; cross-page jumps switch page, wait for tiles, then fit — enabling the source / concordance views to jump the facsimile to a specific bar or movement, including across pages. Wired through PageBasedView / MeasureBasedView / SourceView.
  • Annotation overlays (push model): annotations are pushed as annotation-typed entries in zones-data; the component renders the clickable badges in its Shadow DOM and fires a zone-click event back to the host for the click action. The annotation tooltip is rendered by the component itself from a per-zone tooltip HTML field — the host preloads each annotation's server-rendered tooltip HTML up front (fetched in parallel, with a safety-net timeout) and pushes it into zones-data, and the relevant edition / overlay CSS is injected into the Shadow DOM so the tooltip is styled correctly.
  • Type visibility via visible-types: whole overlay types are shown/hidden through the visible-types attribute (a JSON array of the zone types to render, e.g. ["annotation","measure"]), without discarding the pushed zones-data. This is a pure visibility switch (no re-fetch / re-push) and replaces the former per-feature show-annotations / show-measure-numbers booleans.
  • Generic filtering via hidden-filters (two-way sync): category & priority filtering now uses a single generic hidden-filters set — the union of the taxonomy tokens the user has unchecked; the component hides any annotation carrying one of them. "No filter" is the empty set and "hide all" is every token, so the old ["undefined"] / [] sentinels and the separate visible-categories / visible-priorities attributes are gone. The component emits a filter-changed event (carrying detail.hiddenFilters) whenever the filter changes — including when hidden-filters is set externally (e.g. via DevTools) — so the host keeps its filter-menu checkboxes in sync with no feedback loop.
  • Measure labels folded into zones: measure-number boxes are no longer a separate attribute; each measure zone carries its label and label CSS class (measureInner / measureInnerEmpty), and they show/hide with the measure type via visible-types, re-applied on every page render.
  • Total-pages sync: the component fires a total-pages-changed event when its tile-source set shrinks (e.g. a page is removed), so the host trims its page list / spinner bounds and notifies the owning view.
  • Zoom & jump fixes: because OpenSeadragon's animation spring does not advance in this embedding, all viewport operations are forced to immediate mode, and setZoom now clamps to [minzoomlevel, maxzoomlevel] — fixing zoom-bar overshoot beyond the configured maximum and jump/zoom spring-back.
  • Fullscreen fix: the component overrides OpenSeadragon's default setFullScreen to use the standard Fullscreen API on the component host element.
  • Declarative view-mode: the active source view mode is pushed to the component via the view-mode attribute so host code can react.
  • Documentation: the component README.md documents all attributes (incl. preserveviewport, minzoomlevel, maxzoomlevel, zones-data, zone, visible-types, hidden-filters, fitrect, triggerhome, sequencemode, view-mode), public methods, events (page-changed, total-pages-changed, zone-click, filter-changed, zoom, image-ready), and the "Zones", "Region Navigation" and "Annotation Overlays" guides.

Refs #12

How Has This Been Tested?

Tested locally with the full Edirom Online stack via Docker Compose, building the frontend from this branch against the develop backend:

export FE_BRANCH=image-component-integration && \
export BE_BRANCH=develop && \
docker compose down --volumes --remove-orphans && \
docker compose build --no-cache && \
docker compose up

Manual verification in the running app (eXist-db at localhost:8080, app at /exist/apps/Edirom-Online-Frontend/):

  • Image loading & paging: sources load via the component; page spinner, next/previous, and direct page jumps stay in sync; sequence mode (true) loads a single page item and collection mode (false) loads all images. Removing a page fires total-pages-changed and trims the host page list / spinner.
  • Measure switching: push measures into zones-data, then trigger zone="measure:<key>|<nonce>". Verified both same-page and cross-page jumps fire page-changed and fit the region aspect-correctly and centered (e.g. a measure on page 2 with rect 1170,240 → 1360,1180).
  • Movement (mdiv) switching: push movements into zones-data and trigger zone="mdiv:<key>|<nonce>" to jump to a movement's first page; verified correct page/movement navigation from the source views.
  • Zoom clamping: with maxzoomlevel=5, setZoom(10) clamps to 5; with minzoomlevel=0.5, setZoom(0.01) clamps to 0.5; no jump/zoom spring-back.
  • Annotations: push annotation zones into zones-data (each carrying its type:'annotation', rect, innerClass, preloaded tooltip HTML, click fn, and filters tokens) and make them visible via viewer.setAttribute('visible-types', '["annotation"]'); the badge renders, hovering shows the component-rendered tooltip (styled via the injected CSS), and clicking fires zone-click so the host runs the action.
  • Type visibility: toggling visible-types between ["annotation","measure"] and [] shows/hides whole overlay types without re-pushing zones-data, and the chosen state persists across page changes.
  • Filtering (hidden-filters): viewer.setAttribute('hidden-filters', '["ediromAnnotPrio1"]') hides annotations carrying that token; '[]' shows all. Setting the attribute externally re-checks/unchecks the matching host menu items via the filter-changed event, with no feedback loop.
  • Measure labels: measure zones with a label render the numbered box and empty ones use measureInnerEmpty; visibility follows the measure type in visible-types and persists across page changes.
  • No-warning defaults: a freshly created viewer with zone="", visible-types="[]", hidden-filters="[]", view-mode="" produces no console warnings.

Types of changes

  • New feature (non-breaking change which adds functionality)
  • Bug fix (non-breaking change which fixes an issue)
  • Documentation Update
  • Improvement

Overview

  • I have updated the inline documentation accordingly.
  • I have performed a self-review of my code, according to the style guide
  • I have read the CONTRIBUTING document.

@DennisFriedl

Copy link
Copy Markdown
Member

I will hopefully be able to review this in the coming days. At a first glance I saw that the Image Viewer still references the very old 4.1.1 OpenSeaDragon version. Is there a reason for that?

Also, I think we should deliver this web component with the static minified file of the most recent OpenSeaDragon version in a vendor directory to make the component truly isolated and independet. Do you agree?

@DennisFriedl

DennisFriedl commented Aug 19, 2026

Copy link
Copy Markdown
Member

Also there is a dependency on font-awesome. Could we resolve this with edirom-icons instead?

@DennisFriedl

Copy link
Copy Markdown
Member

@hizclick

Copy link
Copy Markdown
Contributor Author

anks @DennisFriedl for the comments and the PRs. I used the old OpenSeadragon version because I continued working from the old Edirom code; there was no particular reason behind it.

I've approved your PRs.

I also think it's feasible and better to use the edirom-icon component instead of FontAwesome. I've created an issue for it here: #271

@DennisFriedl

Copy link
Copy Markdown
Member

Thank you, @hizclick .
I also noticed that the "Image Viewer" itself is still splitted into many controllers and views in ExtJS:

Controllers

  • MeasureBasedView.js
  • SourceView.js

Views

  • ImageViewer.js
  • OpenSeaDragonViewer.js
  • MeasureBasedView.js
  • PageBasedView.js
  • SourceView.js
  • FacsimileView.js
  • TextFacsimileSplitView.js

Could we maybe take the integration of this Web Component as an opportunity to tidy things up and to "merge" logic together (where it makes sense) for a more unified "Image Viewer" in Edirom?

@hizclick

Copy link
Copy Markdown
Contributor Author

I have created these PRs for issue #271
Front end -> #275
Image viewer -> Edirom/edirom-image-viewer#23

@hizclick

Copy link
Copy Markdown
Contributor Author

Thank you, @hizclick . I also noticed that the "Image Viewer" itself is still splitted into many controllers and views in ExtJS:

Controllers

  • MeasureBasedView.js
  • SourceView.js

Views

  • ImageViewer.js
  • OpenSeaDragonViewer.js
  • MeasureBasedView.js
  • PageBasedView.js
  • SourceView.js
  • FacsimileView.js
  • TextFacsimileSplitView.js

Could we maybe take the integration of this Web Component as an opportunity to tidy things up and to "merge" logic together (where it makes sense) for a more unified "Image Viewer" in Edirom?

Thanks @DennisFriedl , that's a good point, and I agree it could be a good opportunity to tidy this up further. I will take a closer look at the controller/view split across PageBasedView, MeasureBasedView, FacsimileView, and TextFacsimileSplitView and come back with a design proposal; l will share it here once I have something.

For now, as part of the current OpenSeadragon integration work: ImageViewer.jswill be removed, since it's no longer referenced anywhere; every view already goes through OpenSeaDragonViewer exclusively (including digilib sources, which are now handled as just another tile-source type inside the component). That's a safe, isolated first step regardless of how the bigger consolidation ends up looking.

Given the code freeze on September 7, I will take a look at this before then and see how much of the broader consolidation is realistic to fit in ahead of it.

…r.js since the diglib logic is now included in OpenSeaDragonViewer.js
@hizclick

Copy link
Copy Markdown
Contributor Author

.

Thank you, @hizclick . I also noticed that the "Image Viewer" itself is still splitted into many controllers and views in ExtJS:

Controllers

  • MeasureBasedView.js
  • SourceView.js

Views

  • ImageViewer.js
  • OpenSeaDragonViewer.js
  • MeasureBasedView.js
  • PageBasedView.js
  • SourceView.js
  • FacsimileView.js
  • TextFacsimileSplitView.js

Could we maybe take the integration of this Web Component as an opportunity to tidy things up and to "merge" logic together (where it makes sense) for a more unified "Image Viewer" in Edirom?

So the idea, as I understand it, is to reduce the "Image Viewer" from being reimplemented independently across every window type to a single widget (ImageViewer.js) plus one shared host-wiring layer, without collapsing structurally different orchestration logic into a false single file.

The plan would be

  1. to extract the genuinely duplicated host wiring as a setup logic
  2. but I would strain from touching
  • SourceView: doesn't host a viewer at all — it's a card-layout switcher between PageBasedView/MeasureBasedView. Also incompatible.
  • MeasureBasedView doesn't host one viewer; it pools N per-system ImageViewer instances (HorizontalMeasureViewer). Structurally incompatible with "one shared host."
  • Two controllers (Ext.app.Controller) bind to different xtypes (sourceView, measureBasedView, horizontalMeasureViewer); merging their event-routing into a view file breaks the framework's MVC convention

@DennisFriedl

Copy link
Copy Markdown
Member

I am not an OpenSeaDragon expert but could the MeasureBasedView maybe be solved inside the ImageViewer with this: https://openseadragon.github.io/examples/multi-image/ ?

@hizclick

Copy link
Copy Markdown
Contributor Author

I am not an OpenSeaDragon expert but could the MeasureBasedView maybe be solved inside the ImageViewer with this: https://openseadragon.github.io/examples/multi-image/ ?

It could, will check it out

@hizclick hizclick closed this Aug 28, 2026
@github-project-automation github-project-automation Bot moved this from Changes Requested to Done in Edirom Development Aug 28, 2026
@hizclick hizclick reopened this Aug 28, 2026
@hizclick

Copy link
Copy Markdown
Contributor Author

I am not an OpenSeaDragon expert but could the MeasureBasedView maybe be solved inside the ImageViewer with this: https://openseadragon.github.io/examples/multi-image/ ?

Following up on this, I looked into it, and I don't think it makes sense to switch MeasureBasedView to multi-image right now.

Multi-image is OpenSeaDragon's built-in support for loading several tile sources into one shared viewer/world, positioning each with x/y/width and cropping with clip, instead of the current approach where MeasureBasedView stitches together separate ImageViewer instances per system. Adding it doesn't sound like a good idea here because the case it improves is rare in practice while adopting it forces a rewrite of the overlay pipeline (measures, annotations, hover, click, home-bounds fitting) so every one of those resolves the correct image per zone instead of assuming a single fixed image, adds a manual resize-recompute step that CSS flexbox currently handles for free, and replaces a mechanism that already works correctly today; with no real reduction in code or improvement in the common case to show for it.

That said, for relevance elsewhere, it could still make sense to extend the component itself to support multi-image as a general capability, so it's available whenever a future use case (e.g. multi-source reconstructions)

@hizclick

Copy link
Copy Markdown
Contributor Author

Opened PR #276 to extract the duplicated ImageViewer host-side setup into a shared ImageViewerHost.js helper.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

[ftr] Integration of edirom-image-viewer web component

6 participants