diff --git a/client/dive-common/components/TrackSettingsPanel.vue b/client/dive-common/components/TrackSettingsPanel.vue
index 71b1ec1b2..40a1291c7 100644
--- a/client/dive-common/components/TrackSettingsPanel.vue
+++ b/client/dive-common/components/TrackSettingsPanel.vue
@@ -33,6 +33,7 @@ export default defineComponent({
filterTracksByFrame: 'Filter the track list by those with detections in the current frame',
autoZoom: 'Automatically zoom to the track when selected',
showMultiCamToolbar: 'Show multi-camera tools in the top toolbar when a track is selected',
+ autoSave: 'Automatically save annotation changes after a short delay. Changes are batched to reduce server requests.',
});
const modes = ref(['Track', 'Detection']);
// Add unknown as the default type to the typeList
@@ -362,6 +363,45 @@ export default defineComponent({
+
+
+ Auto-Save Settings
+
+
+
+
+
+
+
+
+
+ mdi-help
+
+
+ {{ help.autoSave }}
+
+
+
diff --git a/client/dive-common/components/Viewer.vue b/client/dive-common/components/Viewer.vue
index b3da641be..676e61105 100644
--- a/client/dive-common/components/Viewer.vue
+++ b/client/dive-common/components/Viewer.vue
@@ -412,10 +412,11 @@ export default defineComponent({
handler.stopLinking();
}
});
- async function save(setVal?: string) {
- // If editing the track, disable editing mode before save
+ async function save(setVal?: string, exitEditingMode = false) {
+ // Only exit editing mode if explicitly requested (e.g., manual save)
+ // Auto-save should NOT disrupt the user's editing session
saveInProgress.value = true;
- if (editingTrack.value) {
+ if (exitEditingMode && editingTrack.value) {
handler.trackSelect(selectedTrackId.value, false);
}
const saveSet = setVal === 'default' ? undefined : setVal;
@@ -467,6 +468,32 @@ export default defineComponent({
watch(imageEnhancements, debouncedSaveImageEnhancements, { deep: true });
+ // Auto-save annotations when enabled
+ const debouncedAutoSave = debounce(
+ async () => {
+ if (readonlyState.value) return;
+ if (pendingSaveCount.value === 0) return;
+ if (saveInProgress.value) return;
+ await save(props.currentSet);
+ },
+ 2000,
+ { trailing: true, leading: false },
+ );
+
+ watch(
+ pendingSaveCount,
+ (newCount, oldCount) => {
+ if (
+ clientSettings.autoSaveSettings.enabled
+ && newCount > oldCount
+ && newCount > 0
+ && !readonlyState.value
+ ) {
+ debouncedAutoSave();
+ }
+ },
+ );
+
// Navigation Guards used by parent component
async function warnBrowserExit(event: BeforeUnloadEvent) {
if (pendingSaveCount.value === 0) return;
@@ -766,6 +793,7 @@ export default defineComponent({
handleResize();
});
onBeforeUnmount(() => {
+ debouncedAutoSave.cancel();
if (controlsRef.value) observer.unobserve(controlsRef.value.$el);
});
@@ -1081,7 +1109,7 @@ export default defineComponent({
@click="save(currentSet)"
>
- mdi-content-save
+ {{ clientSettings.autoSaveSettings.enabled ? 'mdi-content-save-cog' : 'mdi-content-save' }}
diff --git a/client/dive-common/store/settings.ts b/client/dive-common/store/settings.ts
index adab2e24f..9a80ed72c 100644
--- a/client/dive-common/store/settings.ts
+++ b/client/dive-common/store/settings.ts
@@ -48,6 +48,9 @@ interface AnnotationSettings {
multiCamSettings: {
showToolbar: boolean;
};
+ autoSaveSettings: {
+ enabled: boolean;
+ };
}
const defaultSettings: AnnotationSettings = {
@@ -105,6 +108,9 @@ const defaultSettings: AnnotationSettings = {
multiCamSettings: {
showToolbar: true,
},
+ autoSaveSettings: {
+ enabled: false, // Disabled by default for backward compatibility
+ },
};
// Utility to safely load from localStorage
diff --git a/client/platform/desktop/frontend/components/Settings.vue b/client/platform/desktop/frontend/components/Settings.vue
index 93ce64c5e..b3b40736c 100644
--- a/client/platform/desktop/frontend/components/Settings.vue
+++ b/client/platform/desktop/frontend/components/Settings.vue
@@ -196,6 +196,18 @@ export default defineComponent({
/>
+
+
+
+
+
Platform support
diff --git a/client/platform/web-girder/views/Settings.vue b/client/platform/web-girder/views/Settings.vue
index 957fd7337..9a594f4f0 100644
--- a/client/platform/web-girder/views/Settings.vue
+++ b/client/platform/web-girder/views/Settings.vue
@@ -1,8 +1,14 @@
@@ -10,8 +16,34 @@ export default Vue.extend({
-
+ Annotation Settings
+
+
+
+
+
+
+
+
+
+
+
+