diff --git a/src/features/forms/components/GenericField.vue b/src/features/forms/components/GenericField.vue index dcdbe35246cfdaf0af393a028d66588e7ec7703a..bb0fa9d1cc80936c118194bd030f12e18cc8b9c0 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 8c76525cad4ae5edf84e92685a33202c6831f6fd..2ae316764c54a03c9f08fd8cefa419ed18c0a161 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 6ba10b847c73a0afbd7278d7896aa4b8b2b3f9bf..fc72fd214b27833c7a24996813ce71356420d6d4 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 666784aa20db434a971ea82d3c260e2aa245c71a..e95329762c3ee4e5e8754ac8aed0ce0067dff05d 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 a1163498b03de22d18e44db2ae41dffbf7f81593..d6bad82a8cbffb3b4c69f751ee909be31084abbc 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 59c39a3423ea01cb076e901c72d879ebe915972b..03ed01540709a1fb6edcb1065489c37bd8aa059b 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 99e1485f70d72a4868b37903a5a9a12c5ffcbb81..12bdb7804cce234b8fa97a7b41ae9b086676ab15 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 662dd05575cf0bcaebe87211a0282c7dd75fbab8..02f7ecb58926a61c6fbaebcde8bf5eee9bff39e5 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 9f9b9809e19bdb091c499ff8b09e0ec7e4e62a22..411357f33a4d68c8408217f2f70cb24e47fde2f0 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 1c0336815f68500b0eda523dc142f3c1d0e21fc1..d526e25a97a2c929e95873dd99bfb77700fc392d 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 5188926827ec8d413c8f10bde432e94884d246ce..97c1e74878e088c90f057b05b835f61378c63db6 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 eb75d4593abd557c23a986b6a2c1ad9cb1ad4712..dcf5e2fb620ca0ff8f175b45ac7a51f34781d8ac 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(); } }