From 7f67ea41db857335b10b80fda3164c1bfefe39a1 Mon Sep 17 00:00:00 2001
From: NathanViaud <nathan.viaud@inria.fr>
Date: Wed, 7 Jun 2023 15:23:18 +0200
Subject: [PATCH] Add undo on change repeat input

---
 .../forms/components/GenericField.vue         | 26 +++++++++++---
 .../forms/components/inputs/GenericInput.vue  |  2 ++
 .../forms/components/inputs/RepeatInput.vue   | 28 +++++++++++----
 src/shared/interfaces/undoRedo.interface.ts   |  7 ++--
 src/shared/services/graph/content.service.ts  | 34 ++++++++++++++-----
 src/shared/stores/undoRedo/functions/form.ts  | 20 +++++++++--
 src/shared/stores/undoRedo/undoRedoStore.ts   |  9 +++--
 7 files changed, 99 insertions(+), 27 deletions(-)

diff --git a/src/features/forms/components/GenericField.vue b/src/features/forms/components/GenericField.vue
index bb0fa9d1..11be3c36 100644
--- a/src/features/forms/components/GenericField.vue
+++ b/src/features/forms/components/GenericField.vue
@@ -1,5 +1,5 @@
 <script setup lang="ts">
-import { FormUpdatedAction, Input } from '@/src/shared/interfaces';
+import { FormRepeatChangeAction, FormUpdatedAction, Input } from '@/src/shared/interfaces';
 import GenericInput from './inputs/GenericInput.vue';
 import { useEditorStore, useUndoRedoStore } from '@/src/shared/stores';
 import { graphService } from '@/src/shared/services';
@@ -115,6 +115,23 @@ function handleChangeRepeatInput(element, value, id: string): void {
     }
 }
 
