screenshot-editor-test
Version:
canvas editor
753 lines (675 loc) • 25 kB
JavaScript
import React from 'react';
import PropTypes from 'prop-types';
var classCallCheck = function (instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
};
var createClass = function () {
function defineProperties(target, props) {
for (var i = 0; i < props.length; i++) {
var descriptor = props[i];
descriptor.enumerable = descriptor.enumerable || false;
descriptor.configurable = true;
if ("value" in descriptor) descriptor.writable = true;
Object.defineProperty(target, descriptor.key, descriptor);
}
}
return function (Constructor, protoProps, staticProps) {
if (protoProps) defineProperties(Constructor.prototype, protoProps);
if (staticProps) defineProperties(Constructor, staticProps);
return Constructor;
};
}();
var _extends = Object.assign || function (target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i];
for (var key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
target[key] = source[key];
}
}
}
return target;
};
var inherits = function (subClass, superClass) {
if (typeof superClass !== "function" && superClass !== null) {
throw new TypeError("Super expression must either be null or a function, not " + typeof superClass);
}
subClass.prototype = Object.create(superClass && superClass.prototype, {
constructor: {
value: subClass,
enumerable: false,
writable: true,
configurable: true
}
});
if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
};
var possibleConstructorReturn = function (self, call) {
if (!self) {
throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
}
return call && (typeof call === "object" || typeof call === "function") ? call : self;
};
var toConsumableArray = function (arr) {
if (Array.isArray(arr)) {
for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) arr2[i] = arr[i];
return arr2;
} else {
return Array.from(arr);
}
};
require('./styles.css');
var Editor = function (_React$Component) {
inherits(Editor, _React$Component);
function Editor(props) {
classCallCheck(this, Editor);
var _this = possibleConstructorReturn(this, (Editor.__proto__ || Object.getPrototypeOf(Editor)).call(this, props));
_this.fillTextOnCanvas = function (ctx) {
var _this$state = _this.state,
textValue = _this$state.textValue,
startX = _this$state.startX,
startY = _this$state.startY;
var lines = textValue.split('\n');
var offset = 0;
ctx.textBaseline = 'top';
ctx.textAlign = 'left';
ctx.font = '14px Arial';
ctx.fillStyle = _this.state.drawingColor;
var textEditor = document.querySelector('.screenshotEditor__text');
if (textEditor) {
var charWidth = 7,
textEditorWidth = textEditor.offsetWidth - 10,
charLimit = textEditorWidth / charWidth;
var _iteratorNormalCompletion = true;
var _didIteratorError = false;
var _iteratorError = undefined;
try {
for (var _iterator = lines[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
var line = _step.value;
var words = line.split(" ");
var subLines = [words[0]];
var k = 0;
for (var i = 1; i < words.length; i++) {
var word = words[i];
if (subLines[k].length + word.length + 1 <= charLimit) {
subLines[k] = subLines[k] + ' ' + word;
} else {
k++;
subLines[k] = word;
}
}
for (var _i = 0; _i < subLines.length; _i++) {
var subLine = subLines[_i];
ctx.fillText(subLine, startX, startY + offset);
offset += 20;
}
offset += 10;
}
} catch (err) {
_didIteratorError = true;
_iteratorError = err;
} finally {
try {
if (!_iteratorNormalCompletion && _iterator.return) {
_iterator.return();
}
} finally {
if (_didIteratorError) {
throw _iteratorError;
}
}
}
}
};
_this.drawRectangle = function (e) {
var _this$state2 = _this.state,
startX = _this$state2.startX,
startY = _this$state2.startY,
canvasWidth = _this$state2.canvasWidth,
canvasHeight = _this$state2.canvasHeight;
var ctx = _this.editorCanvasRef.current.getContext('2d');
var rect = e.target.getBoundingClientRect();
var x = e.clientX - rect.left;
var y = e.clientY - rect.top;
var width = x - startX;
var height = y - startY;
ctx.clearRect(0, 0, canvasWidth, canvasHeight);
ctx.beginPath();
ctx.rect(startX, startY, width, height);
ctx.stroke();
};
_this.drawCircle = function (e) {
var _this$state3 = _this.state,
startX = _this$state3.startX,
startY = _this$state3.startY,
canvasWidth = _this$state3.canvasWidth,
canvasHeight = _this$state3.canvasHeight;
var ctx = _this.editorCanvasRef.current.getContext('2d');
var rect = e.target.getBoundingClientRect();
var x = e.clientX - rect.left;
var y = e.clientY - rect.top;
var circleX = (x + startX) / 2;
var circleY = (y + startY) / 2;
var radius = Math.max(Math.abs(x - startX), Math.abs(y - startY)) / 2;
ctx.clearRect(0, 0, canvasWidth, canvasHeight);
ctx.beginPath();
ctx.arc(circleX, circleY, radius, 0, Math.PI * 2);
ctx.stroke();
ctx.closePath();
};
_this.drawLine = function (e) {
var _this$state4 = _this.state,
startX = _this$state4.startX,
startY = _this$state4.startY,
canvasWidth = _this$state4.canvasWidth,
canvasHeight = _this$state4.canvasHeight;
var ctx = _this.editorCanvasRef.current.getContext('2d');
var rect = e.target.getBoundingClientRect();
var x = e.clientX - rect.left;
var y = e.clientY - rect.top;
ctx.clearRect(0, 0, canvasWidth, canvasHeight);
ctx.beginPath();
ctx.moveTo(startX, startY);
ctx.lineTo(x, y);
ctx.stroke();
ctx.closePath();
};
_this.drawPencil = function (e) {
var ctx = _this.editorCanvasRef.current.getContext('2d');
var rect = e.target.getBoundingClientRect();
var x = e.clientX - rect.left;
var y = e.clientY - rect.top;
ctx.lineTo(x, y);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(x, y);
ctx.closePath();
};
_this.onErasae = function (e) {
var ctx = _this.baseCanvas.current.getContext('2d');
var rect = e.target.getBoundingClientRect();
var x = e.clientX - rect.left;
var y = e.clientY - rect.top;
ctx.lineTo(x, y);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(x, y);
};
_this.handleMouseDown = function (e) {
var _this$state5 = _this.state,
drawingShape = _this$state5.drawingShape,
textValue = _this$state5.textValue,
startX = _this$state5.startX,
startY = _this$state5.startY,
canvasWidth = _this$state5.canvasWidth,
canvasHeight = _this$state5.canvasHeight,
lineWidth = _this$state5.lineWidth,
drawingColor = _this$state5.drawingColor;
var baseImageCavnas = _this.imageAndDrawingMixingCanvas.current.getContext('2d');
var ctx = _this.editorCanvasRef.current.getContext('2d');
var downCtx = _this.baseCanvas.current.getContext('2d');
baseImageCavnas.clearRect(0, 0, canvasWidth, canvasHeight);
ctx.clearRect(0, 0, canvasWidth, canvasHeight);
ctx.strokeStyle = drawingColor;
ctx.fillStyle = drawingColor;
if (drawingShape === 'text') {
_this.fillTextOnCanvas(ctx);
_this.setState({ textValue: '', hideTextBox: false });
} else {
_this.setState({ hideTextBox: true });
}
if (drawingShape === 'eraser') {
ctx.globalCompositeOperation = 'destination-out';
downCtx.globalCompositeOperation = 'destination-out';
downCtx.fillStyle = 'rgba(0,0,0,1)';
downCtx.strokeStyle = 'rgba(0,0,0,1)';
downCtx.lineWidth = lineWidth * 2;
} else {
ctx.globalCompositeOperation = 'source-over';
downCtx.globalCompositeOperation = 'source-over';
ctx.lineWidth = lineWidth;
ctx.lineCap = 'round';
}
downCtx.beginPath();
ctx.beginPath();
var rect = e.target.getBoundingClientRect();
var x = e.clientX - rect.left;
var y = e.clientY - rect.top;
_this.setState({ startX: x, startY: y, isMouseDown: true });
};
_this.handleMouseMove = function (e) {
var _this$state6 = _this.state,
isMouseDown = _this$state6.isMouseDown,
startX = _this$state6.startX,
startY = _this$state6.startY,
drawingShape = _this$state6.drawingShape;
if (isMouseDown && startX && startY) {
switch (drawingShape) {
case 'rect':
_this.drawRectangle(e);
break;
case 'circle':
_this.drawCircle(e);
break;
case 'line':
_this.drawLine(e);
break;
case 'pencil':
_this.drawPencil(e);
break;
case 'eraser':
_this.onErasae(e);
break;
default:
break;
}
}
};
_this.getFinalCanvasUrl = function (cb) {
var _this$state7 = _this.state,
drawnImages = _this$state7.drawnImages,
currentImageIndex = _this$state7.currentImageIndex;
var permanentCanvas = _this.permanentCanvas.current;
var canvas = _this.imageAndDrawingMixingCanvas.current;
var context = canvas.getContext('2d');
context.drawImage(permanentCanvas, 0, 0);
var image = new Image();
image.src = drawnImages[currentImageIndex <= 0 ? null : currentImageIndex - 1];
image.onload = function () {
context.drawImage(image, 0, 0);
cb(canvas.toDataURL('image/png'));
};
image.crossOrigin = 'anonymous';
};
_this.copyEditorCanvasToFinalCanvas = function () {
var editorCanvas = _this.editorCanvasRef.current; // upper canvas
var bottomCanvas = _this.baseCanvas.current.getContext('2d');
bottomCanvas.drawImage(editorCanvas, 0, 0);
};
_this.saveTheCurrentImageInstance = function () {
var _this$state8 = _this.state,
currentImageIndex = _this$state8.currentImageIndex,
drawnImages = _this$state8.drawnImages;
var imageIndex = currentImageIndex;
if (currentImageIndex <= 0) imageIndex = 0;
var tempDrawnImages = [].concat(toConsumableArray(drawnImages));
tempDrawnImages = tempDrawnImages.slice(0, imageIndex);
var dataUrl = _this.baseCanvas.current.toDataURL('image/png');
_this.setState({
drawnImages: [].concat(toConsumableArray(tempDrawnImages), [dataUrl]),
currentImageIndex: imageIndex + 1
});
};
_this.clearEditorCanvas = function () {
var _this$state9 = _this.state,
canvasHeight = _this$state9.canvasHeight,
canvasWidth = _this$state9.canvasWidth;
var editorCanvasCtx = _this.editorCanvasRef.current.getContext('2d');
editorCanvasCtx.clearRect(0, 0, canvasWidth, canvasHeight);
};
_this.handleMouseUp = function (e) {
var onDrawingEnd = _this.props.onDrawingEnd;
// copy the drawen image to the final canvas
// generate the data url of final canvas and store it in the reference array
// clear the editor canvas
_this.copyEditorCanvasToFinalCanvas();
_this.saveTheCurrentImageInstance();
_this.clearEditorCanvas();
_this.setState({
isMouseDown: false
});
_this.getFinalCanvasUrl(function name(params) {
onDrawingEnd(params);
});
};
_this.handleMouseOut = function (e) {
// handler for mouse out
};
_this.handleImageLoad = function (e) {
var imgSrc = _this.props.imgSrc;
var _e$target = e.target,
offsetHeight = _e$target.offsetHeight,
offsetWidth = _e$target.offsetWidth;
var finalBottomImageRef = _this.permanentCanvas.current.getContext('2d');
var image = new Image();
image.onload = function () {
finalBottomImageRef.drawImage(image, 0, 0, offsetWidth, offsetHeight);
};
// image.crossOrigin = "anonymous";
image.crossOrigin = 'anonymous';
image.src = imgSrc;
_this.setState({
isImageLoaded: true,
canvasHeight: offsetHeight,
canvasWidth: offsetWidth
});
};
_this.handleUndo = function () {
// handle the logic for undo here
var _this$state10 = _this.state,
canvasWidth = _this$state10.canvasWidth,
canvasHeight = _this$state10.canvasHeight,
drawnImages = _this$state10.drawnImages,
currentImageIndex = _this$state10.currentImageIndex;
var onDrawingEnd = _this.props.onDrawingEnd;
if (currentImageIndex <= 0) return;
// this.setState({ drawingShape: null })
var oImg = new Image();
var bottomCanvas = _this.baseCanvas.current.getContext('2d');
var upperCanvas = _this.editorCanvasRef.current.getContext('2d');
bottomCanvas.globalCompositeOperation = 'source-over';
upperCanvas.globalCompositeOperation = 'source-over';
bottomCanvas.clearRect(0, 0, canvasWidth, canvasHeight);
upperCanvas.clearRect(0, 0, canvasWidth, canvasHeight);
oImg.onload = function () {
bottomCanvas.drawImage(oImg, 0, 0);
};
oImg.src = drawnImages[currentImageIndex - 2];
_this.setState({ currentImageIndex: currentImageIndex - 1 }, function () {
_this.getFinalCanvasUrl(function name(params) {
onDrawingEnd(params);
});
});
};
_this.handleErase = function () {
_this.setState({ drawingShape: 'eraser' });
};
_this.handleText = function () {
_this.setState({
drawingShape: 'text',
startX: null,
startY: null
});
};
_this.handleAddText = function (e) {
var onDrawingEnd = _this.props.onDrawingEnd;
var _this$state11 = _this.state,
textValue = _this$state11.textValue,
startX = _this$state11.startX,
startY = _this$state11.startY;
if (e.keyCode === 13 && !e.shiftKey) {
var ctx = _this.editorCanvasRef.current.getContext('2d');
_this.fillTextOnCanvas(ctx);
_this.copyEditorCanvasToFinalCanvas();
_this.saveTheCurrentImageInstance();
_this.clearEditorCanvas();
_this.setState({
textValue: '',
startX: null,
startY: null
});
// delay, state updation is taking time here.
setTimeout(function () {
_this.getFinalCanvasUrl(function name(params) {
onDrawingEnd(params);
});
}, 1000);
}
};
_this.handleSelectColor = function (color) {
return _this.setState({ drawingColor: color });
};
_this.editorCanvasRef = React.createRef();
_this.canvasContainerRef = React.createRef();
_this.baseCanvas = React.createRef();
_this.permanentCanvas = React.createRef();
_this.imageAndDrawingMixingCanvas = React.createRef();
_this.linkRef = React.createRef();
var configs = props.configs;
var editorControls = configs && configs.length > 0 && configs.map(function (drawing) {
// if role is text or color or undo
var tempObj = {};
if (drawing.role === 'color') {
tempObj = {
onClick: null,
colors: drawing.colors ? [].concat(toConsumableArray(drawing.colors)) : ['#000000']
};
} else if (drawing.role === 'text') {
tempObj = { onClick: _this.handleText };
} else if (drawing.role === 'erase') {
tempObj = { onClick: _this.handleErase };
} else if (drawing.role === 'undo') {
tempObj = { onClick: _this.handleUndo };
} else {
tempObj = {
onClick: function onClick() {
return _this.setState({ drawingShape: drawing.role });
}
};
}
return _extends({}, drawing, tempObj);
});
_this.state = {
startX: null,
startY: null,
canvasWidth: 400,
canvasHeight: 400,
drawingShape: 'rect',
drawnImages: [],
currentImageIndex: null,
isImageLoaded: false,
isMouseDown: false,
textValue: '',
lineWidth: props.lineWidth,
drawingColor: '#000',
screenshotControls: configs ? [].concat(toConsumableArray(editorControls)) : []
};
return _this;
}
// circle
// draw line
// drawPencil
// onEraseCanvasDrawing
// event handlers
// handler for image loading
// handle erase
// add textarea
createClass(Editor, [{
key: 'componentDidMount',
value: function componentDidMount() {
this.editorCanvasRef.current.addEventListener('mousedown', this.handleMouseDown);
this.editorCanvasRef.current.addEventListener('mousemove', this.handleMouseMove);
this.editorCanvasRef.current.addEventListener('mouseup', this.handleMouseUp);
this.editorCanvasRef.current.addEventListener('mouseout', this.handleMouseOut);
}
}, {
key: 'componentWillUnmount',
value: function componentWillUnmount() {
this.editorCanvasRef.current.removeEventListener('mousedown', this.handleMouseDown);
this.editorCanvasRef.current.removeEventListener('mousemove', this.handleMouseMove);
this.editorCanvasRef.current.removeEventListener('mouseup', this.handleMouseUp);
this.editorCanvasRef.current.removeEventListener('mouseout', this.handleMouseOut);
}
}, {
key: 'render',
value: function render() {
var _this2 = this;
var editorControls = [{
icon: '╲',
title: 'Move',
role: 'line',
onClick: function onClick() {
return _this2.setState({ drawingShape: 'line' });
}
}, {
icon: '⊡',
title: 'Square',
role: 'rect',
onClick: function onClick() {
return _this2.setState({ drawingShape: 'rect' });
}
}, {
icon: '⊙',
title: 'Elipse',
role: 'circle',
onClick: function onClick() {
return _this2.setState({ drawingShape: 'circle' });
}
}, {
icon: '🎨',
title: 'Bucket',
onClick: null,
colors: ['#ff6565', '#ffa000', '#cddc39', '#50e3c2', '#000', '#fff']
}, {
icon: '✎',
title: 'Pencil',
role: 'pencil',
onClick: function onClick() {
return _this2.setState({ drawingShape: 'pencil' });
}
}, {
icon: '𝐓',
title: 'Text',
role: 'text',
onClick: this.handleText
}, {
icon: '⎚',
title: 'Eraser',
role: 'eraser',
onClick: this.handleErase
}, {
icon: '↺',
title: 'Undo',
onClick: this.handleUndo
}];
var _state = this.state,
isImageLoaded = _state.isImageLoaded,
canvasWidth = _state.canvasWidth,
canvasHeight = _state.canvasHeight,
drawingShape = _state.drawingShape,
startX = _state.startX,
startY = _state.startY,
textValue = _state.textValue,
hideTextBox = _state.hideTextBox,
screenshotControls = _state.screenshotControls,
drawingColor = _state.drawingColor;
var _props = this.props,
imgSrc = _props.imgSrc,
configs = _props.configs;
var finalDrawingControls = configs && configs.length > 0 ? screenshotControls : editorControls;
return React.createElement(
'div',
{ className: 'screenshotEditor_wrapper' },
React.createElement(
'div',
{ className: 'screenshotEditorMenu_left border-right' },
React.createElement(
'div',
{ className: 'screenshotEditorMenu' },
finalDrawingControls.map(function (_ref) {
var icon = _ref.icon,
onClick = _ref.onClick,
colors = _ref.colors,
role = _ref.role;
return React.createElement(
'button',
{
key: role,
className: 'screenshotEditorMenu__item ' + (role === drawingShape ? 'screenshotEditorMenu__item--active' : undefined),
onClick: onClick
},
configs ? React.createElement('img', {
src: icon,
alt: '...',
className: 'screenshotEditorMenu__item__icon'
}) : React.createElement(
'span',
null,
icon
),
colors && React.createElement(
'div',
{ className: 'screenshotEditorMenu__item__icon__colorWrapper' },
colors.map(function (color) {
return React.createElement('div', {
style: { backgroundColor: color },
className: 'screenshotEditorMenu__item__icon__colorWrapper__colorBox ' + (drawingColor === color ? 'screenshotEditorMenu__item__icon__colorWrapper__colorBox--active' : ''),
onClick: function onClick() {
return _this2.handleSelectColor(color);
}
});
})
)
);
})
)
),
React.createElement(
'div',
{ className: 'screenshotEditorImgWrapper' },
React.createElement(
'div',
{ className: 'screenshotEditorArea ', ref: this.canvasContainerRef },
!isImageLoaded && React.createElement(
'span',
{ className: 'loading' },
'Loading'
),
React.createElement(
'div',
{ className: 'screenshotEditorArea_child' },
React.createElement('img', {
src: imgSrc,
alt: '',
onLoad: this.handleImageLoad,
className: 'baseScreenshotimg'
}),
React.createElement('canvas', {
ref: this.permanentCanvas,
width: canvasWidth,
height: canvasHeight,
className: 'screenshotEditorArea__permanentBottomcanvas'
}),
React.createElement('canvas', {
ref: this.imageAndDrawingMixingCanvas,
width: canvasWidth,
height: canvasHeight,
className: 'screenshotEditorArea__finalBottomcanvas'
}),
React.createElement('canvas', {
ref: this.baseCanvas,
className: 'screenshotEditorArea__canvas',
width: canvasWidth,
height: canvasHeight
}),
React.createElement('canvas', {
ref: this.editorCanvasRef,
style: { cursor: this.state.drawingShape == "text" ? 'text' : 'crosshair' },
className: 'screenshotEditorArea__editorCanvas',
width: canvasWidth,
height: canvasHeight
}),
drawingShape === 'text' && startX && startY && !hideTextBox && React.createElement('textarea', {
className: 'screenshotEditor__text',
style: { top: startY, left: startX },
value: textValue,
placeholder: 'Enter text to add',
onChange: function onChange(e) {
return _this2.setState({ textValue: e.target.value });
},
onKeyDown: this.handleAddText
})
)
),
this.props.children
)
);
}
}]);
return Editor;
}(React.Component);
Editor.propTypes = {
imgSrc: PropTypes.string.isRequired,
onDrawingEnd: PropTypes.func.isRequired,
lineWidth: PropTypes.number,
configs: PropTypes.oneOfType(Array),
children: PropTypes.node
};
Editor.defaultProps = {
lineWidth: 5,
configs: null
};
export default Editor;
//# sourceMappingURL=index.es.js.map