Skip to content

Commit

Permalink
Merge pull request #18548 from ElectronicBlueberry/undo-stack-ui
Browse files Browse the repository at this point in the history
Undo stack UI
  • Loading branch information
dannon authored Aug 6, 2024
2 parents 94611cb + bb43a51 commit bccac0d
Show file tree
Hide file tree
Showing 9 changed files with 485 additions and 62 deletions.
186 changes: 186 additions & 0 deletions client/src/components/UndoRedo/UndoRedoStack.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
<script setup lang="ts">
import { library } from "@fortawesome/fontawesome-svg-core";
import { faHistory } from "@fortawesome/free-solid-svg-icons";
import { ref, watch } from "vue";
import { useUndoRedoStore } from "@/stores/undoRedoStore";
import Heading from "@/components/Common/Heading.vue";
library.add(faHistory);
const props = defineProps<{
storeId: string;
}>();
const currentStore = ref(useUndoRedoStore(props.storeId));
watch(
() => props.storeId,
(id) => (currentStore.value = useUndoRedoStore(id))
);
function onInput(event: Event) {
const value = (event.target as HTMLInputElement).value;
const valueNumber = parseFloat(value);
const nonNanValue = isNaN(valueNumber) ? 0 : valueNumber;
currentStore.value.savedUndoActions = nonNanValue;
savedUndoActions.value = nonNanValue;
}
const savedUndoActions = ref(currentStore.value.savedUndoActions);
function updateSavedUndoActions() {
savedUndoActions.value = currentStore.value.savedUndoActions;
}
</script>

<template>
<section class="undo-redo-stack">
<Heading h2 size="sm" icon="fa-history">Latest Changes</Heading>

<div class="scroll-list">
<button
v-for="action in currentStore.redoActionStack"
:key="action.id"
class="action future"
@click="currentStore.rollForwardTo(action)">
{{ action.name }}
</button>

<span class="state-indicator"> current state </span>

<button v-if="currentStore.pendingLazyAction" class="action lazy" @click="currentStore.undo">
{{ currentStore.pendingLazyAction.name }}
</button>

<button
v-for="action in [...currentStore.undoActionStack].reverse()"
:key="action.id"
class="action past"
@click="currentStore.rollBackTo(action)">
{{ action.name }}
</button>

<span v-if="currentStore.deletedActions.length !== 0" class="state-indicator"> latest saved state </span>

<span v-for="(action, i) in [...currentStore.deletedActions].reverse()" :key="i" class="action dead">
{{ action }}
</span>

<span class="state-indicator"> start of session </span>
</div>

<span class="info"> click an action to undo/redo multiple changes </span>

<label>
Max saved changes
<input
:value="savedUndoActions"
type="number"
step="1"
:min="currentStore.minUndoActions"
:max="currentStore.maxUndoActions"
@input="onInput"
@focusin="updateSavedUndoActions"
@focusout="updateSavedUndoActions"
@keyup.enter="updateSavedUndoActions" />
</label>
</section>
</template>

