From 5329784f7bbe4b85f53cadd132df10c0df0dfa1a Mon Sep 17 00:00:00 2001 From: Udhul <126940798+Udhul@users.noreply.github.com> Date: Fri, 27 Jun 2025 18:02:04 +0200 Subject: [PATCH 1/8] Improve layer mask workflow --- app/frontend/static/js/canvasController.js | 9 +++++---- app/frontend/static/js/main.js | 7 +++++-- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/app/frontend/static/js/canvasController.js b/app/frontend/static/js/canvasController.js index 5523727..631ba40 100644 --- a/app/frontend/static/js/canvasController.js +++ b/app/frontend/static/js/canvasController.js @@ -521,7 +521,8 @@ class CanvasManager { const color = (this.editingLayerId && l.layerId === this.editingLayerId) ? this.editingColor : l.color; - if (mask) this._drawBinaryMask(mask, color, op); + const hatch = !(this.editingLayerId && l.layerId === this.editingLayerId); + if (mask) this._drawBinaryMask(mask, color, op, hatch); }); } @@ -893,7 +894,7 @@ class CanvasManager { } } - _drawBinaryMask(maskData, colorStr, opacity = 1.0) { + _drawBinaryMask(maskData, colorStr, opacity = 1.0, hatch = true) { if (!maskData || !maskData.length || !maskData[0].length) return; const maskHeight = maskData.length; const maskWidth = maskData[0].length; @@ -909,7 +910,7 @@ class CanvasManager { const [r, g, b, a_int] = this._parseRgbaFromString(colorStr); const finalAlpha = Math.round(Math.min(1, Math.max(0, opacity)) * a_int); - const spacing = 6; // pixel spacing between hatch lines + const spacing = 4; // pixel spacing between hatch lines (tighter pattern) const lineWidth = 2; // hatch line thickness const isBorder = (mx, my) => { @@ -926,7 +927,7 @@ class CanvasManager { if (!maskData[y][x]) continue; const idx = (y * maskWidth + x) * 4; const border = isBorder(x, y); - const drawPixel = border || ((x + y) % spacing < lineWidth); + const drawPixel = border || !hatch || ((x + y) % spacing < lineWidth); if (drawPixel) { pixelData[idx] = r; pixelData[idx + 1] = g; diff --git a/app/frontend/static/js/main.js b/app/frontend/static/js/main.js index b26fc44..e7a2f31 100644 --- a/app/frontend/static/js/main.js +++ b/app/frontend/static/js/main.js @@ -482,7 +482,7 @@ document.addEventListener("DOMContentLoaded", () => { // == ImagePoolHandler Events == document.addEventListener("active-image-set", async (event) => { - const { + const { imageHash, filename, width, @@ -491,6 +491,9 @@ document.addEventListener("DOMContentLoaded", () => { existingMasks, status, } = event.detail; + canvasManager.clearAllCanvasInputs(true); + canvasManager.setManualPredictions(null); + canvasManager.setAutomaskPredictions(null); uiManager.showGlobalStatus( `Loading image '${utils.escapeHTML(filename)}' for annotation...`, "loading", @@ -1125,7 +1128,7 @@ document.addEventListener("DOMContentLoaded", () => { canvasManager.clearAllCanvasInputs(false); canvasManager.setManualPredictions(null); canvasManager.setAutomaskPredictions(null); - canvasManager.setMode("edit"); + canvasManager.setMode("creation"); } if (commitMasksBtn && !commitMasksBtn.dataset.listenerAttached) { From 7e07350815b0be0231049d5a989ca207fb344320 Mon Sep 17 00:00:00 2001 From: Udhul <126940798+Udhul@users.noreply.github.com> Date: Fri, 27 Jun 2025 18:24:31 +0200 Subject: [PATCH 2/8] feat: auto-save mask edits --- app/frontend/static/css/canvas.css | 12 +- app/frontend/static/js/canvasController.js | 181 +++++++++++-------- app/frontend/static/js/editModeController.js | 35 ++-- app/frontend/static/js/main.js | 92 +++++----- app/frontend/templates/index.html | 3 +- docs/annotation_workflow_progress.md | 9 +- docs/annotation_workflow_specification.md | 5 +- 7 files changed, 189 insertions(+), 148 deletions(-) diff --git a/app/frontend/static/css/canvas.css b/app/frontend/static/css/canvas.css index 963887b..69ec3a6 100644 --- a/app/frontend/static/css/canvas.css +++ b/app/frontend/static/css/canvas.css @@ -266,12 +266,12 @@ #save-canvas-png-btn:hover:not(:disabled) { background-color: #0056b3; } -#commit-masks-btn, #edit-save-btn{ +#commit-masks-btn{ background-color: #28a745; padding: 8px 16px; font-size: 15px; } /* Green for commit */ -#commit-masks-btn:hover:not(:disabled), #edit-save-btn:hover:not(:disabled) { +#commit-masks-btn:hover:not(:disabled) { background-color: #218838; } #edit-undo-btn, #edit-redo-btn { @@ -282,7 +282,7 @@ } /* Clear Inputs Button */ -#clear-inputs-btn, #edit-cancel-btn { +#clear-inputs-btn, #edit-discard-btn { background: linear-gradient(135deg, #ff7300, #ffa500); border: none; padding: 8px 16px; @@ -293,12 +293,12 @@ transition: all 0.3s ease; box-shadow: 0 2px 4px rgba(255, 140, 0, 0.2); } -#clear-inputs-btn:hover:not(:disabled), #edit-cancel-btn:hover:not(:disabled) { +#clear-inputs-btn:hover:not(:disabled), #edit-discard-btn:hover:not(:disabled) { background: linear-gradient(135deg, #e67e00, #cb7600); box-shadow: 0 4px 8px rgba(255, 140, 0, 0.3); transform: translateY(-1px); } -#clear-inputs-btn:disabled, #edit-cancel-btn:disabled { +#clear-inputs-btn:disabled, #edit-discard-btn:disabled { background: #ccc; color: #888; box-shadow: none; @@ -306,7 +306,7 @@ cursor: not-allowed; } -#clear-inputs-btn #edit-cancel-btn{ +#clear-inputs-btn #edit-discard-btn{ width: 100%; margin-top: 10px; } diff --git a/app/frontend/static/js/canvasController.js b/app/frontend/static/js/canvasController.js index 631ba40..2b53859 100644 --- a/app/frontend/static/js/canvasController.js +++ b/app/frontend/static/js/canvasController.js @@ -116,9 +116,7 @@ class CanvasManager { this.selectedLayerIds = []; this.mode = 'edit'; // 'creation', 'edit', 'review' - this.editingLayerId = null; - this.editingMask = null; - this.editingColor = '#ff0000'; + this.editingLayers = {}; // { layerId: { mask, color } } this.editHistory = []; this.editHistoryIndex = -1; @@ -515,13 +513,10 @@ class CanvasManager { if (this.mode === 'edit' && this.selectedLayerIds.length > 0) { op = this.selectedLayerIds.includes(l.layerId) ? 1.0 : FADED_MASK_OPACITY; } - const mask = (this.editingLayerId && l.layerId === this.editingLayerId && this.editingMask) - ? this.editingMask - : l.maskData; - const color = (this.editingLayerId && l.layerId === this.editingLayerId) - ? this.editingColor - : l.color; - const hatch = !(this.editingLayerId && l.layerId === this.editingLayerId); + const isEditing = this.editingLayers.hasOwnProperty(l.layerId); + const mask = isEditing ? this.editingLayers[l.layerId].mask : l.maskData; + const color = isEditing ? this.editingLayers[l.layerId].color : l.color; + const hatch = !isEditing; if (mask) this._drawBinaryMask(mask, color, op, hatch); }); } @@ -1101,51 +1096,62 @@ class CanvasManager { this.drawPredictionMaskLayer(); } - startMaskEdit(layerId, maskData, color) { - this.editingLayerId = layerId; - this.editingColor = color || '#ff0000'; - if (Array.isArray(maskData) && Array.isArray(maskData[0])) { - this.editingMask = maskData.map(r => Array.from(r)); - } else if (maskData && maskData.counts && maskData.size) { - const converted = this.Utils.rleToBinaryMask( - maskData, - this.originalImageHeight, - this.originalImageWidth - ); - this.editingMask = converted || this._createEmptyMask(); - } else { - this.editingMask = this._createEmptyMask(); - } - this.editHistory = [this.getEditedMask()]; + startMaskEdit(layers) { + this.editingLayers = {}; + if (!Array.isArray(layers)) layers = []; + layers.forEach(layer => { + let mask; + if (Array.isArray(layer.maskData) && Array.isArray(layer.maskData[0])) { + mask = layer.maskData.map(r => Array.from(r)); + } else if (layer.maskData && layer.maskData.counts && layer.maskData.size) { + mask = this.Utils.rleToBinaryMask( + layer.maskData, + this.originalImageHeight, + this.originalImageWidth + ); + mask = mask || this._createEmptyMask(); + } else { + mask = this._createEmptyMask(); + } + this.editingLayers[layer.layerId] = { mask, color: layer.displayColor || layer.color || '#ff0000' }; + }); + this.editHistory = [this.getEditedMasks()]; this.editHistoryIndex = 0; this.drawPredictionMaskLayer(); } applyBrush(x, y, radius, add = true) { - if (!this.editingMask) return; - const h = this.editingMask.length; - const w = this.editingMask[0].length; + const layerIds = Object.keys(this.editingLayers); + if (layerIds.length === 0) return; const cx = Math.round(x); const cy = Math.round(y); - for (let j = -radius; j <= radius; j++) { - for (let i = -radius; i <= radius; i++) { - if (i*i + j*j <= radius*radius) { - const nx = cx + i; - const ny = cy + j; - if (nx >=0 && ny >=0 && nx < w && ny < h) { - this.editingMask[ny][nx] = add ? 1 : 0; + layerIds.forEach(id => { + const mask = this.editingLayers[id].mask; + const h = mask.length; + const w = mask[0].length; + for (let j = -radius; j <= radius; j++) { + for (let i = -radius; i <= radius; i++) { + if (i * i + j * j <= radius * radius) { + const nx = cx + i; + const ny = cy + j; + if (nx >= 0 && ny >= 0 && nx < w && ny < h) { + mask[ny][nx] = add ? 1 : 0; + } } } } - } + }); this.drawPredictionMaskLayer(); } applyLasso(points, add = true) { - if (!this.editingMask || !points || points.length < 3) return; - const h = this.editingMask.length; - const w = this.editingMask[0].length; - let minX = w, minY = h, maxX = 0, maxY = 0; + const layerIds = Object.keys(this.editingLayers); + if (layerIds.length === 0 || !points || points.length < 3) return; + let minX, minY, maxX, maxY; + const anyMask = this.editingLayers[layerIds[0]].mask; + const h = anyMask.length; + const w = anyMask[0].length; + minX = w; minY = h; maxX = 0; maxY = 0; points.forEach(p => { if (p.x < minX) minX = Math.floor(p.x); if (p.x > maxX) maxX = Math.ceil(p.x); @@ -1154,13 +1160,16 @@ class CanvasManager { }); minX = Math.max(0, minX); minY = Math.max(0, minY); maxX = Math.min(w - 1, maxX); maxY = Math.min(h - 1, maxY); - for (let y = minY; y <= maxY; y++) { - for (let x = minX; x <= maxX; x++) { - if (this._isPointInPolygon({x, y}, points)) { - this.editingMask[y][x] = add ? 1 : 0; + layerIds.forEach(id => { + const mask = this.editingLayers[id].mask; + for (let y = minY; y <= maxY; y++) { + for (let x = minX; x <= maxX; x++) { + if (this._isPointInPolygon({x, y}, points)) { + mask[y][x] = add ? 1 : 0; + } } } - } + }); this.drawPredictionMaskLayer(); } @@ -1189,19 +1198,25 @@ class CanvasManager { } commitHistoryStep() { - if (!this.editingMask) return; + const layerIds = Object.keys(this.editingLayers); + if (layerIds.length === 0) return; if (this.editHistoryIndex < this.editHistory.length - 1) { this.editHistory = this.editHistory.slice(0, this.editHistoryIndex + 1); } - this.editHistory.push(this.getEditedMask()); + this.editHistory.push(this.getEditedMasks()); this.editHistoryIndex = this.editHistory.length - 1; } undoEdit() { if (this.editHistoryIndex > 0) { this.editHistoryIndex -= 1; - const mask = this.editHistory[this.editHistoryIndex]; - this.editingMask = mask.map(r => [...r]); + const snapshot = this.editHistory[this.editHistoryIndex]; + for (const id of Object.keys(snapshot)) { + if (this.editingLayers[id]) { + const mask = snapshot[id]; + this.editingLayers[id].mask = mask.map(r => [...r]); + } + } this.drawPredictionMaskLayer(); } } @@ -1209,41 +1224,60 @@ class CanvasManager { redoEdit() { if (this.editHistoryIndex < this.editHistory.length - 1) { this.editHistoryIndex += 1; - const mask = this.editHistory[this.editHistoryIndex]; - this.editingMask = mask.map(r => [...r]); + const snapshot = this.editHistory[this.editHistoryIndex]; + for (const id of Object.keys(snapshot)) { + if (this.editingLayers[id]) { + const mask = snapshot[id]; + this.editingLayers[id].mask = mask.map(r => [...r]); + } + } this.drawPredictionMaskLayer(); } } growEditingMask() { - if (!this.editingMask) return; - this.editingMask = this._dilateMask(this.editingMask); + const layerIds = Object.keys(this.editingLayers); + if (layerIds.length === 0) return; + layerIds.forEach(id => { + this.editingLayers[id].mask = this._dilateMask(this.editingLayers[id].mask); + }); this.drawPredictionMaskLayer(); } shrinkEditingMask() { - if (!this.editingMask) return; - this.editingMask = this._erodeMask(this.editingMask); + const layerIds = Object.keys(this.editingLayers); + if (layerIds.length === 0) return; + layerIds.forEach(id => { + this.editingLayers[id].mask = this._erodeMask(this.editingLayers[id].mask); + }); this.drawPredictionMaskLayer(); } smoothEditingMask() { - if (!this.editingMask) return; - let mask = this._dilateMask(this._erodeMask(this.editingMask)); - mask = this._erodeMask(this._dilateMask(mask)); - mask = this._smoothMask(mask); - this.editingMask = mask; + const layerIds = Object.keys(this.editingLayers); + if (layerIds.length === 0) return; + layerIds.forEach(id => { + let mask = this.editingLayers[id].mask; + mask = this._dilateMask(this._erodeMask(mask)); + mask = this._erodeMask(this._dilateMask(mask)); + mask = this._smoothMask(mask); + this.editingLayers[id].mask = mask; + }); this.drawPredictionMaskLayer(); } invertEditingMask() { - if (!this.editingMask) return; - const h = this.editingMask.length; const w = this.editingMask[0].length; - for (let y = 0; y < h; y++) { - for (let x = 0; x < w; x++) { - this.editingMask[y][x] = this.editingMask[y][x] ? 0 : 1; + const layerIds = Object.keys(this.editingLayers); + if (layerIds.length === 0) return; + layerIds.forEach(id => { + const mask = this.editingLayers[id].mask; + const h = mask.length; const w = mask[0].length; + for (let y = 0; y < h; y++) { + for (let x = 0; x < w; x++) { + mask[y][x] = mask[y][x] ? 0 : 1; + } } - } + }); this.drawPredictionMaskLayer(); } @@ -1304,13 +1338,18 @@ class CanvasManager { return out; } - getEditedMask() { - return this.editingMask ? this.editingMask.map(r => [...r]) : null; + getEditedMasks() { + const out = {}; + for (const [id, obj] of Object.entries(this.editingLayers)) { + out[id] = obj.mask.map(r => [...r]); + } + return out; } finishMaskEdit() { - this.editingLayerId = null; - this.editingMask = null; + this.editingLayers = {}; + this.editHistory = []; + this.editHistoryIndex = -1; this.drawPredictionMaskLayer(); } diff --git a/app/frontend/static/js/editModeController.js b/app/frontend/static/js/editModeController.js index 6e71d92..4c10f19 100644 --- a/app/frontend/static/js/editModeController.js +++ b/app/frontend/static/js/editModeController.js @@ -11,7 +11,7 @@ class EditModeController { this.stateManager = stateManager; this.apiClient = apiClient; this.utils = utils; - this.activeLayer = null; + this.activeLayers = []; this.brushSize = 10; this.isDrawing = false; this.currentTool = 'brush'; // 'brush' or 'lasso' @@ -33,8 +33,7 @@ class EditModeController { this.invertBtn = document.getElementById('edit-invert-btn'); this.undoBtn = document.getElementById('edit-undo-btn'); this.redoBtn = document.getElementById('edit-redo-btn'); - this.saveBtn = document.getElementById('edit-save-btn'); - this.cancelBtn = document.getElementById('edit-cancel-btn'); + this.discardBtn = document.getElementById('edit-discard-btn'); this.previewEl = document.getElementById('brush-preview'); } @@ -51,8 +50,7 @@ class EditModeController { if (this.invertBtn) this.invertBtn.addEventListener('click', () => this.actionInvert()); if (this.undoBtn) this.undoBtn.addEventListener('click', () => this.actionUndo()); if (this.redoBtn) this.redoBtn.addEventListener('click', () => this.actionRedo()); - if (this.saveBtn) this.saveBtn.addEventListener('click', () => this.save()); - if (this.cancelBtn) this.cancelBtn.addEventListener('click', () => this.cancel()); + if (this.discardBtn) this.discardBtn.addEventListener('click', () => this.discard()); const canvas = this.canvasManager.userInputCanvas; if (canvas) { canvas.addEventListener('mousedown', (e) => this.onMouseDown(e)); @@ -71,17 +69,17 @@ class EditModeController { this.previewEl.style.height = `${r}px`; } - beginEdit(layer) { - if (!layer) return; - this.activeLayer = layer; - this.canvasManager.startMaskEdit(layer.layerId, layer.maskData, layer.displayColor); + beginEdit(layers) { + if (!Array.isArray(layers) || layers.length === 0) return; + this.activeLayers = layers; + this.canvasManager.startMaskEdit(layers); this.showControls(true); this.selectTool('brush'); this.updatePreviewSize(); } endEdit() { - this.activeLayer = null; + this.activeLayers = []; this.showControls(false); this.canvasManager.finishMaskEdit(); if (this.previewEl) this.previewEl.style.display = 'none'; @@ -95,7 +93,7 @@ class EditModeController { } onMouseDown(e) { - if (!this.activeLayer) return; + if (!this.activeLayers || this.activeLayers.length === 0) return; this.isDrawing = true; if (this.currentTool === 'brush' && this.previewEl) { this.previewEl.style.position = 'fixed'; @@ -116,7 +114,7 @@ class EditModeController { onMouseMove(e) { - if (!this.activeLayer) return; + if (!this.activeLayers || this.activeLayers.length === 0) return; if (this.currentTool === 'brush' && this.previewEl) { this.previewEl.style.position = 'fixed'; this.previewEl.style.left = `${e.clientX}px`; @@ -156,7 +154,7 @@ class EditModeController { this.currentTool = tool; if (this.brushBtn) this.brushBtn.classList.toggle('active', tool === 'brush'); if (this.lassoBtn) this.lassoBtn.classList.toggle('active', tool === 'lasso'); - if (this.previewEl) this.previewEl.style.display = tool === 'brush' && this.activeLayer ? 'block' : 'none'; + if (this.previewEl) this.previewEl.style.display = tool === 'brush' && this.activeLayers && this.activeLayers.length > 0 ? 'block' : 'none'; this.canvasManager.clearLassoPreview(); } @@ -188,15 +186,8 @@ class EditModeController { this.canvasManager.redoEdit(); } - save() { - if (!this.activeLayer) return; - const edited = this.canvasManager.getEditedMask(); - this.utils.dispatchCustomEvent('edit-save', { layerId: this.activeLayer.layerId, maskData: edited }); - this.endEdit(); - } - - cancel() { - this.utils.dispatchCustomEvent('edit-cancel', {}); + discard() { + this.utils.dispatchCustomEvent('edit-discard', {}); this.endEdit(); } } diff --git a/app/frontend/static/js/main.js b/app/frontend/static/js/main.js index e7a2f31..a3f705f 100644 --- a/app/frontend/static/js/main.js +++ b/app/frontend/static/js/main.js @@ -164,6 +164,33 @@ document.addEventListener("DOMContentLoaded", () => { let reviewHistoryIndex = -1; let navigatingHistory = false; let suppressStatusChangeEvents = false; + async function autoSaveCurrentEdits() { + if (!activeImageState || !editModeController) return; + const masks = canvasManager.getEditedMasks(); + const projectId = stateManager.getActiveProjectId(); + const ih = activeImageState.imageHash; + for (const [layerId, mask] of Object.entries(masks)) { + const layer = activeImageState.layers.find((l) => l.layerId === layerId); + if (!layer || !mask) continue; + layer.maskData = mask; + layer.status = "edited"; + if (projectId && ih) { + const rle = utils.binaryMaskToRLE(mask); + try { + await apiClient.updateMaskLayer(projectId, ih, layerId, { + mask_data_rle: rle, + status: "edited", + }); + } catch (err) { + uiManager.showGlobalStatus( + `Save edit failed: ${utils.escapeHTML(err.message)}`, + "error", + ); + } + } + onImageDataChange("layer-modified", { layerId }); + } + } function deriveStatusFromLayers() { if (!activeImageState) return "unprocessed"; @@ -1143,11 +1170,18 @@ document.addEventListener("DOMContentLoaded", () => { // Export button now handled by ExportDialog - document.addEventListener("layers-selected", (event) => { + document.addEventListener("layers-selected", async (event) => { if (!activeImageState) return; const ids = Array.isArray(event.detail.layerIds) ? event.detail.layerIds : []; + const prevIds = editModeController && editModeController.activeLayers + ? editModeController.activeLayers.map((l) => l.layerId) + : []; + if (prevIds.length > 0 && JSON.stringify(prevIds) !== JSON.stringify(ids)) { + await autoSaveCurrentEdits(); + if (editModeController) editModeController.endEdit(); + } canvasManager.clearAllCanvasInputs(false); if (ids.length === 0) { canvasManager.setMode("creation"); @@ -1156,9 +1190,11 @@ document.addEventListener("DOMContentLoaded", () => { } canvasManager.setLayers(activeImageState.layers); if (editModeController) { - if (ids.length === 1) { - const layer = activeImageState.layers.find((l) => l.layerId === ids[0]); - editModeController.beginEdit(layer); + if (ids.length > 0) { + const layers = ids + .map((id) => activeImageState.layers.find((l) => l.layerId === id)) + .filter(Boolean); + editModeController.beginEdit(layers); utils.hideElement(creationActions); utils.showElement(editActions, "flex"); updateHelpTooltipForMode("edit"); @@ -1331,40 +1367,8 @@ document.addEventListener("DOMContentLoaded", () => { } }); - document.addEventListener("edit-save", (event) => { - if (!activeImageState) return; - const layer = activeImageState.layers.find( - (l) => l.layerId === event.detail.layerId, - ); - if (layer && event.detail.maskData) { - layer.maskData = event.detail.maskData; - layer.status = "edited"; - const pid = stateManager.getActiveProjectId(); - const ih = activeImageState.imageHash; - if (pid && ih) { - const rle = utils.binaryMaskToRLE(layer.maskData); - apiClient - .updateMaskLayer(pid, ih, layer.layerId, { - mask_data_rle: rle, - status: "edited", - }) - .catch((err) => { - uiManager.showGlobalStatus( - `Save edit failed: ${utils.escapeHTML(err.message)}`, - "error", - ); - }); - } - onImageDataChange("layer-modified", { layerId: layer.layerId }); - } - if (layerViewController) layerViewController.setSelectedLayers([]); - canvasManager.setMode("creation"); - utils.showElement(creationActions, "flex"); - utils.hideElement(editActions); - if (!reviewMode) updateHelpTooltipForMode("creation"); - }); - document.addEventListener("edit-cancel", () => { + document.addEventListener("edit-discard", () => { if (activeImageState) { canvasManager.setLayers(activeImageState.layers); } @@ -1381,7 +1385,7 @@ document.addEventListener("DOMContentLoaded", () => { } }); - document.addEventListener("active-image-set", (event) => { + document.addEventListener("active-image-set", async (event) => { if ( activeImageState && activeImageState.imageHash === event.detail.imageHash @@ -1389,13 +1393,19 @@ document.addEventListener("DOMContentLoaded", () => { activeImageState.status = event.detail.status || "unprocessed"; } onImageDataChange("image-loaded", { imageHash: event.detail.imageHash }); - if (editModeController) editModeController.endEdit(); + if (editModeController && editModeController.activeLayers.length > 0) { + await autoSaveCurrentEdits(); + editModeController.endEdit(); + } canvasManager.setMode("creation"); }); - document.addEventListener("active-image-cleared", () => { + document.addEventListener("active-image-cleared", async () => { + if (editModeController && editModeController.activeLayers.length > 0) { + await autoSaveCurrentEdits(); + editModeController.endEdit(); + } activeImageState = null; - if (editModeController) editModeController.endEdit(); canvasManager.setMode("creation"); updateStatusToggleUI("unprocessed", false); }); diff --git a/app/frontend/templates/index.html b/app/frontend/templates/index.html index b6f3fe3..93039e5 100644 --- a/app/frontend/templates/index.html +++ b/app/frontend/templates/index.html @@ -297,8 +297,7 @@

Export Annotations