diff --git a/babel.config.js b/babel.config.js index 67796265100b1c7423d4d4d4b3e0efd56000eef6..ef1fc3ba7850cebcaf41f9bd941d6b5e590cfc6b 100644 --- a/babel.config.js +++ b/babel.config.js @@ -1,4 +1,9 @@ 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', diff --git a/nwb.config.js b/nwb.config.js index 509e5a75bdeb087340976ed93ffcbb21553e38e1..4281080296fa16739b80dd18df7b3beb97774969 100644 --- a/nwb.config.js +++ b/nwb.config.js @@ -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: { diff --git a/package.json b/package.json index 7e988bc36512b3407c935306fa8747b20760545a..86c75e456435d93170471817b9e1607adb09a5e9 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/AnnotationCreation.js b/src/AnnotationCreation.js index 54d09c628acf4da5703376bc93a83887a3584e21..53f4c7c53dcf9623567f38da5e0ec6c64fbd9540 100644 --- a/src/AnnotationCreation.js +++ b/src/AnnotationCreation.js @@ -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 }), diff --git a/src/AnnotationDrawing.js b/src/AnnotationDrawing.js index 8cb4cfa887b3a36fcdf6bf113caa668fe01f12f5..b817d2425f045a25087d0e6f7b3557ee71b2ccf9 100644 --- a/src/AnnotationDrawing.js +++ b/src/AnnotationDrawing.js @@ -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 { @@ -10,12 +11,23 @@ import RectangleTool, FreeformPathTool, } - from '@psychobolt/react-paperjs-editor'; +from '@psychobolt/react-paperjs-editor'; import { Point } from 'paper'; 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) ); } }