From 727cf341cf44de5a1c531ef9de26507d3da0e1dc Mon Sep 17 00:00:00 2001 From: justFadel19 Date: Sun, 15 Jun 2025 10:09:10 +0300 Subject: [PATCH 1/2] Enhance edit label modal styling and functionality - Redesigned modal with clean, modern styling - Made modal compact yet spacious (380px x 320px) - Added gradient backgrounds and smooth hover effects - Removed suggestions section for simplified UI - Fixed JavaScript errors with null element checks - Improved button styling with subtle animations - Enhanced mobile responsiveness - Removed focus effects as requested - Streamlined user experience for label editing --- web/index.html | 36 ++++++- web/js/annotations.js | 199 ++++++++++++++++++++++++++++++++--- web/styles.css | 237 +++++++++++++++++++++++++++++++++++------- 3 files changed, 415 insertions(+), 57 deletions(-) diff --git a/web/index.html b/web/index.html index fe52f8f..6ff504e 100644 --- a/web/index.html +++ b/web/index.html @@ -202,9 +202,7 @@

Welcome to SAT Annotator

-
- - +
+ + diff --git a/web/js/annotations.js b/web/js/annotations.js index af580ec..b14c41c 100644 --- a/web/js/annotations.js +++ b/web/js/annotations.js @@ -1,12 +1,12 @@ // Annotation management for SAT Annotator -class AnnotationManager { - constructor() { +class AnnotationManager { constructor() { this.annotations = []; this.selectedAnnotation = null; this.currentImageId = null; this.currentLabel = 'building'; // Default label this.customLabels = []; // Store custom labels added by user + this.currentEditingAnnotationId = null; // Track which annotation is being edited try { this.setupEventListeners(); @@ -37,7 +37,45 @@ class AnnotationManager { // Hide context menu when clicking elsewhere document.addEventListener('click', () => this.hideContextMenu()); - } async setCurrentImage(imageId) { + + // Edit Label Modal Events + document.getElementById('closeEditModal').addEventListener('click', () => this.closeEditLabelModal()); + document.getElementById('cancelEditLabel').addEventListener('click', () => this.closeEditLabelModal()); + document.getElementById('saveEditLabel').addEventListener('click', () => this.saveEditedLabel()); + + // Handle dropdown selection change + document.getElementById('labelSelect').addEventListener('change', (e) => { + const customInput = document.getElementById('customLabelEdit'); + if (e.target.value === '__new__') { + customInput.focus(); + } else { + customInput.value = ''; + } + }); + + // Handle adding new label from modal + document.getElementById('addNewLabelBtn').addEventListener('click', () => { + const input = document.getElementById('customLabelEdit'); + const label = input.value.trim().toLowerCase(); + if (label) { + document.getElementById('labelSelect').value = '__new__'; + } + }); + + // Handle Enter key in custom label input + document.getElementById('customLabelEdit').addEventListener('keydown', (e) => { + if (e.key === 'Enter') { + this.saveEditedLabel(); + } + }); + + // Close modal on Escape key + document.addEventListener('keydown', (e) => { + if (e.key === 'Escape' && !document.getElementById('editLabelModal').hidden) { + this.closeEditLabelModal(); + } + }); + }async setCurrentImage(imageId) { console.log(`🔍 setCurrentImage called with: ${imageId}`); console.log(`🔍 Previous currentImageId: ${this.currentImageId}`); @@ -133,7 +171,7 @@ class AnnotationManager { button.innerHTML = ` ${label} - + `; // Add click event @@ -337,17 +375,16 @@ class AnnotationManager {

${annotation.label}

${annotation.source === 'ai' ? 'AI Generated' : 'Manual'} • ${annotation.polygon.length} points${annotation.simplified ? ' (simplified)' : ''}

-
- - ${annotation.originalPolygon ? `` : ''} - -
@@ -383,16 +420,19 @@ class AnnotationManager { // Update header or status somewhere if needed // Could add a status bar showing these counts } editAnnotation(id) { + console.log('editAnnotation called with ID:', id); const annotation = this.annotations.find(ann => ann.id === id); - if (!annotation) return; - - // Simple prompt for now - could be enhanced with inline editing later - const newLabel = prompt('Enter new label:', annotation.label); - if (newLabel && newLabel.trim() !== annotation.label) { - this.updateAnnotation(id, { label: newLabel.trim() }); - this.saveAnnotation(id); + if (!annotation) { + console.error('Annotation not found with ID:', id); + return; } - } editPolygon(id) { + + console.log('Found annotation:', annotation); + this.currentEditingAnnotationId = id; + this.showEditLabelModal(annotation.label); + } + + editPolygon(id) { // Switch to select tool for editing window.canvasManager.setTool('select'); window.canvasManager.updateToolButtons(); @@ -414,6 +454,131 @@ class AnnotationManager { 'Use Edit Mode button to toggle off', 5000 ); + } showEditLabelModal(currentLabel) { + console.log('showEditLabelModal called with label:', currentLabel); + const modal = document.getElementById('editLabelModal'); + const labelSelect = document.getElementById('labelSelect'); + const customLabelInput = document.getElementById('customLabelEdit'); + + if (!modal) { + console.error('Modal element not found!'); + return; + } + + if (!labelSelect) { + console.error('Label select element not found!'); + return; + } + + if (!customLabelInput) { + console.error('Custom label input element not found!'); + return; + } + + console.log('Modal element found, proceeding...'); + + // Clear previous content + labelSelect.innerHTML = ''; + customLabelInput.value = ''; + + // Populate dropdown with all available labels + this.populateLabelDropdown(labelSelect, currentLabel); + // Show modal + console.log('Adding show class to modal'); + modal.classList.add('show'); + console.log('Modal should now be visible'); + } + + populateLabelDropdown(selectElement, currentLabel) { + // Get all available labels (default + custom) + const defaultLabels = ['building', 'road', 'vegetation', 'water', 'parking']; + const allLabels = [...new Set([...defaultLabels, ...this.customLabels])]; + + // Add options to select + allLabels.forEach(label => { + const option = document.createElement('option'); + option.value = label; + option.textContent = label.charAt(0).toUpperCase() + label.slice(1); + + if (label === currentLabel) { + option.selected = true; + } + + selectElement.appendChild(option); + }); + + // Add option for creating new label + const newOption = document.createElement('option'); + newOption.value = '__new__'; + newOption.textContent = '+ Add new label...'; + selectElement.appendChild(newOption); + } + + populateSuggestedLabels(container, currentLabel) { + // Get unique labels from existing annotations + const existingLabels = [...new Set(this.annotations.map(ann => ann.label))] + .filter(label => label !== currentLabel) + .slice(0, 6); // Limit to 6 suggestions + + existingLabels.forEach(label => { + const suggestion = document.createElement('div'); + suggestion.className = 'label-suggestion'; + suggestion.textContent = label; + suggestion.addEventListener('click', () => { + document.getElementById('labelSelect').value = label; + }); + container.appendChild(suggestion); + }); + } closeEditLabelModal() { + const modal = document.getElementById('editLabelModal'); + modal.classList.remove('show'); + this.currentEditingAnnotationId = null; + } + + saveEditedLabel() { + const labelSelect = document.getElementById('labelSelect'); + const customLabelInput = document.getElementById('customLabelEdit'); + + let newLabel = ''; + + if (labelSelect.value === '__new__') { + // User selected "Add new label" + newLabel = customLabelInput.value.trim().toLowerCase(); + if (!newLabel) { + Utils.showToast('Please enter a label name', 'warning'); + customLabelInput.focus(); + return; + } + } else { + newLabel = labelSelect.value; + } + + if (!newLabel) { + Utils.showToast('Please select or enter a label', 'warning'); + return; + } + + // Add to custom labels if it's new + if (!this.customLabels.includes(newLabel) && + !['building', 'road', 'vegetation', 'water', 'parking'].includes(newLabel)) { + this.customLabels.push(newLabel); + this.createCustomLabelButton(newLabel); + } + // Update annotation + const annotation = this.annotations.find(ann => ann.id === this.currentEditingAnnotationId); + if (annotation && annotation.label !== newLabel) { + this.updateAnnotation(this.currentEditingAnnotationId, { label: newLabel }); + this.saveAnnotation(this.currentEditingAnnotationId); + + // Redraw canvas to update label display immediately + if (window.canvasManager) { + window.canvasManager.redraw(); + } + + Utils.showToast(`Label changed to "${newLabel}"`, 'success'); + } + + this.closeEditLabelModal(); } resimplifyPolygon(id) { diff --git a/web/styles.css b/web/styles.css index 9f068b3..5ff6a75 100644 --- a/web/styles.css +++ b/web/styles.css @@ -950,107 +950,198 @@ body { right: 0; bottom: 0; background: rgba(0, 0, 0, 0.5); - display: flex; + display: none; align-items: center; justify-content: center; z-index: 10000; backdrop-filter: blur(4px); -webkit-backdrop-filter: blur(4px); - will-change: opacity; +} + +.modal.show { + display: flex; } .modal-content { background: white; - border-radius: 0.75rem; + border-radius: 12px; width: 90%; - max-width: 400px; - box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1); - will-change: transform; - transform: translateZ(0); + max-width: 380px; + min-height: 320px; + max-height: 80vh; + box-shadow: 0 10px 40px rgba(0, 0, 0, 0.15); + border: 1px solid #f1f5f9; + overflow: hidden; } .modal-header { display: flex; justify-content: space-between; align-items: center; - padding: 1.5rem 1.5rem 0; + padding: 1.5rem 2rem 1.25rem; + background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%); } .modal-header h3 { - font-size: 1.125rem; + font-size: 0.95rem; font-weight: 600; + color: #334155; + margin: 0; } .modal-close { background: none; border: none; - font-size: 1.25rem; + font-size: 1rem; cursor: pointer; - color: #64748b; - width: 32px; - height: 32px; + color: #94a3b8; + width: 24px; + height: 24px; display: flex; align-items: center; justify-content: center; - border-radius: 0.25rem; + border-radius: 6px; + transition: all 0.2s ease; } .modal-close:hover { - background: #f1f5f9; + background: rgba(239, 68, 68, 0.1); + color: #ef4444; } .modal-body { - padding: 1.5rem; + padding: 1.5rem 2rem; + min-height: 220px; } .modal-body label { display: block; font-weight: 500; - margin-bottom: 0.5rem; + margin-bottom: 0.4rem; + color: #475569; + font-size: 0.8rem; } .modal-body input { width: 100%; - padding: 0.75rem; + padding: 0.6rem 0.75rem; border: 1px solid #e2e8f0; - border-radius: 0.5rem; - font-size: 0.875rem; - margin-bottom: 1rem; + border-radius: 8px; + font-size: 0.8rem; + margin-bottom: 0.75rem; + transition: border-color 0.2s ease; } .modal-body input:focus { outline: none; border-color: #3b82f6; - box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1); } -.suggested-labels { + + +.modal-footer { display: flex; - flex-wrap: wrap; - gap: 0.5rem; + justify-content: flex-end; + gap: 1rem; + padding: 1.25rem 2rem 1.5rem; + background: #fafbfc; + border-top: 1px solid #f1f5f9; } -.label-suggestion { +.modal-footer .btn { + padding: 0.5rem 1rem; + border-radius: 8px; + font-size: 0.8rem; + font-weight: 500; + border: none; + cursor: pointer; + transition: all 0.2s ease; +} + +.modal-footer .btn-secondary { + background: #f8fafc; + color: #64748b; + border: 1px solid #e2e8f0; +} + +.modal-footer .btn-secondary:hover { background: #f1f5f9; color: #475569; - padding: 0.25rem 0.5rem; - border-radius: 0.25rem; - font-size: 0.75rem; +} + +.modal-footer .btn-primary { + background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%); + color: white; + box-shadow: 0 2px 4px rgba(59, 130, 246, 0.2); +} + +.modal-footer .btn-primary:hover { + background: linear-gradient(135deg, #2563eb 0%, #1d4ed8 100%); + transform: translateY(-1px); + box-shadow: 0 4px 8px rgba(59, 130, 246, 0.3); +} + +/* Label Selection Modal */ +.label-dropdown-container { + margin-bottom: 0.75rem; +} + +.label-select { + width: 100%; + padding: 0.6rem 0.75rem; + border: 1px solid #e2e8f0; + border-radius: 8px; + font-size: 0.8rem; + background: white; cursor: pointer; - transition: all 0.2s ease; + transition: border-color 0.2s ease; } -.label-suggestion:hover { - background: #e2e8f0; +.label-select:focus { + outline: none; + border-color: #3b82f6; } -.modal-footer { +.label-select option { + padding: 0.4rem; +} + +.custom-label-container { display: flex; - justify-content: flex-end; - gap: 0.75rem; - padding: 0 1.5rem 1.5rem; + gap: 0.4rem; + margin-bottom: 0.75rem; + align-items: stretch; } +.custom-label-container input { + flex: 1; + margin-bottom: 0; +} + +.custom-label-container .btn { + flex-shrink: 0; + align-self: stretch; + display: flex; + align-items: center; + justify-content: center; + min-width: 50px; + padding: 0.6rem; + background: linear-gradient(135deg, #10b981 0%, #059669 100%); + border: none; + color: white; + border-radius: 8px; + font-size: 0.8rem; + font-weight: 500; + transition: all 0.2s ease; +} + +.custom-label-container .btn:hover { + background: linear-gradient(135deg, #059669 0%, #047857 100%); + transform: translateY(-1px); +} + + + /* Section Header with Action Button */ .section-header { display: flex; @@ -1162,6 +1253,80 @@ body { margin: 0; } +/* Animations */ +@keyframes modalFadeIn { + from { + opacity: 0; + transform: scale(0.9) translateY(-20px); + } + to { + opacity: 1; + transform: scale(1) translateY(0); + } +} + +@keyframes shimmer { + 0% { + background-position: -1000px 0; + } + 100% { + background-position: 1000px 0; + } +} + +/* Enhanced Mobile Responsiveness for Modal */ +@media (max-width: 640px) { + .modal-content { + width: 95%; + max-width: none; + margin: 1rem; + border-radius: 1rem; + } + + .modal-header { + padding: 1.5rem 1.5rem 0; + } + + .modal-header::after { + left: 1.5rem; + right: 1.5rem; + } + + .modal-header h3 { + font-size: 1.125rem; + } + + .modal-body { + padding: 1.5rem; + } + + .modal-footer { + padding: 0 1.5rem 1.5rem; + flex-direction: column; + gap: 0.75rem; + } + + .modal-footer::before { + left: 1.5rem; + right: 1.5rem; + } + + .modal-footer .btn { + width: 100%; + justify-content: center; + } + + .custom-label-container { + flex-direction: column; + gap: 0.75rem; + } + + .custom-label-container .btn { + width: 100%; + min-width: auto; + } +} + /* Responsive Design */ .mobile-only { display: none; From 96748f76193a79aecae8d501fd92d91eeb5c55c6 Mon Sep 17 00:00:00 2001 From: justFadel19 Date: Sun, 15 Jun 2025 10:23:57 +0300 Subject: [PATCH 2/2] Add favicon logo to web application - Added logo.png as favicon in HTML head tag - Copied logo.png from docs to web directory for proper access - Favicon will now display in browser tabs and bookmarks --- web/index.html | 9 ++++----- web/logo.png | Bin 0 -> 1107430 bytes 2 files changed, 4 insertions(+), 5 deletions(-) create mode 100644 web/logo.png diff --git a/web/index.html b/web/index.html index 6ff504e..5f78a8b 100644 --- a/web/index.html +++ b/web/index.html @@ -1,9 +1,9 @@ - - + SAT Annotator - Satellite Image Annotation Tool + @@ -12,13 +12,12 @@
- -
+
+