JiraFeedback/dist/screenshot-editor.js
2025-01-23 23:04:49 +05:30

311 lines
10 KiB
JavaScript

/******/ (() => { // webpackBootstrap
class ScreenshotEditor {
constructor(canvas) {
console.log('Initializing ScreenshotEditor');
this.canvas = canvas;
this.ctx = canvas.getContext('2d', {
alpha: false
});
this.isDrawing = false;
this.drawingMode = 'pen';
this.drawColor = '#ff0000';
this.lineWidth = 2;
this.startX = 0;
this.startY = 0;
this.lastX = 0;
this.lastY = 0;
this.undoStack = [];
this.redoStack = [];
this.shapes = [];
this.selectedShape = null;
this.isDragging = false;
this.originalImage = null;
this.currentState = null;
this.scale = 1;
this.paths = [];
this.currentPath = [];
this.setupEventListeners();
this.setupTools();
console.log('ScreenshotEditor initialized');
}
async loadImage(dataUrl) {
console.log('Loading image...');
return new Promise((resolve, reject) => {
if (!dataUrl || typeof dataUrl !== 'string') {
console.error('Invalid dataUrl:', dataUrl);
reject(new Error('Invalid image data'));
return;
}
const img = new Image();
img.onload = () => {
console.log('Image loaded, dimensions:', img.width, 'x', img.height);
this.originalImage = img;
// Use device pixel ratio for better resolution
const dpr = window.devicePixelRatio || 1;
// Calculate dimensions to maintain aspect ratio and high resolution
const maxWidth = Math.min(window.innerWidth * 0.98, img.width); // 98% of window width or original width
const maxHeight = Math.min(window.innerHeight * 0.9, img.height); // 90% of window height or original height
const aspectRatio = img.width / img.height;
let newWidth = img.width;
let newHeight = img.height;
// Scale down if necessary while maintaining aspect ratio
if (newWidth > maxWidth || newHeight > maxHeight) {
if (maxWidth / aspectRatio <= maxHeight) {
newWidth = maxWidth;
newHeight = maxWidth / aspectRatio;
} else {
newHeight = maxHeight;
newWidth = maxHeight * aspectRatio;
}
}
// Set canvas size accounting for device pixel ratio
this.canvas.style.width = newWidth + 'px';
this.canvas.style.height = newHeight + 'px';
this.canvas.width = newWidth * dpr;
this.canvas.height = newHeight * dpr;
// Scale context for high DPI display
this.ctx.scale(dpr, dpr);
// Calculate scale for proper coordinate mapping
this.scale = newWidth / img.width;
// Enable image smoothing
this.ctx.imageSmoothingEnabled = true;
this.ctx.imageSmoothingQuality = 'high';
// Clear canvas and draw image with crisp rendering
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
this.ctx.drawImage(img, 0, 0, newWidth, newHeight);
// Save initial state
this.saveState();
resolve();
};
img.onerror = error => {
console.error('Error loading image:', error);
reject(new Error('Failed to load image'));
};
console.log('Setting image source...');
img.src = dataUrl;
});
}
setupEventListeners() {
this.canvas.addEventListener('mousedown', this.startDrawing.bind(this));
this.canvas.addEventListener('mousemove', this.draw.bind(this));
this.canvas.addEventListener('mouseup', this.stopDrawing.bind(this));
this.canvas.addEventListener('mouseleave', this.stopDrawing.bind(this));
// Setup keyboard shortcuts
document.addEventListener('keydown', e => {
if (e.ctrlKey && e.key === 'z') {
this.undo();
} else if (e.ctrlKey && e.key === 'y') {
this.redo();
}
});
}
setupTools() {
const tools = ['select', 'move', 'pen', 'rectangle', 'arrow', 'crop'];
tools.forEach(tool => {
const button = document.getElementById(`${tool}-tool`);
if (button) {
button.addEventListener('click', () => {
this.setDrawingMode(tool);
// Remove active class from all tools
tools.forEach(t => {
var _document$getElementB;
(_document$getElementB = document.getElementById(`${t}-tool`)) === null || _document$getElementB === void 0 || _document$getElementB.classList.remove('active');
});
// Add active class to selected tool
button.classList.add('active');
});
}
});
// Setup color picker
const colorPicker = document.getElementById('color-picker');
if (colorPicker) {
colorPicker.addEventListener('change', e => {
this.drawColor = e.target.value;
});
}
// Setup line width
const lineWidth = document.getElementById('line-width');
if (lineWidth) {
lineWidth.addEventListener('change', e => {
this.lineWidth = parseInt(e.target.value);
});
}
// Setup undo/redo buttons
const undoBtn = document.getElementById('undo-btn');
const redoBtn = document.getElementById('redo-btn');
if (undoBtn) undoBtn.addEventListener('click', () => this.undo());
if (redoBtn) redoBtn.addEventListener('click', () => this.redo());
}
startDrawing(e) {
const rect = this.canvas.getBoundingClientRect();
const dpr = window.devicePixelRatio || 1;
this.isDrawing = true;
// Calculate coordinates taking into account DPR and canvas scaling
this.startX = (e.clientX - rect.left) * (this.canvas.width / (rect.width * dpr));
this.startY = (e.clientY - rect.top) * (this.canvas.height / (rect.height * dpr));
this.lastX = this.startX;
this.lastY = this.startY;
if (this.drawingMode === 'pen') {
// Start a new path array for the current drawing
this.currentPath = [];
this.currentPath.push({
x: this.startX,
y: this.startY
});
this.paths.push(this.currentPath);
}
}
draw(e) {
if (!this.isDrawing) return;
const rect = this.canvas.getBoundingClientRect();
const dpr = window.devicePixelRatio || 1;
const currentX = (e.clientX - rect.left) * (this.canvas.width / (rect.width * dpr));
const currentY = (e.clientY - rect.top) * (this.canvas.height / (rect.height * dpr));
// Get the current state as an image
const baseImage = new Image();
baseImage.src = this.currentState || this.canvas.toDataURL();
// Clear the canvas
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
// Draw the current state
if (this.currentState) {
const img = new Image();
img.src = this.currentState;
this.ctx.drawImage(img, 0, 0);
} else {
this.ctx.drawImage(this.originalImage, 0, 0, this.canvas.width / dpr, this.canvas.height / dpr);
}
// Set drawing styles
this.ctx.strokeStyle = this.drawColor;
this.ctx.lineWidth = this.lineWidth;
this.ctx.lineCap = 'round';
this.ctx.lineJoin = 'round';
switch (this.drawingMode) {
case 'pen':
// Add point to current path
this.currentPath.push({
x: currentX,
y: currentY
});
// Draw the entire current path
this.ctx.beginPath();
this.ctx.moveTo(this.currentPath[0].x, this.currentPath[0].y);
for (let i = 1; i < this.currentPath.length; i++) {
const point = this.currentPath[i];
this.ctx.lineTo(point.x, point.y);
}
this.ctx.stroke();
break;
case 'rectangle':
this.ctx.beginPath();
this.ctx.rect(this.startX, this.startY, currentX - this.startX, currentY - this.startY);
this.ctx.stroke();
break;
case 'arrow':
this.drawArrow(this.ctx, this.startX, this.startY, currentX, currentY);
break;
}
}
drawArrow(ctx, fromX, fromY, toX, toY) {
const headLength = 20;
const angle = Math.atan2(toY - fromY, toX - fromX);
// Draw the line
ctx.beginPath();
ctx.moveTo(fromX, fromY);
ctx.lineTo(toX, toY);
ctx.stroke();
// Draw the arrow head
ctx.beginPath();
ctx.moveTo(toX, toY);
ctx.lineTo(toX - headLength * Math.cos(angle - Math.PI / 6), toY - headLength * Math.sin(angle - Math.PI / 6));
ctx.moveTo(toX, toY);
ctx.lineTo(toX - headLength * Math.cos(angle + Math.PI / 6), toY - headLength * Math.sin(angle + Math.PI / 6));
ctx.stroke();
}
stopDrawing() {
if (this.isDrawing) {
this.isDrawing = false;
// Save the current state with the completed drawing
this.currentState = this.canvas.toDataURL('image/png');
this.saveState();
// Reset current path
this.currentPath = [];
}
}
undo() {
if (this.undoStack.length > 1) {
this.redoStack.push(this.undoStack.pop()); // Move current state to redo stack
const previousState = this.undoStack[this.undoStack.length - 1];
this.loadStateImage(previousState);
}
}
redo() {
if (this.redoStack.length > 0) {
const nextState = this.redoStack.pop();
this.undoStack.push(nextState);
this.loadStateImage(nextState);
}
}
loadStateImage(dataUrl) {
const img = new Image();
img.onload = () => {
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
this.ctx.drawImage(img, 0, 0);
};
img.src = dataUrl;
}
getImageData() {
// Return the current state with drawings
return this.currentState || this.canvas.toDataURL('image/png');
}
setDrawingMode(mode) {
this.drawingMode = mode;
this.canvas.style.cursor = mode === 'crop' ? 'crosshair' : 'default';
}
saveState() {
const imageData = this.canvas.toDataURL('image/png');
this.undoStack.push(imageData);
this.redoStack = []; // Clear redo stack when new state is saved
}
redrawCanvas() {
if (!this.canvas.width || !this.canvas.height) {
console.warn('Canvas has no dimensions, skipping redraw');
return;
}
// Clear the canvas
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
// Draw the current state if it exists, otherwise draw the original image
if (this.currentState) {
const img = new Image();
img.src = this.currentState;
this.ctx.drawImage(img, 0, 0);
} else if (this.originalImage) {
this.ctx.drawImage(this.originalImage, 0, 0, this.canvas.width, this.canvas.height);
}
}
}
window.ScreenshotEditor = ScreenshotEditor;
/******/ })()
;