Skip to content
Snippets Groups Projects
Verified Commit 2406eda5 authored by Loïs Poujade's avatar Loïs Poujade
Browse files

Merge branch 'annotation-on-video'

parents 4d7b50e6 047b2063
Branches
No related tags found
No related merge requests found
Showing
with 26618 additions and 99 deletions
describe('Basic end to end Mirador with preloaded video content', () => {
beforeAll(async () => {
await page.goto('http://localhost:4488/__tests__/integration/mirador/video.html');
});
it('load multiples audio/video manifests', async () => {
await page.waitForSelector('h2');
await page.waitForSelector('audio');
await page.waitForSelector('video');
await expect(page).toMatchElement('h2', { text: /Video Example 3/ });
await expect(page).toMatchElement('h2', { text: /Partial audio recording of Gustav Mahler's _Symphony No. 3_/ });
await expect(page).toMatchElement('h2', { text: /L'Elisir D'Amore/ });
await expect(page).toMatchElement('h2', { text: /Lunchroom Manners/ });
});
it('can add an image manifest', async () => {
await page.waitForSelector('#addBtn');
await expect(page).toClick('#addBtn');
await expect(page).toClick('.mirador-add-resource-button');
await expect(page).toFill('#manifestURL', 'https://iiif.harvardartmuseums.org/manifests/object/299843');
await expect(page).toClick('#fetchBtn');
await page.waitForSelector('[data-manifestid="https://iiif.harvardartmuseums.org/manifests/object/299843"] button');
await expect(page).toMatchElement('[data-manifestid="https://iiif.harvardartmuseums.org/manifests/object/299843"] button');
await expect(page).toClick('[data-manifestid="https://iiif.harvardartmuseums.org/manifests/object/299843"] button');
await page.waitForSelector('.mirador-osd-container');
await expect(page).toMatchElement('.mirador-osd-container');
});
});
import { shallow } from 'enzyme';
import { Utils } from 'manifesto.js';
import { VideoViewer } from '../../../src/components/VideoViewer';
import videoSimple from '../../fixtures/version-3/video.json';
/** create wrapper */
function createWrapper(props, suspenseFallback) {
......@@ -15,36 +17,18 @@ function createWrapper(props, suspenseFallback) {
describe('VideoViewer', () => {
let wrapper;
describe('render', () => {
const canvasSimple = Utils.parseManifest(videoSimple).getSequences()[0].getCanvases()[0];
it('videoResources', () => {
wrapper = createWrapper({
videoResources: [
{ getFormat: () => 'video/mp4', id: 1 },
{ getFormat: () => 'video/mp4', id: 2 },
],
canvas: canvasSimple,
}, true);
expect(wrapper.contains(<source src={1} type="video/mp4" />)).toBe(true);
expect(wrapper.contains(<source src={2} type="video/mp4" />)).toBe(true);
expect(wrapper.contains(<source src="https://fixtures.iiif.io/video/indiana/30-minute-clock/medium/30-minute-clock.mp4" type="video/mp4" />)).toBe(true);
});
it('passes through configurable options', () => {
wrapper = createWrapper({
videoResources: [
{ getFormat: () => 'video/mp4', id: 1 },
],
canvas: canvasSimple,
}, true);
expect(wrapper.exists('video[crossOrigin="anonymous"]')).toBe(true); // eslint-disable-line jsx-a11y/media-has-caption
});
it('captions', () => {
wrapper = createWrapper({
captions: [
{ getDefaultLabel: () => 'English', getProperty: () => 'en', id: 1 },
{ getDefaultLabel: () => 'French', getProperty: () => 'fr', id: 2 },
],
videoResources: [
{ getFormat: () => 'video/mp4', id: 1 },
],
}, true);
expect(wrapper.contains(<track src={1} label="English" srcLang="en" />)).toBe(true);
expect(wrapper.contains(<track src={2} label="French" srcLang="fr" />)).toBe(true);
});
});
});
......@@ -47,20 +47,10 @@ module.exports = function (api) {
const plugins = [
'babel-plugin-macros',
'@babel/plugin-transform-destructuring',
[
'@babel/plugin-proposal-class-properties',
{
loose: true,
},
],
['@babel/plugin-proposal-private-property-in-object', { loose: true }],
['@babel/plugin-proposal-private-methods', { loose: true }],
[
'@babel/plugin-proposal-object-rest-spread',
{
useBuiltIns: true,
},
],
['@babel/plugin-proposal-private-property-in-object', { loose: true }],
['@babel/plugin-proposal-class-properties', { loose: true }],
['@babel/plugin-proposal-object-rest-spread', { useBuiltIns: true }],
[
'@babel/plugin-transform-runtime',
{
......@@ -69,24 +59,9 @@ module.exports = function (api) {
regenerator: true,
},
],
[
'@babel/plugin-transform-regenerator',
{
async: false,
},
],
['transform-react-remove-prop-types',
{
ignoreFilenames: ['node_modules'],
removeImport: true,
},
],
['lodash', {
id: [
'lodash',
],
},
],
['@babel/plugin-transform-regenerator', { async: false }],
['transform-react-remove-prop-types', { ignoreFilenames: ['node_modules'], removeImport: true }],
['lodash', { id: ['lodash'] }],
].filter(Boolean);
return {
......
module.exports = {
launch: {
args: ['--no-sandbox', '--disable-setuid-sandbox'],
headless: process.env.HEADLESS !== 'false',
},
server: [{
......
......@@ -14,6 +14,9 @@
"setupFiles": [
"<rootDir>/setupJest.js"
],
"setupFilesAfterEnv": [
"<rootDir>/setup-expect-timeout.js"
],
"testMatch": [
"<rootDir>/**/__tests__/**/*.{js,jsx}",
"<rootDir>/src/**/?(*.)(spec|test|unit).{js,jsx}"
......
This diff is collapsed.
......@@ -5,15 +5,17 @@
"main": "dist/cjs/src/index.js",
"module": "dist/es/src/index.js",
"files": [
"dist"
"dist/**"
],
"sideEffects": false,
"scripts": {
"prepare": "npm run clean && npm run build:es && npm run build:cjs",
"clean": "rm -rf ./dist",
"lint": "node_modules/.bin/eslint ./ && npm run lint:translations && npm run lint:containers",
"lint:containers": "node ./scripts/container-lint.js",
"lint:translations": "node ./scripts/i18n-lint.js",
"server": "node_modules/.bin/http-server --cors",
"test:ci": "jest -c jest.json --ci --reporters=default --reporters=jest-junit --watchAll=false --runInBand",
"test": "npm run build && npm run lint && npm run size && jest -c jest.json",
"test:debug": "node --inspect node_modules/.bin/jest -c jest.json --runInBand",
"test:watch": "jest -c jest.json --watch",
......@@ -107,13 +109,15 @@
"eslint-plugin-jsx-a11y": "^6.4.1",
"eslint-plugin-react": "^7.29.4",
"eslint-plugin-react-hooks": "^4.2.0",
"expect-puppeteer": "^6.1.1",
"glob": "^8.0.3",
"http-server": "^14.1.0",
"jest": "^29.3.1",
"jest-fetch-mock": "^3.0.0",
"jest-junit": "^15.0.0",
"jest-puppeteer": "^6.1.0",
"jsdom": "^21.0.0",
"puppeteer": "^13.5.1",
"puppeteer": "^13.7.0",
"react": "^16.14.0",
"react-dom": "^16.14.0",
"react-refresh": "^0.14.0",
......
const { setDefaultOptions } = require('expect-puppeteer');
setDefaultOptions({ timeout: 2000 });
import { Component } from 'react';
import PropTypes from 'prop-types';
import SyncIcon from '@material-ui/icons/Sync';
import SyncDisabledIcon from '@material-ui/icons/SyncDisabled';
import VisibilityIcon from '@material-ui/icons/VisibilitySharp';
import VisibilityOffIcon from '@material-ui/icons/VisibilityOffSharp';
import MiradorMenuButton from '../containers/MiradorMenuButton';
import { VideosReferences } from '../plugins/VideosReferences';
/**
* AnnotationSettings is a component to handle various annotation
......@@ -14,10 +17,14 @@ export class AnnotationSettings extends Component {
*/
render() {
const {
displayAll, displayAllDisabled, t, toggleAnnotationDisplay,
windowId, autoScroll, autoScrollDisabled,
displayAll, displayAllDisabled, t, toggleAnnotationAutoScroll, toggleAnnotationDisplay,
} = this.props;
const mediaIsVideo = typeof VideosReferences.get(windowId) !== 'undefined';
return (
<>
<MiradorMenuButton
aria-label={t(displayAll ? 'displayNoAnnotations' : 'highlightAllAnnotations')}
onClick={toggleAnnotationDisplay}
......@@ -26,14 +33,33 @@ export class AnnotationSettings extends Component {
>
{ displayAll ? <VisibilityIcon /> : <VisibilityOffIcon /> }
</MiradorMenuButton>
{ mediaIsVideo && (
<MiradorMenuButton
aria-label={autoScroll ? 'Disable auto scroll' : 'Enable auto scroll'}
onClick={toggleAnnotationAutoScroll}
disabled={autoScrollDisabled}
size="small"
>
{ autoScroll ? <SyncIcon /> : <SyncDisabledIcon /> }
</MiradorMenuButton>
)}
</>
);
}
}
AnnotationSettings.defaultProps = {
autoScroll: true,
autoScrollDisabled: true,
toggleAnnotationAutoScroll: () => {},
};
AnnotationSettings.propTypes = {
autoScroll: PropTypes.bool,
autoScrollDisabled: PropTypes.bool,
displayAll: PropTypes.bool.isRequired,
displayAllDisabled: PropTypes.bool.isRequired,
t: PropTypes.func.isRequired,
toggleAnnotationAutoScroll: PropTypes.func,
toggleAnnotationDisplay: PropTypes.func.isRequired,
windowId: PropTypes.string.isRequired, // eslint-disable-line react/no-unused-prop-types
};
This diff is collapsed.
......@@ -58,7 +58,7 @@ export class CanvasAnnotations extends Component {
*/
render() {
const {
annotations, classes, index, label, selectedAnnotationId, t, totalSize,
annotations, autoScroll, classes, index, label, selectedAnnotationId, t, totalSize,
listContainerComponent, htmlSanitizationRuleSet, hoveredAnnotationIds,
containerRef,
} = this.props;
......@@ -76,7 +76,7 @@ export class CanvasAnnotations extends Component {
containerRef={containerRef}
key={`${annotation.id}-scroll`}
offsetTop={96} // offset for the height of the form above
scrollTo={selectedAnnotationId === annotation.id}
scrollTo={autoScroll ? (selectedAnnotationId === annotation.id) : false}
>
<MenuItem
button
......@@ -126,6 +126,7 @@ CanvasAnnotations.propTypes = {
id: PropTypes.string.isRequired,
}),
),
autoScroll: PropTypes.bool,
classes: PropTypes.objectOf(PropTypes.string),
containerRef: PropTypes.oneOfType([
PropTypes.func,
......@@ -146,6 +147,7 @@ CanvasAnnotations.propTypes = {
};
CanvasAnnotations.defaultProps = {
annotations: [],
autoScroll: true,
classes: {},
containerRef: undefined,
hoveredAnnotationIds: [],
......
import { Component, Fragment } from 'react';
import flatten from 'lodash/flatten';
import flattenDeep from 'lodash/flattenDeep';
import React, { Component, Fragment } from 'react';
import PropTypes from 'prop-types';
import AnnotationItem from '../lib/AnnotationItem';
import AnnotationsOverlayVideo from '../containers/AnnotationsOverlayVideo';
import WindowCanvasNavigationControlsVideo from '../containers/WindowCanvasNavigationControlsVideo';
/** */
export class VideoViewer extends Component {
/** */
constructor(props) {
super(props);
this.videoRef = React.createRef();
this.state = {
start: 0,
time: 0,
};
}
/** */
componentDidMount() {
const { annotations, setHasTextTrack, setPaused } = this.props;
setPaused(true);
const vttContent = flatten(
flattenDeep([
annotations.map(annotation => annotation.resources.map(
resources_ => resources_.resource,
)),
]).filter(resource => resource.body && resource.body[0] && resource.body[0].format === 'text/vtt'),
);
if (vttContent && vttContent.length > 0) {
setHasTextTrack(true);
}
}
/** */
componentDidUpdate(prevProps) {
const {
canvas, currentTime, muted, paused,
setCurrentTime, setPaused,
textTrackDisabled,
} = this.props;
if (paused !== prevProps.paused) {
if (currentTime === 0) {
this.timerReset();
}
if (paused) {
this.timerStop();
} else {
this.timerStart();
}
}
if (currentTime !== prevProps.currentTime) {
const duration = canvas.getDuration();
if (duration && duration < currentTime) {
if (!paused) {
setPaused(true);
setCurrentTime(0);
this.timerReset();
}
}
}
const video = this.videoRef.current;
if (video) {
if (video.muted !== muted) {
video.muted = muted;
}
if (video.textTracks && video.textTracks.length > 0) {
const newMode = textTrackDisabled ? 'hidden' : 'showing';
if (video.textTracks[0].mode !== newMode) {
video.textTracks[0].mode = newMode;
}
}
}
}
/** */
componentWillUnmount() {
this.timerStop();
}
/** */
timerStart() {
const { currentTime } = this.props;
this.setState({
start: Date.now() - currentTime * 1000,
time: currentTime * 1000,
});
this.timer = setInterval(() => {
const { setCurrentTime } = this.props;
this.setState(prevState => ({
time: Date.now() - prevState.start,
}));
const { time } = this.state;
setCurrentTime(time / 1000);
}, 100);
}
/** */
timerStop() {
clearInterval(this.timer);
}
/** */
timerReset() {
this.setState({ time: 0 });
}
/* eslint-disable jsx-a11y/media-has-caption */
/** */
render() {
const {
captions, classes, videoOptions, videoResources,
annotations, canvas, classes, currentTime, videoOptions, windowId,
} = this.props;
const videoResources = flatten(
flattenDeep([
canvas.getContent().map(annot => {
const annotaion = new AnnotationItem(annot.__jsonld);
const temporalfragment = annotaion.temporalfragmentSelector;
if (temporalfragment && temporalfragment.length > 0) {
const start = temporalfragment[0] || 0;
const end = (temporalfragment.length > 1) ? temporalfragment[1] : Number.MAX_VALUE;
if (start <= currentTime && currentTime < end) {
//
} else {
return {};
}
}
const body = annot.getBody();
return { body, temporalfragment };
}),
]).filter((resource) => resource.body && resource.body[0].__jsonld && resource.body[0].__jsonld.type === 'Video'),
);
const vttContent = flatten(
flattenDeep([
annotations.map(annotation => annotation.resources.map(
resources_ => resources_.resource,
)),
]).filter(resource => resource.body && resource.body[0] && resource.body[0].format === 'text/vtt'),
);
// Only one video can be displayed at a time in this implementation.
const len = videoResources.length;
const video = len > 0
? videoResources[len - 1].body[0] : null;
const videoTargetTemporalfragment = len > 0
? videoResources[len - 1].temporalfragment : [];
let caption = null;
if (vttContent && vttContent.length > 0) {
caption = {
id: vttContent[0].body[0].id,
};
}
return (
<div className={classes.container}>
<video className={classes.video} {...videoOptions}>
{videoResources.map(video => (
<Fragment key={video.id}>
<div className={classes.flexContainer}>
<div className={classes.flexFill}>
{ video && (
<>
<video className={classes.video} key={video.id} ref={this.videoRef} {...videoOptions}>
<source src={video.id} type={video.getFormat()} />
</Fragment>
))}
{captions.map(caption => (
<Fragment key={caption.id}>
<track src={caption.id} label={caption.getDefaultLabel()} srcLang={caption.getProperty('language')} />
</Fragment>
))}
{ caption && (
<track src={caption.id} />
)}
</video>
<AnnotationsOverlayVideo windowId={windowId} videoRef={this.videoRef} videoTarget={videoTargetTemporalfragment} key={`${windowId} ${video.id}`} />
</>
)}
<WindowCanvasNavigationControlsVideo windowId={windowId} />
</div>
</div>
);
}
......@@ -30,14 +178,29 @@ export class VideoViewer extends Component {
}
VideoViewer.propTypes = {
captions: PropTypes.arrayOf(PropTypes.object), // eslint-disable-line react/forbid-prop-types
annotations: PropTypes.arrayOf(PropTypes.object),
canvas: PropTypes.object, // eslint-disable-line react/forbid-prop-types
classes: PropTypes.objectOf(PropTypes.string).isRequired,
currentTime: PropTypes.number,
muted: PropTypes.bool,
paused: PropTypes.bool,
setCurrentTime: PropTypes.func,
setHasTextTrack: PropTypes.func,
setPaused: PropTypes.func,
textTrackDisabled: PropTypes.bool,
videoOptions: PropTypes.object, // eslint-disable-line react/forbid-prop-types
videoResources: PropTypes.arrayOf(PropTypes.object), // eslint-disable-line react/forbid-prop-types
windowId: PropTypes.string.isRequired,
};
VideoViewer.defaultProps = {
captions: [],
annotations: [],
canvas: {},
currentTime: 0,
muted: false,
paused: true,
setCurrentTime: () => {},
setHasTextTrack: () => {},
setPaused: () => {},
textTrackDisabled: true,
videoOptions: {},
videoResources: [],
};
......@@ -15,6 +15,7 @@ export class ViewerNavigation extends Component {
const {
hasNextCanvas, hasPreviousCanvas, setNextCanvas, setPreviousCanvas, t,
classes, viewingDirection,
beforeClick,
} = this.props;
let htmlDir = 'ltr';
......@@ -48,7 +49,7 @@ export class ViewerNavigation extends Component {
aria-label={t('previousCanvas')}
className={ns('previous-canvas-button')}
disabled={!hasPreviousCanvas}
onClick={() => { hasPreviousCanvas && setPreviousCanvas(); }}
onClick={() => { beforeClick(); hasPreviousCanvas && setPreviousCanvas(); }}
>
<NavigationIcon style={previousIconStyle} />
</MiradorMenuButton>
......@@ -56,7 +57,7 @@ export class ViewerNavigation extends Component {
aria-label={t('nextCanvas')}
className={ns('next-canvas-button')}
disabled={!hasNextCanvas}
onClick={() => { hasNextCanvas && setNextCanvas(); }}
onClick={() => { beforeClick(); hasNextCanvas && setNextCanvas(); }}
>
<NavigationIcon style={nextIconStyle} />
</MiradorMenuButton>
......@@ -66,6 +67,7 @@ export class ViewerNavigation extends Component {
}
ViewerNavigation.propTypes = {
beforeClick: PropTypes.func,
classes: PropTypes.objectOf(PropTypes.string).isRequired,
hasNextCanvas: PropTypes.bool,
hasPreviousCanvas: PropTypes.bool,
......@@ -76,6 +78,7 @@ ViewerNavigation.propTypes = {
};
ViewerNavigation.defaultProps = {
beforeClick: () => {},
hasNextCanvas: false,
hasPreviousCanvas: false,
setNextCanvas: () => {},
......
import ClosedCaption from '@material-ui/icons/ClosedCaption';
import ClosedCaptionOutlined from '@material-ui/icons/ClosedCaptionOutlined';
import React, { Component } from 'react';
import PauseRoundedIcon from '@material-ui/icons/PauseRounded';
import PlayArrowRoundedIcon from '@material-ui/icons/PlayArrowRounded';
import PropTypes from 'prop-types';
import Slider from '@material-ui/core/Slider';
import Typography from '@material-ui/core/Typography';
import VolumeOffIcon from '@material-ui/icons/VolumeOff';
import VolumeUpIcon from '@material-ui/icons/VolumeUp';
import MiradorMenuButton from '../containers/MiradorMenuButton';
import ns from '../config/css-ns';
/** ViewerNavigationVideo - based on ViewerNavigation */
export class ViewerNavigationVideo extends Component {
/** */
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
}
/** */
handleChange = (event, newValue) => {
const { paused, setCurrentTime, setSeekTo } = this.props;
if (!paused) {
setSeekTo(newValue);
} else {
setCurrentTime(newValue);
}
};
/**
* Renders things
*/
render() {
const {
classes,
currentTime,
duration,
hasTextTrack,
muted,
paused,
setMuted,
setPaused,
setTextTrackDisabled,
textTrackDisabled,
} = this.props;
const start = (duration > 3600 || duration === undefined) ? 11 : 14;
const len = (duration > 3600 || duration === undefined) ? 8 : 5;
let durationLabel = new Date(currentTime * 1000).toISOString().substr(start, len);
let slider = '';
if (duration !== undefined) {
durationLabel = `${durationLabel} / ${new Date(duration * 1000).toISOString().substr(start, len)}`;
slider = (
<div className={classes.sliderDiv}>
<Slider value={currentTime} min={0} max={duration} onChange={this.handleChange} />
</div>
);
}
return (
<div className={classes.play_controls}>
<MiradorMenuButton
aria-label={paused ? 'Play' : 'Pause'}
className={paused ? ns('next-canvas-button') : ns('next-canvas-button')}
onClick={() => { setPaused(!paused); }}
>
{ paused ? <PlayArrowRoundedIcon /> : <PauseRoundedIcon /> }
</MiradorMenuButton>
{slider}
<span className={classes.timeLabel}>
<Typography variant="caption">
{durationLabel}
</Typography>
</span>
<MiradorMenuButton
aria-label={muted ? 'Unmute' : 'Mute'}
className={muted ? ns('next-canvas-button') : ns('next-canvas-button')}
onClick={() => { setMuted(!muted); }}
>
{ muted ? <VolumeOffIcon /> : <VolumeUpIcon /> }
</MiradorMenuButton>
{ hasTextTrack && (
<MiradorMenuButton
aria-label={textTrackDisabled ? 'CC show' : 'CC hide'}
className={textTrackDisabled ? ns('next-canvas-button') : ns('next-canvas-button')}
onClick={() => { setTextTrackDisabled(!textTrackDisabled); }}
>
{ textTrackDisabled ? <ClosedCaptionOutlined /> : <ClosedCaption /> }
</MiradorMenuButton>
)}
<span className={classes.divider} />
</div>
);
}
}
ViewerNavigationVideo.propTypes = {
classes: PropTypes.objectOf(PropTypes.string).isRequired,
currentTime: PropTypes.number,
duration: PropTypes.number,
hasTextTrack: PropTypes.bool,
muted: PropTypes.bool,
paused: PropTypes.bool,
setCurrentTime: PropTypes.func,
setMuted: PropTypes.func,
setPaused: PropTypes.func,
setSeekTo: PropTypes.func,
setTextTrackDisabled: PropTypes.func,
textTrackDisabled: PropTypes.bool,
};
ViewerNavigationVideo.defaultProps = {
currentTime: 0,
duration: undefined,
hasTextTrack: false,
muted: false,
paused: true,
setCurrentTime: () => {},
setMuted: () => {},
setPaused: () => {},
setSeekTo: () => {},
setTextTrackDisabled: () => {},
textTrackDisabled: true,
};
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import Paper from '@material-ui/core/Paper';
import Typography from '@material-ui/core/Typography';
import ViewerInfo from '../containers/ViewerInfo';
import ViewerNavigation from '../containers/ViewerNavigation';
import ViewerNavigationVideo from '../containers/ViewerNavigationVideo';
import ns from '../config/css-ns';
import { PluginHook } from './PluginHook';
/**
* WindowCanvasNavigationControlsVideo - based on WindowCanvasNavigationControls
* Represents the viewer controls in the mirador workspace.
*/
export class WindowCanvasNavigationControlsVideo extends Component {
/**
* Determine if canvasNavControls are stacked (based on a hard-coded width)
*/
canvasNavControlsAreStacked() {
const { size } = this.props;
return (size && size.width && size.width <= 253);
}
/** */
render() {
const {
classes, visible, windowId, setPaused,
} = this.props;
if (!visible) return (<Typography variant="srOnly" component="div"><ViewerInfo windowId={windowId} /></Typography>);
return (
<Paper
square
className={
classNames(
classes.controls,
ns('canvas-nav'),
classes.canvasNav,
this.canvasNavControlsAreStacked() ? ns('canvas-nav-stacked') : null,
this.canvasNavControlsAreStacked() ? classes.canvasNavStacked : null,
)
}
elevation={0}
>
<ViewerNavigation windowId={windowId} beforeClick={setPaused} />
<ViewerInfo windowId={windowId} />
<ViewerNavigationVideo windowId={windowId} />
<PluginHook {...this.props} />
</Paper>
);
}
}
WindowCanvasNavigationControlsVideo.propTypes = {
classes: PropTypes.objectOf(PropTypes.string),
setPaused: PropTypes.func,
size: PropTypes.shape({ width: PropTypes.number }).isRequired,
visible: PropTypes.bool,
windowId: PropTypes.string.isRequired,
};
WindowCanvasNavigationControlsVideo.defaultProps = {
classes: {},
setPaused: () => {},
visible: true,
};
......@@ -361,7 +361,7 @@ export default {
crossOrigin: 'anonymous',
},
videoOptions: { // Additional props passed to <audio> element
controls: true,
controls: false,
crossOrigin: 'anonymous',
},
auth: {
......
......@@ -13,6 +13,8 @@ import { AnnotationSettings } from '../components/AnnotationSettings';
* Mapping redux state to component props using connect
*/
const mapStateToProps = (state, { windowId }) => ({
autoScroll: getWindow(state, { windowId }).autoScrollAnnotationList,
autoScrollDisabled: getAnnotationResourcesByMotivation(state, { windowId }).length < 2,
displayAll: getWindow(state, { windowId }).highlightAllAnnotations,
displayAllDisabled: getAnnotationResourcesByMotivation(
state,
......@@ -24,6 +26,9 @@ const mapStateToProps = (state, { windowId }) => ({
* Mapping redux action dispatches to component props using connect
*/
const mapDispatchToProps = (dispatch, { windowId }) => ({
toggleAnnotationAutoScroll: () => {
dispatch(actions.toggleAnnotationAutoScroll(windowId));
},
toggleAnnotationDisplay: () => {
dispatch(actions.toggleAnnotationDisplay(windowId));
},
......
/** AnnotationsOverlayVideo - based on AnnotationsOverlay */
import { compose } from 'redux';
import { connect } from 'react-redux';
import { withTranslation } from 'react-i18next';
import { withPlugins } from '../extend/withPlugins';
import { AnnotationsOverlayVideo } from '../components/AnnotationsOverlayVideo';
import * as actions from '../state/actions';
import {
getWindow,
getSearchAnnotationsForWindow,
getCompanionWindowsForContent,
getTheme,
getConfig,
getCurrentCanvas,
getWindowCurrentTime,
getWindowSeekToTime,
getWindowPausedStatus,
getPresentAnnotationsOnSelectedCanvases,
getSelectedAnnotationId,
getCurrentCanvasWorld,
} from '../state/selectors';
/**
* mapStateToProps - used to hook up connect to action creators
* @memberof Window
* @private
*/
const mapStateToProps = (state, { windowId }) => ({
annotations: getPresentAnnotationsOnSelectedCanvases(state, { windowId }),
canvas: (getCurrentCanvas(state, { windowId }) || {}),
canvasWorld: getCurrentCanvasWorld(state, { windowId }),
currentTime: getWindowCurrentTime(state, { windowId }),
drawAnnotations: getConfig(state).window.forceDrawAnnotations || getCompanionWindowsForContent(state, { content: 'annotations', windowId }).length > 0,
drawSearchAnnotations: getConfig(state).window.forceDrawAnnotations || getCompanionWindowsForContent(state, { content: 'search', windowId }).length > 0,
highlightAllAnnotations: getWindow(state, { windowId }).highlightAllAnnotations,
hoveredAnnotationIds: getWindow(state, { windowId }).hoveredAnnotationIds,
palette: getTheme(state).palette,
paused: getWindowPausedStatus(state, { windowId }),
searchAnnotations: getSearchAnnotationsForWindow(
state,
{ windowId },
),
seekToTime: getWindowSeekToTime(state, { windowId }),
selectedAnnotationId: getSelectedAnnotationId(state, { windowId }),
});
/**
* mapDispatchToProps - used to hook up connect to action creators
* @memberof ManifestListItem
* @private
*/
const mapDispatchToProps = (dispatch, { windowId }) => ({
deselectAnnotation: (...args) => dispatch(actions.deselectAnnotation(...args)),
hoverAnnotation: (...args) => dispatch(actions.hoverAnnotation(...args)),
selectAnnotation: (...args) => dispatch(actions.selectAnnotation(...args)),
setCurrentTime: (...args) => dispatch(actions.setWindowCurrentTime(windowId, ...args)),
setPaused: (...args) => dispatch(actions.setWindowPaused(windowId, ...args)),
});
const enhance = compose(
withTranslation(),
connect(mapStateToProps, mapDispatchToProps),
withPlugins('AnnotationsOverlayVideo'),
);
export default enhance(AnnotationsOverlayVideo);
......@@ -9,6 +9,7 @@ import {
getCanvasLabel,
getSelectedAnnotationId,
getConfig,
getWindow,
} from '../state/selectors';
import { CanvasAnnotations } from '../components/CanvasAnnotations';
......@@ -30,6 +31,7 @@ const mapStateToProps = (state, { canvasId, windowId }) => ({
annotations: getIdAndContentOfResources(
getAnnotationResourcesByMotivationForCanvas(state, { canvasId, windowId }),
),
autoScroll: getWindow(state, { windowId }).autoScrollAnnotationList,
htmlSanitizationRuleSet: getConfig(state).annotations.htmlSanitizationRuleSet,
label: getCanvasLabel(state, {
canvasId,
......
......@@ -3,27 +3,52 @@ import { compose } from 'redux';
import { withTranslation } from 'react-i18next';
import { withStyles } from '@material-ui/core';
import { withPlugins } from '../extend/withPlugins';
import * as actions from '../state/actions';
import { VideoViewer } from '../components/VideoViewer';
import { getConfig, getVisibleCanvasCaptions, getVisibleCanvasVideoResources } from '../state/selectors';
import {
getConfig,
getCurrentCanvas,
getCurrentCanvasWorld,
getWindowMutedStatus,
getWindowPausedStatus,
getWindowCurrentTime,
getWindowTextTrackDisabledStatus,
getPresentAnnotationsOnSelectedCanvases,
} from '../state/selectors';
/** */
const mapStateToProps = (state, { windowId }) => (
{
captions: getVisibleCanvasCaptions(state, { windowId }) || [],
const mapStateToProps = (state, { windowId }) => ({
annotations: getPresentAnnotationsOnSelectedCanvases(state, { windowId }),
canvas: (getCurrentCanvas(state, { windowId }) || {}),
canvasWorld: getCurrentCanvasWorld(state, { windowId }),
currentTime: getWindowCurrentTime(state, { windowId }),
muted: getWindowMutedStatus(state, { windowId }),
paused: getWindowPausedStatus(state, { windowId }),
textTrackDisabled: getWindowTextTrackDisabledStatus(state, { windowId }),
videoOptions: getConfig(state).videoOptions,
videoResources: getVisibleCanvasVideoResources(state, { windowId }) || [],
}
);
});
/** */
const mapDispatchToProps = (dispatch, { windowId }) => ({
setCurrentTime: (...args) => dispatch(actions.setWindowCurrentTime(windowId, ...args)),
setHasTextTrack: (...args) => dispatch(actions.setWindowHasTextTrack(windowId, ...args)),
setPaused: (...args) => dispatch(actions.setWindowPaused(windowId, ...args)),
});
/** */
const styles = () => ({
container: {
flexContainer: {
alignItems: 'center',
display: 'flex',
width: '100%',
},
flexFill: {
height: '100%',
position: 'relative',
width: '100%',
},
video: {
maxHeight: '100%',
height: 'auto',
width: '100%',
},
});
......@@ -31,7 +56,7 @@ const styles = () => ({
const enhance = compose(
withTranslation(),
withStyles(styles),
connect(mapStateToProps, null),
connect(mapStateToProps, mapDispatchToProps),
withPlugins('VideoViewer'),
);
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment