Conversation
|
Architectural sketch of what I implemented:
|
|
@mzur It's working now, but the architecture is not really consistent (mixin/options API for lawnmower, composable for volare). I managed to move the volare-logic from the AnnotatorContainer into the composable and would like to do the same for the lawnmower, but would first like to get some feedback from you regarding the overall structure. I'll mark it ready for review anyways. Edit: Oh, and also I've run out of AI credits, so I can't use copilot for reviews right now. Since the switch to the new credits-based pricing model a single larger AI code review uses up about 25% of my $2.00 worth of free credits. Edit 2: I also noticed a bug in the original behaviour that is still present: Focus annotation in volare, filter annotations that keeps the focussed annotation on the screen, remove filter -> wrong annotation is focussed. I will resume development once I get your feedback regarding the structure, though. |
|
I'll skip the review until we finished the discussion in the issue. Let me know if I should request an AI review for you here. |
|
@mzur I don't think volare should be paused when the viewport is changed, that would likely break workflows where users zoom in while in volare. Otherwise this is as we discussed. Let me know what you think about the volare.js structure and I will turn |
|
Do you need/want an AI review first? |
|
@mzur Sure, but I'd also like feedback from you on the composable structure overall. I will use that feedback for the upcoming lawnmower composable. |
There was a problem hiding this comment.
Pull request overview
This PR adds pause/resume behavior to the annotation “cycling” modes (Lawnmower and Volume Label Review/Volare), including persisting/restoring state so users can zoom/pan to inspect details without the modes jumping unpredictably.
Changes:
- Replace simple on/off toggles with a Play/Pause UI for Volare and Lawnmower, including “paused at” timestamps.
- Persist/restore Lawnmower (viewport/section) and Volare (focused annotation) state via
localStorageacross pausing and page reloads. - Add plumbing/events between
annotationCanvasandannotatorContainerto auto-pause Lawnmower on user viewport changes while avoiding pauses during programmatic moves.
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| resources/views/annotations/show/tabs/annotationModes.blade.php | Switch Volare/Lawnmower controls to the new Play/Pause component and display pause timestamps. |
| resources/views/annotations/show.blade.php | Wire new lawnmower save-state prop and new canvas events for pause/restore coordination. |
| resources/assets/sass/annotations/sidebar/_main.scss | Add styling for the new “paused at” timestamp text. |
| resources/assets/js/utils.js | Add required() helper for composable argument validation and export it. |
| resources/assets/js/core/components/powerToggle.vue | Add separate iconOn/iconOff props and switch icon based on active state. |
| resources/assets/js/core/components/playPause.vue | Introduce a new Play/Pause button pair component and PlayPauseState. |
| resources/assets/js/annotations/components/annotationModesTab.vue | Add paused modes + timestamps, and emit new annotation-mode-changed signals. |
| resources/assets/js/annotations/components/annotationCanvas/volare.js | New composable to manage Volare cycling + pause/resume persistence. |
| resources/assets/js/annotations/components/annotationCanvas/lawnmower.vue | Add save/load/discard state for Lawnmower and emit events around programmatic viewport changes. |
| resources/assets/js/annotations/components/annotationCanvas.vue | Emit movestart from the underlying map to support auto-pausing behavior. |
| resources/assets/js/annotations/annotatorContainer.vue | Integrate new Volare composable, wire pause/resume flows, and add auto-pause-on-viewport-change logic. |
Suppressed comments (2)
resources/views/annotations/show/tabs/annotationModes.blade.php:34
- Same issue as above: in an in-DOM template,
@transitionRequestedwill be lowercased and the handler may never run. Use a kebab-case listener so it compiles toonTransitionRequested.
@transitionRequested="onLawnmowerPlayPauseTransitionRequested"
resources/assets/js/annotations/components/annotationModesTab.vue:99
pauseLawnmower()is called without a timestamp (e.g. fromupdateLawnmowerStateand from viewport-change pausing), which makeslawnmowerPausedAtTextblank. Default toDate.now()so pauses always record when they happened.
pauseLawnmower(timestamp) {
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
mzur
left a comment
There was a problem hiding this comment.
This is not a full review, just my take on the composable approach with volare. I like a composable approach like this in general. The main benefit could be that logic can be shared across similar parts of BIIGLE (e.g. image and video annotation tools). All the current mixins that are used more than once are possible candidates for refactoring. Also the mixins of the annotationCanvas and videoScreen could maybe be (partially) merged and reused.
However in the case of volare it "just" moves code from one file to another. It makes the annotatorContainer slightly smaller but also introduces inconsistent architecture now (as the other annotation modes don't use the composable approach). And since the annotation modes are exclusive to the image annotation tool, I don't really see a benefit in making them all composable. It will be mostly refactoring for it's own sake which we should only do with our little resources if there is a real benefit in terms of code complexity.
So in summary I like the composable approach but volare and lawnmower mode may be the wrong features to pilot this. But please feel free to argue your point. We can also discuss this in a VC if you like.
|
@mzur I like the composable approach because of the separation of concerns it offers. Decoupling things from the rest of the codebase makes code testable and easier to reason about. This helps other developers to understand the code, including myself in the future. I do agree that the time investment is probably not worth it here. But I will start considering separating code into composables more often from now on and might default to it for new functionality. |
There was a problem hiding this comment.
The time ("40s ago") should appear immediately if pause is activated. It can show "just now" if the time is <60s. Also it should tick/update (maybe once a minute).
The title of the play buttons should change if the mode is paused (e.g. "continue...").
The title of the pause buttons should change if the mode is paused (e.g. "cancel...").
The title of the inactive pause buttons should explain what it does (save the state so user can resume here later).
This behavior should also be explained in the manual article.
Images can be missing if they were deleted from the volume or a volume filter is active. What happens if a volare/lawnmower state is restored but the image is missing?
Esc doesn't seem to cancel an active volare/lawnmower mode.
Collapsing the sidebar or resizing the browser window (i.e. resizing the map) pauses lawnmower mode but it should not.
| this.$emit('annotation-mode-changed', newMode); | ||
| } | ||
| }, | ||
| timeAgo(timestamp) { |
There was a problem hiding this comment.
This looks like it could be a good util method.
| playButtonTitleText="Start cycling through all annotations" | ||
| stopButtonTitleText="Stop cycling through all annotations 𝗘𝘀𝗰" | ||
| pauseButtonTitleText="Pause Volare" |
There was a problem hiding this comment.
The convention is to use kebab-case for attributes.
| playButtonTitleText="Start cycling through image sections" | ||
| stopButtonTitleText="Stop cycling through image sections 𝗘𝘀𝗰" | ||
| pauseButtonTitleText="Pause Lawnmower" | ||
| :state="currentLawnmowerState" |
| selectedAnnotations: computed(() => this.selectedAnnotations), | ||
| focusAnnotationInCanvas: this.focusAnnotation, | ||
| fitImageInCanvas: (...args) => this.$refs.canvas.fitImage(...args), | ||
| annotationFilter: this.annotationFilter, |
There was a problem hiding this comment.
This is usually null, so the volare watcher would not pick up any changes.
| annotationFilter: this.annotationFilter, | |
| annotationFilter: computed( () => this.annotationFilter), |
| // would automatically update when the resolution changes. But we | ||
| // need the old value to compute the new image section in the | ||
| // resolution watcher first! | ||
| this.imageSectionCenter = this.getImageSectionCenter(section); |
| discardSavedVolareState(); | ||
| resuming = false; | ||
|
|
||
| focussedAnnotationIndex.value = null; |
There was a problem hiding this comment.
Loading/resuming always focuses on annotation 0 first before switching to the actual saved annotation (after nextTick). Maybe this can be updated to avoid the unneeded focus.
| this.imageSectionCenter = state.center; | ||
| this.imageSection = state.imageSection; |
There was a problem hiding this comment.
Does this also work as expected if the state is restored on a resized map (e.g. if the sidebar is now expanded or the window size changed)?
| lawnmowerSaveState: { | ||
| type: String, | ||
| required: true | ||
| } |
There was a problem hiding this comment.
I'm not sure about this. This prop really acts like a command. Elsewhere this is done with direct method calls on a ref. If we don't want this, maybe this could be a hint that the lawnmower logic should also be moved up to the annotatorContainer which just tells the canvas which image section to show? Maybe migrate lawnmower mode to a composable after all?
There was a problem hiding this comment.
This file is no longer a canvas mixin but logic of the container. Put it into a "composables" dir?
| focussedAnnotation() { | ||
| return this.filteredAnnotations[this.focussedAnnotationIndex]; | ||
| }, | ||
| isDefaultAnnotationMode() { |
There was a problem hiding this comment.
This name is now misleading. Since it's only used for the highlight, maybe negate it and call it "should HighlightAnnotationMode", then update the use in the template.
mzur
left a comment
There was a problem hiding this comment.
One more thing: If volare is enabled but the currently focussed annotation disappears because of a filter in the annotations tab, the focussed annotation and viewport is updated correctly. But if the filter is then disabled again, the original annotation is focussed but the viewport does not jump back.
Closes #547