UNPKG

mirador

Version:

An open-source, web-based 'multi-up' viewer that supports zoom-pan-rotate functionality, ability to display/compare simple images, and images with annotations.

294 lines (251 loc) 10 kB
import { all, call, put, select, takeEvery } from 'redux-saga/effects'; import ActionTypes from '../actions/action-types'; import { setContentSearchCurrentAnnotation, selectAnnotation, setWorkspaceViewportPosition, updateWindow, setCanvas, fetchSearch, receiveManifest, fetchInfoResponse, showCollectionDialog, } from '../actions'; import { getSearchForWindow, getSearchAnnotationsForCompanionWindow, getCanvasGrouping, getWindow, getManifestoInstance, getCompanionWindowIdsForPosition, getManifestSearchService, getCanvasForAnnotation, getSelectedContentSearchAnnotationIds, getSortedSearchAnnotationsForCompanionWindow, getVisibleCanvasIds, getWorkspace, getElasticLayout, getCanvases, selectInfoResponses, getWindowConfig, getMiradorCanvasWrapper, getMiradorManifestWrapper, } from '../selectors'; import { fetchManifests } from './iiif'; import { getIiifResourceImageService } from '../../lib/iiif'; /** */ export function* fetchWindowManifest(action) { const { collectionPath, id, manifestId } = action.payload || action.window; if (!manifestId) return; if (action.manifest) { yield put(receiveManifest(manifestId, action.manifest)); } else { yield call(fetchManifests, manifestId, ...(collectionPath || [])); } yield call(setWindowStartingCanvas, action); yield call(setWindowDefaultSearchQuery, action); if (!collectionPath) { yield call(setCollectionPath, { manifestId, windowId: action.id || action.window.id }); } yield call(determineAndShowCollectionDialog, manifestId, id); } /** */ export function* setCanvasOnNewSequence(action) { const windowId = action.id; if (!action || !action.payload || !action.payload.sequenceId) return; const canvases = yield select(getCanvases, { windowId }); if (!canvases || !canvases[0] || !canvases[0].id) return; const thunk = yield call(setCanvas, windowId, canvases[0].id); yield put(thunk); } /** */ export function* setCollectionPath({ manifestId, windowId }) { const manifestoInstance = yield select(getManifestoInstance, { manifestId }); if (manifestoInstance) { const partOfs = manifestoInstance.getProperty('partOf'); const partOf = Array.isArray(partOfs) ? partOfs[0] : partOfs; if (partOf && partOf.id) { yield put(updateWindow(windowId, { collectionPath: [partOf.id] })); } } } /** */ export function* fetchCollectionManifests(action) { const { collectionPath } = action.payload; if (!collectionPath) return; yield call(fetchManifests, ...collectionPath); } /** @private */ export function* setWindowStartingCanvas(action) { const { canvasId, canvasIndex, manifestId } = action.payload || action.window; const windowId = action.id || action.window.id; if (canvasId) { // Preserve viewport when initialViewerConfig exists, event if the preserveViewport OSD setting is set to false const preserveViewport = !!action.payload || !!action.window?.initialViewerConfig; // When canvasId is explicitly provided, always pass preserveViewport flag const thunk = yield call(setCanvas, windowId, canvasId, null, { preserveViewport }); yield put(thunk); } else { const getMiradorManifest = yield select(getMiradorManifestWrapper); const manifestoInstance = yield select(getManifestoInstance, { manifestId }); if (manifestoInstance) { // set the startCanvas const miradorManifest = getMiradorManifest(manifestoInstance); const startCanvas = miradorManifest.startCanvas || miradorManifest.canvasAt(canvasIndex || 0) || miradorManifest.canvasAt(0); if (startCanvas) { const preserveViewport = !!action.payload || !!action.window?.initialViewerConfig; // When canvas is calculated, only pass preserveViewport when true const thunk = preserveViewport ? yield call(setCanvas, windowId, startCanvas.id, null, { preserveViewport }) : yield call(setCanvas, windowId, startCanvas.id); yield put(thunk); } } } } /** @private */ export function* setWindowDefaultSearchQuery(action) { // only for a brand new window if (!action.window || !action.window.defaultSearchQuery) return; const { id: windowId, defaultSearchQuery } = action.window; const searchService = yield select(getManifestSearchService, { windowId }); const companionWindowIds = yield select(getCompanionWindowIdsForPosition, { position: 'left', windowId, }); const companionWindowId = companionWindowIds[0]; if (searchService && companionWindowId) { const searchId = searchService && `${searchService.id}?q=${defaultSearchQuery}`; yield put(fetchSearch(windowId, companionWindowId, searchId, defaultSearchQuery)); } } /** @private */ export function getAnnotationsBySearch(state, { canvasIds, companionWindowIds, windowId }) { const annotationBySearch = companionWindowIds.reduce((accumulator, companionWindowId) => { const annotations = getSearchAnnotationsForCompanionWindow(state, { companionWindowId, windowId, }); const resourceAnnotations = annotations.resources; const hitAnnotation = resourceAnnotations.find((r) => canvasIds.includes(r.targetId)); // eslint-disable-next-line no-param-reassign if (hitAnnotation) accumulator[companionWindowId] = [hitAnnotation.id]; return accumulator; }, {}); return annotationBySearch; } /** @private */ export function* setCurrentAnnotationsOnCurrentCanvas({ annotationId, windowId, visibleCanvases }) { const searches = yield select(getSearchForWindow, { windowId }); const companionWindowIds = Object.keys(searches || {}); if (companionWindowIds.length === 0) return; const annotationBySearch = yield select(getAnnotationsBySearch, { canvasIds: visibleCanvases, companionWindowIds, windowId, }); yield all( Object.keys(annotationBySearch).map((companionWindowId) => put(setContentSearchCurrentAnnotation(windowId, companionWindowId, annotationBySearch[companionWindowId])), ), ); if (Object.values(annotationBySearch).length > 0) { // if the currently selected annotation isn't on this canvas, do a thing. yield put(selectAnnotation(windowId, Object.values(annotationBySearch)[0][0])); } } /** @private */ export function* panToFocusedWindow({ pan, windowId }) { if (!pan) return; const elasticLayout = yield select(getElasticLayout); const { x, y, width, height } = elasticLayout[windowId] || {}; const { viewportPosition: { width: viewWidth, height: viewHeight }, } = yield select(getWorkspace); yield put( setWorkspaceViewportPosition({ x: x + width / 2 - viewWidth / 2, y: y + height / 2 - viewHeight / 2, }), ); } /** @private */ export function* updateVisibleCanvases({ windowId }) { const { canvasId } = yield select(getWindow, { windowId }); const visibleCanvases = yield select(getCanvasGrouping, { canvasId, windowId }); yield put(updateWindow(windowId, { visibleCanvases: (visibleCanvases || []).map((c) => c.id) })); } /** @private */ export function* setCanvasOfFirstSearchResult({ companionWindowId, windowId }) { const { switchCanvasOnSearch } = yield select(getWindowConfig, { windowId }); if (!switchCanvasOnSearch) { return; } const selectedIds = yield select(getSelectedContentSearchAnnotationIds, { companionWindowId, windowId, }); if (selectedIds.length !== 0) return; const annotations = yield select(getSortedSearchAnnotationsForCompanionWindow, { companionWindowId, windowId, }); if (!annotations || annotations.length === 0) return; yield put(selectAnnotation(windowId, annotations[0].id)); } /** @private */ export function* setCanvasforSelectedAnnotation({ annotationId, windowId }) { const canvasIds = yield select(getVisibleCanvasIds, { windowId }); const canvas = yield select(getCanvasForAnnotation, { annotationId, windowId, }); if (!canvas || canvasIds.includes(canvas.id)) return; const thunk = yield call(setCanvas, windowId, canvas.id); yield put(thunk); } /** Fetch info responses for the visible canvases */ export function* fetchInfoResponses({ visibleCanvases: visibleCanvasIds, windowId }) { const canvases = yield select(getCanvases, { windowId }); const infoResponses = yield select(selectInfoResponses); const visibleCanvases = (canvases || []).filter((c) => visibleCanvasIds.includes(c.id)); const getMiradorCanvas = yield select(getMiradorCanvasWrapper); yield all( visibleCanvases.map((canvas) => { const miradorCanvas = getMiradorCanvas(canvas); return all( miradorCanvas.iiifImageResources .map( (imageResource) => !infoResponses[getIiifResourceImageService(imageResource)?.id] && put(fetchInfoResponse({ imageResource, windowId })), ) .filter(Boolean), ); }), ); } /** */ export function* determineAndShowCollectionDialog(manifestId, windowId) { const manifestoInstance = yield select(getManifestoInstance, { manifestId }); if (manifestoInstance && manifestoInstance.isCollection()) { yield put(showCollectionDialog(manifestId, [], windowId)); } } /** */ export default function* windowsSaga() { yield all([ takeEvery(ActionTypes.ADD_WINDOW, fetchWindowManifest), takeEvery(ActionTypes.UPDATE_WINDOW, fetchWindowManifest), takeEvery(ActionTypes.UPDATE_WINDOW, setCanvasOnNewSequence), takeEvery(ActionTypes.UPDATE_WINDOW, fetchCollectionManifests), takeEvery(ActionTypes.SET_CANVAS, setCurrentAnnotationsOnCurrentCanvas), takeEvery(ActionTypes.SET_CANVAS, fetchInfoResponses), takeEvery(ActionTypes.UPDATE_COMPANION_WINDOW, fetchCollectionManifests), takeEvery(ActionTypes.SET_WINDOW_VIEW_TYPE, updateVisibleCanvases), takeEvery(ActionTypes.RECEIVE_SEARCH, setCanvasOfFirstSearchResult), takeEvery(ActionTypes.SELECT_ANNOTATION, setCanvasforSelectedAnnotation), takeEvery(ActionTypes.FOCUS_WINDOW, panToFocusedWindow), ]); }