Skip to content
Snippets Groups Projects
Commit 2aa4e1b1 authored by David Beniamine's avatar David Beniamine
Browse files

Merge branch '1-annotations-on-video' into 'tetras-main'

Allow to add annotations on video

See merge request iiif/mirador-annotations!1
parents 4f136781 ecf8aea1
Branches
No related tags found
No related merge requests found
module.exports = {
plugins: [
// TODO loose: which options is ignored in depencies ?
['@babel/plugin-proposal-private-methods', { loose: true }],
['@babel/plugin-proposal-private-property-in-object', { loose: true }],
],
presets: [
[
'@babel/preset-env',
......
......@@ -4,12 +4,12 @@ module.exports = {
type: 'react-component',
npm: {
esModules: true,
umd: {
global: 'MiradorAnnotation',
externals: {
react: 'React',
},
},
// umd: {
// global: 'MiradorAnnotation',
// externals: {
// react: 'React',
// },
// },
},
webpack: {
aliases: {
......
......@@ -11,8 +11,8 @@
"umd"
],
"scripts": {
"build": "nwb build-react-component",
"clean": "nwb clean-module && nwb clean-demo",
"build": "nwb build-react-component --no-demo",
"clean": "nwb clean-module",
"lint": "eslint ./src ./__tests__",
"prepublishOnly": "npm run build",
"start": "nwb serve-react-demo",
......@@ -26,7 +26,6 @@
"draft-js": "^0.11.6",
"draft-js-export-html": "^1.4.1",
"draft-js-import-html": "^1.4.1",
"immutable": "^4.0.0-rc.12",
"material-ui-color-components": "^0.3.0",
"paper": "^0.12.11",
"react-color": "^2.18.1"
......@@ -61,7 +60,6 @@
"eslint-plugin-jest": "^23.18.0",
"eslint-plugin-jsx-a11y": "^6.3.1",
"eslint-plugin-react": "^7.20.3",
"eslint-plugin-react-hooks": "^4.0.6",
"jest": "^26.1.0",
"jest-canvas-mock": "^2.2.0",
"jest-localstorage-mock": "^2.4.2",
......
......@@ -31,6 +31,27 @@ import TextEditor from './TextEditor';
import WebAnnotation from './WebAnnotation';
import CursorIcon from './icons/Cursor';
/** Extract time information from annotation target */
function timeFromAnnoTarget(annotarget) {
// TODO w3c media fragments: t=,10 t=5,
const r = /t=([0-9]+),([0-9]+)/.exec(annotarget);
if (!r || r.length !== 3) {
return ['', ''];
}
return [r[1], r[2]];
}
/** Extract xywh from annotation target */
function geomFromAnnoTarget(annotarget) {
console.warn('TODO proper extraction');
const r = /xywh=((-?[0-9]+,?)+)/.exec(annotarget);
console.info('extracted from ', annotarget, r);
if (!r || r.length !== 3) {
return ['', ''];
}
return [r[1], r[2]];
}
/** */
class AnnotationCreation extends Component {
/** */
......@@ -38,6 +59,8 @@ class AnnotationCreation extends Component {
super(props);
const annoState = {};
if (props.annotation) {
//
// annotation body
if (Array.isArray(props.annotation.body)) {
annoState.tags = [];
props.annotation.body.forEach((body) => {
......@@ -50,19 +73,27 @@ class AnnotationCreation extends Component {
} else {
annoState.annoBody = props.annotation.body.value;
}
//
// drawing position
if (props.annotation.target.selector) {
if (Array.isArray(props.annotation.target.selector)) {
props.annotation.target.selector.forEach((selector) => {
if (selector.type === 'SvgSelector') {
annoState.svg = selector.value;
} else if (selector.type === 'FragmentSelector') {
annoState.xywh = selector.value.replace('xywh=', '');
// TODO proper fragment selector extraction
annoState.xywh = geomFromAnnoTarget(selector.value);
[annoState.tstart, annoState.tend] = timeFromAnnoTarget(selector.value);
}
});
} else {
annoState.svg = props.annotation.target.selector.value;
// eslint-disable-next-line max-len
[annoState.tstart, annoState.tend] = timeFromAnnoTarget(props.annotation.target.selector.value);
}
}
//
// start/end time
}
this.state = {
activeTool: 'cursor',
......@@ -77,12 +108,16 @@ class AnnotationCreation extends Component {
strokeColor: '#00BFFF',
strokeWidth: 1,
svg: null,
tend: '',
tstart: '',
xywh: null,
...annoState,
};
this.submitForm = this.submitForm.bind(this);
this.updateBody = this.updateBody.bind(this);
this.updateTstart = this.updateTstart.bind(this);
this.updateTend = this.updateTend.bind(this);
this.updateGeometry = this.updateGeometry.bind(this);
this.changeTool = this.changeTool.bind(this);
this.changeClosedMode = this.changeClosedMode.bind(this);
......@@ -152,8 +187,12 @@ class AnnotationCreation extends Component {
annotation, canvases, closeCompanionWindow, receiveAnnotation, config,
} = this.props;
const {
annoBody, tags, xywh, svg,
annoBody, tags, xywh, svg, tstart, tend,
} = this.state;
let fsel = xywh;
if (tstart && tend) {
fsel = `${xywh || ''}&t=${tstart},${tend}`;
}
canvases.forEach((canvas) => {
const storageAdapter = config.annotation.adapter(canvas.id);
const anno = new WebAnnotation({
......@@ -163,7 +202,7 @@ class AnnotationCreation extends Component {
manifestId: canvas.options.resource.id,
svg,
tags,
xywh,
xywh: fsel,
}).toJson();
if (annotation) {
storageAdapter.update(anno).then((annoPage) => {
......@@ -200,6 +239,12 @@ 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 }); }
/** */
updateGeometry({ svg, xywh }) {
this.setState({
......@@ -216,7 +261,9 @@ class AnnotationCreation extends Component {
const {
activeTool, colorPopoverOpen, currentColorType, fillColor, popoverAnchorEl, strokeColor,
popoverLineWeightAnchorEl, lineWeightPopoverOpen, strokeWidth, closedMode, annoBody, svg,
tstart, tend,
} = this.state;
return (
<CompanionWindow
title={annotation ? 'Edit annotation' : 'New annotation'}
......@@ -342,6 +389,15 @@ class AnnotationCreation extends Component {
</Grid>
</Grid>
<Grid container>
<Grid item xs={12}>
<Typography variant="overline">
Duration
</Typography>
</Grid>
<Grid item xs={12}>
<input name="tstart" type="number" step="1" value={tstart} onChange={this.updateTstart} />
<input name="tend" type="number" step="1" value={tend} onChange={this.updateTend} />
</Grid>
<Grid item xs={12}>
<Typography variant="overline">
Content
......@@ -419,6 +475,7 @@ const styles = (theme) => ({
});
AnnotationCreation.propTypes = {
// TODO proper web annotation type ?
annotation: PropTypes.object, // eslint-disable-line react/forbid-prop-types
canvases: PropTypes.arrayOf(
PropTypes.shape({ id: PropTypes.string, index: PropTypes.number }),
......
......@@ -2,6 +2,7 @@ import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import PropTypes from 'prop-types';
import { OSDReferences } from 'mirador/dist/es/src/plugins/OSDReferences';
import { VideoViewersReferences } from 'mirador/dist/es/src/plugins/VideoViewersReferences';
import { renderWithPaperScope, PaperContainer } from '@psychobolt/react-paperjs';
import
{
......@@ -16,6 +17,17 @@ import flatten from 'lodash/flatten';
import EditTool from './EditTool';
import { mapChildren } from './utils';
/** Use a canvas "like a OSD viewport" (temporary) */
function viewportFromAnnotationOverlayVideo(annotationOverlayVideo) {
const { canvas } = annotationOverlayVideo;
return {
getCenter: () => ({ x: canvas.getWidth() / 2, y: canvas.getHeight() / 2 }),
getFlip: () => false,
getRotation: () => false,
getZoom: () => 1,
};
}
/** */
class AnnotationDrawing extends Component {
/** */
......@@ -25,12 +37,6 @@ class AnnotationDrawing extends Component {
this.addPath = this.addPath.bind(this);
}
/** */
componentDidMount() {
const { windowId } = this.props;
this.OSDReference = OSDReferences.get(windowId);
}
/** */
addPath(path) {
const { closed, strokeWidth, updateGeometry } = this.props;
......@@ -61,23 +67,32 @@ class AnnotationDrawing extends Component {
/** */
paperThing() {
const { windowId } = this.props;
let viewport = null;
let img = null;
if (OSDReferences.get(windowId)) {
console.debug('[annotation-plugin] OSD reference: ', OSDReferences.get(windowId));
viewport = OSDReferences.get(windowId).current.viewport;
img = OSDReferences.get(windowId).current.world.getItemAt(0);
} else if (VideoViewersReferences.get(windowId)) {
console.debug('[annotation-plugin] VideoViewers reference: ', VideoViewersReferences.get(windowId));
viewport = viewportFromAnnotationOverlayVideo(VideoViewersReferences.get(windowId).props);
}
const {
activeTool, fillColor, strokeColor, strokeWidth, svg,
} = this.props;
if (!activeTool || activeTool === 'cursor') return null;
// Setup Paper View to have the same center and zoom as the OSD Viewport
const viewportZoom = this.OSDReference.viewport.getZoom(true);
const image1 = this.OSDReference.world.getItemAt(0);
const center = image1.viewportToImageCoordinates(
this.OSDReference.viewport.getCenter(true),
);
const flipped = this.OSDReference.viewport.getFlip();
// 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();
const viewProps = {
center: new Point(center.x, center.y),
rotation: this.OSDReference.viewport.getRotation(),
rotation: viewport.getRotation(),
scaling: new Point(flipped ? -1 : 1, 1),
zoom: image1.viewportToImageZoom(viewportZoom),
zoom: img ? img.viewportToImageZoom(viewport.getZoom()) : viewport.getZoom(),
};
let ActiveTool = RectangleTool;
......@@ -141,9 +156,12 @@ class AnnotationDrawing extends Component {
/** */
render() {
const { windowId } = this.props;
this.OSDReference = OSDReferences.get(windowId).current;
const container = OSDReferences.get(windowId)
? OSDReferences.get(windowId).current.element
: VideoViewersReferences.get(windowId).apiRef.current;
return (
ReactDOM.createPortal(this.paperThing(), this.OSDReference.element)
ReactDOM.createPortal(this.paperThing(), container)
);
}
}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment