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

wip1

parent df4e0e7b
No related branches found
No related tags found
No related merge requests found
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
"page": true, "page": true,
"document": true "document": true
}, },
"parser": "babel-eslint", "parser": "@babel/eslint-parser",
"plugins": ["jest"], "plugins": ["jest"],
"rules": { "rules": {
"import/prefer-default-export": "off", "import/prefer-default-export": "off",
......
...@@ -16,9 +16,15 @@ const config = { ...@@ -16,9 +16,15 @@ const config = {
defaultSideBarPanel: 'annotations', defaultSideBarPanel: 'annotations',
sideBarOpenByDefault: true, sideBarOpenByDefault: true,
}, },
windows: [{ catalog: [
loadedManifest: 'https://iiif.harvardartmuseums.org/manifests/object/299843', { 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]); mirador.viewer(config, [...annotationPlugins]);
This diff is collapsed.
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
"umd" "umd"
], ],
"scripts": { "scripts": {
"build": "nwb build-react-component --no-demo", "build": "nwb build-react-component",
"clean": "nwb clean-module", "clean": "nwb clean-module",
"lint": "eslint ./src ./__tests__", "lint": "eslint ./src ./__tests__",
"prepublishOnly": "npm run build", "prepublishOnly": "npm run build",
...@@ -36,7 +36,7 @@ ...@@ -36,7 +36,7 @@
"@material-ui/icons": "^4.9.1", "@material-ui/icons": "^4.9.1",
"@material-ui/lab": "^4.0.0-alpha.52", "@material-ui/lab": "^4.0.0-alpha.52",
"lodash": "^4.17.11", "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", "prop-types": "^15.7.2",
"react": "^16.8", "react": "^16.8",
"react-dom": "^16.8", "react-dom": "^16.8",
...@@ -44,27 +44,28 @@ ...@@ -44,27 +44,28 @@
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.10.4", "@babel/core": "^7.10.4",
"@babel/eslint-parser": "^7.19.1",
"@babel/preset-env": "^7.10.4", "@babel/preset-env": "^7.10.4",
"@babel/preset-react": "^7.10.4", "@babel/preset-react": "^7.10.4",
"@material-ui/core": "^4.11.0", "@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1", "@material-ui/icons": "^4.9.1",
"@material-ui/lab": "^4.0.0-alpha.56", "@material-ui/lab": "^4.0.0-alpha.56",
"babel-eslint": "^10.1.0",
"canvas": "^2.6.1", "canvas": "^2.6.1",
"enzyme": "^3.11.0", "enzyme": "^3.11.0",
"enzyme-adapter-react-16": "^1.15.2", "enzyme-adapter-react-16": "^1.15.2",
"eslint": "^7.2", "eslint": "^8.11.0",
"eslint-config-airbnb": "^18.2.0", "eslint-config-airbnb": "^19.0.4",
"eslint-plugin-flowtype": "^5.6.0", "eslint-config-react-app": "^7.0.0",
"eslint-plugin-import": "^2.22.0", "eslint-plugin-flowtype": "^8.0.3",
"eslint-plugin-jest": "^23.18.0", "eslint-plugin-import": "^2.25.4",
"eslint-plugin-jsx-a11y": "^6.3.1", "eslint-plugin-jest": "^26.1.1",
"eslint-plugin-jsx-a11y": "^6.4.1",
"eslint-plugin-react": "^7.20.3", "eslint-plugin-react": "^7.20.3",
"jest": "^26.1.0", "jest": "^26.1.0",
"jest-canvas-mock": "^2.2.0", "jest-canvas-mock": "^2.2.0",
"jest-junit": "^15.0.0", "jest-junit": "^15.0.0",
"jest-localstorage-mock": "^2.4.2", "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", "nwb": "^0.24.7",
"prop-types": "^15.7.2", "prop-types": "^15.7.2",
"react": "^16.8", "react": "^16.8",
......
import React, { Component } from 'react'; import React, { Component } from 'react';
import PropTypes from 'prop-types'; 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 { Update, Alarm } from '@material-ui/icons';
import Typography from '@material-ui/core/Typography'; 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 ToggleButton from '@material-ui/lab/ToggleButton';
import ToggleButtonGroup from '@material-ui/lab/ToggleButtonGroup'; import ToggleButtonGroup from '@material-ui/lab/ToggleButtonGroup';
import RectangleIcon from '@material-ui/icons/CheckBoxOutlineBlank'; import RectangleIcon from '@material-ui/icons/CheckBoxOutlineBlank';
...@@ -17,24 +19,24 @@ import StrokeColorIcon from '@material-ui/icons/BorderColor'; ...@@ -17,24 +19,24 @@ import StrokeColorIcon from '@material-ui/icons/BorderColor';
import LineWeightIcon from '@material-ui/icons/LineWeight'; import LineWeightIcon from '@material-ui/icons/LineWeight';
import ArrowDropDownIcon from '@material-ui/icons/ArrowDropDown'; import ArrowDropDownIcon from '@material-ui/icons/ArrowDropDown';
import FormatShapesIcon from '@material-ui/icons/FormatShapes'; 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 { SketchPicker } from 'react-color';
import { v4 as uuid } from 'uuid'; import { v4 as uuid } from 'uuid';
import { withStyles } from '@material-ui/core/styles'; import { withStyles } from '@material-ui/core/styles';
import CompanionWindow from 'mirador/dist/es/src/containers/CompanionWindow'; 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 AnnotationDrawing from './AnnotationDrawing';
import TextEditor from './TextEditor'; import TextEditor from './TextEditor';
import WebAnnotation from './WebAnnotation'; import WebAnnotation from './WebAnnotation';
import CursorIcon from './icons/Cursor'; import CursorIcon from './icons/Cursor';
import HMSInput from './HMSInput';
import { secondsToHMS } from './utils';
/** Extract time information from annotation target */ /** Extract time information from annotation target */
function timeFromAnnoTarget(annotarget) { function timeFromAnnoTarget(annotarget) {
console.info('TODO proper time extraction from: ', annotarget);
// TODO w3c media fragments: t=,10 t=5, // 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) { if (!r || r.length !== 3) {
return ['', '']; return ['', ''];
} }
...@@ -43,13 +45,12 @@ function timeFromAnnoTarget(annotarget) { ...@@ -43,13 +45,12 @@ function timeFromAnnoTarget(annotarget) {
/** Extract xywh from annotation target */ /** Extract xywh from annotation target */
function geomFromAnnoTarget(annotarget) { function geomFromAnnoTarget(annotarget) {
console.warn('TODO proper extraction'); console.info('TODO proper xywh extraction from: ', annotarget);
const r = /xywh=((-?[0-9]+,?)+)/.exec(annotarget); const r = /xywh=((-?[0-9]+,?)+)/.exec(annotarget);
console.info('extracted from ', annotarget, r);
if (!r || r.length !== 3) { if (!r || r.length !== 3) {
return ['', '']; return '';
} }
return [r[1], r[2]]; return r[1];
} }
/** */ /** */
...@@ -57,6 +58,7 @@ class AnnotationCreation extends Component { ...@@ -57,6 +58,7 @@ class AnnotationCreation extends Component {
/** */ /** */
constructor(props) { constructor(props) {
super(props); super(props);
const annoState = {}; const annoState = {};
if (props.annotation) { if (props.annotation) {
// //
...@@ -91,9 +93,10 @@ class AnnotationCreation extends Component { ...@@ -91,9 +93,10 @@ class AnnotationCreation extends Component {
// eslint-disable-next-line max-len // eslint-disable-next-line max-len
[annoState.tstart, annoState.tend] = timeFromAnnoTarget(props.annotation.target.selector.value); [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 = { const toolState = {
...@@ -115,8 +118,8 @@ class AnnotationCreation extends Component { ...@@ -115,8 +118,8 @@ class AnnotationCreation extends Component {
popoverLineWeightAnchorEl: null, popoverLineWeightAnchorEl: null,
svg: null, svg: null,
tend: Math.floor(props.currentTime) + 10, tend: Math.floor(props.currentTime) + 10,
tstart: Math.floor(props.currentTime),
textEditorStateBustingKey: 0, textEditorStateBustingKey: 0,
tstart: Math.floor(props.currentTime),
xywh: null, xywh: null,
...annoState, ...annoState,
}; };
...@@ -157,6 +160,40 @@ class AnnotationCreation extends Component { ...@@ -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) { openChooseColor(e) {
this.setState({ this.setState({
...@@ -200,20 +237,20 @@ class AnnotationCreation extends Component { ...@@ -200,20 +237,20 @@ class AnnotationCreation extends Component {
const { const {
annoBody, tags, xywh, svg, tstart, tend, textEditorStateBustingKey, annoBody, tags, xywh, svg, tstart, tend, textEditorStateBustingKey,
} = this.state; } = this.state;
let fsel = xywh; let fragsel = xywh;
if (tstart && tend) { if (tstart && tend) {
fsel = `${xywh || ''}&t=${tstart},${tend}`; fragsel += `&t=${tstart},${tend}`;
} }
canvases.forEach((canvas) => { canvases.forEach((canvas) => {
const storageAdapter = config.annotation.adapter(canvas.id); const storageAdapter = config.annotation.adapter(canvas.id);
const anno = new WebAnnotation({ const anno = new WebAnnotation({
body: annoBody, body: !annoBody.length ? `${secondsToHMS(tstart)} -> ${secondsToHMS(tend)}` : annoBody,
canvasId: canvas.id, canvasId: canvas.id,
id: (annotation && annotation.id) || `${uuid()}`, id: (annotation && annotation.id) || `${uuid()}`,
manifestId: canvas.options.resource.id, manifestId: canvas.options.resource.id,
svg, svg,
tags, tags,
xywh: fsel, xywh: fragsel,
}).toJson(); }).toJson();
if (annotation) { if (annotation) {
storageAdapter.update(anno).then((annoPage) => { storageAdapter.update(anno).then((annoPage) => {
...@@ -229,10 +266,10 @@ class AnnotationCreation extends Component { ...@@ -229,10 +266,10 @@ class AnnotationCreation extends Component {
this.setState({ this.setState({
annoBody: '', annoBody: '',
svg: null, svg: null,
tend: '',
textEditorStateBustingKey: textEditorStateBustingKey + 1, textEditorStateBustingKey: textEditorStateBustingKey + 1,
tstart: '',
xywh: null, xywh: null,
tsart: null,
tend: null
}); });
} }
...@@ -255,25 +292,6 @@ class AnnotationCreation extends Component { ...@@ -255,25 +292,6 @@ class AnnotationCreation extends Component {
this.setState({ annoBody }); 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 }) { updateGeometry({ svg, xywh }) {
this.setState({ this.setState({
...@@ -293,6 +311,8 @@ class AnnotationCreation extends Component { ...@@ -293,6 +311,8 @@ class AnnotationCreation extends Component {
tstart, tend, textEditorStateBustingKey, tstart, tend, textEditorStateBustingKey,
} = this.state; } = this.state;
const mediaIsVideo = typeof VideosReferences.get(windowId) !== 'undefined';
return ( return (
<CompanionWindow <CompanionWindow
title={annotation ? 'Edit annotation' : 'New annotation'} title={annotation ? 'Edit annotation' : 'New annotation'}
...@@ -308,6 +328,7 @@ class AnnotationCreation extends Component { ...@@ -308,6 +328,7 @@ class AnnotationCreation extends Component {
svg={svg} svg={svg}
updateGeometry={this.updateGeometry} updateGeometry={this.updateGeometry}
windowId={windowId} windowId={windowId}
player={mediaIsVideo ? VideosReferences.get(windowId) : OSDReferences.get(windowId)}
/> />
<form onSubmit={this.submitForm} className={classes.section}> <form onSubmit={this.submitForm} className={classes.section}>
<Grid container> <Grid container>
...@@ -418,19 +439,25 @@ class AnnotationCreation extends Component { ...@@ -418,19 +439,25 @@ class AnnotationCreation extends Component {
</Grid> </Grid>
</Grid> </Grid>
<Grid container> <Grid container>
{ mediaIsVideo && (
<>
<Grid item xs={12}> <Grid item xs={12}>
<Typography variant="overline"> <Typography variant="overline">
Duration Duration
</Typography> </Typography>
</Grid> </Grid>
<Grid item xs={12}> <div className={classes.timesettings}>
<input name="tstart" type="number" step="1" value={tstart} onChange={this.updateTstart} /> <IconButton onClick={this.setTstartNow}><Alarm /></IconButton>
<Button onClick={this.setTstartNow}>now</Button> <IconButton onClick={this.seekToTstart}><Update /></IconButton>
<Button onClick={this.seekToTstart}>seek</Button> <HMSInput seconds={tstart} onChange={this.updateTstart} />
<input name="tend" type="number" step="1" value={tend} onChange={this.updateTend} /> </div>
<Button onClick={this.setTendNow}>now</Button> <div className={classes.timesettings}>
<Button onClick={this.seekToTend}>seek</Button> <IconButton onClick={this.setTendNow}><Alarm /></IconButton>
</Grid> <IconButton onClick={this.seekToTend}><Update /></IconButton>
<HMSInput seconds={tend} onChange={this.updateTend} />
</div>
</>
)}
<Grid item xs={12}> <Grid item xs={12}>
<Typography variant="overline"> <Typography variant="overline">
Content Content
...@@ -515,10 +542,14 @@ const styles = (theme) => ({ ...@@ -515,10 +542,14 @@ const styles = (theme) => ({
paddingRight: theme.spacing(1), paddingRight: theme.spacing(1),
paddingTop: theme.spacing(2), paddingTop: theme.spacing(2),
}, },
timesettings: {
display: 'flex',
flexDirection: 'row',
flexWrap: 'wrap',
},
}); });
AnnotationCreation.propTypes = { AnnotationCreation.propTypes = {
currentTime: PropTypes.number,
// TODO proper web annotation type ? // TODO proper web annotation type ?
annotation: PropTypes.object, // eslint-disable-line react/forbid-prop-types annotation: PropTypes.object, // eslint-disable-line react/forbid-prop-types
canvases: PropTypes.arrayOf( canvases: PropTypes.arrayOf(
...@@ -531,21 +562,26 @@ AnnotationCreation.propTypes = { ...@@ -531,21 +562,26 @@ AnnotationCreation.propTypes = {
adapter: PropTypes.func, adapter: PropTypes.func,
defaults: PropTypes.objectOf( defaults: PropTypes.objectOf(
PropTypes.oneOfType( PropTypes.oneOfType(
[PropTypes.bool, PropTypes.func, PropTypes.number, PropTypes.string] [PropTypes.bool, PropTypes.func, PropTypes.number, PropTypes.string],
) ),
), ),
}), }),
}).isRequired, }).isRequired,
currentTime: PropTypes.number,
id: PropTypes.string.isRequired, id: PropTypes.string.isRequired,
paused: PropTypes.bool,
receiveAnnotation: PropTypes.func.isRequired, receiveAnnotation: PropTypes.func.isRequired,
windowId: PropTypes.string.isRequired, setCurrentTime: PropTypes.func.isRequired,
setSeekTo: PropTypes.func.isRequired, setSeekTo: PropTypes.func.isRequired,
windowId: PropTypes.string.isRequired,
}; };
AnnotationCreation.defaultProps = { AnnotationCreation.defaultProps = {
annotation: null, annotation: null,
canvases: [], canvases: [],
closeCompanionWindow: () => {}, closeCompanionWindow: () => {},
currentTime: 0,
paused: true,
}; };
export default withStyles(styles)(AnnotationCreation); export default withStyles(styles)(AnnotationCreation);
...@@ -4,7 +4,7 @@ import PropTypes from 'prop-types'; ...@@ -4,7 +4,7 @@ import PropTypes from 'prop-types';
import ResizeObserver from 'react-resize-observer'; import ResizeObserver from 'react-resize-observer';
import { OSDReferences } from 'mirador/dist/es/src/plugins/OSDReferences'; import { OSDReferences } from 'mirador/dist/es/src/plugins/OSDReferences';
import { VideosReferences } from 'mirador/dist/es/src/plugins/VideosReferences'; 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 import
{ {
EllipseTool, EllipseTool,
...@@ -18,20 +18,83 @@ import flatten from 'lodash/flatten'; ...@@ -18,20 +18,83 @@ import flatten from 'lodash/flatten';
import EditTool from './EditTool'; import EditTool from './EditTool';
import { mapChildren } from './utils'; import { mapChildren } from './utils';
/** */ /** Create a portal with a drawing canvas and a form to fill annotations details */
class AnnotationDrawing extends Component { class AnnotationDrawing extends Component {
/** */ /** */
constructor(props) { constructor(props) {
super(props); super(props);
this.paper = null; this.paper = null;
this.getViewProps = this.getViewProps.bind(this); this.getDisplayProps = this.getDisplayProps.bind(this);
this.onPaperResize = this.onPaperResize.bind(this); this.onPaperResize = this.onPaperResize.bind(this);
this.paperDidMount = this.paperDidMount.bind(this); this.paperDidMount = this.paperDidMount.bind(this);
this.addPath = this.addPath.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) { addPath(path) {
const { closed, strokeWidth, updateGeometry } = this.props; const { closed, strokeWidth, updateGeometry } = this.props;
// TODO: Compute xywh of bounding container of layers // TODO: Compute xywh of bounding container of layers
...@@ -59,72 +122,14 @@ class AnnotationDrawing extends Component { ...@@ -59,72 +122,14 @@ class AnnotationDrawing extends Component {
}); });
} }
onPaperResize(ev) { /** Save paperjs ref once created */
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();
}
paperDidMount(paper) { paperDidMount(paper) {
console.debug('paper mounted: ', paper);
this.paper = paper; this.paper = paper;
} }
/** */ /** */
paperThing() { paperThing() {
const { viewProps, canvasProps } = this.getDisplayProps();
const { const {
activeTool, fillColor, strokeColor, strokeWidth, svg, activeTool, fillColor, strokeColor, strokeWidth, svg,
} = this.props; } = this.props;
...@@ -150,21 +155,15 @@ class AnnotationDrawing extends Component { ...@@ -150,21 +155,15 @@ class AnnotationDrawing extends Component {
break; 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 ( return (
<div <div
className="foo"
style={{ style={{
height: '100%', left: 0, position: 'absolute', top: 0, width: '100%', height: '100%', left: 0, position: 'absolute', top: 0, width: '100%',
}} }}
> >
<PaperContainer <PaperContainer
canvasProps={{ style: { position: 'absolute', left: 0, top: 0 }, height, width }} canvasProps={canvasProps}
viewProps={this.getViewProps} viewProps={viewProps}
onMount={this.paperDidMount} onMount={this.paperDidMount}
> >
{renderWithPaperScope((paper) => { {renderWithPaperScope((paper) => {
...@@ -197,10 +196,14 @@ class AnnotationDrawing extends Component { ...@@ -197,10 +196,14 @@ class AnnotationDrawing extends Component {
/** */ /** */
render() { render() {
const { windowId } = this.props; const { windowId } = this.props;
console.log('[render] videoref : ', VideosReferences.get(windowId)); const osdref = OSDReferences.get(windowId);
const container = OSDReferences.get(windowId) const videoref = VideosReferences.get(windowId);
? OSDReferences.get(windowId).current.element if (!osdref && !videoref) {
: VideosReferences.get(windowId).ref.current.parentElement; 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 ( return (
ReactDOM.createPortal(this.paperThing(), container) ReactDOM.createPortal(this.paperThing(), container)
); );
......
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { withStyles } from '@material-ui/core/styles';
import { Input } from '@material-ui/core';
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);
}
/** 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);
}
/** Render */
render() {
const { hours, minutes, seconds } = this.state;
const { classes } = this.props;
return (
<div className={classes.root}>
<input className={classes.ni} type="number" name="hours" value={hours} onChange={this.someChange} />
<input className={classes.ni} type="number" name="minutes" value={minutes} onChange={this.someChange} />
<input className={classes.ni} type="number" name="seconds" value={seconds} onChange={this.someChange} />
</div>
);
}
}
/** */
const styles = (theme) => ({
root: {
display: 'flex',
justifyContent: 'end',
},
// eslint-disable-next-line sort-keys
ni: {
width: '20%',
},
});
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 * as actions from 'mirador/dist/es/src/state/actions';
import { getCompanionWindow } from 'mirador/dist/es/src/state/selectors/companionWindows'; 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 { getVisibleCanvases } from 'mirador/dist/es/src/state/selectors/canvases';
import AnnotationCreation from '../AnnotationCreation'; import AnnotationCreation from '../AnnotationCreation';
...@@ -11,19 +12,24 @@ const mapDispatchToProps = (dispatch, { id, windowId }) => ({ ...@@ -11,19 +12,24 @@ const mapDispatchToProps = (dispatch, { id, windowId }) => ({
receiveAnnotation: (targetId, annoId, annotation) => dispatch( receiveAnnotation: (targetId, annoId, annotation) => dispatch(
actions.receiveAnnotation(targetId, annoId, annotation), 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 }) { 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 }); const canvases = getVisibleCanvases(state, { windowId });
let annotation; let annotation = null;
canvases.forEach((canvas) => { canvases.forEach((canvas) => {
const annotationsOnCanvas = state.annotations[canvas.id]; const annotationsOnCanvas = state.annotations[canvas.id];
Object.values(annotationsOnCanvas || {}).forEach((value, i) => { Object.values(annotationsOnCanvas || {}).forEach((value, i) => {
if (value.json && value.json.items) { 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 }) { ...@@ -32,6 +38,8 @@ function mapStateToProps(state, { id: companionWindowId, windowId }) {
annotation, annotation,
canvases, canvases,
config: state.config, config: state.config,
currentTime,
paused: getWindowPausedStatus(state, { windowId }),
}; };
} }
......
...@@ -7,3 +7,13 @@ export function mapChildren(layerThing) { ...@@ -7,3 +7,13 @@ export function mapChildren(layerThing) {
} }
return 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