From 51af51624fe67a60bb74fc1dc79597c1bd30a6c3 Mon Sep 17 00:00:00 2001 From: Benjamin Armintor Date: Wed, 8 Nov 2023 17:36:25 -0500 Subject: [PATCH] COLUMBIA: rough out a NativeObjectViewer for PDFs - Rudimentary fullscreen resizing for NativeObjectViewer - Refactor NativeObjectViewer to use a ResizeObserver effect directly - Linter cleanup - NativeObjectViewer.js - remove a console log in NativeObjectViewer.js - linter fixes for src/components/NativeObjectViewer.js - PrimaryWindowComponent passes textResources to TypeSpecificViewer --- src/components/NativeObjectViewer.js | 70 ++++++++++++++++++++++++++++ src/components/PrimaryWindow.js | 24 +++++++--- src/containers/NativeObjectViewer.js | 23 +++++++++ src/containers/PrimaryWindow.js | 4 +- src/lib/MiradorCanvas.js | 9 +++- src/state/selectors/canvases.js | 29 ++++-------- 6 files changed, 131 insertions(+), 28 deletions(-) create mode 100644 src/components/NativeObjectViewer.js create mode 100644 src/containers/NativeObjectViewer.js diff --git a/src/components/NativeObjectViewer.js b/src/components/NativeObjectViewer.js new file mode 100644 index 0000000000..555266727f --- /dev/null +++ b/src/components/NativeObjectViewer.js @@ -0,0 +1,70 @@ +import { + Fragment, useEffect, useRef, useState, +} from 'react'; +import PropTypes from 'prop-types'; +import { styled } from '@mui/material/styles'; + +const StyledContainer = styled('div')({ + alignItems: 'center', + display: 'flex', + width: '100%', +}); + +const StyledObject = styled('object')({ + width: '100%', +}); + +/** */ +export function NativeObjectViewer({ nativeObjectOptions = {}, nativeObjectResources = [], windowId }) { + /** */ + const defaultDimensions = (originalDimensions) => (originalDimensions || document.body.querySelector(`#${windowId} .mirador-primary-window`)?.getBoundingClientRect()); + + const eleRef = useRef(null); + + const [currentDimensions, setCurrentDimensions] = useState({ + current: eleRef.current?.getBoundingClientRect(), + original: defaultDimensions(null), + }); + + useEffect(() => { + const element = eleRef.current; + + if (!element) return () => {}; + + const originalDimensions = element.getBoundingClientRect(); + const observer = new ResizeObserver((entries) => { + if (entries.length === 0) return; + const current = (document.fullscreenElement) ? entries[0].contentRect : originalDimensions; + + setCurrentDimensions({ current, original: originalDimensions }); + }); + + observer.observe(element); + return () => { + // Cleanup the observer like any event handlers + observer.disconnect(); + }; + }, [eleRef]); + + const dimensions = currentDimensions.current || currentDimensions.original; + /* eslint-disable jsx-a11y/alt-text */ + return ( + + + {nativeObjectResources.map((nativeObject) => ( + + + + ))} + + + ); + /* eslint-disable jsx-a11y/alt-text */ +} +/* eslint-enable jsx-a11y/media-has-caption */ + +NativeObjectViewer.propTypes = { + nativeObjectOptions: PropTypes.object, // eslint-disable-line react/forbid-prop-types + nativeObjectResources: PropTypes.arrayOf(PropTypes.object), // eslint-disable-line react/forbid-prop-types + windowId: PropTypes.string.isRequired, // eslint-disable-line react/forbid-prop-types +}; diff --git a/src/components/PrimaryWindow.js b/src/components/PrimaryWindow.js index c89fe1f4b7..fd058eb455 100644 --- a/src/components/PrimaryWindow.js +++ b/src/components/PrimaryWindow.js @@ -12,6 +12,7 @@ const GalleryView = lazy(() => import('../containers/GalleryView')); const SelectCollection = lazy(() => import('../containers/SelectCollection')); const WindowViewer = lazy(() => import('../containers/WindowViewer')); const VideoViewer = lazy(() => import('../containers/VideoViewer')); +const NativeObjectViewer = lazy(() => import('../containers/NativeObjectViewer')); GalleryView.displayName = 'GalleryView'; SelectCollection.displayName = 'SelectCollection'; @@ -25,8 +26,8 @@ const Root = styled('div', { name: 'PrimaryWindow', slot: 'root' })(() => ({ /** */ const TypeSpecificViewer = ({ - audioResources = [], isCollection = false, - isFetching = false, videoResources = [], view = undefined, windowId, + audioResources = [], isCollection = false, isFetching = false, textResources = [], + videoResources = [], view = undefined, windowId, }) => { if (isCollection) { return ( @@ -43,20 +44,27 @@ const TypeSpecificViewer = ({ /> ); } - if (videoResources.length > 0) { + if (videoResources && videoResources.length > 0) { return ( ); } - if (audioResources.length > 0) { + if (audioResources && audioResources.length > 0) { return ( ); } + if (textResources && textResources.length > 0) { + return ( + + ); + } return ( ( + { + nativeObjectOptions: getConfig(state).nativeObjectOptions, + nativeObjectResources: getVisibleCanvasTextResources(state, { windowId }) || [], + windowId, + } +); + +const enhance = compose( + withTranslation(), + connect(mapStateToProps, null), + withPlugins('NativeObjectViewer'), +); + +export default enhance(NativeObjectViewer); diff --git a/src/containers/PrimaryWindow.js b/src/containers/PrimaryWindow.js index 1895e80539..02a36b7ed9 100644 --- a/src/containers/PrimaryWindow.js +++ b/src/containers/PrimaryWindow.js @@ -2,7 +2,8 @@ import { compose } from 'redux'; import { connect } from 'react-redux'; import { withPlugins } from '../extend/withPlugins'; import { - getManifestoInstance, getVisibleCanvasAudioResources, getVisibleCanvasVideoResources, getWindow, + getManifestoInstance, getVisibleCanvasAudioResources, getVisibleCanvasTextResources, + getVisibleCanvasVideoResources, getWindow, } from '../state/selectors'; import { PrimaryWindow } from '../components/PrimaryWindow'; @@ -13,6 +14,7 @@ const mapStateToProps = (state, { windowId }) => { audioResources: getVisibleCanvasAudioResources(state, { windowId }) || [], isCollection: manifestoInstance && manifestoInstance.isCollection(), isCollectionDialogVisible: getWindow(state, { windowId }).collectionDialogOn, + textResources: getVisibleCanvasTextResources(state, { windowId }) || [], videoResources: getVisibleCanvasVideoResources(state, { windowId }) || [], }; }; diff --git a/src/lib/MiradorCanvas.js b/src/lib/MiradorCanvas.js index 2d9af3504f..ca66d7c584 100644 --- a/src/lib/MiradorCanvas.js +++ b/src/lib/MiradorCanvas.js @@ -1,7 +1,9 @@ import flatten from 'lodash/flatten'; import flattenDeep from 'lodash/flattenDeep'; import { Canvas, AnnotationPage, Annotation } from 'manifesto.js'; -import { audioResourcesFrom, iiifImageResourcesFrom, videoResourcesFrom } from './typeFilters'; +import { + audioResourcesFrom, iiifImageResourcesFrom, textResourcesFrom, videoResourcesFrom, +} from './typeFilters'; import canvasTypes from './canvasTypes'; /** @@ -84,6 +86,11 @@ export default class MiradorCanvas { })); } + /** */ + get textResources() { + return textResourcesFrom(this.imageResources); + } + /** */ get videoResources() { return videoResourcesFrom(this.imageResources); diff --git a/src/state/selectors/canvases.js b/src/state/selectors/canvases.js index 22be1449fb..c2bc6b12ef 100644 --- a/src/state/selectors/canvases.js +++ b/src/state/selectors/canvases.js @@ -228,26 +228,6 @@ const probeReplacements = (resources, probeResponses) => { }); }; -/** */ -const probeReplacements = (resources, probeResponses) => { - if (!probeResponses) return resources; - - return resources.map((r) => { - const probeService = getProbeService(r); - const probeServiceId = probeService && probeService.id; - const probeResponse = probeServiceId && probeResponses[probeServiceId]; - if (!probeResponse || probeResponse.isFetching) return r; - - const probeContentUrl = probeResponse.json && (probeResponse.json.location || probeResponse.json.substitute); - const probeReplacedProperties = {}; - if (probeContentUrl) { - probeReplacedProperties.id = probeContentUrl; - if (probeResponse.json.format) probeReplacedProperties.format = probeResponse.json.format; - } - return new Resource({ ...r.__jsonld, ...probeReplacedProperties }, r.options); - }); -}; - /** * Return visible non tiled canvas resources. * @param {object} @@ -311,6 +291,15 @@ export const getVisibleCanvasAudioResources = createSelector( .map(canvas => probeReplacements(new MiradorCanvas(canvas).audioResources, probeResponses))), ); +export const getVisibleCanvasTextResources = createSelector( + [ + getVisibleCanvases, + selectProbeResponses, + ], + (canvases, probeResponses) => flatten(canvases + .map(canvas => probeReplacements(new MiradorCanvas(canvas).textResources, probeResponses))), +); + /** * Returns info response. * @param {object} state