+function onAddRepeatUndoAction(repeatEvent, formValueId: string): void  {
+    const { type, value, index, id } = repeatEvent;
+    
+    const action: FormRepeatChangeAction = {
+        type: 'formRepeatUpdated',
+        nodeId: currentNode.id,
+        elementId: editorStore.openedElementId,
+        formValueId,
+        repeatId: id,
+        updateType: type,
+        oldValue: value.oldValue,
+        newValue: value.newValue,
+        index: index
+    };
+    
+    undoRedoStore.addAction(action);  
+}
 // Repeat Input end
 
 function onCheck(value: boolean, id:string) {
@@ -126,8 +143,8 @@ function onCheck(value: boolean, id:string) {
     graphService.writeProjectData();
 }
 
-function addUndoAction(event: { oldValue: string, newValue: string }, id: string) {
-    const { oldValue, newValue } = event;
+function onAddUndoAction(value: { oldValue: string, newValue: string }, id: string) {
+    const { oldValue, newValue } = value;
     
     const action: FormUpdatedAction = {
         type: 'formUpdated',
@@ -154,7 +171,8 @@ function addUndoAction(event: { oldValue: string, newValue: string }, id: string
         @input="onInput($event, input.id)"
         @check="onCheck($event, input.id)"
         @repeat-input="onRepeatInput($event, input.id)"
-        @add-undo-action="addUndoAction($event, input.id)"
+        @add-undo-action="onAddUndoAction($event, input.id)"
+        @add-repeat-undo-action="onAddRepeatUndoAction($event, input.id)"
     />
 </template>
 
diff --git a/src/features/forms/components/inputs/GenericInput.vue b/src/features/forms/components/inputs/GenericInput.vue
index 620e3e22..ff026dd8 100644
--- a/src/features/forms/components/inputs/GenericInput.vue
+++ b/src/features/forms/components/inputs/GenericInput.vue
@@ -25,6 +25,7 @@ const emit = defineEmits<{
     (e: 'repeatInput', value): void;
     (e: 'check', value: boolean): void;
     (e: 'add-undo-action', value: { oldValue: string, newValue: string }): void;
+    (e: 'add-repeat-undo-action', value): void; 
 }>();
 
 </script>
@@ -105,5 +106,6 @@ const emit = defineEmits<{
         :field-index="fieldIndex"
         :add-button="input.addButton"
         @change="emit('repeatInput', $event)"
+        @add-undo-action="emit('add-repeat-undo-action', $event)"
     />
 </template>
\ No newline at end of file
diff --git a/src/features/forms/components/inputs/RepeatInput.vue b/src/features/forms/components/inputs/RepeatInput.vue
index 168ac53e..eafaa298 100644
--- a/src/features/forms/components/inputs/RepeatInput.vue
+++ b/src/features/forms/components/inputs/RepeatInput.vue
@@ -2,7 +2,7 @@
 import { useEditorStore } from '@/src/shared/stores';
 import GenericInput from './GenericInput.vue';
 import AddCard from './card/AddCard.vue';
-import { Input } from '@/src/shared/interfaces';
+import { Input, SideAction } from '@/src/shared/interfaces';
 import { ref } from 'vue';
 import { generateContentId } from '@/src/shared/services/graph.service';
 
@@ -16,6 +16,12 @@ const props = defineProps<{
 
 const emit = defineEmits<{
     (e: 'change', value: object | string): void;
+    (e: 'add-undo-action', value: { 
+        type: string, 
+        id: string,
+        index: number,
+        value: { oldValue: string, newValue: string }
+    })
 }>();
 
 const editorStore = useEditorStore();
@@ -34,7 +40,7 @@ const dragOptions = {
 
 const isLast = (index) => props.inputValues.length - 1 === index;
 
-function onInput(value, id, index) {
+function onInput(value, id: string, index: number) {
     emit('change', {
         type: 'change',
         value,
@@ -56,7 +62,7 @@ function addCard() {
     emit('change', { type: 'add', defaultValues });
 }
 
-function removeCard(index) {
+function removeCard(index: number) {
     emit('change', {
         type: 'remove',
         index
@@ -80,7 +86,7 @@ function moveCard(event, oldIndex?: number, newIndex?: number) {
     });
 }
 
-function onCheck(value, id, index) {
+function onCheck(value, id: string, index: number) {
     emit('change', {
         type: 'change',
         value,
@@ -89,7 +95,7 @@ function onCheck(value, id, index) {
     });
 }
 
-function onClick(index, action) {
+function onClick(index: number, action: SideAction | null) {
     const element = currentNode.data.elements?.[index];
 
     if(element && action) {
@@ -98,6 +104,15 @@ function onClick(index, action) {
     }
 }
 
+function onAddUndoAction(value: { oldValue: string, newValue: string }, id: string, index: number) {
+    emit('add-undo-action', {
+        type: 'change',
+        value,
+        id,
+        index
+    });
+}
+
 function start() {
     drag.value = true;
     editorStore.draggedElement = null;
@@ -109,7 +124,7 @@ function end() {
     document.body.classList.remove('cursor-not-allowed', 'cursor-allowed', 'cursor-move');
 }
 
-function dragOver(event) {
+function dragOver(event: DragEvent) {
     if(editorStore.draggedElement) return;
     event.preventDefault();
     document.body.classList.add('cursor-move');
@@ -163,6 +178,7 @@ function dragOver(event) {
                         :pos="index"
                         @input="onInput($event, input.id, index)"
                         @check="onCheck($event, input.id, index)"
+                        @add-undo-action="onAddUndoAction($event, input.id, index)"
                     />
                 </div>
             </div>
diff --git a/src/shared/interfaces/undoRedo.interface.ts b/src/shared/interfaces/undoRedo.interface.ts
index 9480c72f..54b8cb0c 100644
--- a/src/shared/interfaces/undoRedo.interface.ts
+++ b/src/shared/interfaces/undoRedo.interface.ts
@@ -1,8 +1,9 @@
 import { GraphEdge } from '@vue-flow/core';
-import { Form } from './form.interface';
 
 export interface UndoRedoAction {
-    type: 'nodeMoved' | 'nodeAdded' | 'nodeRemoved' | 'nodeUpdated' | 'edgeAdded' | 'edgeUpdated' | 'edgeRemoved' | 'formUpdated' | 'formRepeatUpdated';
+    type: 'nodeMoved' | 'nodeAdded' | 'nodeRemoved' | 'nodeUpdated' |
+        'edgeAdded' | 'edgeUpdated' | 'edgeRemoved' | 'formUpdated' | 
+        'formRepeatUpdated' | 'formRepeatUpdated';
 }
 
 export interface NodeMovedAction extends UndoRedoAction {
@@ -43,7 +44,7 @@ export interface FormUpdatedAction extends UndoRedoAction {
     newValue: string;
 }
 
-export interface FormRepeatUpdatedAction  {
+export interface FormRepeatUpdatedAction extends UndoRedoAction {
     type: 'formRepeatUpdated';
     nodeId: string;
     elementId: string;
diff --git a/src/shared/services/graph/content.service.ts b/src/shared/services/graph/content.service.ts
index 7bf83106..1cf797dc 100644
--- a/src/shared/services/graph/content.service.ts
+++ b/src/shared/services/graph/content.service.ts
@@ -96,21 +96,39 @@ export function getContentDefaultValues(type) {
 }
 
 export function updateElementValue(elementId: string, nodeId: string, valueId: string, value: string): void {
+    const { id, formType, formValues } = getElementInfo(elementId, nodeId);
+    
+    if(editorStore.openedElementId !== id) {
+        const parentId = nodeId !== elementId ? nodeId : null;
+        editorStore.openFormPanel(id, formType, formValues, parentId);
+    }
+
+    formValues[valueId] = value;
+}
+
+export function updateRepeatElementValue(elementId: string, nodeId: string, valueId: string, value: string, repeatIndex: number, repeatId: string): void {
+    const { id, formType, formValues } = getElementInfo(elementId, nodeId);
+    
+    if(editorStore.openedElementId !== id) {
+        const parentId = nodeId !== elementId ? nodeId : null;
+        editorStore.openFormPanel(id, formType, formValues, parentId);
+    }
+
+    const repeatInput = formValues[valueId][repeatIndex];
+    repeatInput[repeatId] = value;
+}
+
+function getElementInfo(elementId: string, nodeId: string): {id: string, formType: string, formValues: any} {
     const node = findNode(nodeId);
 
     let id, formType, formValues;
     if(nodeId === elementId) {
         id = node.id;
-        ({formType, formValues} = node.data);
+        ({ formType, formValues } = node.data);
     } else {
         const element = node.data.elements.find(e => e.id === elementId);
-        ({id, formType, formValues} = element);
+        ({ id, formType, formValues } = element);
     }
     
-    if(editorStore.openedElementId !== id) {
-        const parentId = nodeId !== elementId ? nodeId : null;
-        editorStore.openFormPanel(id, formType, formValues, parentId);
-    }
-
-    formValues[valueId] = value;
+    return { id, formType, formValues };
 }
\ No newline at end of file
diff --git a/src/shared/stores/undoRedo/functions/form.ts b/src/shared/stores/undoRedo/functions/form.ts
index 1dd09b02..4d78d1c2 100644
--- a/src/shared/stores/undoRedo/functions/form.ts
+++ b/src/shared/stores/undoRedo/functions/form.ts
@@ -1,7 +1,7 @@
-import { FormUpdatedAction, UndoRedoAction } from '@/src/shared/interfaces';
-import { updateElementValue } from '@/src/shared/services/graph';
+import { FormRepeatChangeAction, FormUpdatedAction, UndoRedoAction } from '@/src/shared/interfaces';
+import { updateElementValue, updateRepeatElementValue } from '@/src/shared/services/graph';
 
-export function formUpdatedAction(action: FormUpdatedAction, reverseStack: UndoRedoAction[]): void {
+export function updateFormAction(action: FormUpdatedAction, reverseStack: UndoRedoAction[]): void {
     const { elementId, nodeId, formValueId, oldValue, newValue } = action;
     
     updateElementValue(elementId, nodeId, formValueId, oldValue);
@@ -15,6 +15,20 @@ export function formUpdatedAction(action: FormUpdatedAction, reverseStack: UndoR
     reverseStack.push(reverseAction);
 }
 
+export function updateRepeatFormAction(action: FormRepeatChangeAction, reverseStack: UndoRedoAction[]): void {
+    const { elementId, nodeId, formValueId, oldValue, newValue, index, repeatId } = action;
+
+    updateRepeatElementValue(elementId, nodeId, formValueId, oldValue, index, repeatId);
+
+    const reverseAction: FormRepeatChangeAction = {
+        ...action,
+        oldValue: newValue,
+        newValue: oldValue,
+    };
+    
+    reverseStack.push(reverseAction);
+}
+
 export function ignoreUndoRedoOnFocus(event: KeyboardEvent): void {
     const { key, ctrlKey, metaKey } = event;
     if(ctrlKey || metaKey) {
diff --git a/src/shared/stores/undoRedo/undoRedoStore.ts b/src/shared/stores/undoRedo/undoRedoStore.ts
index 97c1e748..a4187790 100644
--- a/src/shared/stores/undoRedo/undoRedoStore.ts
+++ b/src/shared/stores/undoRedo/undoRedoStore.ts
@@ -1,6 +1,6 @@
 import { defineStore } from 'pinia';
-import { UndoRedoAction, NodeMovedAction, NodeMutatedAction, EdgeUpdatedAction, EdgeMutatedAction, FormUpdatedAction } from '../../interfaces';
-import { addEdgeAction, addNodeAction, deleteEdgeAction, deleteNodeAction, formUpdatedAction, moveNodeAction, updateEdgeAction, updateNodeAction } from './functions';
+import { UndoRedoAction, NodeMovedAction, NodeMutatedAction, EdgeUpdatedAction, EdgeMutatedAction, FormUpdatedAction, FormRepeatChangeAction } from '../../interfaces';
+import { addEdgeAction, addNodeAction, deleteEdgeAction, deleteNodeAction, updateFormAction, moveNodeAction, updateEdgeAction, updateNodeAction, updateRepeatFormAction } from './functions';
 
 interface UndoRedoState {
     undoStack: UndoRedoAction[];
@@ -57,7 +57,10 @@ export const useUndoRedoStore = defineStore('epoc', {
                 addEdgeAction(action as EdgeMutatedAction, reverseStack);
                 break;
             case 'formUpdated':
-                formUpdatedAction(action as FormUpdatedAction, reverseStack);
+                updateFormAction(action as FormUpdatedAction, reverseStack);
+                break;
+            case 'formRepeatUpdated':
+                updateRepeatFormAction(action as FormRepeatChangeAction, reverseStack);
                 break;
             }
         },
-- 
GitLab