From 1f55bf00a93829532e672500f0167ca2909713b1 Mon Sep 17 00:00:00 2001 From: K8Sewell Date: Mon, 30 Jun 2025 16:38:56 -0500 Subject: [PATCH 1/8] Reincorporate iiif gallery component back into main application --- package-lock.json | 29 - package.json | 1 - src/content-handlers/iiif/JQueryPlugins.ts | 2 + .../uv-contentleftpanel-module/GalleryView.ts | 2 +- .../css/styles.less | 1 - .../MultiSelectDialogue.ts | 2 +- .../uv-shared-module/GalleryComponent.ts | 766 ++++++++++++++++++ .../css/iiif-gallery-component.less | 251 ++++++ .../modules/uv-shared-module/css/mixins.less | 8 + .../modules/uv-shared-module/css/styles.less | 6 + 10 files changed, 1035 insertions(+), 33 deletions(-) create mode 100644 src/content-handlers/iiif/modules/uv-shared-module/GalleryComponent.ts create mode 100644 src/content-handlers/iiif/modules/uv-shared-module/css/iiif-gallery-component.less diff --git a/package-lock.json b/package-lock.json index f4b497724..66f554294 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,7 +15,6 @@ "@google/model-viewer": "^4.0.0", "@iiif/base-component": "2.0.1", "@iiif/iiif-av-component": "1.2.4", - "@iiif/iiif-gallery-component": "^1.1.23", "@iiif/iiif-metadata-component": "^1.2.1", "@iiif/iiif-tree-component": "^2.0.7", "@iiif/manifold": "^2.1.1", @@ -1248,34 +1247,6 @@ "webpack": "^4.0.0 || ^5.0.0" } }, - "node_modules/@iiif/iiif-gallery-component": { - "version": "1.1.23", - "resolved": "https://registry.npmjs.org/@iiif/iiif-gallery-component/-/iiif-gallery-component-1.1.23.tgz", - "integrity": "sha512-r6eUdBlCPhlqWp9fNiTkS0GeYFzFk0yXlEb4z1vRgGYdjRNUyPhe4doQvXgxQt7oShPLdpjfl92etydBwA6pfQ==", - "license": "MIT", - "dependencies": { - "@edsilv/jquery-plugins": "1.0.7", - "@edsilv/utils": "1.0.2", - "@iiif/base-component": "2.0.1", - "@iiif/manifold": "2.*", - "@iiif/vocabulary": "1.0.11", - "@types/jquery": "3.3.14", - "manifesto.js": "4.*" - } - }, - "node_modules/@iiif/iiif-gallery-component/node_modules/@iiif/vocabulary": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@iiif/vocabulary/-/vocabulary-1.0.11.tgz", - "integrity": "sha512-JjPbZ+SCn0ljsfs9Nf0U1OWNZK7tauw7iHezDJA+28AAzmMwpFS/lTOe/4N0ynZsnk4x7cA9NL6CK3K0zDd50w==" - }, - "node_modules/@iiif/iiif-gallery-component/node_modules/@types/jquery": { - "version": "3.3.14", - "resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.3.14.tgz", - "integrity": "sha512-M6m6Xm6RtsmYOlGk7YS0D7T19Axsc3x30+Mj9b7Fqb4c7c2hPmvBJsrMmuhwJy96iSx/3BQkOmbtEKijs2iQPg==", - "dependencies": { - "@types/sizzle": "*" - } - }, "node_modules/@iiif/iiif-metadata-component": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@iiif/iiif-metadata-component/-/iiif-metadata-component-1.2.1.tgz", diff --git a/package.json b/package.json index 2775492fd..879e42519 100644 --- a/package.json +++ b/package.json @@ -103,7 +103,6 @@ "@google/model-viewer": "^4.0.0", "@iiif/base-component": "2.0.1", "@iiif/iiif-av-component": "1.2.4", - "@iiif/iiif-gallery-component": "^1.1.23", "@iiif/iiif-metadata-component": "^1.2.1", "@iiif/iiif-tree-component": "^2.0.7", "@iiif/manifold": "^2.1.1", diff --git a/src/content-handlers/iiif/JQueryPlugins.ts b/src/content-handlers/iiif/JQueryPlugins.ts index 921295626..65be3e2b0 100644 --- a/src/content-handlers/iiif/JQueryPlugins.ts +++ b/src/content-handlers/iiif/JQueryPlugins.ts @@ -558,11 +558,13 @@ interface JQuery { css: any; append: any; text: any; + link: any; toggle: any; html: any; empty: any; one: any; remove: any; + render: any; height: any; contents: any; outerWidth: any; diff --git a/src/content-handlers/iiif/modules/uv-contentleftpanel-module/GalleryView.ts b/src/content-handlers/iiif/modules/uv-contentleftpanel-module/GalleryView.ts index a935aa407..b73539015 100644 --- a/src/content-handlers/iiif/modules/uv-contentleftpanel-module/GalleryView.ts +++ b/src/content-handlers/iiif/modules/uv-contentleftpanel-module/GalleryView.ts @@ -1,7 +1,7 @@ import { IIIFEvents } from "../../IIIFEvents"; import { ContentLeftPanel } from "../../extensions/config/ContentLeftPanel"; import { BaseView } from "../uv-shared-module/BaseView"; -import { GalleryComponent } from "@iiif/iiif-gallery-component"; +import { GalleryComponent } from "../uv-shared-module/GalleryComponent"; import $ from "jquery"; export class GalleryView extends BaseView { diff --git a/src/content-handlers/iiif/modules/uv-contentleftpanel-module/css/styles.less b/src/content-handlers/iiif/modules/uv-contentleftpanel-module/css/styles.less index fa25251a1..bb4a82457 100644 --- a/src/content-handlers/iiif/modules/uv-contentleftpanel-module/css/styles.less +++ b/src/content-handlers/iiif/modules/uv-contentleftpanel-module/css/styles.less @@ -1,4 +1,3 @@ -@import "@iiif/iiif-gallery-component/dist-css/styles.less"; @import "@iiif/iiif-tree-component/dist-css/styles.less"; @import "../../uv-shared-module/css/thumbs-view.less"; diff --git a/src/content-handlers/iiif/modules/uv-multiselectdialogue-module/MultiSelectDialogue.ts b/src/content-handlers/iiif/modules/uv-multiselectdialogue-module/MultiSelectDialogue.ts index bb47f06bd..87678e58b 100644 --- a/src/content-handlers/iiif/modules/uv-multiselectdialogue-module/MultiSelectDialogue.ts +++ b/src/content-handlers/iiif/modules/uv-multiselectdialogue-module/MultiSelectDialogue.ts @@ -4,7 +4,7 @@ import { Dialogue } from "../uv-shared-module/Dialogue"; import OpenSeadragonExtension from "../../extensions/uv-openseadragon-extension/Extension"; import { Mode } from "../../extensions/uv-openseadragon-extension/Mode"; import { Bools } from "@edsilv/utils"; -import { GalleryComponent } from "@iiif/iiif-gallery-component"; +import { GalleryComponent } from "../uv-shared-module/GalleryComponent"; // import { GalleryComponent } from "../../GalleryComponent"; import { MultiSelectState } from "@iiif/manifold"; import { Config } from "../../extensions/uv-openseadragon-extension/config/Config"; diff --git a/src/content-handlers/iiif/modules/uv-shared-module/GalleryComponent.ts b/src/content-handlers/iiif/modules/uv-shared-module/GalleryComponent.ts new file mode 100644 index 000000000..69abaf229 --- /dev/null +++ b/src/content-handlers/iiif/modules/uv-shared-module/GalleryComponent.ts @@ -0,0 +1,766 @@ +import { ViewingDirection } from "@iiif/vocabulary"; +import { Canvas, Range, Thumb } from "manifesto.js"; +import { + AnnotationGroup, + Helper, + MultiSelectableThumb, + MultiSelectState, + MultiSelectableCanvas, + MultiSelectableRange, +} from "@iiif/manifold"; +import { BaseComponent, IBaseComponentOptions } from "@iiif/base-component"; +import { Strings, Maths } from "@edsilv/utils"; + +export interface IGalleryComponentContent { + searchResult: string; + searchResults: string; + select: string; + selectAll: string; +} + +export interface IGalleryComponentData { + chunkedResizingThreshold?: number; + content?: IGalleryComponentContent; + debug?: boolean; + helper?: Helper | null; + imageFadeInDuration?: number; + initialZoom?: number; + minLabelWidth?: number; + pageModeEnabled?: boolean; + searchResults?: AnnotationGroup[]; + scrollStopDuration?: number; + sizingEnabled?: boolean; + thumbHeight?: number; + thumbLoadPadding?: number; + thumbWidth?: number; + viewingDirection?: ViewingDirection; +} + +export class GalleryComponent extends BaseComponent { + public options: IBaseComponentOptions; + + private _$element: JQuery; + private _$header: JQuery; + private _$leftOptions: JQuery; + private _$main: JQuery; + private _$multiSelectOptions: JQuery; + private _$rightOptions: JQuery; + private _$selectAllButton: JQuery; + private _$selectAllButtonCheckbox: JQuery; + private _$selectButton: JQuery; + private _$selectedThumb: JQuery; + private _$sizeDownButton: JQuery; + private _$sizeRange: JQuery; + private _$sizeUpButton: JQuery; + private _$thumbs: JQuery; + private _data: IGalleryComponentData = this.data(); + private _range: number; + private _thumbs: MultiSelectableThumb[]; + private _thumbsCache: JQuery | null; + + constructor(options: IBaseComponentOptions) { + super(options); + this._data = this.options.data; + this._init(); + this._resize(); + } + + protected _init(): boolean { + super._init(); + + this._$element = $(this.el); + + this._$header = $('
'); + this._$element.append(this._$header); + + this._$leftOptions = $('
'); + this._$header.append(this._$leftOptions); + + this._$rightOptions = $('
'); + this._$header.append(this._$rightOptions); + + this._$sizeDownButton = $( + '' + ); + this._$leftOptions.append(this._$sizeDownButton); + + this._$sizeRange = $( + '' + ); + this._$leftOptions.append(this._$sizeRange); + + this._$sizeUpButton = $( + '' + ); + this._$leftOptions.append(this._$sizeUpButton); + + this._$multiSelectOptions = $('
'); + this._$rightOptions.append(this._$multiSelectOptions); + + this._$selectAllButton = $( + '
" + ); + this._$multiSelectOptions.append(this._$selectAllButton); + this._$selectAllButtonCheckbox = $( + this._$selectAllButton.find("input:checkbox") + ); + + this._$selectButton = $( + '' + this.options.data.content.select + "" + ); + this._$multiSelectOptions.append(this._$selectButton); + + this._$main = $('
'); + this._$element.append(this._$main); + + this._$thumbs = $('
'); + this._$main.append(this._$thumbs); + + this._$sizeDownButton.on("click", () => { + var val = Number(this._$sizeRange.val()) - 1; + + if (val >= Number(this._$sizeRange.attr("min"))) { + this._$sizeRange.val(val.toString()); + this._$sizeRange.trigger("change"); + this.fire(Events.DECREASE_SIZE); + } + }); + + this._$sizeUpButton.on("click", () => { + var val = Number(this._$sizeRange.val()) + 1; + + if (val <= Number(this._$sizeRange.attr("max"))) { + this._$sizeRange.val(val.toString()); + this._$sizeRange.trigger("change"); + this.fire(Events.INCREASE_SIZE); + } + }); + + this._$sizeRange.on("change", () => { + this._updateThumbs(); + this._scrollToThumb(this._getSelectedThumbIndex()); + }); + + this._$selectAllButton.checkboxButton((checked: boolean) => { + const multiSelectState: MultiSelectState | null = + this._getMultiSelectState(); + + if (multiSelectState) { + if (checked) { + multiSelectState.selectAll(true); + } else { + multiSelectState.selectAll(false); + } + } + + this.set(this.options.data); + }); + + this._$selectButton.on("click", () => { + const multiSelectState: MultiSelectState | null = + this._getMultiSelectState(); + + if (multiSelectState) { + var ids: string[] = multiSelectState + .getAllSelectedCanvases() + .map((canvas: Canvas) => { + return canvas.id; + }); + + this.fire(Events.MULTISELECTION_MADE, ids); + } + }); + + this._setRange(); + + // use unevent to detect scroll stop. + this._$main.on( + "scroll", + () => { + this._updateThumbs(); + }, + this.options.data.scrollStopDuration + ); + + if (!this.options.data.sizingEnabled) { + this._$sizeRange.hide(); + } + + return true; + } + + public data(): IGalleryComponentData { + return { + chunkedResizingThreshold: 400, + content: { + searchResult: "{0} search result", + searchResults: "{0} search results", + select: "Select", + selectAll: "Select All", + }, + debug: false, + helper: null, + imageFadeInDuration: 300, + initialZoom: 6, + minLabelWidth: 20, + pageModeEnabled: false, + scrollStopDuration: 100, + searchResults: [], + sizingEnabled: true, + thumbHeight: 320, + thumbLoadPadding: 3, + thumbWidth: 200, + viewingDirection: ViewingDirection.LEFT_TO_RIGHT, + } as IGalleryComponentData; + } + + public set(data: IGalleryComponentData): void { + this._data = Object.assign(this._data, data); + + if ( + this._data.helper && + this._data.thumbWidth !== undefined && + this._data.thumbHeight !== undefined + ) { + this._thumbs = ( + this._data.helper.getThumbs( + this._data.thumbWidth, + this._data.thumbHeight + ) + ); + } + + if (this._data.viewingDirection) { + if (this._data.viewingDirection === ViewingDirection.BOTTOM_TO_TOP) { + this._thumbs.reverse(); + } + + this._$thumbs.addClass(this._data.viewingDirection); // defaults to "left-to-right" + } + + if (this._data.searchResults && this._data.searchResults.length) { + for (let i = 0; i < this._data.searchResults.length; i++) { + var searchResult: AnnotationGroup = this._data.searchResults[i]; + + // find the thumb with the same canvasIndex and add the searchResult + const thumb: Thumb = this._thumbs.filter( + (t) => t.index === searchResult.canvasIndex + )[0]; + + // clone the data so searchResults isn't persisted on the canvas. + const data = $.extend(true, {}, thumb.data); + data.searchResults = searchResult.rects.length; + thumb.data = data; + } + } + + this._thumbsCache = null; // delete cache + + this._createThumbs(); + + if (this._data.helper) { + this.selectIndex(this._data.helper.canvasIndex); + } + + const multiSelectState: MultiSelectState | null = + this._getMultiSelectState(); + + if (multiSelectState && multiSelectState.isEnabled) { + this._$multiSelectOptions.show(); + this._$thumbs.addClass("multiSelect"); + + for (let i = 0; i < multiSelectState.canvases.length; i++) { + const canvas: MultiSelectableCanvas = multiSelectState.canvases[i]; + const thumb: Thumb = this._getThumbByCanvas(canvas); + this._updateThumbHtmlMultiSelected(thumb.index, canvas.multiSelected); + } + + // range selections override canvas selections + for (let i = 0; i < multiSelectState.ranges.length; i++) { + const range: MultiSelectableRange = multiSelectState.ranges[i]; + const thumbs: Thumb[] = this._getThumbsByRange(range); + + for (let i = 0; i < thumbs.length; i++) { + const thumb: Thumb = thumbs[i]; + this._updateThumbHtmlMultiSelected(thumb.index, range.multiSelected); + } + } + } else { + this._$multiSelectOptions.hide(); + this._$thumbs.removeClass("multiSelect"); + } + + // this._update(); + } + + private _update(): void { + var multiSelectState: MultiSelectState | null = this._getMultiSelectState(); + + if (multiSelectState && multiSelectState.isEnabled) { + // check/uncheck Select All checkbox + this._$selectAllButtonCheckbox.prop( + "checked", + multiSelectState.allSelected() + ); + + const anySelected: boolean = + multiSelectState.getAll().filter((t) => t.multiSelected).length > 0; + + if (!anySelected) { + this._$selectButton.hide(); + } else { + this._$selectButton.show(); + } + } + } + + private _getMultiSelectState(): MultiSelectState | null { + if (this._data.helper) { + return this._data.helper.getMultiSelectState(); + } + + return null; + } + + private _escapeHtml = (text: string | number | boolean): string => { + return String(text) + .replace(/&/g, "&") // Escape '&' first to avoid double escaping + .replace(//g, ">") // Escape '>' + .replace(/"/g, """) // Escape '"' + .replace(/'/g, "'"); // Escape "'" + }; + + private _galleryThumbsTemplate = (thumb: MultiSelectableThumb): string => { + const multiSelectEnabled = thumb.multiSelectEnabled; + + const galleryThumbClassName = this._escapeHtml( + this._galleryThumbClassName(thumb) + ); + const label = this._escapeHtml(thumb.label); + const uri = this._escapeHtml(thumb.uri); + const index = this._escapeHtml(thumb.index); + const visible = this._escapeHtml(thumb.visible); + const width = this._escapeHtml(thumb.width); + const height = this._escapeHtml(thumb.height); + const initialWidth = this._escapeHtml(thumb.initialWidth); + const initialHeight = this._escapeHtml(thumb.initialHeight); + const searchResults = this._escapeHtml(thumb.data.searchResults || ""); + const searchResultsTitle = this._escapeHtml( + this._galleryThumbSearchResultsTitle(thumb) || "" + ); + + const htmlTemplate = ` + + `; + + return htmlTemplate; + }; + + private _galleryThumbClassName = (thumb: Thumb): string => { + let className = "thumb preLoad"; + if (thumb.index === 0) { + className += " first"; + } + if (!thumb.uri) { + className += " placeholder"; + } + return className; + }; + + private _galleryThumbSearchResultsTitle = (thumb: Thumb): string | null => { + const searchResults = Number(thumb.data.searchResults); + if (searchResults) { + if (searchResults > 1) { + return Strings.format( + this.options.data.content.searchResults, + searchResults.toString() + ); + } + return Strings.format( + this.options.data.content.searchResults, + searchResults.toString() + ); + } + return null; + }; + + private _createThumbs(): void { + const that = this; + + if (!this._thumbs) return; + + this._$thumbs.undelegate(".thumb", "click"); + this._$thumbs.empty(); + + const multiSelectState: MultiSelectState | null = + this._getMultiSelectState(); + + // set initial thumb sizes + const heights: number[] = []; + + for (let i = 0; i < this._thumbs.length; i++) { + const thumb: MultiSelectableThumb = this._thumbs[i]; + const initialWidth: number = thumb.width; + const initialHeight: number = thumb.height; + thumb.initialWidth = initialWidth; + //thumb.initialHeight = initialHeight; + heights.push(initialHeight); + thumb.multiSelectEnabled = multiSelectState + ? multiSelectState.isEnabled + : false; + } + + const medianHeight: number = Maths.median(heights); + + for (let i = 0; i < this._thumbs.length; i++) { + const thumb: MultiSelectableThumb = this._thumbs[i]; + thumb.initialHeight = medianHeight; + } + + const renderedHtml = this._thumbs.map(this._galleryThumbsTemplate).join(""); + this._$thumbs.html(renderedHtml); + + if (multiSelectState && !multiSelectState.isEnabled) { + // add a selection click event to all thumbs + this._$thumbs.delegate(".thumb", "click", function (e: any) { + const thumbIndex = parseInt(this.dataset.index as string); + const thumb: MultiSelectableThumb = that._thumbs[thumbIndex]; + that.fire(Events.THUMB_SELECTED, thumb); + }); + } else { + // make each thumb a checkboxButton + const thumbs: JQuery = this._$thumbs.find(".thumb"); + + for (var i = 0; i < thumbs.length; i++) { + const that = this; + const $thumb = $(thumbs[i]); + + $thumb.checkboxButton(function (_checked: boolean) { + const thumbIndex = parseInt(this.dataset.index as string); + const thumb: MultiSelectableThumb = that._thumbs[thumbIndex]; + const multiSelected = that._getThumbMultiSelected(thumbIndex); + that._updateThumbHtmlMultiSelected(thumb.index, multiSelected); + const range: MultiSelectableRange = ( + that.options.data.helper.getCanvasRange(thumb.data) + ); + const multiSelectState: MultiSelectState | null = + that._getMultiSelectState(); + + if (multiSelectState) { + if (range) { + multiSelectState.selectRange( + range, + multiSelected + ); + } else { + multiSelectState.selectCanvas( + thumb.data, + multiSelected + ); + } + } + + that._update(); + + that.fire(Events.THUMB_MULTISELECTED, thumb); + }); + } + } + } + + private _getThumbMultiSelected(thumbIndex: number): boolean { + const $checkbox = this._getThumbByIndex(thumbIndex).find( + `#thumb-checkbox-${thumbIndex}` + ); + return $checkbox.prop("checked"); + } + + private _getThumbByCanvas(canvas: Canvas): Thumb { + return this._thumbs.filter((c) => c.data.id === canvas.id)[0]; + } + + private _sizeThumb($thumb: JQuery): void { + const initialWidth: number = $thumb.data().initialwidth; + const initialHeight: number = $thumb.data().initialheight; + + const width: number = Number(initialWidth); + const height: number = Number(initialHeight); + + const newWidth: number = Math.floor(width * this._range); + const newHeight: number = Math.floor(height * this._range); + + const $wrap: JQuery = $thumb.find(".wrap"); + const $label: JQuery = $thumb.find(".label"); + const $index: JQuery = $thumb.find(".index"); + const $searchResults: JQuery = $thumb.find(".searchResults"); + + let newLabelWidth: number = newWidth; + + // if search results are visible, size index/label to accommodate it. + // if the resulting size is below options.minLabelWidth, hide search results. + if (this._data.searchResults && this._data.searchResults.length) { + $searchResults.show(); + + newLabelWidth = newWidth - ($searchResults).outerWidth(); + + if ( + this._data.minLabelWidth !== undefined && + newLabelWidth < this._data.minLabelWidth + ) { + $searchResults.hide(); + newLabelWidth = newWidth; + } else { + $searchResults.show(); + } + } + + if (this._data.pageModeEnabled) { + $index.hide(); + $label.show(); + } else { + $index.show(); + $label.hide(); + } + + $wrap.outerWidth(newWidth); + $wrap.outerHeight(newHeight); + $index.outerWidth(newLabelWidth); + $label.outerWidth(newLabelWidth); + } + + private _loadThumb($thumb: JQuery, cb?: (img: JQuery) => void): void { + const $wrap: JQuery = $thumb.find(".wrap"); + + if ($wrap.hasClass("loading") || $wrap.hasClass("loaded")) return; + + $thumb.removeClass("preLoad"); + + // if no img has been added yet + + const visible: string | undefined = $thumb.attr("data-visible"); + const fadeDuration: number = this._data.imageFadeInDuration || 0; + + if (visible !== "false") { + $wrap.addClass("loading"); + const src: string | undefined = $thumb.attr("data-src"); + const $img: JQuery = $(''); + // fade in on load. + $img.hide(); + + $img.on("load", function () { + $(this).fadeIn(fadeDuration, function () { + ($(this).parent()).switchClass("loading", "loaded"); + }); + }); + + $wrap.prepend($img); + + if (cb) cb($img); + } else { + $wrap.addClass("hidden"); + } + } + + private _getThumbsByRange(range: Range): Thumb[] { + const thumbs: Thumb[] = []; + + if (!this._data.helper) { + return thumbs; + } + + for (let i = 0; i < this._thumbs.length; i++) { + const thumb: Thumb = this._thumbs[i]; + const canvas: Canvas = thumb.data; + const r: Range = ( + this._data.helper.getCanvasRange(canvas, range.path) + ); + + if (r && r.id === range.id) { + thumbs.push(thumb); + } + } + + return thumbs; + } + + private _updateThumbs(): void { + const debug: boolean = !!this._data.debug; + + // cache range size + this._setRange(); + + const scrollTop: number | undefined = this._$main.scrollTop(); + const scrollHeight: number | undefined = this._$main.height(); + const scrollBottom: number = + (scrollTop as number) + (scrollHeight as number); + + if (debug) { + console.log("scrollTop %s, scrollBottom %s", scrollTop, scrollBottom); + } + + // test which thumbs are scrolled into view + const thumbs = this._getAllThumbs(); + + let numToUpdate: number = 0; + + for (let i = 0; i < thumbs.length; i++) { + const $thumb: JQuery = $(thumbs[i]); + const thumbTop: number = $thumb.position().top; + const thumbHeight: number | undefined = $thumb.outerHeight(); + const thumbBottom: number = thumbTop + (thumbHeight as number); + + const padding: number = + (thumbHeight as number) * (this._data.thumbLoadPadding as number); + + // check all thumbs to see if they are within the scroll area plus padding + if ( + thumbTop <= scrollBottom + padding && + thumbBottom >= (scrollTop as number) - padding + ) { + numToUpdate += 1; + + //let $label: JQuery = $thumb.find('span:visible').not('.searchResults'); + + // if (debug) { + // $thumb.addClass('debug'); + // $label.empty().append('t: ' + thumbTop + ', b: ' + thumbBottom); + // } else { + // $thumb.removeClass('debug'); + // } + + this._sizeThumb($thumb); + + $thumb.addClass("insideScrollArea"); + + // if (debug) { + // $label.append(', i: true'); + // } + + this._loadThumb($thumb); + } else { + $thumb.removeClass("insideScrollArea"); + + // if (debug) { + // $label.append(', i: false'); + // } + } + } + + if (debug) { + console.log("number of thumbs to update: " + numToUpdate); + } + } + + private _getSelectedThumbIndex(): number { + return Number(this._$selectedThumb.data("index")); + } + + private _getAllThumbs(): JQuery { + if (!this._thumbsCache) { + this._thumbsCache = this._$thumbs.find(".thumb"); + } + return this._thumbsCache; + } + + private _getThumbByIndex(canvasIndex: number): JQuery { + return this._$thumbs.find('[data-index="' + canvasIndex + '"]'); + } + + private _scrollToThumb(canvasIndex: number): void { + const $thumb: JQuery = this._getThumbByIndex(canvasIndex); + this._$main.scrollTop($thumb.position().top); + } + + // these don't work well because thumbs are loaded in chunks + + // public searchPreviewStart(canvasIndex: number): void { + // this._scrollToThumb(canvasIndex); + // const $thumb: JQuery = this._getThumbByIndex(canvasIndex); + // $thumb.addClass('searchpreview'); + // } + + // public searchPreviewFinish(): void { + // this._scrollToThumb(this._data.helper.canvasIndex); + // this._getAllThumbs().removeClass('searchpreview'); + // } + + public selectIndex(index: number): void { + if (!this._thumbs || !this._thumbs.length) return; + this._getAllThumbs().removeClass("selected"); + this._$selectedThumb = this._getThumbByIndex(index); + this._$selectedThumb.addClass("selected"); + this._scrollToThumb(index); + // make sure visible images are loaded. + this._updateThumbs(); + } + + private _setRange(): void { + const norm = Maths.normalise(Number(this._$sizeRange.val()), 0, 10); + this._range = Maths.clamp(norm, 0.05, 1); + } + + // Update the DOM when the multiSelected state changes + private _updateThumbHtmlMultiSelected( + thumbIndex: number, + multiSelected: boolean + ): void { + const $thumb = this._getThumbByIndex(thumbIndex); + + // Update the "wrap" div class + const $wrap = $thumb.find(".wrap"); + if (multiSelected) { + $wrap.addClass("multiSelected"); + } else { + $wrap.removeClass("multiSelected"); + } + + // Update all the checkbox state + const $checkbox = $thumb.find(`#thumb-checkbox-${thumbIndex}`); + if ($checkbox.length) { + $checkbox.prop("checked", multiSelected); + } + } + + protected _resize(): void {} +} + +class Events { + static DECREASE_SIZE: string = "decreaseSize"; + static INCREASE_SIZE: string = "increaseSize"; + static MULTISELECTION_MADE: string = "multiSelectionMade"; + static THUMB_SELECTED: string = "thumbSelected"; + static THUMB_MULTISELECTED: string = "thumbMultiSelected"; +} diff --git a/src/content-handlers/iiif/modules/uv-shared-module/css/iiif-gallery-component.less b/src/content-handlers/iiif/modules/uv-shared-module/css/iiif-gallery-component.less new file mode 100644 index 000000000..cd62b66a8 --- /dev/null +++ b/src/content-handlers/iiif/modules/uv-shared-module/css/iiif-gallery-component.less @@ -0,0 +1,251 @@ +.iiif-gallery-component { + // Reset the box-sizing + *, + *::before, + *::after { + .box-sizing(border-box); + } + + .header { + overflow: hidden; + height: 45px; + + .left { + float: left; + overflow: hidden; + } + + .right { + float: right; + overflow: hidden; + + .multiSelectOptions { + .multiSelectAll { + float: left; + } + + .select { + margin-left: @margin-medium-horizontal; + } + } + } + + .btn { + float: left; + width: 30px; + height: 30px; + + &.size-up { + margin-left: 5px; + } + + &.size-down { + margin-right: 5px; + } + } + + input[type="range"] { + float: left; + margin-top: 12px; + width: 100px; + } + } + + .main { + overflow: auto; + overflow-x: hidden; + height: 1000px; // main must have a default height, otherwise all thumbs will be initially loaded + + .thumbs { + margin-right: @margin-medium-horizontal; + overflow: hidden; + position: relative; + + // thumb spacing-related styles + + &.multiSelect { + .thumb { + input { + display: none; + } + .wrap.multiSelected { + background: none; + border: 2px solid transparent; + } + } + .thumb.insideScrollArea { + input { + display: block; + } + .wrap.multiSelected { + border: 2px solid @brand-primary; + } + } + } + + .thumb { + background-color: inherit; + border: 0; + padding: 0; + text-align: left; + + margin: 0 7px 7px 0; + + .wrap { + //border: 2px dotted grey; // debug + border: 2px solid transparent; + padding: 2px; + + img { + display: none; + } + } + + .info { + span { + color: transparent; + padding: 2px 0 0; + } + + .searchResults { + background: none; + } + } + + &.selected { + .wrap { + border: 2px solid transparent; + } + } + } + + .thumb.insideScrollArea { + .wrap { + border: 2px solid @panel-border-color; + + img { + display: block; + } + } + + .info { + span { + color: #fff; + } + + .searchResults { + color: @brand-primary; + background-image: data-uri("../img/search_result.png"); + background-repeat: no-repeat; + background-position-x: 0; + background-position-y: 3px; + } + } + + &.selected { + .wrap { + border: 2px solid #fff; + } + } + + &.searchpreview { + .wrap { + border: 2px solid @link-color; + } + } + } + + .thumb { + float: left; + cursor: pointer; + + .wrap { + overflow: hidden; + text-align: center; + background: none; + position: relative; + + &.loading { + .loading(@loader-black-bg); + } + + &.hidden { + background-image: data-uri("../img/hidden_thumb.png"); + background-repeat: no-repeat; + background-position-x: 50%; + background-position-y: 50%; + } + + img { + max-width: 100%; + max-height: 100%; + } + + input { + position: absolute; + cursor: pointer; + top: 0; + left: 0; + } + } + + .info { + overflow: hidden; + + span { + font-size: @font-size-small; + display: block; + overflow-x: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + .index, + .label { + float: left; + } + + .searchResults { + display: none; + float: right; + width: 32px; + padding-left: 14px; + } + } + + &.placeholder { + .wrap { + background-image: data-uri("../img/unavailable.png"); + background-repeat: no-repeat; + background-position-x: 50%; + background-position-y: 50%; + } + } + } + + &.multiSelect { + .thumb { + .wrap.multiSelected { + background: @brand-primary; + + .thumbImage { + .opacity(0.35); + } + } + } + } + + &.left-to-right { + .thumb { + float: left; + } + } + + &.right-to-left { + .thumb { + float: right; + } + } + } + } +} diff --git a/src/content-handlers/iiif/modules/uv-shared-module/css/mixins.less b/src/content-handlers/iiif/modules/uv-shared-module/css/mixins.less index 0236d12e2..c1fc98fae 100644 --- a/src/content-handlers/iiif/modules/uv-shared-module/css/mixins.less +++ b/src/content-handlers/iiif/modules/uv-shared-module/css/mixins.less @@ -648,6 +648,14 @@ } } +// Loading +// ------------------------- +.loading(@img) { + background-image: url(@img); + background-repeat: no-repeat; + background-position: 50% 50%; +} + // Responsive utilities // ------------------------- // More easily include all the states for responsive-utilities.less. diff --git a/src/content-handlers/iiif/modules/uv-shared-module/css/styles.less b/src/content-handlers/iiif/modules/uv-shared-module/css/styles.less index eb749ca10..1a8690c69 100644 --- a/src/content-handlers/iiif/modules/uv-shared-module/css/styles.less +++ b/src/content-handlers/iiif/modules/uv-shared-module/css/styles.less @@ -12,6 +12,7 @@ @import "center-panel"; @import "footer-panel"; @import "overlays"; +@import "iiif-gallery-component"; @import "catch-all"; @import "sm"; @import "md"; @@ -296,6 +297,11 @@ }); } + .iiif-gallery-component .main { + overflow: auto; + overflow-x: hidden; + } + .footerPanel { position: relative; margin: 0 @panel-margin; From 5c2f3d34ca16150f1de0793795faf966b5642162 Mon Sep 17 00:00:00 2001 From: K8Sewell Date: Tue, 1 Jul 2025 16:26:52 -0500 Subject: [PATCH 2/8] Adds test for gallery component --- __tests__/test.js | 106 ++++++++++++++++++++++++++++++++++------------ 1 file changed, 80 insertions(+), 26 deletions(-) diff --git a/__tests__/test.js b/__tests__/test.js index 9c0aed1e4..f9aba84d7 100644 --- a/__tests__/test.js +++ b/__tests__/test.js @@ -1,73 +1,127 @@ -test.skip('Configuration options', () => {}); +test.skip("Configuration options", () => {}); -const puppeteer = require('puppeteer'); +const puppeteer = require("puppeteer"); -describe('Universal Viewer', () => { +describe("Universal Viewer", () => { let browser; let page; beforeAll(async () => { browser = await puppeteer.launch(); page = await browser.newPage(); - await page.goto('http://localhost:4444'); + await page.goto("http://localhost:4444"); }); afterAll(async () => { await browser.close(); }); - it('has the correct page title', async () => { + it("has the correct page title", async () => { const title = await page.title(); - expect(title).toBe('Universal Viewer Examples'); + expect(title).toBe("Universal Viewer Examples"); }); - it('loads the viewer images', async () => { - await page.waitForSelector('#thumb-0'); - const imageSrc = await page.$eval('#thumb-0 img', e => e.src); + it("loads the viewer images", async () => { + await page.waitForSelector("#thumb-0"); + const imageSrc = await page.$eval("#thumb-0 img", (e) => e.src); expect(imageSrc).toEqual( expect.stringContaining( - 'https://iiif.wellcomecollection.org/image/b18035723_0001.JP2/full/90,/0/default.jpg' + "https://iiif.wellcomecollection.org/image/b18035723_0001.JP2/full/90,/0/default.jpg" ) ); }); - it('can toggle thumbnail label truncation', async () => { - await page.waitForSelector('#truncateThumbnailLabels'); + it("can toggle thumbnail label truncation", async () => { + await page.waitForSelector("#truncateThumbnailLabels"); - const isCheckedBeforeToggle = await page.$eval('#truncateThumbnailLabels', checkbox => checkbox.checked); + const isCheckedBeforeToggle = await page.$eval( + "#truncateThumbnailLabels", + (checkbox) => checkbox.checked + ); expect(isCheckedBeforeToggle).toBe(true); const labelOverflowBeforeToggle = await page.evaluate(() => { - const label = document.querySelector('.thumbsView .thumbs .thumb .info .label'); - return getComputedStyle(label).overflowX; + const label = document.querySelector( + ".thumbsView .thumbs .thumb .info .label" + ); + return getComputedStyle(label).overflowX; }); - expect(labelOverflowBeforeToggle).toBe('hidden'); + expect(labelOverflowBeforeToggle).toBe("hidden"); await page.evaluate(() => { - document.querySelector('#truncateThumbnailLabels').click(); + document.querySelector("#truncateThumbnailLabels").click(); }); - const isCheckedAfterToggle = await page.$eval('#truncateThumbnailLabels', checkbox => checkbox.checked); + const isCheckedAfterToggle = await page.$eval( + "#truncateThumbnailLabels", + (checkbox) => checkbox.checked + ); expect(isCheckedAfterToggle).toBe(false); const labelOverflowAfterToggle = await page.evaluate(() => { - const label = document.querySelector('.thumbsView .thumbs .thumb .info .label'); - return getComputedStyle(label).overflowX; + const label = document.querySelector( + ".thumbsView .thumbs .thumb .info .label" + ); + return getComputedStyle(label).overflowX; }); - expect(labelOverflowAfterToggle).toBe('visible'); + expect(labelOverflowAfterToggle).toBe("visible"); }); - it('settings button is visible', async () => { + it("can toggle gallery view", async () => { + // gallery view is not default view + const galleryViewBeforeToggle = await page.evaluate(() => { + const galleryViewOverlay = document.querySelector( + ".iiif-gallery-component .header" + ); + return getComputedStyle(galleryViewOverlay).overflowX; + }); + expect(galleryViewBeforeToggle).toBe("hidden"); + + // gallery toggle icon is visible + await page.waitForSelector(".uv-icon-gallery"); + const galleryViewToggle = await page.evaluate(() => { + const toggle = document.querySelector(".uv-icon-gallery"); + return getComputedStyle(toggle).overflowX; + }); + expect(galleryViewToggle).toBe("visible"); - await page.waitForSelector('.btn.imageBtn.settings'); + // gallery view can be toggled on + await page.evaluate(() => { + document.querySelector(".uv-icon-gallery").click(); + }); + const galleryViewAfterToggle = await page.evaluate(() => { + const galleryViewOverlay = document.querySelector( + ".iiif-gallery-component" + ); + return getComputedStyle(galleryViewOverlay).overflowX; + }); + expect(galleryViewAfterToggle).toBe("visible"); + + // gallery view can be toggled off + await page.evaluate(() => { + document.querySelector(".uv-icon-two-up").click(); + }); + const galleryViewAfterTwoUpToggle = await page.evaluate(() => { + const galleryViewOverlay = document.querySelector( + ".iiif-gallery-component .header" + ); + return getComputedStyle(galleryViewOverlay).overflowX; + }); + expect(galleryViewAfterTwoUpToggle).toBe("hidden"); + }); + + it("settings button is visible", async () => { + await page.waitForSelector(".btn.imageBtn.settings"); const isSettingsButtonVisible = await page.evaluate(() => { - const settingsButton = document.querySelector('.btn.imageBtn.settings'); + const settingsButton = document.querySelector(".btn.imageBtn.settings"); const style = window.getComputedStyle(settingsButton); - return style.getPropertyValue('visibility') !== 'hidden' && style.getPropertyValue('display') !== 'none'; + return ( + style.getPropertyValue("visibility") !== "hidden" && + style.getPropertyValue("display") !== "none" + ); }); expect(isSettingsButtonVisible).toBe(true); }); }); - From aeb66b7ad2a650926d69d6735108611f6331ffc6 Mon Sep 17 00:00:00 2001 From: K8Sewell Date: Tue, 1 Jul 2025 16:36:54 -0500 Subject: [PATCH 3/8] Remove not needed code --- src/content-handlers/iiif/JQueryPlugins.ts | 2 -- .../iiif/modules/uv-shared-module/css/mixins.less | 8 -------- 2 files changed, 10 deletions(-) diff --git a/src/content-handlers/iiif/JQueryPlugins.ts b/src/content-handlers/iiif/JQueryPlugins.ts index 65be3e2b0..921295626 100644 --- a/src/content-handlers/iiif/JQueryPlugins.ts +++ b/src/content-handlers/iiif/JQueryPlugins.ts @@ -558,13 +558,11 @@ interface JQuery { css: any; append: any; text: any; - link: any; toggle: any; html: any; empty: any; one: any; remove: any; - render: any; height: any; contents: any; outerWidth: any; diff --git a/src/content-handlers/iiif/modules/uv-shared-module/css/mixins.less b/src/content-handlers/iiif/modules/uv-shared-module/css/mixins.less index c1fc98fae..0236d12e2 100644 --- a/src/content-handlers/iiif/modules/uv-shared-module/css/mixins.less +++ b/src/content-handlers/iiif/modules/uv-shared-module/css/mixins.less @@ -648,14 +648,6 @@ } } -// Loading -// ------------------------- -.loading(@img) { - background-image: url(@img); - background-repeat: no-repeat; - background-position: 50% 50%; -} - // Responsive utilities // ------------------------- // More easily include all the states for responsive-utilities.less. From 844708ea87aae6aa5dd0fc1822e1208bc8b98940 Mon Sep 17 00:00:00 2001 From: K8Sewell Date: Tue, 1 Jul 2025 16:37:19 -0500 Subject: [PATCH 4/8] Add dependency --- package-lock.json | 14 +++++++++++--- package.json | 1 + 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 66f554294..160d7afcf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "license": "MIT", "dependencies": { "@edsilv/http-status-codes": "1.0.3", + "@edsilv/jquery-plugins": "^1.0.9", "@edsilv/key-codes": "1.0.0", "@edsilv/utils": "^1.0.2", "@google/model-viewer": "^4.0.0", @@ -779,9 +780,10 @@ "integrity": "sha512-HLK2FS5sZqxPqD53D6hhZxC6C8THTVwlyZDZ7J0iWsrB8JmMA69m/CQuNKZc1kki9WSVeck2fXna26NL0SE7cg==" }, "node_modules/@edsilv/jquery-plugins": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/@edsilv/jquery-plugins/-/jquery-plugins-1.0.7.tgz", - "integrity": "sha512-lz+Rsax7QrBEnHI+WwZ+A4jxqLypTl6RvKP5jI3P2vxG3975fTj2qOvxl1/npxuJ7wCerrvYcBqwZd743iPSdg==" + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@edsilv/jquery-plugins/-/jquery-plugins-1.0.9.tgz", + "integrity": "sha512-XdvY2HHXe5Tgv/v/zog+mdD6MgeA9Q7O9nZ462tqahNN9+GTElGXW6K+SDf5oPtaPAkAB1NeXaGGFpuq6oQd3Q==", + "license": "MIT" }, "node_modules/@edsilv/key-codes": { "version": "1.0.0", @@ -1261,6 +1263,12 @@ "manifesto.js": "^4.2.21" } }, + "node_modules/@iiif/iiif-metadata-component/node_modules/@edsilv/jquery-plugins": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@edsilv/jquery-plugins/-/jquery-plugins-1.0.7.tgz", + "integrity": "sha512-lz+Rsax7QrBEnHI+WwZ+A4jxqLypTl6RvKP5jI3P2vxG3975fTj2qOvxl1/npxuJ7wCerrvYcBqwZd743iPSdg==", + "license": "MIT" + }, "node_modules/@iiif/iiif-tree-component": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/@iiif/iiif-tree-component/-/iiif-tree-component-2.0.7.tgz", diff --git a/package.json b/package.json index 879e42519..0d6161b07 100644 --- a/package.json +++ b/package.json @@ -98,6 +98,7 @@ }, "dependencies": { "@edsilv/http-status-codes": "1.0.3", + "@edsilv/jquery-plugins": "^1.0.9", "@edsilv/key-codes": "1.0.0", "@edsilv/utils": "^1.0.2", "@google/model-viewer": "^4.0.0", From d2fa32ecf8beebb4f6d1911533eb67b708ac1337 Mon Sep 17 00:00:00 2001 From: K8Sewell Date: Tue, 1 Jul 2025 16:48:22 -0500 Subject: [PATCH 5/8] Fix merge conflict --- package-lock.json | 3 --- 1 file changed, 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0690698a2..48c1b0768 100644 --- a/package-lock.json +++ b/package-lock.json @@ -778,15 +778,12 @@ "resolved": "https://registry.npmjs.org/@edsilv/http-status-codes/-/http-status-codes-1.0.3.tgz", "integrity": "sha512-HLK2FS5sZqxPqD53D6hhZxC6C8THTVwlyZDZ7J0iWsrB8JmMA69m/CQuNKZc1kki9WSVeck2fXna26NL0SE7cg==" }, -<<<<<<< HEAD "node_modules/@edsilv/jquery-plugins": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/@edsilv/jquery-plugins/-/jquery-plugins-1.0.9.tgz", "integrity": "sha512-XdvY2HHXe5Tgv/v/zog+mdD6MgeA9Q7O9nZ462tqahNN9+GTElGXW6K+SDf5oPtaPAkAB1NeXaGGFpuq6oQd3Q==", "license": "MIT" }, -======= ->>>>>>> e5e0c980f1f3b2eb8e13335bf09984ac7e78c2f2 "node_modules/@edsilv/key-codes": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@edsilv/key-codes/-/key-codes-1.0.0.tgz", From a560f59318355421137c5da4a3cfa7a664e41d71 Mon Sep 17 00:00:00 2001 From: K8Sewell Date: Wed, 2 Jul 2025 13:54:32 -0500 Subject: [PATCH 6/8] Adjust loading icon --- .../modules/uv-shared-module/css/iiif-gallery-component.less | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/content-handlers/iiif/modules/uv-shared-module/css/iiif-gallery-component.less b/src/content-handlers/iiif/modules/uv-shared-module/css/iiif-gallery-component.less index cd62b66a8..6cc38863d 100644 --- a/src/content-handlers/iiif/modules/uv-shared-module/css/iiif-gallery-component.less +++ b/src/content-handlers/iiif/modules/uv-shared-module/css/iiif-gallery-component.less @@ -166,7 +166,7 @@ position: relative; &.loading { - .loading(@loader-black-bg); + .loading(@loader-white-bg); } &.hidden { From 194fb50e38713ad0eb37e2d4766b5b75b7540b47 Mon Sep 17 00:00:00 2001 From: K8Sewell Date: Wed, 2 Jul 2025 15:20:28 -0500 Subject: [PATCH 7/8] Restore expected loading icon --- .../modules/uv-shared-module/css/iiif-gallery-component.less | 2 +- .../iiif/modules/uv-shared-module/css/variables.less | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/content-handlers/iiif/modules/uv-shared-module/css/iiif-gallery-component.less b/src/content-handlers/iiif/modules/uv-shared-module/css/iiif-gallery-component.less index 6cc38863d..4e15d0380 100644 --- a/src/content-handlers/iiif/modules/uv-shared-module/css/iiif-gallery-component.less +++ b/src/content-handlers/iiif/modules/uv-shared-module/css/iiif-gallery-component.less @@ -166,7 +166,7 @@ position: relative; &.loading { - .loading(@loader-white-bg); + .loading(@gallery-loader-black-bg); } &.hidden { diff --git a/src/content-handlers/iiif/modules/uv-shared-module/css/variables.less b/src/content-handlers/iiif/modules/uv-shared-module/css/variables.less index 72543b0c8..d521a30c1 100644 --- a/src/content-handlers/iiif/modules/uv-shared-module/css/variables.less +++ b/src/content-handlers/iiif/modules/uv-shared-module/css/variables.less @@ -216,6 +216,7 @@ //@loader-black-bg: 'data:image/gif;base64,R0lGODlhFAAUAMQSAGxsbFpaWnh4eEZGRhwcHD8/PzU1NVJSUisrKwgICH5+fkxMTGNjYyUlJXJycnt7exISEoCAgAAAAP///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH/C05FVFNDQVBFMi4wAwEAAAAh/wtYTVAgRGF0YVhNUDw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMDE0IDc5LjE1Njc5NywgMjAxNC8wOC8yMC0wOTo1MzowMiAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDo1OTFjMzhjNS0xZmQyLTg5NGUtYjMyZi1kYTNkZWQ5NmQ5YjMiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6Qzc0OEE1NDA5OTMzMTFFNUFBRjI5MkI3NTFFODJFRTYiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6Qzc0OEE1M0Y5OTMzMTFFNUFBRjI5MkI3NTFFODJFRTYiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTQgKFdpbmRvd3MpIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6NTkxYzM4YzUtMWZkMi04OTRlLWIzMmYtZGEzZGVkOTZkOWIzIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjU5MWMzOGM1LTFmZDItODk0ZS1iMzJmLWRhM2RlZDk2ZDliMyIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PgH//v38+/r5+Pf29fTz8vHw7+7t7Ovq6ejn5uXk4+Lh4N/e3dzb2tnY19bV1NPS0dDPzs3My8rJyMfGxcTDwsHAv769vLu6ubi3trW0s7KxsK+urayrqqmop6alpKOioaCfnp2cm5qZmJeWlZSTkpGQj46NjIuKiYiHhoWEg4KBgH9+fXx7enl4d3Z1dHNycXBvbm1sa2ppaGdmZWRjYmFgX15dXFtaWVhXVlVUU1JRUE9OTUxLSklIR0ZFRENCQUA/Pj08Ozo5ODc2NTQzMjEwLy4tLCsqKSgnJiUkIyIhIB8eHRwbGhkYFxYVFBMSERAPDg0MCwoJCAcGBQQDAgEAACH5BAkAABIALAAAAAAUABQAAAVAoCSOZGmeaKqmSeu+LRpEdG0Hsq1H+DnvtZ7pB+TlikYfMjlcCktE4JMU3U1HVd1VlL2hCIiweExYmc/otHoUAgAh+QQJAAASACwAAAAAFAAUAAAFWqAkjmRpnmgqEgyhnkykHNA7GlEePUXyJg6dzoFQDYTI1gnyQCIVC1PA6YySGlSkwEcCZIWGEu6bA5gSCwW5gWJlAy+EV6io/QrN3MA2ShwUW3wkBGyChocSIQAh+QQFAAASACwAAAAAFAAUAAAFXKAkjmRpnmhaJsuimoYQKcQrNkCkR8wLBbsgApUYKIJBR8LUcCCfBRPh+Aw+IKZDFXkwQR7bHc1UCO96q1yYUTMhtg6DioFUDJaq6S6AtUkWEQANfiMJcoSIiSUhADs='; @loader-black-bg: "data:image/gif;base64,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"; +@gallery-loader-black-bg: "data:image/gif;base64,R0lGODlhFAAUAMQSAGxsbFpaWnh4eEZGRhwcHD8/PzU1NVJSUisrKwgICH5+fkxMTGNjYyUlJXJycnt7exISEoCAgAAAAP///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH/C05FVFNDQVBFMi4wAwEAAAAh/wtYTVAgRGF0YVhNUDw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMDE0IDc5LjE1Njc5NywgMjAxNC8wOC8yMC0wOTo1MzowMiAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDo1OTFjMzhjNS0xZmQyLTg5NGUtYjMyZi1kYTNkZWQ5NmQ5YjMiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6Qzc0OEE1NDA5OTMzMTFFNUFBRjI5MkI3NTFFODJFRTYiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6Qzc0OEE1M0Y5OTMzMTFFNUFBRjI5MkI3NTFFODJFRTYiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTQgKFdpbmRvd3MpIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6NTkxYzM4YzUtMWZkMi04OTRlLWIzMmYtZGEzZGVkOTZkOWIzIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjU5MWMzOGM1LTFmZDItODk0ZS1iMzJmLWRhM2RlZDk2ZDliMyIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PgH//v38+/r5+Pf29fTz8vHw7+7t7Ovq6ejn5uXk4+Lh4N/e3dzb2tnY19bV1NPS0dDPzs3My8rJyMfGxcTDwsHAv769vLu6ubi3trW0s7KxsK+urayrqqmop6alpKOioaCfnp2cm5qZmJeWlZSTkpGQj46NjIuKiYiHhoWEg4KBgH9+fXx7enl4d3Z1dHNycXBvbm1sa2ppaGdmZWRjYmFgX15dXFtaWVhXVlVUU1JRUE9OTUxLSklIR0ZFRENCQUA/Pj08Ozo5ODc2NTQzMjEwLy4tLCsqKSgnJiUkIyIhIB8eHRwbGhkYFxYVFBMSERAPDg0MCwoJCAcGBQQDAgEAACH5BAkAABIALAAAAAAUABQAAAVAoCSOZGmeaKqmSeu+LRpEdG0Hsq1H+DnvtZ7pB+TlikYfMjlcCktE4JMU3U1HVd1VlL2hCIiweExYmc/otHoUAgAh+QQJAAASACwAAAAAFAAUAAAFWqAkjmRpnmgqEgyhnkykHNA7GlEePUXyJg6dzoFQDYTI1gnyQCIVC1PA6YySGlSkwEcCZIWGEu6bA5gSCwW5gWJlAy+EV6io/QrN3MA2ShwUW3wkBGyChocSIQAh+QQFAAASACwAAAAAFAAUAAAFXKAkjmRpnmhaJsuimoYQKcQrNkCkR8wLBbsgApUYKIJBR8LUcCCfBRPh+Aw+IKZDFXkwQR7bHc1UCO96q1yYUTMhtg6DioFUDJaq6S6AtUkWEQANfiMJcoSIiSUhADs="; // scrollbars @scrollbar-width: 12px; From 3f93b95dfad3b0c26798b5092b20a49e4013c2b3 Mon Sep 17 00:00:00 2001 From: K8Sewell Date: Thu, 3 Jul 2025 14:17:19 -0500 Subject: [PATCH 8/8] Remove duplicated styles --- .../iiif/modules/uv-shared-module/css/styles.less | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/content-handlers/iiif/modules/uv-shared-module/css/styles.less b/src/content-handlers/iiif/modules/uv-shared-module/css/styles.less index 2aa9ab173..701bf5179 100644 --- a/src/content-handlers/iiif/modules/uv-shared-module/css/styles.less +++ b/src/content-handlers/iiif/modules/uv-shared-module/css/styles.less @@ -299,11 +299,6 @@ }); } - .iiif-gallery-component .main { - overflow: auto; - overflow-x: hidden; - } - .footerPanel { position: relative; margin: 0 @panel-margin;