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

wip

parent df4e0e7b
No related branches found
No related tags found
No related merge requests found
Pipeline #1235 failed
......@@ -7,7 +7,7 @@
"page": true,
"document": true
},
"parser": "babel-eslint",
"parser": "@babel/eslint-parser",
"plugins": ["jest"],
"rules": {
"import/prefer-default-export": "off",
......
......@@ -16,9 +16,15 @@ const config = {
defaultSideBarPanel: 'annotations',
sideBarOpenByDefault: true,
},
windows: [{
loadedManifest: 'https://iiif.harvardartmuseums.org/manifests/object/299843',
}],
catalog: [
{ manifestId: 'https://dzkimgs.l.u-tokyo.ac.jp/videos/iiif_in_japan_2017/manifest.json' },
{ manifestId: 'https://iiif.io/api/cookbook/recipe/0219-using-caption-file/manifest.json' },
{ manifestId: 'https://preview.iiif.io/cookbook/master/recipe/0003-mvm-video/manifest.json' },
{ manifestId: 'https://iiif.io/api/cookbook/recipe/0065-opera-multiple-canvases/manifest.json' },
{ manifestId: 'https://iiif.io/api/cookbook/recipe/0064-opera-one-canvas/manifest.json' },
{ manifestId: 'https://iiif.io/api/cookbook/recipe/0074-multiple-language-captions/manifest.json' },
{ manifestId: 'https://iiif.harvardartmuseums.org/manifests/object/299843' }
]
};
mirador.viewer(config, [...annotationPlugins]);
This diff is collapsed.
......@@ -11,7 +11,7 @@
"umd"
],
"scripts": {
"build": "nwb build-react-component --no-demo",
"build": "nwb build-react-component",
"clean": "nwb clean-module",
"lint": "eslint ./src ./__tests__",
"prepublishOnly": "npm run build",
......@@ -36,7 +36,7 @@
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "^4.0.0-alpha.52",
"lodash": "^4.17.11",
"mirador": "git+https://gitlab.tetras-libre.fr/iiif/mirador-video-annotation#annotation-on-video",
"mirador": "file:mirador",
"prop-types": "^15.7.2",
"react": "^16.8",
"react-dom": "^16.8",
......@@ -44,27 +44,28 @@
},
"devDependencies": {
"@babel/core": "^7.10.4",
"@babel/eslint-parser": "^7.19.1",
"@babel/preset-env": "^7.10.4",
"@babel/preset-react": "^7.10.4",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "^4.0.0-alpha.56",
"babel-eslint": "^10.1.0",
"canvas": "^2.6.1",
"enzyme": "^3.11.0",
"enzyme-adapter-react-16": "^1.15.2",
"eslint": "^7.2",
"eslint-config-airbnb": "^18.2.0",
"eslint-plugin-flowtype": "^5.6.0",
"eslint-plugin-import": "^2.22.0",
"eslint-plugin-jest": "^23.18.0",
"eslint-plugin-jsx-a11y": "^6.3.1",
"eslint": "^8.11.0",
"eslint-config-airbnb": "^19.0.4",
"eslint-config-react-app": "^7.0.0",
"eslint-plugin-flowtype": "^8.0.3",
"eslint-plugin-import": "^2.25.4",
"eslint-plugin-jest": "^26.1.1",
"eslint-plugin-jsx-a11y": "^6.4.1",
"eslint-plugin-react": "^7.20.3",
"jest": "^26.1.0",
"jest-canvas-mock": "^2.2.0",
"jest-junit": "^15.0.0",
"jest-localstorage-mock": "^2.4.2",
"mirador": "git+https://gitlab.tetras-libre.fr/iiif/mirador-video-annotation#annotation-on-video",
"mirador": "file:mirador",
"nwb": "^0.24.7",
"prop-types": "^15.7.2",
"react": "^16.8",
......
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import Button from '@material-ui/core/Button';
import {
IconButton, Button, Paper, Grid, Popover, Divider,
MenuList, MenuItem, ClickAwayListener,
} from '@material-ui/core';
import { Alarm, LastPage } from '@material-ui/icons';
import Typography from '@material-ui/core/Typography';
import Paper from '@material-ui/core/Paper';
import Grid from '@material-ui/core/Grid';
import ToggleButton from '@material-ui/lab/ToggleButton';
import ToggleButtonGroup from '@material-ui/lab/ToggleButtonGroup';
import RectangleIcon from '@material-ui/icons/CheckBoxOutlineBlank';
......@@ -17,24 +19,24 @@ import StrokeColorIcon from '@material-ui/icons/BorderColor';
import LineWeightIcon from '@material-ui/icons/LineWeight';
import ArrowDropDownIcon from '@material-ui/icons/ArrowDropDown';
import FormatShapesIcon from '@material-ui/icons/FormatShapes';
import Popover from '@material-ui/core/Popover';
import Divider from '@material-ui/core/Divider';
import MenuItem from '@material-ui/core/MenuItem';
import ClickAwayListener from '@material-ui/core/ClickAwayListener';
import MenuList from '@material-ui/core/MenuList';
import { SketchPicker } from 'react-color';
import { v4 as uuid } from 'uuid';
import { withStyles } from '@material-ui/core/styles';
import CompanionWindow from 'mirador/dist/es/src/containers/CompanionWindow';
import { VideosReferences } from 'mirador/dist/es/src/plugins/VideosReferences';
import { OSDReferences } from 'mirador/dist/es/src/plugins/OSDReferences';
import AnnotationDrawing from './AnnotationDrawing';
import TextEditor from './TextEditor';
import WebAnnotation from './WebAnnotation';
import CursorIcon from './icons/Cursor';
import HMSInput from './HMSInput';
import { secondsToHMS } from './utils';
/** Extract time information from annotation target */
function timeFromAnnoTarget(annotarget) {
console.info('TODO proper time extraction from: ', annotarget);
// TODO w3c media fragments: t=,10 t=5,
const r = /t=([0-9]+),([0-9]+)/.exec(annotarget);
const r = /t=([0-9.]+),([0-9.]+)/.exec(annotarget);
if (!r || r.length !== 3) {
return ['', ''];
}
......@@ -43,13 +45,12 @@ function timeFromAnnoTarget(annotarget) {
/** Extract xywh from annotation target */
function geomFromAnnoTarget(annotarget) {
console.warn('TODO proper extraction');
console.info('TODO proper xywh extraction from: ', annotarget);
const r = /xywh=((-?[0-9]+,?)+)/.exec(annotarget);
console.info('extracted from ', annotarget, r);
if (!r || r.length !== 3) {
return ['', ''];
return '';
}
return [r[1], r[2]];
return r[1];
}
/** */
......@@ -57,6 +58,7 @@ class AnnotationCreation extends Component {
/** */
constructor(props) {
super(props);
const annoState = {};
if (props.annotation) {
//
......@@ -91,9 +93,10 @@ class AnnotationCreation extends Component {
// eslint-disable-next-line max-len
[annoState.tstart, annoState.tend] = timeFromAnnoTarget(props.annotation.target.selector.value);
}
} else if (typeof props.annotation.target === 'string') {
annoState.xywh = geomFromAnnoTarget(props.annotation.target);
[annoState.tstart, annoState.tend] = timeFromAnnoTarget(props.annotation.target);
}
//
// start/end time
}
const toolState = {
......@@ -115,8 +118,8 @@ class AnnotationCreation extends Component {
popoverLineWeightAnchorEl: null,
svg: null,
tend: Math.floor(props.currentTime) + 10,
tstart: Math.floor(props.currentTime),
textEditorStateBustingKey: 0,
tstart: Math.floor(props.currentTime),
xywh: null,
...annoState,
};
......@@ -157,6 +160,40 @@ class AnnotationCreation extends Component {
});
}
/** set annotation start time to current time */
setTstartNow() { this.setState({ tstart: Math.floor(this.props.currentTime) }); }
/** set annotation end time to current time */
setTendNow() { this.setState({ tend: Math.floor(this.props.currentTime) }); }
/** update annotation start time */
updateTstart(value) { console.log('UPDATE TSTART', value); this.setState({ tstart: value }); }
/** update annotation end time */
updateTend(value) { this.setState({ tend: value }); }
/** seekTo/goto annotation start time */
seekToTstart() {
const { paused, setCurrentTime, setSeekTo } = this.props;
const { tstart } = this.state;
if (!paused) {
this.setState(setSeekTo(tstart));
} else {
this.setState(setCurrentTime(tstart));
}
}
/** seekTo/goto annotation end time */
seekToTend() {
const { paused, setCurrentTime, setSeekTo } = this.props;
const { tend } = this.state;
if (!paused) {
this.setState(setSeekTo(tend));
} else {
this.setState(setCurrentTime(tend));
}
}
/** */
openChooseColor(e) {
this.setState({
......@@ -200,20 +237,20 @@ class AnnotationCreation extends Component {
const {
annoBody, tags, xywh, svg, tstart, tend, textEditorStateBustingKey,
} = this.state;
let fsel = xywh;
let fragsel = xywh;
if (tstart && tend) {
fsel = `${xywh || ''}&t=${tstart},${tend}`;
fragsel += `&t=${tstart},${tend}`;
}
canvases.forEach((canvas) => {
const storageAdapter = config.annotation.adapter(canvas.id);
const anno = new WebAnnotation({
body: annoBody,
body: !annoBody.length ? `${secondsToHMS(tstart)} -> ${secondsToHMS(tend)}` : annoBody,
canvasId: canvas.id,
id: (annotation && annotation.id) || `${uuid()}`,
manifestId: canvas.options.resource.id,
svg,
tags,
xywh: fsel,
xywh: fragsel,
}).toJson();
if (annotation) {
storageAdapter.update(anno).then((annoPage) => {
......@@ -229,10 +266,10 @@ class AnnotationCreation extends Component {
this.setState({
annoBody: '',
svg: null,
tend: '',
textEditorStateBustingKey: textEditorStateBustingKey + 1,
tstart: '',
xywh: null,
tsart: null,
tend: null
});
}
......@@ -255,25 +292,6 @@ class AnnotationCreation extends Component {
this.setState({ annoBody });
}
/** update annotation start time */
updateTstart(ev) { this.setState({ tstart: ev.target.value }); }
/** update annotation end time */
updateTend(ev) { this.setState({ tend: ev.target.value }); }
/** set annotation start time to current time */
setTstartNow() { this.setState({ tstart: Math.floor(this.currentTime) }); }
/** set annotation end time to current time */
setTendNow() { this.setState({ tend: Math.floor(this.props.currentTime) }); }
/** seekTo annotation start time */
seekToTstart() { this.props.setSeekTo(this.state.tstart); }
/** seekTo annotation end time */
seekToTend() { this.props.setSeekTo(this.state.tend); }
/** */
updateGeometry({ svg, xywh }) {
this.setState({
......@@ -293,6 +311,8 @@ class AnnotationCreation extends Component {
tstart, tend, textEditorStateBustingKey,
} = this.state;
const mediaIsVideo = typeof VideosReferences.get(windowId) !== 'undefined';
return (
<CompanionWindow
title={annotation ? 'Edit annotation' : 'New annotation'}
......@@ -308,6 +328,7 @@ class AnnotationCreation extends Component {
svg={svg}
updateGeometry={this.updateGeometry}
windowId={windowId}
player={mediaIsVideo ? VideosReferences.get(windowId) : OSDReferences.get(windowId)}
/>
<form onSubmit={this.submitForm} className={classes.section}>
<Grid container>
......@@ -418,19 +439,33 @@ class AnnotationCreation extends Component {
</Grid>
</Grid>
<Grid container>
<Grid item xs={12}>
{ mediaIsVideo && (
<>
<Grid item xs={12} onClick={this.seekToTstart}>
<IconButton size="small"><LastPage /></IconButton>
<Typography variant="overline">
Duration
Start
</Typography>
</Grid>
<Grid item xs={12}>
<input name="tstart" type="number" step="1" value={tstart} onChange={this.updateTstart} />
<Button onClick={this.setTstartNow}>now</Button>
<Button onClick={this.seekToTstart}>seek</Button>
<input name="tend" type="number" step="1" value={tend} onChange={this.updateTend} />
<Button onClick={this.setTendNow}>now</Button>
<Button onClick={this.seekToTend}>seek</Button>
<Grid item xs={12} className={classes.paper}>
<IconButton onClick={this.setTstartNow}><Alarm /></IconButton>
<HMSInput seconds={tstart} onChange={this.updateTstart} />
</Grid>
<Grid item xs={12} onClick={this.seekToTend}>
<Typography variant="overline">
<IconButton size="small"><LastPage /></IconButton>
End
</Typography>
</Grid>
<Grid item xs={12} className={classes.paper}>
<IconButton onClick={this.setTendNow}><Alarm /></IconButton>
<HMSInput seconds={tend} onChange={this.updateTend} />
</Grid>
</>
)}
<Grid item xs={12}>
<Typography variant="overline">
Content
......@@ -518,7 +553,6 @@ const styles = (theme) => ({
});
AnnotationCreation.propTypes = {
currentTime: PropTypes.number,
// TODO proper web annotation type ?
annotation: PropTypes.object, // eslint-disable-line react/forbid-prop-types
canvases: PropTypes.arrayOf(
......@@ -531,21 +565,28 @@ AnnotationCreation.propTypes = {
adapter: PropTypes.func,
defaults: PropTypes.objectOf(
PropTypes.oneOfType(
[PropTypes.bool, PropTypes.func, PropTypes.number, PropTypes.string]
)
[PropTypes.bool, PropTypes.func, PropTypes.number, PropTypes.string],
),
),
}),
}).isRequired,
currentTime: PropTypes.number,
id: PropTypes.string.isRequired,
paused: PropTypes.bool,
receiveAnnotation: PropTypes.func.isRequired,
setCurrentTime: PropTypes.func,
setSeekTo: PropTypes.func,
windowId: PropTypes.string.isRequired,
setSeekTo: PropTypes.func.isRequired,
};
AnnotationCreation.defaultProps = {
annotation: null,
canvases: [],
closeCompanionWindow: () => {},
currentTime: 0,
paused: true,
setCurrentTime: () => {},
setSeekTo: () => {},
};
export default withStyles(styles)(AnnotationCreation);
......@@ -4,7 +4,7 @@ import PropTypes from 'prop-types';
import ResizeObserver from 'react-resize-observer';
import { OSDReferences } from 'mirador/dist/es/src/plugins/OSDReferences';
import { VideosReferences } from 'mirador/dist/es/src/plugins/VideosReferences';
import { renderWithPaperScope, PaperContainer, Size } from '@psychobolt/react-paperjs';
import { renderWithPaperScope, PaperContainer } from '@psychobolt/react-paperjs';
import
{
EllipseTool,
......@@ -18,20 +18,83 @@ import flatten from 'lodash/flatten';
import EditTool from './EditTool';
import { mapChildren } from './utils';
/** */
/** Create a portal with a drawing canvas and a form to fill annotations details */
class AnnotationDrawing extends Component {
/** */
constructor(props) {
super(props);
this.paper = null;
this.getViewProps = this.getViewProps.bind(this);
this.getDisplayProps = this.getDisplayProps.bind(this);
this.onPaperResize = this.onPaperResize.bind(this);
this.paperDidMount = this.paperDidMount.bind(this);
this.addPath = this.addPath.bind(this);
}
/** */
/** Sync drawing canvas on componentDidMount */
componentDidMount() {
this.onPaperResize();
}
/** Sync drawing canvas on componentDidUpdate */
componentDidUpdate() {
this.onPaperResize();
}
/** Sync drawing canvas size/zoom with annotations canvas */
onPaperResize(ev) {
const { windowId } = this.props;
if (VideosReferences.get(windowId) && this.paper) {
const { canvasOverlay } = VideosReferences.get(windowId);
const { height } = canvasOverlay.ref.current;
const { width } = canvasOverlay.ref.current;
this.paper.view.viewSize = new this.paper.Size(width, height);
this.paper.view.zoom = canvasOverlay.scale;
}
}
/** Build parameters to paperjs View and canvas */
getDisplayProps() {
const { windowId } = this.props;
const osdref = OSDReferences.get(windowId);
const videoref = VideosReferences.get(windowId);
if (osdref && videoref) {
console.error('Unhandled case: both OpenSeadragon (picture viewer) and video player on the same canvas');
}
if (osdref) {
const { viewport } = osdref.current;
const img = osdref.current.world.getItemAt(0);
const center = img.viewportToImageCoordinates(viewport.getCenter(true));
return {
canvasProps: { style: { height: '100%', width: '100%' } },
viewProps: {
center: new Point(center.x, center.y),
rotation: viewport.getRotation(),
scaling: new Point(viewport.getFlip() ? -1 : 1, 1),
zoom: img.viewportToImageZoom(viewport.getZoom()),
},
};
}
if (videoref) {
const { height, width } = videoref.canvasOverlay.ref.current;
return {
canvasProps: { height, style: { left: 0, position: 'absolute', top: 0 }, width },
viewProps: {
center: new Point(width / 2, height / 2),
height,
width,
zoom: videoref.canvasOverlay.scale,
},
};
}
throw new Error('Unknown or missing data player, not OpenSeadragon (picture viewer) nor the video player');
}
/** Draw SVG on canvas */
addPath(path) {
const { closed, strokeWidth, updateGeometry } = this.props;
// TODO: Compute xywh of bounding container of layers
......@@ -59,72 +122,14 @@ class AnnotationDrawing extends Component {
});
}
onPaperResize(ev) {
if (this.paper) {
console.debug('size: ', this.paper.view.viewSize);
console.debug('el: ', this.paper.view.element);
const { canvasOverlay } = VideosReferences.get(this.props.windowId);
const height = canvasOverlay.ref.current.height;
const width = canvasOverlay.ref.current.width;
this.paper.view.viewSize = new this.paper.Size(width, height);
this.paper.view.zoom = canvasOverlay.scale;
console.debug('new scale: ', canvasOverlay.scale);
}
}
getViewport() {
const { canvasOverlay } = VideosReferences.get(this.props.windowId);
const height = canvasOverlay.ref.current.height;
const width = canvasOverlay.ref.current.width;
return {
getCenter: () => ({ x: width / 2, y: height / 2 }),
getFlip: () => false,
getRotation: () => false,
getZoom: () => canvasOverlay.scale,
};
}
getViewProps() {
const { windowId } = this.props;
let viewport = null;
let img = null;
if (OSDReferences.get(windowId)) {
viewport = OSDReferences.get(windowId).current.viewport;
img = OSDReferences.get(windowId).current.world.getItemAt(0);
} else if (VideosReferences.get(windowId)) {
viewport = this.getViewport();
}
// Setup Paper View to have the same center and zoom as the OSD Viewport/video canvas
const center = img
? img.viewportToImageCoordinates(viewport.getCenter(true))
: viewport.getCenter();
const flipped = viewport.getFlip();
return {
center: new Point(center.x, center.y),
rotation: viewport.getRotation(),
scaling: new Point(flipped ? -1 : 1, 1),
zoom: img ? img.viewportToImageZoom(viewport.getZoom()) : viewport.getZoom(),
};
}
componentDidMount() {
console.debug('componentDidMount');
this.onPaperResize();
}
componentDidUpdate() {
console.debug('componentDidUpdate');
this.onPaperResize();
}
/** Save paperjs ref once created */
paperDidMount(paper) {
console.debug('paper mounted: ', paper);
this.paper = paper;
}
/** */
paperThing() {
const { viewProps, canvasProps } = this.getDisplayProps();
const {
activeTool, fillColor, strokeColor, strokeWidth, svg,
} = this.props;
......@@ -150,21 +155,15 @@ class AnnotationDrawing extends Component {
break;
}
const { canvasOverlay } = VideosReferences.get(this.props.windowId);
const height = canvasOverlay.ref.current.height;
const width = canvasOverlay.ref.current.width;
// canvasProps={{ style: { left: 0, position: 'absolute', top: 0}, height: height, width: width, resize: 'true' }}
return (
<div
className="foo"
style={{
height: '100%', left: 0, position: 'absolute', top: 0, width: '100%',
}}
>
<PaperContainer
canvasProps={{ style: { position: 'absolute', left: 0, top: 0 }, height, width }}
viewProps={this.getViewProps}
canvasProps={canvasProps}
viewProps={viewProps}
onMount={this.paperDidMount}
>
{renderWithPaperScope((paper) => {
......@@ -197,10 +196,14 @@ class AnnotationDrawing extends Component {
/** */
render() {
const { windowId } = this.props;
console.log('[render] videoref : ', VideosReferences.get(windowId));
const container = OSDReferences.get(windowId)
? OSDReferences.get(windowId).current.element
: VideosReferences.get(windowId).ref.current.parentElement;
const osdref = OSDReferences.get(windowId);
const videoref = VideosReferences.get(windowId);
if (!osdref && !videoref) {
throw new Error("Unknown or missing data player, didn't found OpenSeadragon (image viewer) nor the video player");
}
const container = osdref
? osdref.current.element
: videoref.ref.current.parentElement;
return (
ReactDOM.createPortal(this.paperThing(), container)
);
......
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { withStyles } from '@material-ui/core/styles';
import { IconButton, Input } from '@material-ui/core';
import { ArrowDownward, ArrowUpward } from '@material-ui/icons';
import { secondsToHMSarray } from './utils';
/** hh:mm:ss input which behave like a single input for parent */
class HMSInput extends Component {
/** Initialize state structure & bindings */
constructor(props) {
super(props);
// eslint-disable-next-line react/destructuring-assignment
const [h, m, s] = secondsToHMSarray(this.props.seconds);
this.state = {
hours: h,
minutes: m,
seconds: s,
};
this.someChange = this.someChange.bind(this);
this.addOneSec = this.addOneSec.bind(this);
this.subOneSec = this.subOneSec.bind(this);
}
/** update */
componentDidUpdate(prevProps) {
const { seconds } = this.props;
if (prevProps.seconds === seconds) return;
const [h, m, s] = secondsToHMSarray(seconds);
this.setState({
hours: h,
minutes: m,
seconds: s,
});
}
/** If one value is updated, tell the parent component the total seconds counts */
someChange(ev) {
const { onChange } = this.props;
const { state } = this;
state[ev.target.name] = Number(ev.target.value);
onChange(state.hours * 3600 + state.minutes * 60 + state.seconds);
}
/** Add one second */
addOneSec() {
const { seconds } = this.state;
this.setState({ seconds: seconds + 1 });
}
/** Substract one second */
subOneSec() {
const { seconds } = this.state;
this.setState({ seconds: seconds - 1 });
}
/** Render */
render() {
const { hours, minutes, seconds } = this.state;
const { classes } = this.props;
return (
<div className={classes.root}>
<div className={classes.root}>
<Input className={classes.input} name="hours" value={hours} onChange={this.someChange} />
<Input className={classes.input} name="minutes" value={minutes} onChange={this.someChange} />
<Input className={classes.input} name="seconds" value={seconds} onChange={this.someChange} />
</div>
<div className={classes.flexcol} onClick={this.addOneSec}>
<IconButton size="small">
<ArrowUpward />
</IconButton>
<IconButton size="small" onClick={this.subOneSec}>
<ArrowDownward />
</IconButton>
</div>
</div>
);
}
}
/** */
const styles = (theme) => ({
root: {
alignItems: 'center',
display: 'flex',
justifyContent: 'end',
},
// eslint-disable-next-line sort-keys
flexcol: {
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
},
// eslint-disable-next-line sort-keys
input: {
height: 'fit-content',
margin: '2px',
textAlign: 'center',
width: '4ch',
},
});
HMSInput.propTypes = {
classes: PropTypes.object.isRequired,
onChange: PropTypes.func.isRequired,
seconds: PropTypes.number.isRequired,
};
HMSInput.defaultProps = {
};
export default withStyles(styles)(HMSInput);
import * as actions from 'mirador/dist/es/src/state/actions';
import { getCompanionWindow } from 'mirador/dist/es/src/state/selectors/companionWindows';
import { getWindowCurrentTime, getWindowPausedStatus } from 'mirador/dist/es/src/state/selectors/window';
import { getVisibleCanvases } from 'mirador/dist/es/src/state/selectors/canvases';
import AnnotationCreation from '../AnnotationCreation';
......@@ -11,19 +12,24 @@ const mapDispatchToProps = (dispatch, { id, windowId }) => ({
receiveAnnotation: (targetId, annoId, annotation) => dispatch(
actions.receiveAnnotation(targetId, annoId, annotation),
),
setCurrentTime: (...args) => dispatch(actions.setWindowCurrentTime(windowId, ...args)),
setSeekTo: (...args) => dispatch(actions.setWindowSeekTo(windowId, ...args)),
});
/** */
function mapStateToProps(state, { id: companionWindowId, windowId }) {
const { annotationid } = getCompanionWindow(state, { companionWindowId, windowId });
const currentTime = getWindowCurrentTime(state, { windowId });
const cw = getCompanionWindow(state, { companionWindowId, windowId });
const { annotationid } = cw;
const canvases = getVisibleCanvases(state, { windowId });
let annotation;
let annotation = null;
canvases.forEach((canvas) => {
const annotationsOnCanvas = state.annotations[canvas.id];
Object.values(annotationsOnCanvas || {}).forEach((value, i) => {
if (value.json && value.json.items) {
annotation = value.json.items.find((anno) => anno.id === annotationid);
const maybeAnnot = value.json.items.find((anno) => anno.id === annotationid);
if (maybeAnnot !== undefined) annotation = maybeAnnot;
}
});
});
......@@ -32,6 +38,8 @@ function mapStateToProps(state, { id: companionWindowId, windowId }) {
annotation,
canvases,
config: state.config,
currentTime,
paused: getWindowPausedStatus(state, { windowId }),
};
}
......
......@@ -7,3 +7,13 @@ export function mapChildren(layerThing) {
}
return layerThing;
}
/** Pretty print a seconds count into HH:mm:ss */
export function secondsToHMS(secs) {
return `${Math.floor(secs / 3600)}:${Math.floor(secs / 60)}:${secs % 60}`;
}
/** Split a second to [hours, minutes, seconds] */
export function secondsToHMSarray(secs) {
return [Math.floor(secs / 3600), Math.floor(secs / 60), secs % 60];
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment