Files
lpj/resources/views/surveyor/js/camera-editor.blade.php
2024-10-30 14:29:01 +07:00

586 lines
22 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
@push('scripts')
<script>
class UniversalCameraEditor {
constructor() {
// Initialize elements
this.video = document.getElementById('video');
this.canvas = document.getElementById('canvas');
this.drawingCanvas = document.getElementById('drawingCanvas');
this.ctx = this.drawingCanvas.getContext('2d');
// Buttons
this.startButton = document.getElementById('startButton');
this.takePhotoButton = document.getElementById('takePhotoButton');
this.switchButton = document.getElementById('switchButton');
this.backButton = document.getElementById('backButton');
this.saveButton = document.getElementById('saveButton');
this.undoButton = document.getElementById('undoButton');
this.resetButton = document.getElementById('resetButton');
// Drawing controls
this.colorPicker = document.getElementById('colorPicker');
this.brushSize = document.getElementById('brushSize');
this.brushSizeValue = document.getElementById('brushSizeValue');
// Text modal elements
this.textModal = document.getElementById('textModal');
this.textInput = document.getElementById('textInput');
this.confirmTextBtn = document.getElementById('confirmTextBtn');
this.cancelTextBtn = document.getElementById('cancelTextBtn');
// Initialize state
this.stream = null;
this.currentFacingMode = 'environment';
this.isDrawing = false;
this.currentTool = 'brush';
this.drawingHistory = [];
this.historyIndex = -1;
// Drawing coordinates
this.startX = 0;
this.startY = 0;
this.lastX = 0;
this.lastY = 0;
// Setup
this.setupCanvas();
this.setupEventListeners();
this.setupDrawingContext();
// Add text overlay container
this.textOverlay = document.getElementById('textOverlay');
this.activeTextElement = null;
this.isDraggingText = false;
this.textOffset = {
x: 0,
y: 0
};
// Current input field reference
this.currentInputField = null;
}
setupCanvas() {
// Set initial canvas size
this.canvas.width = 1280;
this.canvas.height = 960;
this.drawingCanvas.width = 1280;
this.drawingCanvas.height = 960;
}
setupDrawingContext() {
this.ctx.strokeStyle = this.colorPicker.value;
this.ctx.lineWidth = this.brushSize.value;
this.ctx.lineCap = 'round';
this.ctx.lineJoin = 'round';
}
async startCamera() {
try {
if (this.stream) {
this.stream.getTracks().forEach(track => track.stop());
}
const constraints = {
video: {
facingMode: this.currentFacingMode,
width: {
ideal: 1280
},
height: {
ideal: 960
}
},
audio: false
};
this.stream = await navigator.mediaDevices.getUserMedia(constraints);
this.video.srcObject = this.stream;
// Enable buttons after camera starts
this.takePhotoButton.disabled = false;
this.switchButton.disabled = false;
// Show video, hide canvas
this.video.style.display = 'block';
this.canvas.style.display = 'none';
this.drawingCanvas.style.display = 'none';
// Hide editor controls
document.getElementById('editorControls').classList.add('hidden');
document.getElementById('cameraControls').classList.remove('hidden');
} catch (error) {
console.error('Error accessing camera:', error);
alert('Error accessing camera. Please make sure you have granted camera permissions.');
}
}
takePhoto() {
// Draw video frame to canvas
const context = this.canvas.getContext('2d');
context.drawImage(this.video, 0, 0, this.canvas.width, this.canvas.height);
// Hide video, show canvases
this.video.style.display = 'none';
this.canvas.style.display = 'block';
this.drawingCanvas.style.display = 'block';
// Show editor controls, hide camera controls
document.getElementById('editorControls').classList.remove('hidden');
document.getElementById('cameraControls').classList.add('hidden');
// Clear drawing history
this.drawingHistory = [];
this.historyIndex = -1;
this.saveDrawingState();
}
switchCamera() {
this.currentFacingMode = this.currentFacingMode === 'environment' ? 'user' : 'environment';
this.startCamera();
}
resetToCamera() {
// Clear canvases
const context = this.canvas.getContext('2d');
context.clearRect(0, 0, this.canvas.width, this.canvas.height);
this.ctx.clearRect(0, 0, this.drawingCanvas.width, this.drawingCanvas.height);
// Reset history
this.drawingHistory = [];
this.historyIndex = -1;
// Restart camera
this.startCamera();
}
saveDrawingState() {
// Trim history if we're not at the end
if (this.historyIndex < this.drawingHistory.length - 1) {
this.drawingHistory = this.drawingHistory.slice(0, this.historyIndex + 1);
}
// Save current state
this.drawingHistory.push(this.drawingCanvas.toDataURL());
this.historyIndex++;
}
undo() {
if (this.historyIndex > 0) {
this.historyIndex--;
this.redrawHistory();
}
}
resetDrawing() {
this.ctx.clearRect(0, 0, this.drawingCanvas.width, this.drawingCanvas.height);
this.saveDrawingState();
}
// Implement all event listeners from previous code here
setupEventListeners() {
// Camera controls
this.startButton.onclick = () => this.startCamera();
this.takePhotoButton.onclick = () => this.takePhoto();
this.switchButton.onclick = () => this.switchCamera();
this.backButton.onclick = () => this.resetToCamera();
// Drawing controls
this.setupDrawingEvents();
this.setupToolButtons();
// Settings changes
this.colorPicker.oninput = (e) => this.ctx.strokeStyle = e.target.value;
this.brushSize.oninput = (e) => {
this.ctx.lineWidth = e.target.value;
this.brushSizeValue.textContent = `${e.target.value}px`;
};
// History controls
this.undoButton.onclick = () => this.undo();
this.resetButton.onclick = () => this.resetDrawing();
// Save functionality
this.saveButton.onclick = () => this.saveAndUpload();
// Text tool modal controls
this.confirmTextBtn.onclick = () => {
const text = this.textInput.value.trim();
if (text) {
this.addDraggableText(text);
this.textInput.value = '';
}
};
}
setupToolButtons() {
const toolButtons = document.querySelectorAll('.tool-btn');
toolButtons.forEach(button => {
button.onclick = () => {
toolButtons.forEach(btn => btn.classList.remove('active'));
button.classList.add('active');
this.currentTool = button.dataset.tool;
};
});
}
// Implement drawing events from previous code here
setupDrawingEvents() {
// Mouse events
this.drawingCanvas.addEventListener('mousedown', (e) => this.startDrawing(e));
this.drawingCanvas.addEventListener('mousemove', (e) => this.draw(e));
this.drawingCanvas.addEventListener('mouseup', () => this.stopDrawing());
this.drawingCanvas.addEventListener('mouseout', () => this.stopDrawing());
// Touch events
this.drawingCanvas.addEventListener('touchstart', (e) => {
if (this.shouldPreventDefault(e)) {
e.preventDefault();
}
const touch = e.touches[0];
const mouseEvent = new MouseEvent('mousedown', {
clientX: touch.clientX,
clientY: touch.clientY
});
this.startDrawing(mouseEvent);
}, {
passive: false
});
this.drawingCanvas.addEventListener('touchmove', (e) => {
if (this.shouldPreventDefault(e)) {
e.preventDefault();
}
const touch = e.touches[0];
const mouseEvent = new MouseEvent('mousemove', {
clientX: touch.clientX,
clientY: touch.clientY
});
this.draw(mouseEvent);
}, {
passive: false
});
this.drawingCanvas.addEventListener('touchend', () => {
this.stopDrawing();
}, {
passive: true
});
}
// Implement drawing methods from previous code here
startDrawing(e) {
this.isDrawing = true;
const rect = this.drawingCanvas.getBoundingClientRect();
const scaleX = this.drawingCanvas.width / rect.width;
const scaleY = this.drawingCanvas.height / rect.height;
this.startX = (e.clientX - rect.left) * scaleX;
this.startY = (e.clientY - rect.top) * scaleY;
this.lastX = this.startX;
this.lastY = this.startY;
if (this.currentTool === 'brush') {
this.ctx.beginPath();
this.ctx.moveTo(this.startX, this.startY);
}
}
addDraggableText(text) {
const textElement = document.createElement('div');
textElement.className = 'draggable-text';
textElement.style.cssText = `
position: absolute;
cursor: move;
user-select: none;
color: ${this.colorPicker.value};
font-size: ${this.brushSize.value * 2}px;
padding: 5px;
border-radius: 3px;
display: flex;
align-items: center;
gap: 8px;
`;
// Create text span
const textSpan = document.createElement('span');
textSpan.textContent = text;
textElement.appendChild(textSpan);
// Create delete button
const deleteBtn = document.createElement('button');
deleteBtn.innerHTML = '×';
deleteBtn.style.cssText = `
border: none;
background: none;
color: #ff4444;
font-size: 20px;
cursor: pointer;
padding: 0;
line-height: 1;
opacity: 0;
transition: opacity 0.2s;
`;
textElement.appendChild(deleteBtn);
// Show/hide delete button on hover
textElement.addEventListener('mouseenter', () => {
deleteBtn.style.opacity = '1';
});
textElement.addEventListener('mouseleave', () => {
deleteBtn.style.opacity = '0';
});
// Delete functionality
deleteBtn.addEventListener('click', (e) => {
e.stopPropagation();
textElement.remove();
this.saveDrawingState();
});
// Center the text initially
textElement.style.left = '50%';
textElement.style.top = '50%';
textElement.style.transform = 'translate(-50%, -50%)';
// Add drag functionality
this.setupDraggableText(textElement);
this.textOverlay.appendChild(textElement);
this.saveDrawingState();
}
setupDraggableText(element) {
let isDragging = false;
let currentX;
let currentY;
let initialX;
let initialY;
let xOffset = 0;
let yOffset = 0;
const dragStart = (e) => {
if (this.currentTool === 'text') {
const event = e.type === 'mousedown' ? e : e.touches[0];
initialX = event.clientX - xOffset;
initialY = event.clientY - yOffset;
if (e.target === element || e.target.parentNode === element) {
isDragging = true;
}
}
};
const drag = (e) => {
if (isDragging) {
e.preventDefault();
const event = e.type === 'mousemove' ? e : e.touches[0];
currentX = event.clientX - initialX;
currentY = event.clientY - initialY;
xOffset = currentX;
yOffset = currentY;
setTranslate(currentX, currentY, element);
}
};
const dragEnd = () => {
if (isDragging) {
isDragging = false;
this.saveDrawingState();
}
};
const setTranslate = (xPos, yPos, el) => {
el.style.transform = `translate(${xPos}px, ${yPos}px)`;
};
// Mouse events
element.addEventListener('mousedown', dragStart);
document.addEventListener('mousemove', drag);
document.addEventListener('mouseup', dragEnd);
// Touch events
element.addEventListener('touchstart', dragStart);
document.addEventListener('touchmove', drag);
document.addEventListener('touchend', dragEnd);
}
// Add this CSS to your stylesheet
draw(e) {
if (!this.isDrawing) return;
const rect = this.drawingCanvas.getBoundingClientRect();
const scaleX = this.drawingCanvas.width / rect.width;
const scaleY = this.drawingCanvas.height / rect.height;
const x = (e.clientX - rect.left) * scaleX;
const y = (e.clientY - rect.top) * scaleY;
switch (this.currentTool) {
case 'arrow':
// Restore the previous state before drawing new arrow
if (this.historyIndex >= 0) {
this.redrawHistory();
} else {
this.ctx.clearRect(0, 0, this.drawingCanvas.width, this.drawingCanvas.height);
}
this.drawArrow(this.startX, this.startY, x, y);
break;
case 'brush':
this.ctx.lineTo(x, y);
this.ctx.stroke();
break;
case 'rectangle':
// Restore the previous state before drawing new rectangle
if (this.historyIndex >= 0) {
this.redrawHistory();
} else {
this.ctx.clearRect(0, 0, this.drawingCanvas.width, this.drawingCanvas.height);
}
this.ctx.beginPath();
this.ctx.strokeRect(this.startX, this.startY, x - this.startX, y - this.startY);
break;
case 'circle':
// Restore the previous state before drawing new circle
if (this.historyIndex >= 0) {
this.redrawHistory();
} else {
this.ctx.clearRect(0, 0, this.drawingCanvas.width, this.drawingCanvas.height);
}
const radius = Math.sqrt(Math.pow(x - this.startX, 2) + Math.pow(y - this.startY, 2));
this.ctx.beginPath();
this.ctx.arc(this.startX, this.startY, radius, 0, Math.PI * 2);
this.ctx.stroke();
break;
}
this.lastX = x;
this.lastY = y;
}
drawArrow(fromX, fromY, toX, toY) {
const headLength = 20;
const headAngle = Math.PI / 6;
// Calculate angle
const angle = Math.atan2(toY - fromY, toX - fromX);
// Draw main line
this.ctx.beginPath();
this.ctx.moveTo(fromX, fromY);
this.ctx.lineTo(toX, toY);
this.ctx.stroke();
// Draw arrowhead
this.ctx.beginPath();
this.ctx.moveTo(toX, toY);
this.ctx.lineTo(
toX - headLength * Math.cos(angle - headAngle),
toY - headLength * Math.sin(angle - headAngle)
);
this.ctx.moveTo(toX, toY);
this.ctx.lineTo(
toX - headLength * Math.cos(angle + headAngle),
toY - headLength * Math.sin(angle + headAngle)
);
this.ctx.stroke();
}
startDrawing(e) {
this.isDrawing = true;
const rect = this.drawingCanvas.getBoundingClientRect();
const scaleX = this.drawingCanvas.width / rect.width;
const scaleY = this.drawingCanvas.height / rect.height;
this.startX = (e.clientX - rect.left) * scaleX;
this.startY = (e.clientY - rect.top) * scaleY;
this.lastX = this.startX;
this.lastY = this.startY;
if (this.currentTool === 'brush') {
this.ctx.beginPath();
this.ctx.moveTo(this.startX, this.startY);
}
}
stopDrawing() {
if (this.isDrawing) {
this.isDrawing = false;
if (this.currentTool === 'brush') {
this.ctx.closePath();
}
this.saveDrawingState();
}
}
redrawHistory() {
if (this.historyIndex >= 0 && this.drawingHistory[this.historyIndex]) {
const img = new Image();
img.src = this.drawingHistory[this.historyIndex];
this.ctx.clearRect(0, 0, this.drawingCanvas.width, this.drawingCanvas.height);
this.ctx.drawImage(img, 0, 0);
} else {
this.ctx.clearRect(0, 0, this.drawingCanvas.width, this.drawingCanvas.height);
}
}
closeEditor() {
if (this.stream) {
this.stream.getTracks().forEach(track => track.stop());
}
// Clear canvases and text overlay
this.ctx.clearRect(0, 0, this.drawingCanvas.width, this.drawingCanvas.height);
const mainCtx = this.canvas.getContext('2d');
mainCtx.clearRect(0, 0, this.canvas.width, this.canvas.height);
this.textOverlay.innerHTML = '';
// Reset state
this.drawingHistory = [];
this.historyIndex = -1;
this.currentInputField = null;
// Close modals
this.cameraModal.hide();
this.textInputModal.hide();
}
// Override this method in your implementation
saveAndUpload() {
}
closeCamera() {
// Stop the video stream
if (this.stream) {
this.stream.getTracks().forEach(track => track.stop());
}
// Hide video and canvas elements
this.video.style.display = 'none';
this.canvas.style.display = 'none';
this.drawingCanvas.style.display = 'none';
// Reset any additional state if needed
this.stream = null;
}
}
</script>
@endpush