303 lines
11 KiB
JavaScript
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);
|
|
}
|
|
}
|
|
}
|
|
/******/ })()
|
|
; |