diff --git a/mirador b/mirador index 2a11c195b2274ae892e01df86c60e1903fd266a1..9f36a451d213fffb794e41198bc312e2b9cfa80e 160000 --- a/mirador +++ b/mirador @@ -1 +1 @@ -Subproject commit 2a11c195b2274ae892e01df86c60e1903fd266a1 +Subproject commit 9f36a451d213fffb794e41198bc312e2b9cfa80e diff --git a/package.json b/package.json index f5f114d1181b4bf7ff310417c05f142bf77a2c85..de5a520d78b55fe3f8680e3c484162018d2e10fa 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,8 @@ "test:ci": "jest --ci --reporters=default --reporters=jest-junit --watchAll=false" }, "dependencies": { + "@emotion/react": "^11.11.3", + "@emotion/styled": "^11.11.0", "@mui/system": "^5.15.1", "@psychobolt/react-paperjs": "^1.0.3", "@psychobolt/react-paperjs-editor": "0.0.11", diff --git a/src/AnnotationCreation.js b/src/AnnotationCreation.js index 049f8877d5068c3914c5fea8a88085a0d92eb421..3d2c604fd333d3f7eca6a1f98abcb3594a9f0760 100644 --- a/src/AnnotationCreation.js +++ b/src/AnnotationCreation.js @@ -33,6 +33,7 @@ import CursorIcon from './icons/Cursor'; import HMSInput from './HMSInput'; import ImageFormField from './ImageFormField'; import { secondsToHMS } from './utils'; +import RangeSlider from './slider'; /** Extract time information from annotation target */ function timeFromAnnoTarget(annotarget) { @@ -106,6 +107,7 @@ class AnnotationCreation extends Component { } } + const toolState = { activeTool: 'cursor', closedMode: 'closed', @@ -136,11 +138,11 @@ class AnnotationCreation extends Component { textBody: '', textEditorStateBustingKey: 0, // eslint-disable-next-line sort-keys,max-len - // TO DO : The state must be updated with the video's timing information when the component is mounted valueTime: [0, 1], xywh: null, ...annoState, valuetextTime: '', + mediaVideo: null, }; this.submitForm = this.submitForm.bind(this); @@ -166,6 +168,10 @@ class AnnotationCreation extends Component { this.valuetextTime = this.valuetextTime.bind(this); } + componentDidMount() { + const mediaVideo = VideosReferences.get(this.props.windowId); + this.setState({ mediaVideo }); // Update mediaVideo in state + } /** */ handleImgChange(newUrl, imgRef) { const { image } = this.state; @@ -347,10 +353,10 @@ class AnnotationCreation extends Component { this.setState({ image: { id: null }, svg: null, - tend: null, + tend: 0, textBody: '', textEditorStateBustingKey: textEditorStateBustingKey + 1, - tstart: null, + tstart: 0, xywh: null, }); } @@ -412,17 +418,18 @@ class AnnotationCreation extends Component { textEditorStateBustingKey, image, valueTime, + mediaVideo, } = this.state; - let mediaVideo; // TODO : Vérifier ce code, c'est étrange de comprarer un typeof à une chaine de caractère. const mediaIsVideo = typeof VideosReferences.get(windowId) !== 'undefined'; if (mediaIsVideo) { - mediaVideo = VideosReferences.get(windowId); valueTime[0] = tstart; valueTime[1] = tend; } + const isVideoDataLoaded = mediaVideo && mediaVideo.video && !isNaN(mediaVideo.video.duration) && mediaVideo.video.duration > 0; + return ( <CompanionWindow title={annotation ? 'Edit annotation' : 'New annotation'} @@ -473,22 +480,29 @@ class AnnotationCreation extends Component { <Typography id="range-slider" variant="overline"> Display period </Typography> - {/* <Typography> - {mediaIsVideo ? mediaVideo?.video.duration : null} - </Typography> */} - <Slider - value={valueTime} - onChange={this.handleChangeTime} - valueLabelDisplay="auto" - aria-labelledby="range-slider" - getAriaValueText={secondsToHMS} - max={mediaVideo ? mediaVideo.video.duration : null} - color="secondary" - windowId={windowId} - sx={{ - color: 'rgba(1, 0, 0, 0.38)', - }} - /> + {isVideoDataLoaded ? ( + <div> + <Typography> + {this.state.mediaVideo.video.duration} + </Typography> + <Slider + value={valueTime} + onChange={this.handleChangeTime} + valueLabelDisplay="auto" + aria-labelledby="range-slider" + max={Math.round(this.state.mediaVideo.video.duration)} + color="secondary" + windowId={windowId} + sx={{ + color: 'rgba(1, 0, 0, 0.38)', + }} + /> + </div> + ) : ( + <Typography>Loading video data...</Typography> + )} + + </Grid> <div style={{ alignContent: 'center', diff --git a/src/CanvasListItem.js b/src/CanvasListItem.js index f82e5e6bb997424bf0de89ae544724170cd8e74c..ddd839997a0195ef4b7cf381c6eb3c19e11a57e0 100644 --- a/src/CanvasListItem.js +++ b/src/CanvasListItem.js @@ -1,4 +1,4 @@ -import React, { Component } from 'react'; +import React, { Component, createRef, forwardRef } from 'react'; import PropTypes from 'prop-types'; import DeleteIcon from '@mui/icons-material/DeleteForever'; import EditIcon from '@mui/icons-material/Edit'; @@ -84,13 +84,14 @@ class CanvasListItem extends Component { /** */ render() { - const { children } = this.props; const { isHovering } = this.state; const { windowViewType, toggleSingleCanvasDialogOpen } = this.context; + return ( <div onMouseEnter={this.handleMouseHover} onMouseLeave={this.handleMouseHover} + className="mirador-annotation-list-item" > {isHovering && this.editable() && ( <div @@ -124,7 +125,6 @@ class CanvasListItem extends Component { <li {...this.props} // eslint-disable-line react/jsx-props-no-spreading > - {children} </li> </div> ); @@ -141,4 +141,4 @@ CanvasListItem.propTypes = { CanvasListItem.contextType = AnnotationActionsContext; -export default CanvasListItem; +export default forwardRef((props, ref) => <CanvasListItem {...props} containerRef={ref} />); diff --git a/src/HMSInput.js b/src/HMSInput.js index 84d0eed6db4471362b1c344d2b0f832845ec0f9a..583f8513f2fd377bff51f767f1d459a61591b72d 100644 --- a/src/HMSInput.js +++ b/src/HMSInput.js @@ -25,12 +25,12 @@ const StyledRoot = styled('div')({ }); function HMSInput({ seconds, onChange }) { - const [hms, setHms] = useState(secondsToHMSarray(seconds)); - console.log('hms', secondsToHMSarray(seconds)); - console.log('seconds', seconds); + const [hms, setHms] = useState(secondsToHMSarray(0)); useEffect(() => { + if(seconds != null) { setHms(secondsToHMSarray(seconds)); + } }, [seconds]); const someChange = (ev) => { diff --git a/src/ImageFormField.js b/src/ImageFormField.js index 5fe3d13796d9daf52ed274780063bb7e35f9b09d..b5583a644eb2e0b9c83a632349e417f7b6a8376f 100644 --- a/src/ImageFormField.js +++ b/src/ImageFormField.js @@ -19,7 +19,7 @@ function ImageFormField({ value: image, onChange }) { } else { setImgIsValid(!!image.id); } - }, [image]); +}, [image]); const imgUrl = image.id === null ? '' : image.id; diff --git a/src/plugins/canvasAnnotationsPlugin.js b/src/plugins/canvasAnnotationsPlugin.js index 241e7ffcac2165f7fff9f2e35ceaf0f29a197908..dee3bb9b02dc3d1345e61689b1284b31b54c174f 100644 --- a/src/plugins/canvasAnnotationsPlugin.js +++ b/src/plugins/canvasAnnotationsPlugin.js @@ -30,10 +30,15 @@ class CanvasAnnotationsWrapper extends Component { render() { const { addCompanionWindow, annotationsOnCanvases, canvases, config, receiveAnnotation, - switchToSingleCanvasView, TargetComponent, targetProps, windowViewType, + switchToSingleCanvasView, TargetComponent, targetProps, windowViewType, containerRef, } = this.props; const { singleCanvasDialogOpen } = this.state; + const props = { + ...targetProps, + listContainerComponent: CanvasListItem, + }; + return ( <AnnotationActionsContext.Provider value={{ @@ -49,7 +54,8 @@ class CanvasAnnotationsWrapper extends Component { }} > <TargetComponent - {...targetProps} // eslint-disable-line react/jsx-props-no-spreading + {...props} + ref={containerRef} /> {windowViewType !== 'single' && ( <SingleCanvasDialog @@ -74,6 +80,10 @@ CanvasAnnotationsWrapper.propTypes = { adapter: PropTypes.func, }), }).isRequired, + containerRef: PropTypes.oneOfType([ + PropTypes.func, + PropTypes.shape({ current: PropTypes.instanceOf(Element) }), + ]), receiveAnnotation: PropTypes.func.isRequired, switchToSingleCanvasView: PropTypes.func.isRequired, TargetComponent: PropTypes.oneOfType([ @@ -87,6 +97,7 @@ CanvasAnnotationsWrapper.propTypes = { CanvasAnnotationsWrapper.defaultProps = { annotationsOnCanvases: {}, canvases: [], + containerRef: null, }; /** */ diff --git a/src/slider.js b/src/slider.js new file mode 100644 index 0000000000000000000000000000000000000000..405103ded92188e8807775a9eb9f9a83d717e6cf --- /dev/null +++ b/src/slider.js @@ -0,0 +1,34 @@ +import * as React from 'react'; +import Box from '@mui/material/Box'; +import Slider from '@mui/material/Slider'; + +// eslint-disable-next-line require-jsdoc +function valuetext(value) { + return `${value}°C`; +} + +export default function RangeSlider({max, valueTime, windowId, onChange}) { + const [value, setValue] = React.useState(valueTime); + // eslint-disable-next-line require-jsdoc + const handleChange = (event, newValue) => { + setValue(newValue); + onChange(newValue); + }; + + return ( + <Box sx={{ width: 300 }}> + <Slider + getAriaLabel={() => 'Temperature range'} + value={value} + max = {max} + onChange={handleChange} + valueLabelDisplay="auto" + getAriaValueText={valuetext} + windowId={windowId} + sx={{ + color: 'rgba(1, 0, 0, 0.38)', + }} + /> + </Box> + ); +}