Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Undo/redo for tools #394

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open

Undo/redo for tools #394

wants to merge 4 commits into from

Conversation

floryst
Copy link
Collaborator

@floryst floryst commented Aug 16, 2023

  • Adds core undo/redo functionality
  • Adds basic undo/redo support to some of the tools (add/remove for ruler, rectangle, and polygon)

@floryst floryst requested a review from PaulHax August 16, 2023 18:24
@netlify
Copy link

netlify bot commented Aug 16, 2023

Deploy Preview for volview-dev ready!

Name Link
🔨 Latest commit 3f48d1c
🔍 Latest deploy log https://app.netlify.com/sites/volview-dev/deploys/64dd147caad7b200089ee8d9
😎 Deploy Preview https://deploy-preview-394--volview-dev.netlify.app/
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify site configuration.

@floryst floryst changed the title Undo/redo Undo/redo for tools Aug 16, 2023
Copy link
Collaborator

@PaulHax PaulHax left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Works good! Tests even, wowy.

import { createPinia, setActivePinia } from 'pinia';
import { it, beforeEach, describe } from 'vitest';

interface Collection {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No prepended I before interface 😱 I'm actualy good with that. In good company: https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#interfaces

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is just an interface for a test, so I'm not too concerned about the I prefix.

@@ -33,7 +36,10 @@ const tools = computed(() => {
});

const remove = (id: ToolID) => {
props.toolStore.removeTool(id);
const imageID = currentImageID.value;
if (!imageID) return;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No imageID, but showing remove button in toolList would be strange. Throw Error or derive imageID from toolID so imageID is guarrentied? Worried that defensive programming could lead to corner case runtime quirks.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like deriving the image ID from the tool ID in this case.

};
}

export function createUpdateToolOperation<
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
export function createUpdateToolOperation<
// Not used at the moment
export function createUpdateToolOperation<

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I intended to add support for undoing stuff like changing labels, moving rectangle points, etc. That hasn't happened yet, so I'm good to omit this function for now instead of adding a comment that we will have to later remove anyways.

Comment on lines +174 to +177
useHistoryStore().pushOperation(
{ datasetID: imageID },
addToolOperation!
);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think about hiding pushOperation and its ilk in the tool stores?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be good to do it later down the road. I'm opting not to for now because I don't want to make the tool stores undo/redo aware just yet.


const props = defineProps<{
toolStore: AnnotationToolStore<ToolID>;
toolStore: AnnotationToolStore<ToolID> & Store;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the future, should I put & Store on the AnnotationToolStore shared type?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I think that is a good idea. I could add that as an extra commit to this PR if you'd like.

export interface IHistoryOperation<T = void> {
apply(): T;
revert(): void;
isApplied(): boolean;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wonder if we should remove the isApplied idempotentcy escape hatch. Is there a valid case where the HistoryManager will call apply twice? Or is isApplied helpful elsehwere?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I left isApplied as just a way for other code to know if an operation has been applied or reverted, and a convenient way to perform idempotency checks in the operation apply/revert code. HistoryManager itself implicitly keeps track of this state via the undo/redo stacks.

@floryst
Copy link
Collaborator Author

floryst commented Aug 17, 2023

I'm identifying possible shortcomings with the current architecture: hierarchical transactions. One motivating example is the polygon tool: when placing points, undo would ideally remove dropped points, but when the polygon is finalized, undo should remove the entire polygon and not individual points.

@PaulHax
Copy link
Collaborator

PaulHax commented Aug 17, 2023

Would be cool if we made the polygon resumable. So can drop a few points, change tools/slice, switch back and close the polygon. Can then drop placing flag headache, it becomes "closed" for polygon case.

Avoids the "hierarchical transactions" problem for this case.

@floryst
Copy link
Collaborator Author

floryst commented Aug 17, 2023

So can drop a few points, change tools/slice, switch back and close the polygon.

I'm not sure this is desired behavior, only because I've never observed such behavior. I've always considered editing an ephemeral state.

In any case, I think we still need some semblance of a hierarchical/transactional history mechanism, since tools may have sub-histories only relevant to their operation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants