From f476e93abce04317bc4ddaf57daa922c5a99493e Mon Sep 17 00:00:00 2001 From: Jerry Date: Thu, 27 Feb 2025 10:10:02 -0500 Subject: [PATCH] Making edits work --- app/static/js/Canvas.js | 35 ++- app/static/js/scout/add.js | 4 +- app/static/js/scout/edit.js | 388 ++++++++++++++++++++++++++++++- app/templates/scouting/edit.html | 208 ++++++++++++----- 4 files changed, 569 insertions(+), 66 deletions(-) diff --git a/app/static/js/Canvas.js b/app/static/js/Canvas.js index 9d67dfc..afd30ca 100644 --- a/app/static/js/Canvas.js +++ b/app/static/js/Canvas.js @@ -108,8 +108,8 @@ class Canvas { this.lastWidth = 0; this.velocityFilterWeight = 0.7; - // Auto-save interval (every 30 seconds) - this.autoSaveInterval = setInterval(() => this.autoSave(), 30000); + // // Auto-save interval (every 30 seconds) + // this.autoSaveInterval = setInterval(() => this.autoSave(), 30000); // Initialize this.resizeCanvas(); @@ -917,6 +917,7 @@ class Canvas { window.addEventListener('resize', () => this.resizeCanvas()); // Mouse wheel for zooming + let zoomTimeout; this.container.addEventListener('wheel', (e) => { e.preventDefault(); @@ -948,8 +949,13 @@ class Canvas { // Redraw this.redrawCanvas(); - // Show zoom level - this.showStatus(`Zoom: ${Math.round(this.scale * 100)}%`); + // Clear any existing timeout + clearTimeout(zoomTimeout); + + // // Set new timeout to show zoom level after zooming stops + // zoomTimeout = setTimeout(() => { + // this.showStatus(`Zoom: ${Math.round(this.scale * 100)}%`); + // }, 150); // Wait 150ms after last wheel event before showing status }); // Mouse events for drawing and panning @@ -1338,14 +1344,20 @@ class Canvas { // Add keyboard shortcuts window.addEventListener('keydown', (e) => { + // Check if the event has already been handled + if (e.defaultPrevented) { + return; + } + if (e.key === 'Escape' && (this.previewShape || this.selectionRect)) { + e.preventDefault(); this.previewShape = null; this.selectionRect = null; this.startX = null; this.startY = null; this.selectedStrokes = []; this.redrawCanvas(); - this.showStatus('Operation cancelled'); + // this.showStatus('Operation cancelled'); return; } @@ -1353,6 +1365,7 @@ class Canvas { switch(e.key.toLowerCase()) { case 'z': e.preventDefault(); + e.stopPropagation(); // Stop event from bubbling if (e.shiftKey) { this.redo(); } else { @@ -1361,23 +1374,28 @@ class Canvas { break; case 'y': e.preventDefault(); + e.stopPropagation(); // Stop event from bubbling this.redo(); break; case 'a': e.preventDefault(); + e.stopPropagation(); // Stop event from bubbling this.setTool('select'); break; case 'p': e.preventDefault(); + e.stopPropagation(); // Stop event from bubbling this.setTool('pen'); break; case 'r': e.preventDefault(); + e.stopPropagation(); // Stop event from bubbling this.setTool('rectangle'); break; case 'c': if (!e.shiftKey) { e.preventDefault(); + e.stopPropagation(); // Stop event from bubbling if (e.altKey) { this.setTool('circle'); } else { @@ -1387,31 +1405,38 @@ class Canvas { break; case 'x': e.preventDefault(); + e.stopPropagation(); // Stop event from bubbling this.cutSelectedStrokes(); break; case 'v': e.preventDefault(); + e.stopPropagation(); // Stop event from bubbling this.pasteStrokes(); break; case 'l': e.preventDefault(); + e.stopPropagation(); // Stop event from bubbling this.setTool('line'); break; case 'h': e.preventDefault(); + e.stopPropagation(); // Stop event from bubbling this.setTool('hexagon'); break; case 's': e.preventDefault(); + e.stopPropagation(); // Stop event from bubbling this.setTool('star'); break; case 'f': e.preventDefault(); + e.stopPropagation(); // Stop event from bubbling this.setFill(!this.isFilled); break; } } else if ((e.key === 'Backspace' || e.key === 'Delete') && this.selectedStrokes.length > 0) { e.preventDefault(); + e.stopPropagation(); // Stop event from bubbling this.deleteSelectedStrokes(); } }); diff --git a/app/static/js/scout/add.js b/app/static/js/scout/add.js index 443445f..64c8526 100644 --- a/app/static/js/scout/add.js +++ b/app/static/js/scout/add.js @@ -326,7 +326,9 @@ document.addEventListener('DOMContentLoaded', function() { if (e.shiftKey) { CanvasField.redo(); } else { - CanvasField.undo(); + if (!e.repeat) { // Only trigger once when key is first pressed + CanvasField.undo(); + } } updatePathData(); break; diff --git a/app/static/js/scout/edit.js b/app/static/js/scout/edit.js index 44f49f4..d526f95 100644 --- a/app/static/js/scout/edit.js +++ b/app/static/js/scout/edit.js @@ -8,12 +8,396 @@ document.addEventListener('DOMContentLoaded', function() { }); } - // Form submission handler + // Initialize CanvasField helper + const CanvasField = new Canvas({ + canvas: document.getElementById('autoPath'), + container: document.getElementById('autoPathContainer'), + showStatus: (message) => { + const flashContainer = document.querySelector('.container'); + if (!flashContainer) return; + + const messageDiv = document.createElement('div'); + messageDiv.className = 'fixed bottom-6 left-1/2 -translate-x-1/2 sm:left-auto sm:right-6 sm:-translate-x-0 z-50 w-[90%] sm:w-full max-w-xl min-h-[60px] sm:min-h-[80px] mx-auto sm:mx-0 animate-fade-in-up'; + + const innerDiv = document.createElement('div'); + innerDiv.className = 'flex items-center p-6 rounded-lg shadow-xl bg-green-50 text-green-800 border-2 border-green-200'; + + const icon = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); + icon.setAttribute('class', 'w-6 h-6 mr-3 flex-shrink-0'); + icon.setAttribute('fill', 'currentColor'); + icon.setAttribute('viewBox', '0 0 20 20'); + + const path = document.createElementNS('http://www.w3.org/2000/svg', 'path'); + path.setAttribute('fill-rule', 'evenodd'); + path.setAttribute('d', 'M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z'); + path.setAttribute('clip-rule', 'evenodd'); + + icon.appendChild(path); + + const text = document.createElement('p'); + text.className = 'text-base font-medium'; + text.textContent = message; + + const closeButton = document.createElement('button'); + closeButton.className = 'ml-auto -mx-1.5 -my-1.5 rounded-lg p-1.5 inline-flex h-8 w-8 text-green-500 hover:bg-green-100'; + closeButton.onclick = () => messageDiv.remove(); + + const closeIcon = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); + closeIcon.setAttribute('class', 'w-5 h-5'); + closeIcon.setAttribute('fill', 'currentColor'); + closeIcon.setAttribute('viewBox', '0 0 20 20'); + + const closePath = document.createElementNS('http://www.w3.org/2000/svg', 'path'); + closePath.setAttribute('fill-rule', 'evenodd'); + closePath.setAttribute('d', 'M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z'); + closePath.setAttribute('clip-rule', 'evenodd'); + + closeIcon.appendChild(closePath); + closeButton.appendChild(closeIcon); + + innerDiv.appendChild(icon); + innerDiv.appendChild(text); + innerDiv.appendChild(closeButton); + messageDiv.appendChild(innerDiv); + + flashContainer.appendChild(messageDiv); + + setTimeout(() => { + if (messageDiv.parentNode === flashContainer) { + messageDiv.remove(); + } + }, 3000); + }, + initialColor: '#2563eb', + initialThickness: 3, + maxPanDistance: 1000, + backgroundImage: '/static/images/field-2025.png' + }); + + // Verify background image loading + const testImage = new Image(); + testImage.onload = () => { + console.log('Background image loaded successfully'); + CanvasField.showStatus('Field image loaded'); + }; + testImage.onerror = () => { + console.error('Failed to load background image'); + CanvasField.showStatus('Error loading field image'); + }; + testImage.src = '/static/images/field-2025.png'; + + // Load existing path data if available + const pathDataInput = document.getElementById('autoPathData'); + if (pathDataInput && pathDataInput.value) { + try { + let sanitizedValue = pathDataInput.value.trim(); + + // Remove any potential HTML entities + sanitizedValue = sanitizedValue.replace(/"/g, '"') + .replace(/"/g, '"') + .replace(/'/g, "'") + .replace(/&/g, '&'); + + // Check if the value is actually JSON + if (sanitizedValue && sanitizedValue !== "None" && sanitizedValue !== "null") { + // Try to fix common JSON formatting issues + if (sanitizedValue.startsWith("'") && sanitizedValue.endsWith("'")) { + sanitizedValue = sanitizedValue.slice(1, -1); + } + + // Convert single quotes to double quotes + sanitizedValue = sanitizedValue.replace(/'/g, '"'); + + // Convert Python boolean values to JSON boolean values + sanitizedValue = sanitizedValue.replace(/: True/g, ': true') + .replace(/: False/g, ': false'); + + try { + const pathData = JSON.parse(sanitizedValue); + + if (Array.isArray(pathData)) { + CanvasField.drawingHistory = pathData; + CanvasField.redrawCanvas(); + CanvasField.showStatus('Existing path loaded'); + } else { + CanvasField.drawingHistory = []; + CanvasField.redrawCanvas(); + CanvasField.showStatus('Invalid path data format'); + } + } catch (error) { + console.error('Error loading existing path:', error); + CanvasField.drawingHistory = []; + CanvasField.redrawCanvas(); + CanvasField.showStatus('Error loading existing path'); + } + } else { + CanvasField.drawingHistory = []; + CanvasField.redrawCanvas(); + } + } catch (error) { + console.error('Error loading existing path:', error); + CanvasField.drawingHistory = []; + CanvasField.redrawCanvas(); + CanvasField.showStatus('Error loading existing path'); + } + } + + // Prevent page scrolling when using mouse wheel on canvas + const canvas = document.getElementById('autoPath'); + canvas.addEventListener('wheel', (e) => { + if (e.target === canvas && CanvasField.isPanning) { + e.preventDefault(); + } + }, { passive: false }); + + // Prevent page scrolling when middle mouse button is pressed + canvas.addEventListener('mousedown', (e) => { + if (e.button === 1 && e.target === canvas) { // Middle mouse button + e.preventDefault(); + CanvasField.startPanning(e); + } + }); + + // Add mouseup handler for panning + canvas.addEventListener('mouseup', (e) => { + if (e.button === 1 && e.target === canvas) { + CanvasField.stopPanning(); + } + }); + + // Configure Coloris + Coloris({ + theme: 'polaroid', + themeMode: 'light', + alpha: false, + formatToggle: false, + swatches: [ + '#2563eb', // Default blue + '#000000', + '#ffffff', + '#db4437', + '#4285f4', + '#0f9d58', + '#ffeb3b', + '#ff7f00' + ] + }); + + // Tool buttons + const toolButtons = { + select: document.getElementById('selectTool'), + pen: document.getElementById('penTool'), + rectangle: document.getElementById('rectangleTool'), + circle: document.getElementById('circleTool'), + line: document.getElementById('lineTool'), + hexagon: document.getElementById('hexagonTool'), + star: document.getElementById('starTool') + }; + + // Function to update active tool button + function updateActiveToolButton(activeTool) { + Object.entries(toolButtons).forEach(([tool, button]) => { + if (tool === activeTool) { + button.classList.add('active'); + } else { + button.classList.remove('active'); + } + }); + } + + // Add tool button event listeners + Object.entries(toolButtons).forEach(([tool, button]) => { + button.addEventListener('click', (e) => { + e.preventDefault(); // Prevent form submission + CanvasField.setTool(tool); + updateActiveToolButton(tool); + }); + }); + + // Color picker + document.getElementById('pathColorPicker').addEventListener('change', function(e) { + CanvasField.setColor(this.value); + }); + + // Thickness control + const thicknessSlider = document.getElementById('pathThickness'); + const thicknessValue = document.getElementById('pathThicknessValue'); + + thicknessSlider.addEventListener('input', function() { + const value = this.value; + thicknessValue.textContent = value; + CanvasField.setThickness(parseInt(value)); + }); + + // Fill toggle button + const fillToggleBtn = document.getElementById('fillToggle'); + fillToggleBtn.addEventListener('click', function(e) { + e.preventDefault(); // Prevent form submission + const newFillState = !CanvasField.isFilled; + CanvasField.setFill(newFillState); + this.textContent = `Fill: ${newFillState ? 'On' : 'Off'}`; + this.classList.toggle('bg-blue-800', newFillState); + }); + + // Function to update hidden path data + function updatePathData() { + const pathData = document.getElementById('autoPathData'); + if (pathData) { + pathData.value = JSON.stringify(CanvasField.drawingHistory); + } + } + + // Add mouseup listener to update path data after drawing + canvas.addEventListener('mouseup', updatePathData); + + // Undo button + document.getElementById('undoPath').addEventListener('click', (e) => { + e.preventDefault(); // Prevent form submission + CanvasField.undo(); + updatePathData(); + }); + + // Redo button + document.getElementById('redoPath').addEventListener('click', (e) => { + e.preventDefault(); // Prevent form submission + CanvasField.redo(); + updatePathData(); + }); + + // Clear button + document.getElementById('clearPath').addEventListener('click', (e) => { + e.preventDefault(); // Prevent form submission + if (confirm('Are you sure you want to clear the path?')) { + CanvasField.clear(); + updatePathData(); + } + }); + + // Save button + document.getElementById('savePath').addEventListener('click', (e) => { + e.preventDefault(); // Prevent form submission + const jsonString = JSON.stringify(CanvasField.drawingHistory); + const blob = new Blob([jsonString], { type: 'application/json' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = `autopath-${new Date().toISOString().slice(0, 10)}.json`; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + URL.revokeObjectURL(url); + + CanvasField.showStatus('Path saved'); + }); + + // Load button and file input + const loadBtn = document.getElementById('loadPath'); + const loadFile = document.getElementById('loadFile'); + + loadBtn.addEventListener('click', (e) => { + e.preventDefault(); // Prevent form submission + loadFile.click(); + }); + + loadFile.addEventListener('change', (e) => { + if (e.target.files.length === 0) return; + + const file = e.target.files[0]; + const reader = new FileReader(); + + reader.onload = function(event) { + try { + const pathData = JSON.parse(event.target.result); + CanvasField.drawingHistory = pathData; + CanvasField.redrawCanvas(); + updatePathData(); + CanvasField.showStatus('Path loaded'); + } catch (error) { + console.error('Error loading path:', error); + CanvasField.showStatus('Error loading path'); + } + }; + + reader.readAsText(file); + e.target.value = null; // Reset file input + }); + + // Add keyboard shortcuts + document.addEventListener('keydown', (e) => { + if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') return; + + if (e.ctrlKey) { + switch (e.key.toLowerCase()) { + case 'a': + e.preventDefault(); + CanvasField.setTool('select'); + updateActiveToolButton('select'); + break; + case 'p': + e.preventDefault(); + CanvasField.setTool('pen'); + updateActiveToolButton('pen'); + break; + case 'r': + e.preventDefault(); + CanvasField.setTool('rectangle'); + updateActiveToolButton('rectangle'); + break; + case 'c': + e.preventDefault(); + CanvasField.setTool('circle'); + updateActiveToolButton('circle'); + break; + case 'l': + e.preventDefault(); + CanvasField.setTool('line'); + updateActiveToolButton('line'); + break; + case 'h': + e.preventDefault(); + CanvasField.setTool('hexagon'); + updateActiveToolButton('hexagon'); + break; + case 's': + if (!e.shiftKey) { + e.preventDefault(); + CanvasField.setTool('star'); + updateActiveToolButton('star'); + } + break; + case 'z': + e.preventDefault(); + if (e.shiftKey) { + CanvasField.redo(); + } else { + if (!e.repeat) { // Only trigger once when key is first pressed + CanvasField.undo(); + } + } + updatePathData(); + break; + case 'y': + e.preventDefault(); + CanvasField.redo(); + updatePathData(); + break; + case 'f': + e.preventDefault(); + fillToggleBtn.click(); + break; + } + } + }); + + // Form submission handling const form = document.getElementById('scoutingForm'); if (form) { form.addEventListener('submit', async function(e) { e.preventDefault(); + // Update path data before submission + updatePathData(); + const teamNumber = form.querySelector('input[name="team_number"]').value; const eventCode = form.querySelector('input[name="event_code"]').value; const matchNumber = form.querySelector('input[name="match_number"]').value; @@ -27,7 +411,7 @@ document.addEventListener('DOMContentLoaded', function() { alert(`Team ${teamNumber} already exists in match ${matchNumber} for event ${eventCode}`); return; } - + form.submit(); } catch (error) { console.error('Error checking team:', error); diff --git a/app/templates/scouting/edit.html b/app/templates/scouting/edit.html index a89fac4..a0b18769 100644 --- a/app/templates/scouting/edit.html +++ b/app/templates/scouting/edit.html @@ -1,4 +1,49 @@ {% extends "base.html" %} +{% block head %} + + + + +{% endblock %} {% block content %}
@@ -252,67 +297,114 @@

Defense

-
-

Auto Path

-
- -
- 100% +
+

Auto Path

+
+ +
+
-
- -
+ + +
+ +
+
+ +
+
+ + + 3 +
+
+ +
+
+ + +
+
+ + + + + + + +
+ +
+ + + + + + +
- - - -
- -
- - -
+ + +
- -
-

Draw the robot's autonomous path

-
- - -
+ +
+ +
@@ -343,6 +435,6 @@

Additional Notes

- + {% endblock %} \ No newline at end of file