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

303 lines
11 KiB
JavaScript

/******/ (() => { // webpackBootstrap
/******/ "use strict";
/******/ // The require scope
/******/ var __webpack_require__ = {};
/******/
/************************************************************************/
/******/ /* webpack/runtime/define property getters */
/******/ (() => {
/******/ // define getter functions for harmony exports
/******/ __webpack_require__.d = (exports, definition) => {
/******/ for(var key in definition) {
/******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {
/******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
/******/ }
/******/ }
/******/ };
/******/ })();
/******/
/******/ /* webpack/runtime/hasOwnProperty shorthand */
/******/ (() => {
/******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
/******/ })();
/******/
/************************************************************************/
var __webpack_exports__ = {};
/* unused harmony export ScreenshotAnnotator */
class ScreenshotAnnotator {
constructor() {
this.canvas = null;
this.currentTool = 'brush';
this.currentColor = '#ff0000';
this.currentSize = 5;
this.drawingMode = true;
}
initialize(imageUrl, container) {
console.log('Initializing annotator with container:', container);
return new Promise((resolve, reject) => {
try {
// Create canvas container
const canvasContainer = document.createElement('div');
canvasContainer.className = 'canvas-container';
// Create toolbar
const toolbar = document.createElement('div');
toolbar.className = 'annotation-toolbar';
toolbar.innerHTML = `
<div class="tool-group">
<button class="tool-btn active" data-tool="brush" title="Brush">
<svg viewBox="0 0 24 24"><path fill="currentColor" d="M20.71 5.63l-2.34-2.34c-.39-.39-1.02-.39-1.41 0l-3.12 3.12-1.93-1.91-1.41 1.41 1.42 1.42L3 16.25V21h4.75l8.92-8.92 1.42 1.42 1.41-1.41-1.92-1.92 3.12-3.12c.4-.4.4-1.03.01-1.42zM6.92 19H5v-1.92l8.06-8.06 1.92 1.92L6.92 19z"/></svg>
</button>
<button class="tool-btn" data-tool="text" title="Text">
<svg viewBox="0 0 24 24"><path fill="currentColor" d="M2.5 4v3h5v12h3V7h5V4h-13zm19 5h-9v3h3v7h3v-7h3V9z"/></svg>
</button>
<button class="tool-btn" data-tool="arrow" title="Arrow">
<svg viewBox="0 0 24 24"><path fill="currentColor" d="M16.01 11H4v2h12.01v3L20 12l-3.99-4z"/></svg>
</button>
<button class="tool-btn" data-tool="rectangle" title="Rectangle">
<svg viewBox="0 0 24 24"><path fill="currentColor" d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H5V5h14v14z"/></svg>
</button>
</div>
<div class="color-group">
<input type="color" id="color-picker" value="#ff0000" title="Color">
</div>
<div class="size-group">
<input type="range" id="size-slider" min="1" max="20" value="5" title="Size">
</div>
<div class="action-group">
<button class="action-btn" id="undo-btn" title="Undo">
<svg viewBox="0 0 24 24"><path fill="currentColor" d="M12.5 8c-2.65 0-5.05.99-6.9 2.6L2 7v9h9l-3.62-3.62c1.39-1.16 3.16-1.88 5.12-1.88 3.54 0 6.55 2.31 7.6 5.5l2.37-.78C21.08 11.03 17.15 8 12.5 8z"/></svg>
</button>
<button class="action-btn" id="clear-btn" title="Clear">
<svg viewBox="0 0 24 24"><path fill="currentColor" d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/></svg>
</button>
<button class="action-btn" id="done-btn" title="Done">
<svg viewBox="0 0 24 24"><path fill="currentColor" d="M9 16.2L4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4L9 16.2z"/></svg>
</button>
</div>
`;
// Create canvas wrapper for scrolling
const canvasWrapper = document.createElement('div');
canvasWrapper.className = 'canvas-wrapper';
// Create canvas
const canvas = document.createElement('canvas');
canvas.style.margin = 'auto';
canvas.style.boxShadow = '0 2px 8px rgba(0,0,0,0.1)';
// Add elements to DOM
canvasWrapper.appendChild(canvas);
canvasContainer.appendChild(toolbar);
canvasContainer.appendChild(canvasWrapper);
container.appendChild(canvasContainer);
console.log('Creating Fabric canvas');
// Initialize Fabric canvas
this.canvas = new Canvas(canvas);
// Load image
console.log('Loading image:', imageUrl.substring(0, 100) + '...');
const img = new window.Image();
img.onerror = error => {
console.error('Error loading image:', error);
reject(error);
};
img.onload = () => {
console.log('Image loaded:', img.width, 'x', img.height);
try {
// Set canvas size to match window size while maintaining aspect ratio
const maxWidth = window.innerWidth - 40;
const maxHeight = window.innerHeight - 100;
const scale = Math.min(maxWidth / img.width, maxHeight / img.height);
const width = img.width * scale;
const height = img.height * scale;
console.log('Setting canvas size:', width, 'x', height);
this.canvas.setWidth(width);
this.canvas.setHeight(height);
// Create Fabric image
console.log('Creating Fabric image');
Image.fromURL(imageUrl, fabricImg => {
try {
fabricImg.scale(scale);
fabricImg.selectable = false;
// Add image to canvas
this.canvas.add(fabricImg);
this.canvas.renderAll();
// Set up event listeners
this.setupEventListeners(toolbar);
console.log('Setup complete');
// Return promise that resolves when done button is clicked
document.getElementById('done-btn').addEventListener('click', () => {
try {
const dataUrl = this.canvas.toDataURL();
this.cleanup();
resolve(dataUrl);
} catch (error) {
console.error('Error getting data URL:', error);
reject(error);
}
});
} catch (error) {
console.error('Error setting up Fabric image:', error);
reject(error);
}
});
} catch (error) {
console.error('Error in image onload:', error);
reject(error);
}
};
img.src = imageUrl;
} catch (error) {
console.error('Error in initialize:', error);
reject(error);
}
});
}
setupEventListeners(toolbar) {
console.log('Setting up event listeners');
try {
// Tool buttons
const toolButtons = toolbar.querySelectorAll('.tool-btn');
toolButtons.forEach(button => {
button.addEventListener('click', () => {
toolButtons.forEach(btn => btn.classList.remove('active'));
button.classList.add('active');
this.currentTool = button.dataset.tool;
this.canvas.isDrawingMode = this.currentTool === 'brush';
});
});
// Color picker
const colorPicker = document.getElementById('color-picker');
colorPicker.addEventListener('change', e => {
this.currentColor = e.target.value;
if (this.canvas.isDrawingMode) {
this.canvas.freeDrawingBrush.color = this.currentColor;
}
});
// Size slider
const sizeSlider = document.getElementById('size-slider');
sizeSlider.addEventListener('input', e => {
this.currentSize = parseInt(e.target.value);
if (this.canvas.isDrawingMode) {
this.canvas.freeDrawingBrush.width = this.currentSize;
}
});
// Undo button
document.getElementById('undo-btn').addEventListener('click', () => {
const objects = this.canvas.getObjects();
if (objects.length > 1) {
// Keep background image
this.canvas.remove(objects[objects.length - 1]);
}
});
// Clear button
document.getElementById('clear-btn').addEventListener('click', () => {
const objects = this.canvas.getObjects();
// Remove all objects except background image
for (let i = objects.length - 1; i > 0; i--) {
this.canvas.remove(objects[i]);
}
});
// Set initial brush settings
this.canvas.freeDrawingBrush.color = this.currentColor;
this.canvas.freeDrawingBrush.width = this.currentSize;
// Mouse down handler for shapes and text
this.canvas.on('mouse:down', options => {
if (this.canvas.isDrawingMode) return;
const pointer = this.canvas.getPointer(options.e);
const startX = pointer.x;
const startY = pointer.y;
switch (this.currentTool) {
case 'rectangle':
const rect = new Rect({
left: startX,
top: startY,
width: 0,
height: 0,
fill: 'transparent',
stroke: this.currentColor,
strokeWidth: this.currentSize
});
this.canvas.add(rect);
this.canvas.setActiveObject(rect);
break;
case 'arrow':
const line = new Line([startX, startY, startX, startY], {
stroke: this.currentColor,
strokeWidth: this.currentSize
});
this.canvas.add(line);
this.canvas.setActiveObject(line);
break;
case 'text':
const text = new IText('Type here...', {
left: startX,
top: startY,
fill: this.currentColor,
fontSize: this.currentSize * 3
});
this.canvas.add(text);
text.enterEditing();
text.selectAll();
break;
}
});
// Mouse move handler for shapes
this.canvas.on('mouse:move', options => {
if (!this.canvas.isDrawingMode && this.canvas.getActiveObject()) {
const pointer = this.canvas.getPointer(options.e);
const activeObj = this.canvas.getActiveObject();
if (this.currentTool === 'rectangle') {
const width = pointer.x - activeObj.left;
const height = pointer.y - activeObj.top;
activeObj.set({
width,
height
});
} else if (this.currentTool === 'arrow') {
const points = [activeObj.x1, activeObj.y1, pointer.x, pointer.y];
activeObj.set({
x2: pointer.x,
y2: pointer.y
});
}
this.canvas.renderAll();
}
});
// Mouse up handler
this.canvas.on('mouse:up', () => {
this.canvas.setActiveObject(null);
});
} catch (error) {
console.error('Error in setupEventListeners:', error);
throw error;
}
}
cleanup() {
try {
// Remove event listeners and clean up resources
if (this.canvas) {
this.canvas.dispose();
this.canvas = null;
}
} catch (error) {
console.error('Error in cleanup:', error);
}
}
}
/******/ })()
;