diff --git a/src/AnnotationCreation.js b/src/AnnotationCreation.js index d94d9c227dda97a7fd3e710439605efd615e7ef9..f8bfa995f812c647b89b91712fb0ed3636415ff5 100644 --- a/src/AnnotationCreation.js +++ b/src/AnnotationCreation.js @@ -345,7 +345,7 @@ class AnnotationCreation extends Component { id: (annotation && annotation.id) || `${uuid()}`, image, manifestId: canvas.options.resource.id, - svg:svg, + svg, tags, }).toJson(); @@ -457,12 +457,16 @@ class AnnotationCreation extends Component { strokeColor={strokeColor} strokeWidth={strokeWidth} closed={closedMode === 'closed'} - svg={svg} + updateGeometry={this.updateGeometry} windowId={windowId} player={mediaIsVideo ? VideosReferences.get(windowId) : OSDReferences.get(windowId)} - width={mediaIsVideo ? VideosReferences.get(windowId).video.videoWidth : OSDReferences.get(windowId).viewer.world.getItemAt(0).source.dimensions.x} - height={mediaIsVideo ? VideosReferences.get(windowId).video.videoHeight : OSDReferences.get(windowId).viewer.world.getItemAt(0).source.dimensions.y} + /// we need to pass the width and height of the image to the annotation drawing component + width={1920} + height={1080} + + + /> <StyledForm onSubmit={this.submitForm} diff --git a/src/AnnotationDrawing.js b/src/AnnotationDrawing.js index 32ffa474070510a210d891bba869e6986adb6b5d..13e1ba4f26a115a908e875f8a385f28cf88bd934 100644 --- a/src/AnnotationDrawing.js +++ b/src/AnnotationDrawing.js @@ -12,454 +12,16 @@ import { } from 'react-konva'; import { exportStageSVG } from 'react-konva-to-svg'; -class FreeHand extends React.Component { - constructor(props) { - super(props); - this.shapeRef = React.createRef(); - this.trRef = React.createRef(); - } - componentDidMount() { - if (this.trRef.current) { - this.trRef.current.nodes([this.shapeRef.current]); - this.trRef.current.getLayer().batchDraw(); - } - } +import ParentComponent from './shapes/ParentComponent'; - handleClick = () => { - this.props.onShapeClick(this.props.shape); - }; - render() { - const { activeTool,points,fill } = this.props; - const isSelected = this.props.selected - -// will be a custom shape - return ( - - <React.Fragment> - - - <Shape - - ref={this.shapeRef} - x={0} - y={0} - width={this.props.width || 1920} - height={this.props.height || 1080} - points={this.props.points || [0, 0, 100, 0, 100, 100]} - fill={this.props.fill || 'red'} - stroke={this.props.stroke || 'black'} - strokeWidth={this.props.strokeWidth || 1} - id={this.props._id} - draggable={activeTool === 'cursor' || activeTool === 'edit'} - onClick={this.handleClick} - sceneFunc={(context, shape) => { - console.log('scene func',points); - - for (let i = 0; i < points.length; i += 2) { - context.beginPath(); - //draw rect for each point - - context.rect(points[i] - 2.5, points[i + 1]- 2.5, 5, 5); - // fill rect with color - context.closePath(); - - - context.fillStrokeShape(shape); - } - - - // context.beginPath(); - // context.moveTo(20, 50); - // context.lineTo(220, 80); - // context.quadraticCurveTo(150, 100, 260, 170); - // context.closePath(); - // // (!) Konva specific method, it is very important - // context.fillStrokeShape(shape); - - - - }} - - /> - - <Transformer ref={this.trRef} - - visible={activeTool === 'edit' && isSelected} - /> - </React.Fragment> - ); - - } - - } -class EllipseNode extends React.Component { - constructor(props) { - super(props); - this.shapeRef = React.createRef(); - this.trRef = React.createRef(); - } - - componentDidMount() { - if (this.trRef.current) { - this.trRef.current.nodes([this.shapeRef.current]); - this.trRef.current.getLayer().batchDraw(); - } - } - - handleClick = () => { - this.props.onShapeClick(this.props.shape); - - }; - - render() { - const { activeTool } = this.props; - const isSelected = this.props.selected - - return ( - <React.Fragment> - <Ellipse - // map props to konva - ref={this.shapeRef} - x={this.props.x || 100} - y={this.props.y || 100} - width={this.props.width || 100} - height={this.props.height || 100} - fill={this.props.fill || 'red'} - stroke={this.props.stroke || 'black'} - strokeWidth={this.props.strokeWidth || 1} - id={this.props._id} - draggable={activeTool === 'cursor' || activeTool === 'edit'} - onClick={this.handleClick} - - /> - - <Transformer ref={this.trRef} - - visible={activeTool === 'edit' && isSelected} - /> - - </React.Fragment> - ); - } -} - - - -class TextNode extends React.Component { - constructor(props) { - super(props); - - this.shapeRef = React.createRef(); - this.trRef = React.createRef(); - - } - - handleClick = () => { - this.props.onShapeClick(this.props.shape); - - }; - - - componentDidMount() { - if (this.trRef.current) { - this.trRef.current.nodes([this.shapeRef.current]); - this.trRef.current.getLayer().batchDraw(); - - // add event listener for key down - // this.shapeRef.current.addEventListener('keydown', this.handleKeyDown); - } - } - - - render() { - const { activeTool } = this.props; - const isSelected = this.props.selected - - - return ( - <React.Fragment> - <Text - ref={this.shapeRef} - x={this.props.x} - y={this.props.y} - fontSize={this.props.fontSize} - fill={this.props.fill} - text={this.props.text} - id={this.props._id} - - - //dragable if tool is cursor or edit - draggable={activeTool === 'cursor' || activeTool === 'edit'} - onClick={this.handleClick} - onKeyDown={this.handleKeyDown} - - // onClick={activeTool === 'cursor' ? null : null} - // onDblClick={acveTool === 'edit' ? this.handleClick : null}ti - - - - /> - - <Transformer ref={this.trRef} - - visible={activeTool === 'edit' && isSelected} - /> - - - - </React.Fragment> - ); - } - - -} - - -class Rectangle extends React.Component { - constructor(props) { - super(props); - this.shapeRef = React.createRef(); - this.trRef = React.createRef(); - } - - - - componentDidMount() { - if (this.trRef.current) { - this.trRef.current.nodes([this.shapeRef.current]); - this.trRef.current.getLayer().batchDraw(); - } - } - - handleClick = () => { - this.props.onShapeClick(this.props.shape); - - }; - - render() { - const { activeTool } = this.props; - const isSelected = this.props.selected - - return ( - <React.Fragment> - <Rect - // map props to konva - ref={this.shapeRef} - x={this.props.x || 100} - y={this.props.y || 100} - width={this.props.width || 100} - height={this.props.height || 100} - fill={this.props.fill || 'red'} - stroke={this.props.stroke || 'black'} - strokeWidth={this.props.strokeWidth || 1} - id={this.props._id} - draggable={activeTool === 'cursor' || activeTool === 'edit'} - onClick={this.handleClick} - - /> - - - - - - <Transformer ref={this.trRef} - - visible={activeTool === 'edit' && isSelected} - /> - - - - - </React.Fragment> - ); - } -} - - - -class ParentComponent extends React.Component { - constructor(props) { - super(props); - - this.state = { - selectedShapeId: this.props.selectedShapeId, - selectedShape: null, - - }; - - } - - - handleShapeClick = (shape) => { - - this.setState({ - selectedShapeId: shape.id, - selectedShape: shape - }); - this.props.onShapeClick(shape); - // this.setState({ selectedShapeId: id }); - }; - - - - render() { - - const { shapes - } = this.props; - - const { selectedShapeId } = this.state; - let selected = false; - let selid = selectedShapeId; - let selectedShape = null; - - //if length is 1 and selected shape is null - if (shapes.length === 1 && !selectedShapeId) { - selected = true; - selid = shapes[0].id; - selectedShape = shapes[0]; - - } - - return ( - <Layer> - {shapes.map((shape, i) => { - - if (selectedShape?.id === shape.id) { - selected = true; - selectedShape = shape; - } - - - switch (shape.type) { - - case 'rectangle': - - return ( - <Rectangle - - - shape={shape} - selectedShapeId={selid} - selectedShape={selectedShape} - onShapeClick={this.handleShapeClick} - activeTool={this.props.activeTool} - _id={shape.id} - key={i} - x={shape.x} - y={shape.y} - width={shape.width} - height={shape.height} - fill={shape.fill} - stroke={shape.strokeColor} - strokeWidth={shape.strokeWidth} - draggable={this.props.activeTool === 'cursor'} - selected={selected} - // onShapeClick={this.handleShapeClick} - - - - - /> - ); - break; - case 'text': - return ( - <TextNode - - shape={shape} - selectedShapeId={selid} - selectedShape={selectedShape} - onShapeClick={this.handleShapeClick} - activeTool={this.props.activeTool} - selected={selected} - - _id={shape.id} - key={i} - x={shape.x} - y={shape.y} - fontSize={shape.fontSize} - fill={shape.fill} - text={shape.text} - draggable={this.props.activeTool === 'cursor'} - onClick={this.props.activeTool === 'cursor' ? () => this.setState({ currentShape: shape }) : null} - // onDragEnd={this.handleDragEnd(shape.id)} // Add this line - onDblClick={this.handleShapeDblClick} - - - /> - ); - break; - case 'ellipse': - - return ( - <EllipseNode - - shape={shape} - selectedShapeId={selid} - selectedShape={selectedShape} - onShapeClick={this.handleShapeClick} - activeTool={this.props.activeTool} - selected={selected} - - _id={shape.id} - key={i} - x={shape.x} - y={shape.y} - width={shape.width} - height={shape.height} - fill={shape.fill} - stroke={shape.strokeColor} - strokeWidth={shape.strokeWidth} - draggable={this.props.activeTool === 'cursor'} - onClick={this.props.activeTool === 'cursor' ? () => this.setState({ currentShape: shape }) : null} - // onDragEnd={this.handleDragEnd(shape.id)} // Add this line - onDblClick={this.handleShapeDblClick} - - - /> - ); - break; - case 'freehand': - return ( - <FreeHand - - shape={shape} - selectedShapeId={selid} - selectedShape={selectedShape} - onShapeClick={this.handleShapeClick} - activeTool={this.props.activeTool} - selected={selected} - - _id={shape.id} - key={i} - x={shape.x} - y={shape.y} - points={shape.points} - fill={shape.fill} - stroke={shape.strokeColor} - strokeWidth={shape.strokeWidth} - draggable={this.props.activeTool === 'cursor'} - onClick={this.props.activeTool === 'cursor' ? () => this.setState({ currentShape: shape }) : null} - // onDragEnd={this.handleDragEnd(shape.id)} // Add this line - onDblClick={this.handleShapeDblClick} - - /> - ); - break; - } - })} - </Layer> - ); - } -} /** Create a portal with a drawing canvas and a form to fill annotations details */ class AnnotationDrawing extends Component { @@ -511,7 +73,6 @@ class AnnotationDrawing extends Component { onShapeClick = (shape) => { - this.svg(); this.setState({ selectedShapeId: shape.id }); const id = shape.id; // find shape by id diff --git a/src/shapes/EllipseNode.js b/src/shapes/EllipseNode.js new file mode 100644 index 0000000000000000000000000000000000000000..e378f2dee502cc33be9a3e00d9542ceb3b53e218 --- /dev/null +++ b/src/shapes/EllipseNode.js @@ -0,0 +1,75 @@ +import React, { Component, useState } from 'react'; +import ReactDOM from 'react-dom'; +import PropTypes from 'prop-types'; + +import { + Stage, Layer, Star, Text, Circle, Rect + , Ellipse, Transformer,Shape + +} from 'react-konva'; + + + +class EllipseNode extends React.Component { + + constructor(props) { + super(props); + this.shapeRef = React.createRef(); + this.trRef = React.createRef(); + } + + componentDidMount() { + if (this.trRef.current) { + this.trRef.current.nodes([this.shapeRef.current]); + this.trRef.current.getLayer().batchDraw(); + } + } + + handleClick = () => { + this.props.onShapeClick(this.props.shape); + + }; + + render() { + const { activeTool } = this.props; + const isSelected = this.props.selectedShapeId === this.props.shape.id + + return ( + <React.Fragment> + <Ellipse + // map props to konva + ref={this.shapeRef} + x={this.props.x || 100} + y={this.props.y || 100} + width={this.props.width || 100} + height={this.props.height || 100} + fill={this.props.fill || 'red'} + stroke={this.props.stroke || 'black'} + strokeWidth={this.props.strokeWidth || 1} + id={this.props._id} + draggable={activeTool === 'cursor' || activeTool === 'edit'} + onClick={this.handleClick} + + /> + + <Transformer ref={this.trRef} + + visible={activeTool === 'edit' && isSelected} + /> + + </React.Fragment> + ); + } +} + + + +EllipseNode.propTypes = { + onShapeClick: PropTypes.func.isRequired, + shape: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types + activeTool: PropTypes.string.isRequired, + selected: PropTypes.bool.isRequired, +}; + +export default EllipseNode; + diff --git a/src/shapes/FreeHand.js b/src/shapes/FreeHand.js new file mode 100644 index 0000000000000000000000000000000000000000..df1376a526e6afa00f930a8d2baa4054abfffe00 --- /dev/null +++ b/src/shapes/FreeHand.js @@ -0,0 +1,123 @@ +import React, { Component, useState } from 'react'; + +import PropTypes from 'prop-types'; + + +import { + Stage, Layer, Star, Text, Circle, Rect + , Ellipse, Transformer,Shape + +} from 'react-konva'; + +class FreeHand extends React.Component { + + constructor(props) { + super(props); + this.shapeRef = React.createRef(); + this.trRef = React.createRef(); + } + + componentDidMount() { + if (this.trRef.current) { + this.trRef.current.nodes([this.shapeRef.current]); + this.trRef.current.getLayer().batchDraw(); + } + } + + handleClick = () => { + this.props.onShapeClick(this.props.shape); + + }; + + render() { + const { activeTool,points,fill } = this.props; + const isSelected = this.props.selectedShapeId === this.props.shape.id + +// will be a custom shape + + return ( + + + <React.Fragment> + + + <Shape + + ref={this.shapeRef} + x={0} + y={0} + width={this.props.width || 1920} + height={this.props.height || 1080} + points={this.props.points || [0, 0, 100, 0, 100, 100]} + fill={this.props.fill || 'red'} + stroke={this.props.stroke || 'black'} + strokeWidth={this.props.strokeWidth || 1} + id={this.props._id} + draggable={activeTool === 'cursor' || activeTool === 'edit'} + onClick={this.handleClick} + sceneFunc={(context, shape) => { + console.log('scene func',points); + + for (let i = 0; i < points.length; i += 2) { + context.beginPath(); + //draw rect for each point + + context.rect(points[i] - 2.5, points[i + 1]- 2.5, 5, 5); + // fill rect with color + context.closePath(); + + + context.fillStrokeShape(shape); + } + + + // context.beginPath(); + // context.moveTo(20, 50); + // context.lineTo(220, 80); + // context.quadraticCurveTo(150, 100, 260, 170); + // context.closePath(); + // // (!) Konva specific method, it is very important + // context.fillStrokeShape(shape); + + + + }} + + /> + + <Transformer ref={this.trRef} + + visible={activeTool === 'edit' && isSelected} + /> + </React.Fragment> + ); + + } + + } + + + FreeHand.propTypes = { + activeTool: PropTypes.string.isRequired, + fill: PropTypes.string, + height: PropTypes.number, + onShapeClick: PropTypes.func.isRequired, + points: PropTypes.array, + selected: PropTypes.bool, + shape: PropTypes.object.isRequired, + stroke: PropTypes.string, + strokeWidth: PropTypes.number, + width: PropTypes.number, + }; + + FreeHand.defaultProps = { + fill: 'red', + height: 1080, + points: [0, 0, 100, 0, 100, 100], + selected: false, + stroke: 'black', + strokeWidth: 1, + width: 1920, + }; + + export default FreeHand; \ No newline at end of file diff --git a/src/shapes/ParentComponent.js b/src/shapes/ParentComponent.js new file mode 100644 index 0000000000000000000000000000000000000000..a57324b5fbe4d6105a4b3f14cf13d092959912dc --- /dev/null +++ b/src/shapes/ParentComponent.js @@ -0,0 +1,205 @@ +import React, { Component, useState } from 'react'; +import ReactDOM from 'react-dom'; +import PropTypes from 'prop-types'; + + +import FreeHand from './FreeHand'; +import Rectangle from './Rectangle'; + +import EllipseNode from './EllipseNode'; +import TextNode from './TextNode'; +import { + Stage, Layer, Star, Text, Circle, Rect + , Ellipse, Transformer,Shape + +} from 'react-konva'; + +class ParentComponent extends React.Component { + constructor(props) { + super(props); + + this.state = { + selectedShapeId: this.props.selectedShapeId, + selectedShape: null, + + }; + + } + + + handleShapeClick = (shape) => { + + this.setState({ + selectedShapeId: shape.id, + selectedShape: shape + }); + this.props.onShapeClick(shape); + // this.setState({ selectedShapeId: id }); + }; + + + + render() { + + const { shapes + } = this.props; + + const { selectedShapeId } = this.state; + let selected = false; + let selid = selectedShapeId; + let selectedShape = null; + + //if length is 1 and selected shape is null + if (shapes.length === 1 && !selectedShapeId) { + selected = true; + selid = shapes[0].id; + selectedShape = shapes[0]; + + } + + return ( + <Layer> + {shapes.map((shape, i) => { + + if (selectedShape?.id === shape.id) { + selected = true; + selectedShape = shape; + } + + + switch (shape.type) { + + case 'rectangle': + + return ( + <Rectangle + + + shape={shape} + selectedShapeId={selid} + selectedShape={selectedShape} + onShapeClick={this.handleShapeClick} + activeTool={this.props.activeTool} + _id={shape.id} + key={i} + x={shape.x} + y={shape.y} + width={shape.width} + height={shape.height} + fill={shape.fill} + stroke={shape.strokeColor} + strokeWidth={shape.strokeWidth} + draggable={this.props.activeTool === 'cursor'} + selected={selected} + // onShapeClick={this.handleShapeClick} + + + + + /> + ); + break; + case 'text': + return ( + <TextNode + + shape={shape} + selectedShapeId={selid} + selectedShape={selectedShape} + onShapeClick={this.handleShapeClick} + activeTool={this.props.activeTool} + selected={selected} + + _id={shape.id} + key={i} + x={shape.x} + y={shape.y} + fontSize={shape.fontSize} + fill={shape.fill} + text={shape.text} + draggable={this.props.activeTool === 'cursor'} + onClick={this.props.activeTool === 'cursor' ? () => this.setState({ currentShape: shape }) : null} + // onDragEnd={this.handleDragEnd(shape.id)} // Add this line + onDblClick={this.handleShapeDblClick} + + + /> + ); + break; + case 'ellipse': + + return ( + <EllipseNode + + shape={shape} + selectedShapeId={selid} + selectedShape={selectedShape} + onShapeClick={this.handleShapeClick} + activeTool={this.props.activeTool} + selected={selected} + + _id={shape.id} + key={i} + x={shape.x} + y={shape.y} + width={shape.width} + height={shape.height} + fill={shape.fill} + stroke={shape.strokeColor} + strokeWidth={shape.strokeWidth} + draggable={this.props.activeTool === 'cursor'} + onClick={this.props.activeTool === 'cursor' ? () => this.setState({ currentShape: shape }) : null} + // onDragEnd={this.handleDragEnd(shape.id)} // Add this line + onDblClick={this.handleShapeDblClick} + + + /> + ); + break; + case 'freehand': + return ( + <FreeHand + + shape={shape} + selectedShapeId={selid} + selectedShape={selectedShape} + onShapeClick={this.handleShapeClick} + activeTool={this.props.activeTool} + selected={selected} + + _id={shape.id} + key={i} + x={shape.x} + y={shape.y} + points={shape.points} + fill={shape.fill} + stroke={shape.strokeColor} + strokeWidth={shape.strokeWidth} + draggable={this.props.activeTool === 'cursor'} + onClick={this.props.activeTool === 'cursor' ? () => this.setState({ currentShape: shape }) : null} + // onDragEnd={this.handleDragEnd(shape.id)} // Add this line + onDblClick={this.handleShapeDblClick} + + /> + ); + break; + } + })} + </Layer> + ); + } +} + + +ParentComponent.propTypes = { + shapes: PropTypes.arrayOf(PropTypes.object).isRequired, + onShapeClick: PropTypes.func.isRequired, + selectedShapeId: PropTypes.string, + activeTool: PropTypes.string.isRequired, +}; + +ParentComponent.defaultProps = { + selectedShapeId: null, +}; + +export default ParentComponent; \ No newline at end of file diff --git a/src/shapes/Rectangle.js b/src/shapes/Rectangle.js new file mode 100644 index 0000000000000000000000000000000000000000..5ec844062088bb3bdded9b483ad7d102347cd9de --- /dev/null +++ b/src/shapes/Rectangle.js @@ -0,0 +1,88 @@ +import React, { Component, useState } from 'react'; + +import PropTypes from 'prop-types'; + + +import { + Stage, Layer, Star, Text, Circle, Rect + , Ellipse, Transformer,Shape + +} from 'react-konva'; + + +class Rectangle extends React.Component { + constructor(props) { + super(props); + this.shapeRef = React.createRef(); + this.trRef = React.createRef(); + } + + + + componentDidMount() { + if (this.trRef.current) { + this.trRef.current.nodes([this.shapeRef.current]); + this.trRef.current.getLayer().batchDraw(); + } + } + + handleClick = () => { + this.props.onShapeClick(this.props.shape); + + }; + + render() { + const { activeTool } = this.props; + const isSelected = this.props.selectedShapeId === this.props.shape.id + + + console.log('rect props', this.props); + return ( + <React.Fragment> + <Rect + // map props to konva + ref={this.shapeRef} + x={this.props.x || 100} + y={this.props.y || 100} + width={this.props.width || 100} + height={this.props.height || 100} + fill={this.props.fill || 'red'} + stroke={this.props.stroke || 'black'} + strokeWidth={this.props.strokeWidth || 1} + id={this.props._id} + draggable={activeTool === 'cursor' || activeTool === 'edit'} + onClick={this.handleClick} + + /> + + + + + + <Transformer ref={this.trRef} + + visible={activeTool === 'edit' && isSelected} + /> + + + + + </React.Fragment> + ); + } +} + + + +Rectangle.propTypes = { + shape: PropTypes.object.isRequired, + onShapeClick: PropTypes.func.isRequired, + activeTool: PropTypes.string.isRequired, + selected: PropTypes.bool, +}; + +Rectangle.defaultProps = { + selected: false, +}; + +export default Rectangle; \ No newline at end of file diff --git a/src/shapes/TextNode.js b/src/shapes/TextNode.js new file mode 100644 index 0000000000000000000000000000000000000000..f7c7250a84d2940c01b12855a5be2d3cf304669a --- /dev/null +++ b/src/shapes/TextNode.js @@ -0,0 +1,81 @@ +import React, { Component, useState } from 'react'; + +import PropTypes from 'prop-types'; + + +import { + Stage, Layer, Star, Text, Circle, Rect + , Ellipse, Transformer,Shape + +} from 'react-konva'; +class TextNode extends React.Component { + constructor(props) { + super(props); + + this.shapeRef = React.createRef(); + this.trRef = React.createRef(); + + } + + handleClick = () => { + this.props.onShapeClick(this.props.shape); + + }; + + + componentDidMount() { + if (this.trRef.current) { + this.trRef.current.nodes([this.shapeRef.current]); + this.trRef.current.getLayer().batchDraw(); + + // add event listener for key down + // this.shapeRef.current.addEventListener('keydown', this.handleKeyDown); + } + } + + + render() { + const { activeTool } = this.props; + const isSelected = this.props.selectedShapeId === this.props.shape.id + + + return ( + <React.Fragment> + <Text + ref={this.shapeRef} + x={this.props.x} + y={this.props.y} + fontSize={this.props.fontSize} + fill={this.props.fill} + text={this.props.text} + id={this.props._id} + + + //dragable if tool is cursor or edit + draggable={activeTool === 'cursor' || activeTool === 'edit'} + onClick={this.handleClick} + onKeyDown={this.handleKeyDown} + + // onClick={activeTool === 'cursor' ? null : null} + // onDblClick={acveTool === 'edit' ? this.handleClick : null}ti + + + + /> + + <Transformer ref={this.trRef} + + visible={activeTool === 'edit' && isSelected} + /> + + + + </React.Fragment> + ); + } + + +} + + +export default TextNode; \ No newline at end of file