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.
328 lines (297 loc) • 8.67 kB
JavaScript
import { createSelector } from 'reselect';
import { PropertyValue } from 'manifesto.js';
import flatten from 'lodash/flatten';
import AnnotationList from '../../lib/AnnotationList';
import { getCanvas, getCanvases } from './canvases';
import { getWindow } from './getters';
import { getManifestLocale } from './manifests';
import { miradorSlice, EMPTY_ARRAY, EMPTY_OBJECT } from './utils';
/**
* Get searches from state.
*/
const getSearches = (state) => miradorSlice(state).searches;
/**
* Returns the search result for a specific window.
* @param {object} state
* @param {string} windowId
* @returns {object}
*/
export const getSearchForWindow = createSelector(
[
(state, { windowId }) => windowId,
getSearches,
],
(windowId, searches) => {
if (!windowId || !searches) return EMPTY_OBJECT;
return searches[windowId];
},
);
/**
* Returns the search result for a specific companion window.
* @param {object} state
* @param {string} companionWindowId
* @returns {object|undefined}
*/
const getSearchForCompanionWindow = createSelector(
[
getSearchForWindow,
(state, { companionWindowId }) => companionWindowId,
],
(results, companionWindowId) => {
if (!results || !companionWindowId) return undefined;
return results[companionWindowId];
},
);
/**
* Returns an array of search responses for a specific companion window.
* @param {object} state
* @returns {Array}
*/
const getSearchResponsesForCompanionWindow = createSelector(
[
getSearchForCompanionWindow,
],
(results) => {
if (!results) return EMPTY_ARRAY;
return Object.values(results.data);
},
);
/**
* Returns the search query for a specific companion window.
* @param {object} state
* @param {string} windowId
* @returns {string|undefined}
*/
export const getSearchQuery = createSelector(
[
getSearchForCompanionWindow,
],
results => results && results.query,
);
/**
* Returns if search response for a companion window is fetching.
* @param {object} state
* @returns {boolean}
*/
export const getSearchIsFetching = createSelector(
[
getSearchResponsesForCompanionWindow,
],
results => results.some(result => result.isFetching),
);
/**
* Returns the total number of search results for a companion window.
* @param {object} state
* @param {string} windowId
* @returns {number|undefined}
*/
export const getSearchNumTotal = createSelector(
[
getSearchForCompanionWindow,
],
(results) => {
if (!results || !results.data) return undefined;
const resultWithWithin = Object.values(results.data).find(result => (
!result.isFetching
&& result.json
&& result.json.within
));
return resultWithWithin?.json?.within?.total;
},
);
/**
* Returns the Id of the next search.
* @param {object} state
* @param {string} windowId
* @returns {number|undefined}
*/
export const getNextSearchId = createSelector(
[
getSearchForCompanionWindow,
],
(results) => {
if (!results || !results.data) return undefined;
const resultWithAnUnresolvedNext = Object.values(results.data).find(result => (
!result.isFetching
&& result.json
&& result.json.next
&& !results.data[result.json.next]
));
return resultWithAnUnresolvedNext
&& resultWithAnUnresolvedNext.json
&& resultWithAnUnresolvedNext.json.next;
},
);
const getSearchHitsForCompanionWindow = createSelector(
[
getSearchResponsesForCompanionWindow,
],
results => flatten(results.map((result) => {
if (!result || !result.json || result.isFetching || !result.json.hits) return EMPTY_ARRAY;
return result.json.hits;
})),
);
export const getSearchAnnotationsForCompanionWindow = createSelector(
[
getSearchResponsesForCompanionWindow,
],
results => results && searchResultsToAnnotation(results),
);
/**
* Returns sorted search hits based on canvas order.
* @param {object} state
* @param {string} manifestId
* @returns {Array}
*/
export const getSortedSearchHitsForCompanionWindow = createSelector(
[
getSearchHitsForCompanionWindow,
getCanvases,
getSearchAnnotationsForCompanionWindow,
],
(searchHits, canvases, annotation) => {
if (!canvases || canvases.length === 0) return EMPTY_ARRAY;
if (!searchHits || searchHits.length === 0) return EMPTY_ARRAY;
const canvasIds = canvases.map(canvas => canvas.id);
return [].concat(searchHits).sort((a, b) => {
const hitA = annotation.resources.find(
r => r.id === a.annotations[0],
);
const hitB = annotation.resources.find(
r => r.id === b.annotations[0],
);
return canvasIds.indexOf(hitA.targetId) - canvasIds.indexOf(hitB.targetId);
});
},
);
/** convert search results to an annotation */
const searchResultsToAnnotation = (results) => {
const annotations = results.map((result) => {
if (!result || !result.json || result.isFetching || !result.json.resources) return undefined;
const anno = new AnnotationList(result.json);
return {
id: anno.id,
resources: anno.resources,
};
}).filter(Boolean);
return {
id: (annotations.find(a => a.id) || {}).id,
resources: flatten(annotations.map(a => a.resources)),
};
};
/**
* Sorts search annotations based on canvas order.
* @returns {Array}
*/
export function sortSearchAnnotationsByCanvasOrder(searchAnnotations, canvases) {
if (!searchAnnotations
|| !searchAnnotations.resources
|| searchAnnotations.length === 0) return EMPTY_ARRAY;
if (!canvases || canvases.length === 0) return EMPTY_ARRAY;
const canvasIds = canvases.map(canvas => canvas.id);
return [].concat(searchAnnotations.resources).sort(
(annoA, annoB) => canvasIds.indexOf(annoA.targetId) - canvasIds.indexOf(annoB.targetId),
);
}
/**
* Returns sorted search annotations for companion window.
* @param {object} state
* @param {string} companionWindowId
* @returns {Array}
*/
export const getSortedSearchAnnotationsForCompanionWindow = createSelector(
[
getSearchAnnotationsForCompanionWindow,
getCanvases,
],
(searchAnnotations, canvases) => sortSearchAnnotationsByCanvasOrder(searchAnnotations, canvases),
);
/**
* Returns sorted search annotations for window.
* @param {object} state
* @param {string} windowId
* @returns {Array}
*/
export const getSearchAnnotationsForWindow = createSelector(
[
getSearchForWindow,
],
(results) => {
if (!results) return EMPTY_ARRAY;
const data = Object.values(results).map(r => Object.values(r.data));
return data.map(d => searchResultsToAnnotation(d)).filter(a => a.resources.length > 0);
},
);
/**
* Returns ids of selected content search annotations.
* @param {object} state
* @param {string} windowId
* @returns {Array}
*/
export const getSelectedContentSearchAnnotationIds = createSelector(
[
getWindow,
getSearchForCompanionWindow,
],
(window, search) => (search && search.selectedContentSearchAnnotationIds)
|| [],
);
/**
* Returns resource annotations for search hit.
* @param {object} state
* @param {string} windowId
* @returns {Array}
*/
export const getResourceAnnotationForSearchHit = createSelector(
[
getSearchAnnotationsForCompanionWindow,
(state, { annotationUri }) => annotationUri,
],
(annotation, annotationUri) => annotation.resources.find(
r => r.id === annotationUri,
),
);
/**
* Returns annotation label.
* @param {object} state
* @param {string} windowId
* @returns {Array}
*/
export const getResourceAnnotationLabel = createSelector(
[
getResourceAnnotationForSearchHit,
getManifestLocale,
],
(resourceAnnotation, locale) => {
if (
!(resourceAnnotation && resourceAnnotation.resource && resourceAnnotation.resource.label)
) return EMPTY_ARRAY;
return PropertyValue.parse(resourceAnnotation.resource.label).getValues(locale);
},
);
const getAnnotationById = createSelector(
[
getSearchAnnotationsForWindow,
(state, { annotationId }) => (annotationId),
],
(annotations, annotationId) => {
const resourceAnnotations = flatten(annotations.map(a => a.resources));
return resourceAnnotations.find(r => r.id === annotationId);
},
);
/**
* Returns annotation label.
* @param {object} state
* @param {string} windowId
* @returns {Array}
*/
export const getCanvasForAnnotation = createSelector(
[
getAnnotationById,
(state, { windowId }) => canvasId => getCanvas(state, { canvasId, windowId }),
],
(annotation, getCanvasById) => {
const canvasId = annotation && annotation.targetId;
return canvasId && getCanvasById(canvasId);
},
);