Skip to content
Snippets Groups Projects
Commit fc7e81b2 authored by Samuel Jugnet's avatar Samuel Jugnet
Browse files

transform konvas

parent a4899c55
No related branches found
No related tags found
1 merge request!10Draft: MigratingAnnotationCreation to MUI5.
Pipeline #1702 failed
import React, { Component, useState } from 'react'; import React, { Component, useState } from 'react';
import ReactDOM from 'react-dom'; import ReactDOM from 'react-dom';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
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 } from '@psychobolt/react-paperjs';
import { v4 as uuidv4 } from 'uuid'; import { v4 as uuidv4 } from 'uuid';
import {
EllipseTool,
PolygonTool,
RectangleTool,
FreeformPathTool,
}
from '@psychobolt/react-paperjs-editor';
import { import {
Stage, Layer, Star, Text, Circle, Rect Stage, Layer, Star, Text, Circle, Rect
, Ellipse , Ellipse, Transformer,
} from 'react-konva'; } from 'react-konva';
import { Point } from 'paper';
import flatten from 'lodash/flatten';
import EditTool from './EditTool';
import { mapChildren } from './utils';
/** Create a portal with a drawing canvas and a form to fill annotations details */ class TextNode extends React.Component {
class AnnotationDrawing extends Component {
/** */
constructor(props) { constructor(props) {
super(props); super(props);
this.shapeRef = React.createRef();
this.trRef = React.createRef();
this.paper = null;
this.konvas = null;
this.getDisplayProps = this.getDisplayProps.bind(this);
this.onPaperResize = this.onPaperResize.bind(this);
this.paperDidMount = this.paperDidMount.bind(this);
this.addPath = this.addPath.bind(this);
this.state = {
shapes: [],
newShape: null,
currentShape: null,
};
} }
/** Sync drawing canvas on componentDidMount */ handleClick = () => {
this.props.onShapeClick(this.props.id);
};
componentDidMount() { componentDidMount() {
this.onPaperResize(); if (this.trRef.current) {
this.trRef.current.nodes([this.shapeRef.current]);
this.trRef.current.getLayer().batchDraw();
}
} }
/** Sync drawing canvas on componentDidUpdate */ render() {
componentDidUpdate() { const { activeTool } = this.props;
this.onPaperResize(); console.log("selectedId", this.props.selectedShapeId);
const isSelected = this.props.id === this.props.selectedShapeId;
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}
//dragable if tool is cursor or edit
draggable={activeTool === 'cursor' || activeTool === 'edit'}
onClick={this.handleClick}
// onClick={activeTool === 'cursor' ? null : null}
// onDblClick={acveTool === 'edit' ? this.handleClick : null}ti
/>
<Transformer ref={this.trRef}
visible={activeTool === 'edit' && isSelected}
/>
</React.Fragment>
);
} }
/** Sync drawing canvas size/zoom with annotations canvas */
onPaperResize(ev) {
const { windowId } = this.props;
if (VideosReferences.get(windowId) && this.paper) {
const { canvasOverlay, video } = VideosReferences.get(windowId);
const { height, width } = canvasOverlay.ref.current;
const { videoHeight, videoWidth } = video;
this.paper.view.center = new Point(videoWidth / 2, videoHeight / 2);
this.paper.view.zoom = canvasOverlay.scale;
this.paper.view.viewSize = new this.paper.Size(width, height);
} }
class Rectangle extends React.Component {
constructor(props) {
super(props);
this.shapeRef = React.createRef();
this.trRef = React.createRef();
} }
/** Build parameters to paperjs View and canvas */
getDisplayProps() {
const { windowId } = this.props;
const osdref = OSDReferences.get(windowId);
const videoref = VideosReferences.get(windowId);
if (osdref) {
const { viewport } = osdref.current; componentDidMount() {
const img = osdref.current.world.getItemAt(0); if (this.trRef.current) {
const center = img.viewportToImageCoordinates(viewport.getCenter(true)); this.trRef.current.nodes([this.shapeRef.current]);
return { this.trRef.current.getLayer().batchDraw();
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()),
},
};
} }
}
handleClick = () => {
this.props.onShapeClick(this.props.id);
if (videoref) {
const { height, width } = videoref.canvasOverlay.ref.current;
return {
canvasProps: {
height,
resize: 'true',
style: {
left: 0, position: 'absolute', top: 0,
},
width,
},
viewProps: {
center: new Point(width / 2, height / 2),
height,
width,
zoom: videoref.canvasOverlay.scale,
},
}; };
render() {
const { activeTool } = this.props;
console.log('active tool ===>', activeTool);
console.log("selectedId", this.props.selectedShapeId);
const isSelected = this.props.id === this.props.selectedShapeId;
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}
draggable={activeTool === 'cursor' || activeTool === 'edit'}
onClick={this.handleClick}
/>
<Transformer ref={this.trRef}
visible={activeTool === 'edit'}
/>
</React.Fragment>
);
}
} }
throw new Error('Unknown or missing data player, not OpenSeadragon (image viewer) nor the video player');
class ParentComponent extends React.Component {
constructor(props) {
super(props);
} }
/** Draw SVG on canvas */
addPath(path) { handleShapeClick = (id) => {
const { closed, strokeWidth, updateGeometry } = this.props; console.log('shape clicked', id);
// TODO: Compute xywh of bounding container of layers this.setState({ selectedShapeId: id });
const { bounds } = path; };
const {
x, y, width, height, render() {
} = bounds;
path.closed = closed; // eslint-disable-line no-param-reassign const { shapes, selectedShapeId
// Reset strokeWidth for persistence } = this.props;
path.strokeWidth = strokeWidth; // eslint-disable-line no-param-reassign
path.data.state = null; // eslint-disable-line no-param-reassign console.log("selectedId", this.props.selectedShapeId);
const svgExports = flatten(path.project.layers.map((layer) => ( return (
flatten(mapChildren(layer)).map((aPath) => aPath.exportSVG({ asString: true })) <Layer>
))); {shapes.map((shape, i) => {
svgExports.unshift("<svg xmlns='http://www.w3.org/2000/svg'>"); console.log('shape', shape);
svgExports.push('</svg>'); switch (shape.type) {
updateGeometry({
svg: svgExports.join(''), case 'rectangle':
xywh: [
Math.floor(x), return (
Math.floor(y), <Rectangle
Math.floor(width),
Math.floor(height), selectedShapeId={this.props.selectedShapeId}
].join(','), 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'}
onShapeClick={this.handleShapeClick}
/>
);
break;
case 'text':
return (
<TextNode
activeTool={this.props.activeTool}
selectedShapeId={this.props.selectedShapeId}
_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}
onShapeClick={this.handleShapeClick}
/>
);
break;
} }
})}
</Layer>
);
}
}
/** Create a portal with a drawing canvas and a form to fill annotations details */
class AnnotationDrawing extends Component {
/** */
constructor(props) {
super(props);
this.paper = null;
this.konvas = null;
// this.addPath = this.addPath.bind(this);
this.drawKonvas = this.drawKonvas.bind(this);
this.state = {
shapes: [],
newShape: null,
currentShape: null,
};
this.shapeRefs = {};
this.transformerRefs = {};
/** Save paperjs ref once created */
paperDidMount(paper) {
this.paper = paper;
} }
handleMouseDown = (e) => {
console.log('mouse down', this.props);
//on dbl click
handleKonvasDblClick = (e) => {
console.log('db click', this.props);
const pos = e.target.getStage().getPointerPosition(); const pos = e.target.getStage().getPointerPosition();
...@@ -217,16 +332,14 @@ class AnnotationDrawing extends Component { ...@@ -217,16 +332,14 @@ class AnnotationDrawing extends Component {
break; break;
} }
// this.setState({
// shapes: [...shapes, newShape],
// currentShape: newShape,
// newShape: null,
// });
};
handleMouseUp = () => {
const { newShape, shapes, currentShape } = this.state; const { newShape, shapes, currentShape } = this.state;
console.log('mouse up', newShape);
if (newShape) { if (newShape) {
this.setState({ this.setState({
shapes: [...shapes, newShape], shapes: [...shapes, newShape],
...@@ -236,408 +349,61 @@ class AnnotationDrawing extends Component { ...@@ -236,408 +349,61 @@ class AnnotationDrawing extends Component {
}); });
} }
};
handleMouseMove = (e) => {
const { newShape, currentShape, currentColorType } = this.state;
console.log('mouse move', newShape);
if (newShape) {
switch (newShape.type) {
case 'rectangle':
const pos = e.target.getStage().getPointerPosition();
console.log('pos', pos);
let width = pos.x - newShape.x;
let height = pos.y - newShape.y;
this.setState({
newShape: {
...newShape,
width,
height,
},
})
case 'ellipse':
const pos3 = e.target.getStage().getPointerPosition();
console.log('pos', pos);
let width3 = pos3.x - newShape.x;
let height3 = pos3.y - newShape.y;
// Negative radius is not allowed
if (width3 < 0) {
width3 = Math.abs(width3);
newShape.x = pos3.x;
}
if (height3 < 0) {
height3 = Math.abs(height3);
newShape.y = pos3.y;
}
break;
case 'text':
const pos2 = e.target.getStage().getPointerPosition();
console.log('pos', pos2);
this.setState({
newShape: {
...newShape,
x: pos2.x,
y: pos2.y,
}
})
break;
default:
break;
}
}
// do we need to do something here ?
if (currentShape) {
}
}; };
handleKeyPress = (e) => {
const { currentShape,shapes } = this.state;
console.log('key press',e, e.key);
if (currentShape) {
console.log('current shape', currentShape);
// if supr remove shape
if (e.key === 'Delete') {
const index = shapes.findIndex((gshape) => shape.id === currentShape.id);
console.log('index', index);
const newShapes = [...shapes];
newShapes.splice(index, 1);
this.setState({
shapes: newShapes,
currentShape: null,
});
}
if (e.key === 'Backspace') {
// Remove the last character
this.setState({
currentShape: {
...currentShape,
text: currentShape.text.slice(0, -1),
},
});
const index = shapes.findIndex((shape) => shape.id === currentShape.id);
console.log('index', index);
const newShapes = [...shapes];
newShapes[index] = currentShape;
this.setState({
shapes: newShapes,
});
//replace in array
} else {
// Add the character to the text
this.setState({
currentShape: {
...currentShape,
text: currentShape.text + e.key,
},
});
console.log('current shape', currentShape);
const index = shapes.findIndex((shape) => shape.id === currentShape.id);
console.log('index', index);
const newShapes = [...shapes];
newShapes[index] = currentShape;
this.setState({
shapes: newShapes,
});
}
}
drawKonvas() {
};
// ... console.log('draw konvas', this.props);
componentDidUpdate(prevProps) {
if (prevProps.activeTool === 'text' && this.props.activeTool !== 'text') {
// Remove global key press event listener
window.removeEventListener('keypress', this.handleKeyPress);
}
}
drawKonvas() {
const { shapes, newShape, currentShape } = this.state;
const { shapes, newShape, currentShape } = this.state;
// console.log(JSON.stringify(shapes, null, 2));
return ( return (
<Stage width={1920} height={1080} <Stage
width={1920}
height={1080}
style={{ style={{
height: '100%', left: 0, position: 'absolute', top: 0, width: '100%', height: '100%', left: 0, position: 'absolute', top: 0, width: '100%',
}} }}
onMouseDown={this.handleMouseDown} // onMouseDown={this.handleMouseDown}
onMouseUp={this.handleMouseUp} // onMouseUp={this.handleMouseUp}
onMouseMove={this.handleMouseMove} // onMouseMove={this.handleMouseMove}
onDblClick={this.handleKonvasDblClick}
> >
<Layer>
{shapes.map((shape, i) => {
console.log('shape', shape);
switch (shape.type) {
case 'rectangle':
console.log('drawing rectangle');
return (
<Rect
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
/>
);
case 'ellipse':
return (
<Ellipse
key={i}
x={shape.x}
y={shape.y}
radiusX={shape.width}
radiusY={shape.height}
stroke={shape.strokeColor}
fill={shape.fill}
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
/>
);
case 'text':
return (
<Text
//color$
fill={shape.fill} <ParentComponent shapes={shapes}
fontFamily="Calibri" selectedShapeId
key={i} activeTool={this.props.activeTool}
x={shape.x}
y={shape.y}
text={shape.text}
fontSize={shape.fontSize}
draggable={this.props.activeTool === 'cursor'}
onClick={this.props.activeTool === 'cursor' ? () => this.setState({ currentShape: shape }) : null}
onDragEnd={this.handleDragEnd(shape.id)} // Add this line
/>
);
// Add cases for other shapes here
default:
return null;
}
})}
{newShape && newShape.type === 'rectangle' && (
<Rect
x={newShape.x}
y={newShape.y}
width={newShape.width}
height={newShape.height}
stroke={newShape.strokeColor}
fill={newShape.fill}
strokeWidth={newShape.strokeWidth}
draggable={this.props.activeTool === 'cursor'}
/>
)}
{newShape && newShape.type === 'ellipse' && (
<Ellipse
x={newShape.x}
y={newShape.y}
radiusX={newShape.width}
radiusY={newShape.height}
stroke={newShape.strokeColor}
fill={newShape.fill}
strokeWidth={newShape.strokeWidth}
draggable={this.props.activeTool === 'cursor'}
/>
)}
{newShape && newShape.type === 'text' && (
<Text
x={newShape.x}
y={newShape.y}
text={newShape.text}
fill={newShape.fill}
fontSize={newShape.fontSize}
draggable={this.props.activeTool === 'cursor'}
/> />
)}
</Layer>
</Stage> </Stage>
); );
}
handleDragEnd = (id) => (e) => {
const { shapes } = this.state;
const shapeNode = e.target;
const updatedShapes = shapes.map(shape =>
shape.id === id ? { ...shape, x: shapeNode.x(), y: shapeNode.y() } : shape
);
this.setState({ shapes: updatedShapes });
// update the current shape
const { currentShape } = this.state;
if (currentShape && currentShape.id === id) {
this.setState({ currentShape: { ...currentShape, x: shapeNode.x(), y: shapeNode.y() } });
} }
};
/** */
paperThing() {
const { viewProps, canvasProps } = this.getDisplayProps();
const {
activeTool, fillColor, strokeColor, strokeWidth, svg,
} = this.props;
if (!activeTool || activeTool === 'cursor') return null;
let ActiveTool = RectangleTool;
switch (activeTool) {
case 'rectangle':
ActiveTool = RectangleTool;
break;
case 'ellipse':
ActiveTool = EllipseTool;
break;
case 'polygon':
ActiveTool = PolygonTool;
break;
case 'freehand':
ActiveTool = FreeformPathTool;
break;
case 'edit':
ActiveTool = EditTool;
break;
default:
break;
}
console.log('rendering konva');
// replace with konva
return (
<Stage width={1920} height={1080}
style={{
height: '100%', left: 0, position: 'absolute', top: 0, width: '100%',
}}
onMouseDown={handleMouseDown}
onMouseUp={handleMouseUp}
onMouseMove={handleMouseMove}
>
<Layer>
</Layer>
</Stage>
);
// return (
// <div
// style={{
// height: '100%', left: 0, position: 'absolute', top: 0, width: '100%',
// }}
// >
// <PaperContainer
// canvasProps={canvasProps}
// viewProps={viewProps}
// onMount={this.paperDidMount}
// >
// {renderWithPaperScope((paper) => {
// const paths = flatten(paper.project.layers.map((layer) => (
// flatten(mapChildren(layer)).map((aPath) => aPath)
// )));
// if (svg && paths.length === 0) {
// paper.project.importSVG(svg);
// }
// paper.settings.handleSize = 10; // eslint-disable-line no-param-reassign
// paper.settings.hitTolerance = 10; // eslint-disable-line no-param-reassign
// return (
// <ActiveTool
// onPathAdd={this.addPath}
// pathProps={{
// fillColor,
// strokeColor,
// strokeWidth: strokeWidth / paper.view.zoom,
// }}
// paper={paper}
// />
// );
// })}
// </PaperContainer>
// <ResizeObserver onResize={this.onPaperResize} />
//</div>
//);
}
/** */ /** */
render() { render() {
...@@ -661,6 +427,7 @@ class AnnotationDrawing extends Component { ...@@ -661,6 +427,7 @@ class AnnotationDrawing extends Component {
AnnotationDrawing.propTypes = { AnnotationDrawing.propTypes = {
activeTool: PropTypes.string, activeTool: PropTypes.string,
selectedShapeId: PropTypes.string,
closed: PropTypes.bool, closed: PropTypes.bool,
fillColor: PropTypes.string, fillColor: PropTypes.string,
strokeColor: PropTypes.string, strokeColor: PropTypes.string,
...@@ -672,6 +439,7 @@ AnnotationDrawing.propTypes = { ...@@ -672,6 +439,7 @@ AnnotationDrawing.propTypes = {
AnnotationDrawing.defaultProps = { AnnotationDrawing.defaultProps = {
activeTool: null, activeTool: null,
selectedShapeId: null,
closed: true, closed: true,
fillColor: null, fillColor: null,
strokeColor: '#00BFFF', strokeColor: '#00BFFF',
......
This diff is collapsed.
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment