From c3b0219313389ebc71bf6e290588628e7973fce5 Mon Sep 17 00:00:00 2001 From: NathanViaud <nathan.viaud@inria.fr> Date: Tue, 6 Jun 2023 17:32:00 +0200 Subject: [PATCH] Adding undo redo on text type input --- .../forms/components/GenericField.vue | 20 +++++++++++++++++-- .../forms/components/inputs/GenericInput.vue | 5 +++++ .../forms/components/inputs/HtmlInput.vue | 11 ++++++++++ .../forms/components/inputs/ScoreInput.vue | 13 +++++++++++- .../forms/components/inputs/TextAreaInput.vue | 13 +++++++++++- .../forms/components/inputs/TextInput.vue | 13 +++++++++++- src/shared/interfaces/undoRedo.interface.ts | 11 +++++++++- src/shared/services/graph/content.service.ts | 5 +++++ src/shared/stores/undoRedo/functions/form.ts | 16 +++++++++++---- src/shared/stores/undoRedo/functions/node.ts | 3 +-- src/shared/stores/undoRedo/undoRedoStore.ts | 7 +++++-- src/views/EditorPage.vue | 9 +++++++-- 12 files changed, 110 insertions(+), 16 deletions(-) diff --git a/src/features/forms/components/GenericField.vue b/src/features/forms/components/GenericField.vue index dcdbe352..bb0fa9d1 100644 --- a/src/features/forms/components/GenericField.vue +++ b/src/features/forms/components/GenericField.vue @@ -1,11 +1,12 @@ <script setup lang="ts"> -import { Input } from '@/src/shared/interfaces'; +import { FormUpdatedAction, Input } from '@/src/shared/interfaces'; import GenericInput from './inputs/GenericInput.vue'; -import { useEditorStore } from '@/src/shared/stores'; +import { useEditorStore, useUndoRedoStore } from '@/src/shared/stores'; import { graphService } from '@/src/shared/services'; import { deleteElement, changeContentOrder } from '@/src/shared/services/graph'; const editorStore = useEditorStore(); +const undoRedoStore = useUndoRedoStore(); defineProps<{ inputs: Input[]; @@ -125,6 +126,20 @@ function onCheck(value: boolean, id:string) { graphService.writeProjectData(); } +function addUndoAction(event: { oldValue: string, newValue: string }, id: string) { + const { oldValue, newValue } = event; + + const action: FormUpdatedAction = { + type: 'formUpdated', + nodeId: currentNode.id, + elementId: editorStore.openedElementId, + oldValue, + newValue, + formValueId: id + }; + undoRedoStore.addAction(action); +} + </script> <template> @@ -139,6 +154,7 @@ function onCheck(value: boolean, 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)" /> </template> diff --git a/src/features/forms/components/inputs/GenericInput.vue b/src/features/forms/components/inputs/GenericInput.vue index 8c76525c..2ae31676 100644 --- a/src/features/forms/components/inputs/GenericInput.vue +++ b/src/features/forms/components/inputs/GenericInput.vue @@ -24,6 +24,7 @@ const emit = defineEmits<{ (e: 'input', value: string): void; (e: 'repeatInput', value): void; (e: 'check', value: boolean): void; + (e: 'add-undo-action', value: { oldValue: string, newValue: string }): void; }>(); </script> @@ -36,6 +37,7 @@ const emit = defineEmits<{ :input-value="inputValue" :inside-card="insideCard" @input="emit('input', $event)" + @add-undo-action="emit('add-undo-action', $event)" /> <HtmlInput v-if="input.type === 'html'" @@ -44,6 +46,7 @@ const emit = defineEmits<{ :input-value="inputValue" :inside-card="insideCard" @input="emit('input', $event)" + @add-undo-action="emit('add-undo-action', $event)" /> <TextAreaInput v-if="input.type === 'textarea'" @@ -52,6 +55,7 @@ const emit = defineEmits<{ :input-value="inputValue" :inside-card="insideCard" @input="emit('input', $event)" + @add-undo-action="emit('add-undo-action', $event)" /> <FileInput v-if="input.type === 'file'" @@ -66,6 +70,7 @@ const emit = defineEmits<{ :label="input.label" :input-value="inputValue" @input="emit('input', $event)" + @add-undo-action="emit('add-undo-action', $event)" /> <CheckBoxInput v-if="input.type === 'checkbox'" diff --git a/src/features/forms/components/inputs/HtmlInput.vue b/src/features/forms/components/inputs/HtmlInput.vue index 6ba10b84..fc72fd21 100644 --- a/src/features/forms/components/inputs/HtmlInput.vue +++ b/src/features/forms/components/inputs/HtmlInput.vue @@ -14,6 +14,7 @@ const props = defineProps<{ const emit = defineEmits<{ (e: 'input', value: string): void; + (e: 'add-undo-action', value: { oldValue: string, newValue: string }): void; }>(); const editor = ref(null); @@ -74,6 +75,14 @@ function handleFilePicker(callback) { }); } +let initialValue = null; + +function addUndoAction() { + if(initialValue !== props.inputValue) { + emit('add-undo-action', { oldValue: initialValue, newValue: props.inputValue }); + } +} + </script> <template> @@ -98,6 +107,8 @@ function handleFilePicker(callback) { }" @init="init" @drop.stop.prevent="drop" + @focus="initialValue = content" + @blur="addUndoAction" @keydown="ignoreUndoRedoOnFocus" /> </template> \ No newline at end of file diff --git a/src/features/forms/components/inputs/ScoreInput.vue b/src/features/forms/components/inputs/ScoreInput.vue index 666784aa..e9532976 100644 --- a/src/features/forms/components/inputs/ScoreInput.vue +++ b/src/features/forms/components/inputs/ScoreInput.vue @@ -1,12 +1,13 @@ <script setup lang="ts"> import { ignoreUndoRedoOnFocus } from '@/src/shared/stores/undoRedo/functions'; -defineProps<{ +const props = defineProps<{ inputValue: string; }>(); const emit = defineEmits<{ (e: 'input', value: string): void; + (e: 'add-undo-action', value: { oldValue: string, newValue: string }): void; }>(); function minus(inputValue: string) { @@ -19,6 +20,14 @@ function plus(inputValue: string) { emit('input', `${value}`); } +let initialValue = null; + +function addUndoAction() { + if(initialValue !== props.inputValue) { + emit('add-undo-action', { oldValue: initialValue, newValue: props.inputValue }); + } +} + </script> <template> @@ -29,6 +38,8 @@ function plus(inputValue: string) { type="number" :value="inputValue" @input="emit('input', ($event.target as HTMLInputElement).value)" + @focus="initialValue = inputValue" + @blur="addUndoAction" @keydown="ignoreUndoRedoOnFocus" > <button @click="plus(inputValue)"><i class="icon-plus-circle"></i></button> diff --git a/src/features/forms/components/inputs/TextAreaInput.vue b/src/features/forms/components/inputs/TextAreaInput.vue index a1163498..d6bad82a 100644 --- a/src/features/forms/components/inputs/TextAreaInput.vue +++ b/src/features/forms/components/inputs/TextAreaInput.vue @@ -1,7 +1,7 @@ <script setup lang="ts"> import { ignoreUndoRedoOnFocus } from '@/src/shared/stores/undoRedo/functions'; -defineProps<{ +const props = defineProps<{ label: string; placeholder: string; inputValue: string; @@ -11,8 +11,17 @@ defineProps<{ const emit = defineEmits<{ (e: 'input', value: string): void; + (e: 'add-undo-action', value: { oldValue: string, newValue: string }): void; }>(); +let initialValue = null; + +function addUndoAction() { + if(initialValue !== props.inputValue) { + emit('add-undo-action', { oldValue: initialValue, newValue: props.inputValue }); + } +} + </script> <template> @@ -24,6 +33,8 @@ const emit = defineEmits<{ :placeholder="placeholder" :value="inputValue" @input="emit('input', ($event.target as HTMLInputElement).value)" + @focus="initialValue = inputValue" + @blur="addUndoAction" @keydown="ignoreUndoRedoOnFocus" ></textarea> </template> \ No newline at end of file diff --git a/src/features/forms/components/inputs/TextInput.vue b/src/features/forms/components/inputs/TextInput.vue index 59c39a34..03ed0154 100644 --- a/src/features/forms/components/inputs/TextInput.vue +++ b/src/features/forms/components/inputs/TextInput.vue @@ -1,7 +1,7 @@ <script setup lang="ts"> import { ignoreUndoRedoOnFocus } from '@/src/shared/stores/undoRedo/functions'; -defineProps<{ +const props = defineProps<{ label: string; placeholder?: string; inputValue: string; @@ -10,8 +10,17 @@ defineProps<{ const emit = defineEmits<{ (e: 'input', value: string): void; + (e: 'add-undo-action', value: { oldValue: string, newValue: string }): void; }>(); +let initialValue = null; + +function addUndoAction() { + if(initialValue !== props.inputValue) { + emit('add-undo-action', { oldValue: initialValue, newValue: props.inputValue }); + } +} + </script> <template> @@ -24,6 +33,8 @@ const emit = defineEmits<{ :placeholder="placeholder" :value="inputValue" @input="emit('input', ($event.target as HTMLInputElement).value)" + @focus="initialValue = inputValue" + @blur="addUndoAction" @keydown="ignoreUndoRedoOnFocus" > </template> \ No newline at end of file diff --git a/src/shared/interfaces/undoRedo.interface.ts b/src/shared/interfaces/undoRedo.interface.ts index 99e1485f..12bdb780 100644 --- a/src/shared/interfaces/undoRedo.interface.ts +++ b/src/shared/interfaces/undoRedo.interface.ts @@ -1,7 +1,7 @@ import { GraphEdge } from '@vue-flow/core'; export interface UndoRedoAction { - type: 'nodeMoved' | 'nodeAdded' | 'nodeRemoved' | 'nodeUpdated' | 'edgeAdded' | 'edgeUpdated' | 'edgeRemoved'; + type: 'nodeMoved' | 'nodeAdded' | 'nodeRemoved' | 'nodeUpdated' | 'edgeAdded' | 'edgeUpdated' | 'edgeRemoved' | 'formUpdated'; } export interface NodeMovedAction extends UndoRedoAction { @@ -31,4 +31,13 @@ export interface EdgeUpdatedAction extends UndoRedoAction { type: 'edgeUpdated'; newEdge: GraphEdge; oldEdge: GraphEdge +} + +export interface FormUpdatedAction extends UndoRedoAction { + type: 'formUpdated'; + nodeId: string; + elementId: string; + formValueId: string; + oldValue: string; + newValue: string; } \ No newline at end of file diff --git a/src/shared/services/graph/content.service.ts b/src/shared/services/graph/content.service.ts index 662dd055..02f7ecb5 100644 --- a/src/shared/services/graph/content.service.ts +++ b/src/shared/services/graph/content.service.ts @@ -93,4 +93,9 @@ export function getContentDefaultValues(type) { }, {}); return {...acc, ...keyValues}; }, {}); +} + +export function updateElementValue(elementId: string, nodeId: string, valueId: string, value: string): void { + const node = findNode(nodeId); + node.data.formValues[valueId] = value; } \ 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 9f9b9809..411357f3 100644 --- a/src/shared/stores/undoRedo/functions/form.ts +++ b/src/shared/stores/undoRedo/functions/form.ts @@ -1,9 +1,17 @@ -import { UndoRedoAction } from '@/src/shared/interfaces'; +import { FormUpdatedAction, UndoRedoAction } from '@/src/shared/interfaces'; +import { updateElementValue } from '@/src/shared/services/graph'; -export function formUpdated(action: UndoRedoAction, reverseStack: UndoRedoAction[]): void { - const reverseAction: UndoRedoAction = { - type: action.type, +export function formUpdatedAction(action: FormUpdatedAction, reverseStack: UndoRedoAction[]): void { + const { elementId, nodeId, formValueId, oldValue, newValue } = action; + + updateElementValue(elementId, nodeId, formValueId, oldValue); + + const reverseAction: FormUpdatedAction = { + ...action, + oldValue: newValue, + newValue: oldValue, }; + reverseStack.push(reverseAction); } diff --git a/src/shared/stores/undoRedo/functions/node.ts b/src/shared/stores/undoRedo/functions/node.ts index 1c033681..d526e25a 100644 --- a/src/shared/stores/undoRedo/functions/node.ts +++ b/src/shared/stores/undoRedo/functions/node.ts @@ -39,7 +39,6 @@ export function addNodeAction(action: NodeMutatedAction, reverseStack: UndoRedoA edges: action.edges }; reverseStack.push(reverseAction); - } export function updateNodeAction(): void { @@ -62,4 +61,4 @@ export function moveNodeAction(action: NodeMovedAction, reverseStack: UndoRedoAc } }; reverseStack.push(reverseAction); -} +} \ No newline at end of file diff --git a/src/shared/stores/undoRedo/undoRedoStore.ts b/src/shared/stores/undoRedo/undoRedoStore.ts index 51889268..97c1e748 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 } from '../../interfaces'; -import { addEdgeAction, addNodeAction, deleteEdgeAction, deleteNodeAction, moveNodeAction, updateEdgeAction, updateNodeAction } from './functions'; +import { UndoRedoAction, NodeMovedAction, NodeMutatedAction, EdgeUpdatedAction, EdgeMutatedAction, FormUpdatedAction } from '../../interfaces'; +import { addEdgeAction, addNodeAction, deleteEdgeAction, deleteNodeAction, formUpdatedAction, moveNodeAction, updateEdgeAction, updateNodeAction } from './functions'; interface UndoRedoState { undoStack: UndoRedoAction[]; @@ -56,6 +56,9 @@ export const useUndoRedoStore = defineStore('epoc', { case 'edgeRemoved': addEdgeAction(action as EdgeMutatedAction, reverseStack); break; + case 'formUpdated': + formUpdatedAction(action as FormUpdatedAction, reverseStack); + break; } }, } diff --git a/src/views/EditorPage.vue b/src/views/EditorPage.vue index eb75d459..dcf5e2fb 100644 --- a/src/views/EditorPage.vue +++ b/src/views/EditorPage.vue @@ -15,7 +15,8 @@ const undoRedoStore = useUndoRedoStore(); editorService.setup(); document.body.addEventListener('keydown', function(event) { - const key = event.key; + const { key, ctrlKey, metaKey }= event; + if ((key === 'Backspace' || key === 'Delete')) { if((event.target as HTMLElement).className.indexOf('vue-flow') !== -1 || event.target === document.body) { event.stopPropagation(); @@ -23,11 +24,15 @@ document.body.addEventListener('keydown', function(event) { } } - if (event.ctrlKey || event.metaKey) { + if (ctrlKey || metaKey) { if (key === 'z') { + event.preventDefault(); + event.stopPropagation(); undo(); } if (key === 'y') { + event.preventDefault(); + event.stopPropagation(); redo(); } } -- GitLab