<style scoped lang="scss">
@import "theme/blue.scss";
.info {
line-height: 1;
margin-bottom: 0.5rem;
margin-top: 0.25rem;
color: $text-muted;
font-style: italic;
}
.undo-redo-stack {
width: 100%;
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
overflow-y: hidden;
display: flex;
flex-direction: column;
.scroll-list {
display: flex;
flex-direction: column;
overflow-y: auto;
flex: 1;
}
}
.action {
text-align: left;
background-color: transparent;
border: none;
padding: 0.1rem;
padding-left: 0;
line-height: 1.2;
display: grid;
grid-template-columns: 8px auto;
align-items: center;
gap: 0.5rem;
&::before {
content: "";
display: block;
height: 2px;
background-color: $brand-secondary;
}
&:focus-visible {
background-color: $brand-secondary;
}
&.lazy {
color: $text-muted;
}
&.future {
color: $text-light;
}
&.past {
&::before {
background-color: $text-light;
}
}
&.dead {
color: $text-light;
&::before {
background-color: transparent;
}
}
}
.state-indicator {
display: grid;
grid-template-columns: 1rem auto 1fr;
gap: 0.5rem;
align-items: center;
color: $text-light;
&::before,
&::after {
content: "";
display: block;
height: 2px;
background-color: $brand-secondary;
}
}
</style>
74 changes: 61 additions & 13 deletions client/src/components/Workflow/Editor/Actions/commentActions.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,20 @@
import { LazyUndoRedoAction, UndoRedoAction } from "@/stores/undoRedoStore";
import {
type BaseWorkflowComment,
type WorkflowComment,
type WorkflowCommentColor,
type WorkflowCommentStore,
import type {
BaseWorkflowComment,
WorkflowComment,
WorkflowCommentColor,
WorkflowCommentStore,
WorkflowCommentType,
} from "@/stores/workflowEditorCommentStore";

function getCommentName(comment: { color: WorkflowCommentColor; type: WorkflowCommentType }) {
if (comment.color !== "none") {
return `${comment.color} ${comment.type} comment`;
} else {
return `${comment.type} comment`;
}
}

class CommentAction extends UndoRedoAction {
protected store: WorkflowCommentStore;
protected comment: WorkflowComment;
Expand All @@ -15,11 +24,15 @@ class CommentAction extends UndoRedoAction {
this.store = store;
this.comment = structuredClone(comment) as WorkflowComment;
}

protected get commentName() {
return getCommentName(this.comment);
}
}

export class AddCommentAction extends CommentAction {
get name() {
return `add ${this.comment.type} comment`;
return `add ${this.commentName}`;
}

undo() {
Expand All @@ -33,7 +46,7 @@ export class AddCommentAction extends CommentAction {

export class DeleteCommentAction extends CommentAction {
get name() {
return `delete ${this.comment.type} comment`;
return `delete ${this.commentName}`;
}

run() {
Expand Down Expand Up @@ -75,10 +88,11 @@ export class ChangeColorAction extends UndoRedoAction {
}

class LazyMutateCommentAction<K extends keyof WorkflowComment> extends LazyUndoRedoAction {
private commentId: number;
private startData: WorkflowComment[K];
private endData: WorkflowComment[K];
protected commentId: number;
protected startData: WorkflowComment[K];
protected endData: WorkflowComment[K];
protected type;
protected color: WorkflowCommentColor;
protected applyDataCallback: (commentId: number, data: WorkflowComment[K]) => void;

constructor(
Expand All @@ -93,14 +107,19 @@ class LazyMutateCommentAction<K extends keyof WorkflowComment> extends LazyUndoR
this.endData = structuredClone(data);
this.applyDataCallback = applyDataCallback;
this.type = comment.type;
this.color = comment.color;
}

queued() {
this.applyDataCallback(this.commentId, this.endData);
}

protected get commentName() {
return getCommentName({ type: this.type, color: this.color });
}

get name() {
return `change ${this.type} comment`;
return `change ${this.commentName}`;
}

updateData(data: WorkflowComment[K]) {
Expand All @@ -122,6 +141,35 @@ export class LazyChangeDataAction extends LazyMutateCommentAction<"data"> {
const callback = store.changeData;
super(comment, "data", data, callback);
}

get name() {
type TitleData = { title: string };
type TextData = { text: string };
type SizeData = { size: number };
type FormatData = { bold?: true; italic?: true };

if ((this.startData as TitleData).title !== (this.endData as TitleData).title) {
return `edit title of ${this.commentName}`;
}

if ((this.startData as TextData).text !== (this.endData as TextData).text) {
return `edit text of ${this.commentName}`;
}

if ((this.startData as SizeData).size !== (this.endData as SizeData).size) {
return `change text size of ${this.commentName}`;
}

if ((this.startData as FormatData).bold !== (this.endData as FormatData).bold) {
return `toggle bold of ${this.commentName}`;
}

if ((this.startData as FormatData).italic !== (this.endData as FormatData).italic) {
return `toggle italic of ${this.commentName}`;
}

return super.name;
}
}

export class LazyChangePositionAction extends LazyMutateCommentAction<"position"> {
Expand All @@ -131,7 +179,7 @@ export class LazyChangePositionAction extends LazyMutateCommentAction<"position"
}

get name() {
return `change ${this.type} comment position`;
return `move ${this.commentName}`;
}
}

Expand All @@ -142,7 +190,7 @@ export class LazyChangeSizeAction extends LazyMutateCommentAction<"size"> {
}

get name() {
return `resize ${this.type} comment`;
return `resize ${this.commentName}`;
}
}

Expand Down
Loading

0 comments on commit bccac0d

Please sign in to comment.