From 1f20c11ff975491475b966ad49ae3bcad06dde8c Mon Sep 17 00:00:00 2001 From: Tom Kazimiers Date: Wed, 18 Dec 2024 16:00:39 +0100 Subject: [PATCH] Review widget: improve handling of node deletions When a node is deleted in a skeleton under review, the Review Widget will now refresh the segment respresentation and move on to the next possible node in the segment. Also, a warning is now shown if a node can't be found on the server when marking it reviewed, assuming it might be deleted. This, however, should usually not be visible, because the segments should have up-to-date information. Fixes catmaid/catmaid#2270 --- CHANGELOG.md | 6 +++ .../catmaid/static/js/widgets/review.js | 40 ++++++++++++++++--- .../static/libs/catmaid/models/nodes.js | 2 +- 3 files changed, 42 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 99a3532566..a6b272a0f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -114,6 +114,12 @@ Measurement widget: number easier, a percentage is shown as well for each entry that reflects how many nodes of the respective skeleton have a radius assigned. +Review widget: + +- Deleting a treenode when the skeleton is under review in the Review Widget + will cause the Review Widget to refresh its segment representation and move to + the next possible node. + Miscellaneous: - The built-in API docs (/apis endpoint) now supports deep links, which allows diff --git a/django/applications/catmaid/static/js/widgets/review.js b/django/applications/catmaid/static/js/widgets/review.js index 59ffb4b33d..877f75aad8 100644 --- a/django/applications/catmaid/static/js/widgets/review.js +++ b/django/applications/catmaid/static/js/widgets/review.js @@ -101,6 +101,20 @@ else throw new CATMAID.ValueError('Unknown reference orientation'); }; + /** + * If a node of a reviewed skeleton is deleted, recomputed the review state. + */ + this.handleDeletedSkeleton = function(nodeId, parentId, skeletonId) { + if (skeletonId == this.currentSkeletonId) { + CATMAID.info('Review widget updated due to skeleton change'); + const atnId = SkeletonAnnotations.getActiveNodeId(); + this.startSkeletonToReview(skeletonId, undefined, true, () => { + this.current_segment = this.skeleton_segments[this.current_segment.id]; + this.goToNodeIndexOfSegmentSequence(this.current_segment_index, true); + }); + } + }; + /** * If the active skeleton changes, the review system will register it. The * widget will make sure the view is centered at the last active node, when @@ -674,6 +688,14 @@ updateClientNodeReview(node, json.reviewer_id, json.review_time); resolve(node); } + }, + false, + false, + (error) => { + if (error instanceof CATMAID.ResourceUnavailableError) { + CATMAID.warn('The reviewed node seems to be deleted'); + return true; + } }); } else { updateClientNodeReview(node, CATMAID.session.userid, new Date().toISOString()); @@ -1050,7 +1072,7 @@ this.startSkeletonToReview( skid, subarborNodeId, true ); }; - this.startSkeletonToReview = function( skid, nodeId, forceRefresh ) { + this.startSkeletonToReview = function( skid, nodeId, forceRefresh, postUpdate ) { var dataChanged = false; if (!skid) { CATMAID.error('No skeleton ID provided for review.'); @@ -1065,15 +1087,15 @@ return; } if (dataChanged || forceRefresh) { - this.refresh(); + this.refresh(postUpdate); } }; - this.refresh = function() { + this.refresh = function(postUpdate) { if (this.filterRules.length > 0 && this.applyFilterRules) { this.updateFilter(); } else { - this.update(); + this.update(postUpdate); } }; @@ -1157,6 +1179,8 @@ // Register to the active node change event SkeletonAnnotations.on(SkeletonAnnotations.EVENT_ACTIVE_NODE_CHANGED, this.handleActiveNodeChange, this); + CATMAID.Nodes.on(CATMAID.Nodes.EVENT_NODE_DELETED, + this.handleDeletedSkeleton, this); }; CATMAID.ReviewSystem.prototype = new InstanceRegistry(); @@ -1696,7 +1720,7 @@ } }; - CATMAID.ReviewSystem.prototype.update = function() { + CATMAID.ReviewSystem.prototype.update = function(postUpdate) { if (this.currentSkeletonId) { var url = `${this.projectId}/skeletons/${this.currentSkeletonId}/review`; var self = this; @@ -1704,6 +1728,9 @@ function(skeleton_data) { self.createReviewSkeletonTable(skeleton_data); self.redraw(); + if (CATMAID.tools.isFn(postUpdate)) { + postUpdate(); + } }); } else { this.redraw(); @@ -1913,6 +1940,9 @@ CATMAID.ReviewSystem.prototype.destroy = function() { SkeletonAnnotations.off(SkeletonAnnotations.EVENT_ACTIVE_NODE_CHANGED, this.handleActiveNodeChange, this); + CATMAID.Nodes.off(CATMAID.Nods.EVENT_NODE_DELETED, + this.handleDeletedSkeleton, this); + if (lastFocused === this) { lastFocused = null; } diff --git a/django/applications/catmaid/static/libs/catmaid/models/nodes.js b/django/applications/catmaid/static/libs/catmaid/models/nodes.js index 26a84c1d8b..22decc1ee6 100644 --- a/django/applications/catmaid/static/libs/catmaid/models/nodes.js +++ b/django/applications/catmaid/static/libs/catmaid/models/nodes.js @@ -348,7 +348,7 @@ }) .then(function(result) { CATMAID.Nodes.trigger(CATMAID.Nodes.EVENT_NODE_DELETED, nodeId, - result.parent_id); + result.parent_id, result.skeleton_id); // Emit deletion event, if the last node was removed and the neuron // deleted. Otherwise, trigger a change event for the neuron. if (result.deleted_neuron) {