diff --git a/src/components/ContentButton.vue b/src/components/ContentButton.vue index e3ea3cc70b1ea5c4c2559c113d882898307f979e..426a7b90c995370f326077acd0adee46179e35f2 100644 --- a/src/components/ContentButton.vue +++ b/src/components/ContentButton.vue @@ -2,11 +2,10 @@ defineProps<{ icon: string; - classList: object; - isActive: boolean; isDraggable: boolean; + classList?: object; subtitle?: string; - isBlue?: boolean + isActive?: boolean; }>(); const emit = defineEmits<{ @@ -21,8 +20,8 @@ function click(event) { <template> <div class="btn btn-content" - :class="[classList, { active: isActive }, { 'draggable': isDraggable }, { 'btn-content-blue': isBlue }]" - :draggable="isDraggable" + :class="[classList, { 'draggable': isDraggable }, { 'active': isActive }]" + :draggable="isDraggable" @click.stop="click($event)" > <i :class="icon" /> diff --git a/src/components/ValidationModal.vue b/src/components/ValidationModal.vue index 8bb38b24c65b9483b2baa4520b3c7de84dc85c9c..09de9734138951f21d60e8180d63a8a301c3010d 100644 --- a/src/components/ValidationModal.vue +++ b/src/components/ValidationModal.vue @@ -4,16 +4,24 @@ import { useEditorStore } from '../shared/stores'; const editorStore = useEditorStore(); +const modalScreen = ref(null); + + onMounted(() => { modalScreen.value.focus(); }); -const modalScreen = ref(null); </script> <template> - <div ref="modalScreen" class="modal-backdrop" tabindex="0" @keyup.enter="editorStore.deleteSelectedNodes" @keyup.escape="editorStore.validationModal = false"> + <div + ref="modalScreen" + class="modal-backdrop" + tabindex="0" + @keyup.enter="editorStore.deleteSelectedNodes" + @keyup.escape="editorStore.validationModal = false" + > <div class="modal"> <h3>Souhaitez-vous vraiment supprimer cet élément ?</h3> <button class="btn btn-close" @click="editorStore.validationModal = false"><i class="icon-x"></i></button> diff --git a/src/features/ePocFlow/ePocFlow.vue b/src/features/ePocFlow/ePocFlow.vue index 6fe9810a8b99496fd69efa17176c03a77eaff071..aeedea7b1e3412cbf93e3ff307410249009d1bec 100644 --- a/src/features/ePocFlow/ePocFlow.vue +++ b/src/features/ePocFlow/ePocFlow.vue @@ -87,9 +87,9 @@ function update(event) { } function nodeChange(event) { - if(event[0].type === 'remove') { - editorStore.closeFormPanel(); - } + const { type } = event[0]; + + if(type === 'remove') editorStore.closeFormPanel(); } </script> @@ -139,10 +139,4 @@ function nodeChange(event) { /> </template> </VueFlow> -</template> - -<style scoped lang="scss"> -.node { - margin: auto; -} -</style> \ No newline at end of file +</template> \ No newline at end of file diff --git a/src/features/ePocFlow/nodes/ChapterNode.vue b/src/features/ePocFlow/nodes/ChapterNode.vue index 9502eef2deec13e14242c613e440e898028e7476..61576d3be271e0d8dbaa09edec545ecb7e7b32b2 100644 --- a/src/features/ePocFlow/nodes/ChapterNode.vue +++ b/src/features/ePocFlow/nodes/ChapterNode.vue @@ -1,9 +1,9 @@ <script setup lang="ts"> -import ContentButton from '@/src/components/ContentButton.vue'; import { useEditorStore } from '@/src/shared/stores'; import { Handle, useVueFlow, getConnectedEdges } from '@vue-flow/core'; import { Position } from '@vue-flow/core'; import { computed } from 'vue'; +import ContentButton from '@/src/components/ContentButton.vue'; const editorStore = useEditorStore(); @@ -18,31 +18,40 @@ const props = defineProps<{ const { findNode, nodes, edges } = useVueFlow({ id: 'main' }); -const node = findNode(props.id); - -function openForm() { - editorStore.openFormPanel(node.id, node.data.formType, node.data.formValues); -} +const currentNode = findNode(props.id); const subtitle = computed(() => { const chapters = nodes.value.filter(node => node.type === 'chapter'); const epocNode = findNode('1'); - let label = epocNode.data.formValues.chapterParameter ? epocNode.data.formValues.chapterParameter : 'Chapitre'; - label = label.length > 8 ? label.substring(0, 7) + '...' : label; - return `${label} ${chapters.findIndex(chapter => chapter.id === node.id) + 1}`; + const chapterParameter = epocNode?.data?.formValues?.chapterParameter || 'Chapitre'; + const label = chapterParameter.length > 8 ? chapterParameter.substring(0, 7) + '...' : chapterParameter; + const chapterIndex = chapters.findIndex(chapter => chapter.id === currentNode.id) + 1; + + return `${label} ${chapterIndex}`; }); -const isSource = computed(() => getConnectedEdges([node], edges.value).some((edge) => edge.sourceNode.id === props.id)); +const isSource = computed(() => getConnectedEdges([currentNode], edges.value).some((edge) => edge.sourceNode.id === props.id)); + +const classList = { + 'clickable': true, + 'btn-content-node': true, + 'btn-content-large': true, +}; + + +function openForm() { + editorStore.openFormPanel(currentNode.id, currentNode.data.formType, currentNode.data.formValues); +} </script> <template> <div> <ContentButton - :icon="node.data.action.icon" - :is-active="editorStore.openedNodeId ? editorStore.openedNodeId === node.id : false" + :icon="currentNode.data.action.icon" :is-draggable="false" - :class-list="{ 'btn-content-blue' : false, 'clickable': true, 'btn-content-node': true, 'btn-content-large': true }" + :class-list="classList" + :is-active="editorStore.openedElementId ? editorStore.openedElementId === currentNode.id : false" :subtitle="subtitle" @click="openForm()" @mousedown="editorStore.closeFormPanel()" diff --git a/src/features/ePocFlow/nodes/PageNode.vue b/src/features/ePocFlow/nodes/PageNode.vue index 822b7fe80bd92402d2ed419dd455c9cf0b1d1089..f740d39c66e345e65a6a460dfbf1dd17f5c41561 100644 --- a/src/features/ePocFlow/nodes/PageNode.vue +++ b/src/features/ePocFlow/nodes/PageNode.vue @@ -1,9 +1,9 @@ <script setup lang="ts"> import { Handle, Position, getConnectedEdges, useVueFlow } from '@vue-flow/core'; -import ContentButton from '@/src/components/ContentButton.vue'; import { computed, ref } from 'vue'; import { useEditorStore } from '@/src/shared/stores'; import { NodeElement } from '@/src/shared/interfaces'; +import ContentButton from '@/src/components/ContentButton.vue'; const editorStore = useEditorStore(); @@ -12,19 +12,38 @@ const props = defineProps<{ data: { type: object; required: true; - readyToDrop: boolean; elements: NodeElement[]; isSource: boolean; isTarget: boolean; } }>(); - const { findNode, edges } = useVueFlow({ id: 'main' }); -const node = findNode(props.id); +const currentNode = findNode(props.id); const dropped = ref(false); +const isSource = computed(() => getConnectedEdges([currentNode], edges.value).some((edge) => edge.source === props.id)); +const isTarget = computed(() => getConnectedEdges([currentNode], edges.value).some((edge) => edge.target === props.id)); + +const classList = { + 'clickable': true, + 'btn-content-node': true, +}; + +const isCondition = ref(currentNode.data.type === 'condition'); + +const dragOptions = ref({ + group: { + name: 'node', + put: !isCondition.value, + }, + filter: '.condition', + sort: !isCondition.value, + ghostClass: 'ghost', +}); + + function openForm(element: NodeElement) { editorStore.openFormPanel(element.id, element.formType, element.formValues, element.parentId); } @@ -52,36 +71,41 @@ function dragOver(event) { } function change(event) { - if(event.added && dropped.value) { + const { added, moved, removed } = event; + const { data } = currentNode; + + if(added && dropped.value) { + const { element } = added; let newElement: NodeElement; - if(event.added.element.action) { - newElement = event.added.element; - newElement.parentId = props.id; + + if(element.action) { + newElement = { ...element, parentId: props.id }; } else { newElement = { id: editorStore.generateId(), parentId: props.id, - action: event.added.element, - formType: event.added.element.type, - formValues: event.added.element.formValues, + action: element, + formType: element.type, + formValues: element.formValues, contentId: editorStore.generateContentId(), }; } - node.data.elements.splice(event.added.newIndex, 0, newElement); - dropped.value = false; - const action = event.added.element.action ? event.added.element.action : event.added.element; + data.elements.splice(added.newIndex, 0, newElement); + dropped.value = false; - editorStore.addElementToScreen(node.id, action, event.added.newIndex); + const action = element.action || element; + editorStore.addElementToPage(currentNode.id, action, added.newIndex); + } - } if(event.moved) { - const oldIndex = event.moved.oldIndex; - const newIndex = event.moved.newIndex; - + if(moved) { + const { oldIndex, newIndex } = moved; editorStore.changeElementOrder(oldIndex, newIndex, props.id); + } - } if(event.removed) { - editorStore.removeElementFromScreen(event.removed.oldIndex, props.id, true); + if(removed) { + const { oldIndex } = removed; + editorStore.removeElementFromScreen(oldIndex, props.id, true); } } @@ -89,18 +113,6 @@ function drop() { dropped.value = true; } -const isCondition = ref(node.data.type === 'condition'); - -const dragOptions = ref({ - group: { - name: 'node', - put: !isCondition.value, - }, - filter: '.condition', - sort: !isCondition.value, - ghostClass: 'ghost', -}); - function dragStart(event, element: NodeElement, index: number) { event.dataTransfer.dropEffect= 'move'; event.dataTransfer.effectAllowed= 'move'; @@ -112,21 +124,18 @@ function closeFormPanel() { editorStore.closeFormPanel(); } -const isSource = computed(() => getConnectedEdges([node], edges.value).some((edge) => edge.source === props.id)); -const isTarget = computed(() => getConnectedEdges([node], edges.value).some((edge) => edge.target === props.id)); - </script> <template> <div> <div class="container" - @click.exact="openPageForm(node.id, node.data.formType, node.data.formValues)" + @click.exact="openPageForm(currentNode.id, currentNode.data.formType, currentNode.data.formValues)" @click.meta="closeFormPanel" @click.ctrl="closeFormPanel" @mousedown="closeFormPanel" > - <p class="node-title" :class="{ 'active': editorStore.openedNodeId ? editorStore.openedNodeId === props.id : false }">{{ node.data.formValues?.title || 'Page' }}</p> + <p class="node-title" :class="{ 'active': editorStore.openedElementId ? editorStore.openedElementId === props.id : false }">{{ currentNode.data.formValues?.title || 'Page' }}</p> <Handle :class="{ 'not-connected': !isTarget }" type="target" @@ -135,7 +144,7 @@ const isTarget = computed(() => getConnectedEdges([node], edges.value).some((edg /> <div :id="'node'+ props.id" - :class=" { 'active': editorStore.openedNodeId ? editorStore.openedNodeId === props.id : false }" + :class=" { 'active': editorStore.openedElementId ? editorStore.openedElementId === props.id : false }" class="node" @dragover="dragOver($event)" @dragleave="dragLeave($event)" @@ -156,9 +165,9 @@ const isTarget = computed(() => getConnectedEdges([node], edges.value).some((edg <ContentButton :key="index" :icon="element.action.icon" - :is-active="editorStore.openedNodeId ? editorStore.openedNodeId === element.id : false" :is-draggable="!isCondition" - :class-list="{ 'btn-content-blue' : false, 'clickable': true, 'btn-content-node': true }" + :is-active="editorStore.openedElementId ? editorStore.openedElementId === element.id : false" + :class-list="classList" @click.exact="openForm(element)" @click.meta="closeFormPanel" @click.ctrl="closeFormPanel" diff --git a/src/features/ePocFlow/nodes/ePocNode.vue b/src/features/ePocFlow/nodes/ePocNode.vue index 3bc2a294fe78dbd9839331a5806bb635d5258dc1..44800729e530364832f0374a764328236c76c5d6 100644 --- a/src/features/ePocFlow/nodes/ePocNode.vue +++ b/src/features/ePocFlow/nodes/ePocNode.vue @@ -1,7 +1,7 @@ <script setup lang="ts"> -import ContentButton from '@/src/components/ContentButton.vue'; import { useEditorStore } from '@/src/shared/stores'; import { useVueFlow } from '@vue-flow/core'; +import ContentButton from '@/src/components/ContentButton.vue'; const editorStore = useEditorStore(); const props = defineProps<{ @@ -14,10 +14,17 @@ const props = defineProps<{ const { findNode } = useVueFlow({ id: 'main' }); -const node = findNode(props.id); +const currentNode = findNode(props.id); + +const classList = { + 'clickable': true, + 'btn-content-node': true, + 'btn-content-large': true, +}; + function openForm() { - editorStore.openFormPanel(node.id, node.data.formType, node.data.formValues); + editorStore.openFormPanel(currentNode.id, currentNode.data.formType, currentNode.data.formValues); } </script> @@ -25,10 +32,10 @@ function openForm() { <template> <div> <ContentButton - :icon="node.data.action.icon" - :is-active="editorStore.openedNodeId ? editorStore.openedNodeId === node.id : false" + :icon="currentNode.data.action.icon" :is-draggable="false" - :class-list="{ 'btn-content-blue' : false, 'clickable': true, 'btn-content-node': true, 'btn-content-large': true }" + :class-list="classList" + :is-active="editorStore.openedElementId ? editorStore.openedElementId === currentNode.id : false" subtitle="ePoc" @click="openForm()" @mousedown="editorStore.closeFormPanel()" diff --git a/src/features/forms/FormPanel.vue b/src/features/forms/FormPanel.vue index 597240795f9e20be589773a635de950fe8d7b718..1af42e8efd4bf817e8f987cc7fa5e5fb48858ca2 100644 --- a/src/features/forms/FormPanel.vue +++ b/src/features/forms/FormPanel.vue @@ -4,19 +4,16 @@ import FormButton from './components/FormButton.vue'; import GenericField from './components/GenericField.vue'; import { Input } from '@/src/shared/interfaces'; import { projectService, editorService } from '@/src/shared/services'; -import { useVueFlow } from '@vue-flow/core'; import { createToaster } from '@meforma/vue-toaster'; +const editorStore = useEditorStore(); + const toaster = createToaster({ duration: 1000, queue: true }); -const editorStore = useEditorStore(); - -const { findNode } = useVueFlow({ id: 'main' }); - -const node = editorStore.openedParentId ? findNode(editorStore.openedParentId) : findNode(editorStore.openedNodeId); +const currentNode = editorStore.getCurrentGraphNode; function actionOnForm(action: string) { switch (action) { @@ -42,7 +39,7 @@ function actionOnForm(action: string) { break; case 'save-model': - if(editorStore.savePageModel(node.data.elements.map((element) => element.action))) toaster.success('Modèle sauvegardé 👌'); + if(editorStore.savePageModel(currentNode.data.elements.map((element) => element.action))) toaster.success('Modèle sauvegardé 👌'); else toaster.error('Le modèle existe déjà 🤔'); break; } diff --git a/src/features/forms/components/FormButton.vue b/src/features/forms/components/FormButton.vue index 389255485f67aa661fd6b963dcbd467975e511b2..42d47b7b0a95e70966f38c715671b9f01256d4eb 100644 --- a/src/features/forms/components/FormButton.vue +++ b/src/features/forms/components/FormButton.vue @@ -12,7 +12,11 @@ const emit = defineEmits<{ </script> <template> - <button class="btn btn-form" :class="{ 'btn-delete' : label === 'Supprimer' }" @click="emit('click')"> + <button + class="btn btn-form" + :class="{ 'btn-delete' : label === 'Supprimer' }" + @click="emit('click')" + > <i :class="icon"></i> {{ label }} </button> diff --git a/src/features/forms/components/GenericField.vue b/src/features/forms/components/GenericField.vue index 505b033e28a6a696e7a774475667e913a1f9558f..99390a93586b02fc0600688c9a01431ba2e92354 100644 --- a/src/features/forms/components/GenericField.vue +++ b/src/features/forms/components/GenericField.vue @@ -2,7 +2,6 @@ import { Input } from '@/src/shared/interfaces'; import GenericInput from './inputs/GenericInput.vue'; import { useEditorStore } from '@/src/shared/stores'; -import { useVueFlow } from '@vue-flow/core'; import { projectService } from '@/src/shared/services'; defineProps<{ @@ -12,88 +11,90 @@ defineProps<{ displayFieldIndex: boolean; }>(); -const { findNode } = useVueFlow({ id:'main' }); - const editorStore = useEditorStore(); -const node = editorStore.openedParentId ? findNode(editorStore.openedParentId) : findNode(editorStore.openedNodeId); +const currentNode = editorStore.getCurrentGraphNode; + +const getInputValue = (input) => { + const formValues = editorStore.openedParentId + ? (currentNode.data.elements.find(e => e.id === editorStore.openedElementId)?.formValues ?? {}) + : currentNode.data.formValues; + + return formValues[input.id] ?? input.value; +}; -function onInput(value, id) { - if(editorStore.openedParentId) { - const element = node.data.elements.find(element => element.id === editorStore.openedNodeId); - element.formValues[id] = value; - } else { - node.data.formValues[id] = value; - } +function onInput(value: string, id: string) { + const element = editorStore.openedParentId + ? currentNode.data.elements.find(e => e.id === editorStore.openedElementId) + : currentNode.data; + + element.formValues[id] = value; projectService.writeProjectData(); } -function onRepeatInput(value, id) { - let element = null; - if(editorStore.openedParentId) { - element = node.data.elements.find(element => element.id === editorStore.openedNodeId); - } +function onRepeatInput(value, id: string) { + const element = editorStore.openedParentId + ? currentNode.data.elements.find(e => e.id === editorStore.openedElementId) + : null; + + + switch(value.type) { - if(value.type === 'add') { - if(element) { - if(!element.formValues[id]) element.formValues[id] = []; + case 'add': + { + const formValues = element ? element.formValues : currentNode.data.formValues; + if(!formValues[id]) formValues[id] = []; + formValues[id].push(value.defaultValues); - element.formValues[id].push(value.defaultValues); - } else { - if(!node.data.formValues[id]) node.data.formValues[id] = []; + break; + } - node.data.formValues[id].push(value.defaultValues); - } - } else if(value.type === 'remove') { - element ? element.formValues[id].splice(value.index, 1) : node.data.formValues[id].splice(value.index, 1); + case 'remove': + { + const formValues = element ? element.formValues[id] : currentNode.data.formValues[id]; + formValues.splice(value.index, 1); - if (node.data.elements && node.data.elements[value.index] && !editorStore.openedParentId) { - const nodeToDelete = node.data.elements[value.index]; - editorStore.deleteElement( nodeToDelete.id, nodeToDelete.parentId); + const currentElement = currentNode.data.elements?.[value.index]; + if(!editorStore.openedParentId && currentElement) { + editorStore.deleteElement(currentElement.id, currentElement.parentId); } - } else if(value.type === 'move') { - if (node.data.elements && !editorStore.openedParentId) { - editorStore.changeElementOrder(value.oldIndex, value.newIndex, node.id); + + break; + } + + case 'move': + if(currentNode.data.elements && !editorStore.openedParentId) { + editorStore.changeElementOrder(value.oldIndex, value.newIndex, currentNode.id); } else { - if(element) { - const item = element.formValues[id].splice(value.oldIndex, 1); - element.formValues[id].splice(value.newIndex, 0, item[0]); - } else { - const tmp = node.data.formValues[id][value.oldIndex]; - node.data.formValues[id][value.oldIndex] = node.data.formValues[id][value.newIndex]; - node.data.formValues[id][value.newIndex] = tmp; - } + const items = element ? element.formValues[id] : currentNode.data.formValues[id]; + const item = items.splice(value.oldIndex, 1); + items.splice(value.newIndex, 0, item[0]); } - } else if(value.type === 'change') { + + break; + + case 'change': + { + const formValues = element ? element.formValues : currentNode.data.formValues; + if(value.id === '') { - element ? element.formValues[id][value.index] = value.value : node.data.formValues[id][value.index] = value.value; + formValues[value.index] = value.value; } else { - if(element) { - if(!element.formValues[id]) element.formValues[id] = {}; - if(!element.formValues[id][value.index]) element.formValues[id][value.index] = {}; - - element.formValues[id][value.index][value.id] = value.value; - } else { - if(!node.data.formValues[id][value.index]) node.data.formValues[id][value.index] = {}; - - node.data.formValues[id][value.index][value.id] = value.value; - } + if(!formValues[id]) formValues[id] = {}; + if(!formValues[id][value.index]) formValues[id][value.id] = {}; + + formValues[id][value.index][value.id] = value.value; } - } - projectService.writeProjectData(); -} -function getInputValue(input) { - if(editorStore.openedParentId) { - const element = node.data.elements.find(element => element.id === editorStore.openedNodeId); + break; + } + + default: break; + } - if(!element.formValues) element.formValues = {}; + projectService.writeProjectData(); - return element.formValues[input.id] ? element.formValues[input.id] : input.value; - } else { - return node.data.formValues[input.id] ? node.data.formValues[input.id] : input.value; - } } </script> diff --git a/src/features/forms/components/inputs/QuillEditor.vue b/src/features/forms/components/inputs/QuillEditor.vue index b9baca305de6e370135aa56efe3ff2d6a66df841..7c6bec8a0de8c6c4bada094bbe106e491797e7f6 100644 --- a/src/features/forms/components/inputs/QuillEditor.vue +++ b/src/features/forms/components/inputs/QuillEditor.vue @@ -18,19 +18,33 @@ const emit = defineEmits<{ }>(); const toolbar = [ - ['bold', 'italic', 'underline', { 'list': 'ordered' }, { 'list': 'bullet' }, { 'align': null}, {'align': 'center'}, {'align': 'right'}, 'link', 'image'] + [ + 'bold', + 'italic', + 'underline', + { 'list': 'ordered' }, + { 'list': 'bullet' }, + { 'align': null}, + {'align': 'center'}, + {'align': 'right'}, + 'link', + 'image' + ] ]; const modules = { name: 'imageUploader', module: ImageUploader, options: { - upload: (file) => { - return projectService.importFile(file.path); - } + upload: (file) => projectService.importFile(file.path) } }; +const qlEditor = ref(null); + +const content: Ref<string> = ref(null); + + function textChange() { emit('input', content.value); } @@ -39,10 +53,6 @@ function initQuill() { content.value = props.inputValue; } -const qlEditor = ref(null); - -const content: Ref<string> = ref(null); - watch( () => props.inputValue, () => { diff --git a/src/features/forms/components/inputs/RepeatInput.vue b/src/features/forms/components/inputs/RepeatInput.vue index 8ed169f167092aac911d336598643e333bc28d90..4a10ee4fb05dacc2dd47d4633569b1540a925983 100644 --- a/src/features/forms/components/inputs/RepeatInput.vue +++ b/src/features/forms/components/inputs/RepeatInput.vue @@ -4,8 +4,6 @@ import GenericInput from './GenericInput.vue'; import AddCard from './card/AddCard.vue'; import { Input } from '@/src/shared/interfaces'; import { ref } from 'vue'; -import { useVueFlow } from '@vue-flow/core'; - const props = defineProps<{ inputs: Input[]; @@ -19,11 +17,21 @@ const emit = defineEmits<{ (e: 'change', value: object | string): void; }>(); -const { findNode } = useVueFlow({ id: 'main' }); const editorStore = useEditorStore(); -const node = editorStore.openedParentId ? findNode(editorStore.openedParentId) : findNode(editorStore.openedNodeId); -const disabled = node.data.type === 'template'; +const currentNode = editorStore.getCurrentGraphNode; +const disabled = currentNode.data.type === 'template'; + +const dragging = ref(false); + +const dragOptions = { + animation: 200, + group: 'cards', + disabled: false, + ghostClass: 'ghost', +}; + +const isLast = (index) => props.inputValues.length - 1 === index; function onInput(value, id, index) { emit('change', { @@ -35,15 +43,16 @@ function onInput(value, id, index) { } function addCard() { - emit('change', { - type: 'add', - defaultValues: props.inputs.length === 1 ? props.inputs[0].value : { - ...props.inputs.reduce((defaultValues, input) => { - defaultValues[input['id']] = input['type'] === 'hidden' ? editorStore.generateContentId() : input['value']; - return defaultValues; - }, {}) - } - }); + const defaultValues = props.inputs.length === 1 + ? props.inputs[0].value + : props.inputs.reduce((defaultValues, input) => { + defaultValues[input.id] = input.type === 'hidden' + ? editorStore.generateContentId() + : input.value; + return defaultValues; + }, {}); + + emit('change', { type: 'add', defaultValues }); } function removeCard(index) { @@ -70,23 +79,12 @@ function onCheck(value, id, index) { }); } -function isLast(index) { - return props.inputValues.length - 1 === index; -} - -const dragOptions = { - animation: 200, - group: 'cards', - disabled: false, - ghostClass: 'ghost', -}; - -const drag = ref(false); - function onClick(index, action) { - if (node.data.elements && node.data.elements[index] && action) { - const element = node.data.elements[index]; - editorStore.openFormPanel(element.id, element.formType, element.formValues, element.parentId); + const element = currentNode.data.elements?.[index]; + + if(element && action) { + const { id, formType, formValues, parentId } = element; + editorStore.openFormPanel(id, formType, formValues, parentId); } } @@ -102,12 +100,16 @@ function onClick(index, action) { filter=".fixed" :disabled="disabled" @change="moveCard($event.moved.oldIndex, $event.moved.newIndex)" - @start="drag = true" - @end="drag = false" + @start="dragging = true" + @end="dragging = false" > <template #item="{ element, index }"> <div :key="index" class="card draggable-card"> - <div class="card-header" :class="{ 'border-bottom': inputs.length >= 1, 'clickable': element.action }" @click="onClick(index, element.action)"> + <div + class="card-header" + :class="{ 'border-bottom': inputs.length >= 1, 'clickable': element.action }" + @click="onClick(index, element.action)" + > <div v-if="element.action" class="component-container"> <div class="form-icon"><i :class="element.action.icon"></i></div> <h3>{{ element.action.label }}</h3> diff --git a/src/features/forms/components/inputs/ScoreInput.vue b/src/features/forms/components/inputs/ScoreInput.vue index 88e304ed9d10b0faa7916fdfc2de4dadba7212c9..c1be3016fcb4f3b9b2935783652e7c2caac5def8 100644 --- a/src/features/forms/components/inputs/ScoreInput.vue +++ b/src/features/forms/components/inputs/ScoreInput.vue @@ -9,13 +9,13 @@ const emit = defineEmits<{ }>(); function minus(inputValue: string) { - const value = parseInt(inputValue) - 1; - emit('input', value.toString()); + const value = Number(inputValue) - 1; + emit('input', `${value}`); } function plus(inputValue: string) { - const value = parseInt(inputValue) + 1; - emit('input', value.toString()); + const value = Number(inputValue) + 1; + emit('input', `${value}`); } </script> diff --git a/src/features/forms/components/inputs/card/components/SelectInput.vue b/src/features/forms/components/inputs/card/components/SelectInput.vue index 1f7246117658897354efafc0fa5f60dc1e3831ab..bf1c1864e8adcbc89a70395854da21f7619bea92 100644 --- a/src/features/forms/components/inputs/card/components/SelectInput.vue +++ b/src/features/forms/components/inputs/card/components/SelectInput.vue @@ -1,5 +1,4 @@ <script setup lang="ts"> -import { useVueFlow } from '@vue-flow/core'; import { useEditorStore } from '@/src/shared/stores'; const props = defineProps<{ @@ -14,16 +13,13 @@ const emit = defineEmits<{ (e: 'change', value: string): void; }>(); -const { findNode } = useVueFlow({ id: 'main' }); const editorStore = useEditorStore(); -const node = editorStore.openedParentId ? findNode(editorStore.openedParentId) : findNode(editorStore.openedNodeId); -const element = node.data.elements.find(e => e.id === editorStore.openedNodeId); +const currentNode = editorStore.getCurrentGraphNode; +const currentElement = currentNode.data.elements.find(e => e.id === editorStore.openedElementId); +const getOptions = () => props.linkedOptions ? currentElement.formValues[props.linkedOptions] : props.options; -function getOptions() { - return props.linkedOptions ? element.formValues[props.linkedOptions] : props.options; -} </script> diff --git a/src/features/sideBar/components/PageTemplate.vue b/src/features/sideBar/components/PageTemplate.vue index baf908b72beea2586f0189f1f62cd35387b3622c..62782995ae7e1bee2fd268ef6b4551ea04dd760d 100644 --- a/src/features/sideBar/components/PageTemplate.vue +++ b/src/features/sideBar/components/PageTemplate.vue @@ -37,8 +37,6 @@ function dragStart(event, elements) { v-for="(element, index) in elements" :key="index" :icon="element.icon" - :class-list="{ 'btn-content-blue': false }" - :is-active="false" :is-draggable="false" /> </div> diff --git a/src/features/sideBar/components/SideActions.vue b/src/features/sideBar/components/SideActions.vue index 1da4140d76ad739bc933dbdfacef06584a1b039e..95428b9f15b67823cb36bb86a4bd03bfaebcdea3 100644 --- a/src/features/sideBar/components/SideActions.vue +++ b/src/features/sideBar/components/SideActions.vue @@ -4,13 +4,13 @@ import { ref } from 'vue'; import ContentButton from '@/src/components/ContentButton.vue'; import { useEditorStore } from '@/src/shared/stores'; - const editorStore = useEditorStore(); -const standardContent = editorStore.standardScreens.filter((item) => item.type !== 'condition' && item.type !== 'question' && item.type !== 'model'); -const questionContent = editorStore.standardScreens.find((item) => item.type === 'question'); -const conditionContent = editorStore.standardScreens.find((item) => item.type === 'condition'); -const modelContent = editorStore.standardScreens.find((item) => item.type === 'model'); +const { standardScreens } = editorStore; +const standardContent = standardScreens.filter(({ type }) => !['condition', 'question', 'model'].includes(type)); +const questionContent = standardScreens.find(({ type }) => type === 'question'); +const conditionContent = standardScreens.find(({ type }) => type === 'condition'); +const modelContent = standardScreens.find(({ type }) => type === 'model'); const dragging = ref(false); @@ -25,11 +25,7 @@ const dragOptions = { ghostClass: 'ghost' }; -const classList = (item: SideAction) => { - return { - 'clickable': item.type === 'question' || item.type === 'model', - }; -}; +const classList = (item: SideAction) => ({ 'clickable': item.type === 'question' || item.type === 'model' }); function dragStart(event, sideAction) { event.dataTransfer.dropEffect= 'move'; @@ -39,7 +35,7 @@ function dragStart(event, sideAction) { } function showQuestionsMenu() { - editorStore.floatingMenu = !editorStore.floatingMenu; + editorStore.questionMenu = !editorStore.questionMenu; } function showTemplateMenu() { @@ -62,9 +58,8 @@ function showTemplateMenu() { :key="index" v-tippy="{content: element.tooltip, placement: 'right', arrow : true, arrowType : 'round', animation : 'fade'}" :icon="element.icon" - :class-list="{ 'btn-content-blue': true }" - :is-active="false" :is-draggable="true" + :class-list="{ 'btn-content-blue': true }" @dragstart="dragStart($event, element)" /> </div> @@ -74,12 +69,12 @@ function showTemplateMenu() { <ContentButton v-tippy="{content: questionContent.tooltip, placement: 'right', arrow : true, arrowType : 'round', animation : 'fade'}" :icon="questionContent.icon" - :class-list="classList(questionContent)" - :is-active="editorStore.floatingMenu" :is-draggable="false" + :class-list="classList(questionContent)" + :is-active="editorStore.questionMenu" @click="showQuestionsMenu" /> - <div v-if="editorStore.floatingMenu" class="floating-menu" @click.stop> + <div v-if="editorStore.questionMenu" class="floating-menu" @click.stop> <div class="arrow-wrapper"> <div class="arrow"> </div> @@ -98,7 +93,6 @@ function showTemplateMenu() { v-tippy="{content: element.label, placement: 'right', arrow : true, arrowType : 'round', animation : 'fade'}" :icon="element.icon" :class-list="{ 'btn-content-blue': true }" - :is-active="false" :is-draggable="true" @dragstart="dragStart($event, element)" /> @@ -111,7 +105,6 @@ function showTemplateMenu() { v-tippy="{content: conditionContent.tooltip, placement: 'right', arrow : true, arrowType : 'round', animation : 'fade'}" :icon="conditionContent.icon" :class-list="{ 'btn-content-blue': true }" - :is-active="false" :is-draggable="true" @dragstart="dragStart($event, conditionContent)" @dragend="dragging = false" @@ -120,9 +113,9 @@ function showTemplateMenu() { <ContentButton v-tippy="{content: modelContent.tooltip, placement: 'right', arrow : true, arrowType : 'round', animation : 'fade'}" :icon="modelContent.icon" - :class-list="classList(modelContent)" - :is-active="editorStore.modelMenu" :is-draggable="false" + :is-active="editorStore.modelMenu" + :class-list="classList(modelContent)" @click="showTemplateMenu" /> </template> diff --git a/src/features/topBar/TopActionButton.vue b/src/features/topBar/TopActionButton.vue index d51e60a77bf7faa45b75e5a6f7b41bd22d260964..c0be608d94e34d40e4420f0286c5dac0f99aba25 100644 --- a/src/features/topBar/TopActionButton.vue +++ b/src/features/topBar/TopActionButton.vue @@ -3,7 +3,7 @@ defineProps<{ icon: string; text?: string; - textBefore?: string; + position?: 'left' | 'right'; disabled?: boolean; }>(); @@ -11,9 +11,9 @@ defineProps<{ <template> <button class="btn btn-top-bar" :class="{ 'btn-squared': !text }" :disabled="disabled"> - <span v-if="textBefore" class="text-top-bar zoom-span">{{ textBefore }}</span> + <span v-if="position === 'left'" class="text-top-bar zoom-span">{{ text }}</span> <i :class="icon" /> - <span v-if="text" class="text-top-bar">{{ text }}</span> + <span v-if="position === 'right'" class="text-top-bar">{{ text }}</span> </button> </template> diff --git a/src/features/topBar/TopBar.vue b/src/features/topBar/TopBar.vue index f52b2920ca1ad303be59da0e045ae0b58dc6ddd6..cdfe4d6868548a91c72efcf27965b5923864309d 100644 --- a/src/features/topBar/TopBar.vue +++ b/src/features/topBar/TopBar.vue @@ -9,12 +9,12 @@ import { useVueFlow } from '@vue-flow/core'; const editorStore = useEditorStore(); const { zoomTo, fitView, onViewportChangeEnd } = useVueFlow({ id: 'main' }); -const savedSince = ref(since(editorStore.currentProject.modified)); - editorStore.$subscribe(() => { savedSince.value = since(editorStore.currentProject.modified); }); +const savedSince = ref(since(editorStore.currentProject.modified)); + const zoom = ref(1); const zoomString = computed(() => zoom.value === 0 ? 'Ajuster' : `${Math.round(zoom.value * 100)}%`); @@ -47,6 +47,7 @@ function since(date) { setInterval(() => { savedSince.value = since(editorStore.currentProject.modified); }, 60000); + </script> <template> @@ -57,14 +58,14 @@ setInterval(() => { <small>Dernière sauvegarde : {{ savedSince }}</small> </div> <div class="top-bar-actions"> - <TopActionDropdown icon="icon-chevron" :text-before="zoomString" :input-value="zoom" @change="updateZoom" /> + <TopActionDropdown icon="icon-chevron" :text="zoomString" position="left" :input-value="zoom" @change="updateZoom" /> <hr class="vertical-separator"> <TopActionButton icon="icon-arriere" :disabled="editorStore.undoStack.length <= 0" @click="editorStore.undo" /> <TopActionButton icon="icon-avant" :disabled="editorStore.redoStack.length <= 0" @click="editorStore.redo" /> <hr class="vertical-separator"> - <TopActionButton icon="icon-save" text="Sauvegarder" :disabled="editorStore.saving" @click="editorService.saveEpocProject" /> - <TopActionButton icon="icon-play" text="Aperçu" :disabled="editorStore.loadingPreview" @click="editorService.runPreview()" /> - <TopActionButton icon="icon-export" text="Exporter archive" :disabled="editorStore.exporting" @click="editorService.exportProject()" /> + <TopActionButton icon="icon-save" text="Sauvegarder" position="right" :disabled="editorStore.saving" @click="editorService.saveEpocProject" /> + <TopActionButton icon="icon-play" text="Aperçu" position="right" :disabled="editorStore.loadingPreview" @click="editorService.runPreview()" /> + <TopActionButton icon="icon-export" text="Exporter archive" position="right" :disabled="editorStore.exporting" @click="editorService.exportProject()" /> </div> </div> </div> diff --git a/src/shared/data/form.data.ts b/src/shared/data/form.data.ts index a12609ec256b836b3be7d57e4728085fcafa3772..4a87c08f6e10b3279b6d670993e0f5bff6afc6cd 100644 --- a/src/shared/data/form.data.ts +++ b/src/shared/data/form.data.ts @@ -720,7 +720,6 @@ export const swipeForm: Form = { }, { name: 'Explication', - index: 5, inputs: [ { id: 'explanation', diff --git a/src/shared/interfaces/class-diagram.md b/src/shared/interfaces/class-diagram.md index 5ce5f7ed3607b722b859789e2edc68b5c18c0b5a..6ca02a1137aba9d3d3be7a2d0dcddcd66235372f 100644 --- a/src/shared/interfaces/class-diagram.md +++ b/src/shared/interfaces/class-diagram.md @@ -1,5 +1,5 @@ -## Data structure in the menu bar +<!-- ## Data structure in the menu bar ```mermaid classDiagram Screen -- SideAction : 1..* @@ -28,4 +28,4 @@ classDiagram Field -- Card: 0..* Card -- Input: 1..* -``` \ No newline at end of file +``` --> \ No newline at end of file diff --git a/src/shared/interfaces/field.interface.ts b/src/shared/interfaces/field.interface.ts index 1554b36b8280d2f73788a9afa1fd5cb59be83169..f611a4a070da44fc064d45bd3d632e321d4cfc82 100644 --- a/src/shared/interfaces/field.interface.ts +++ b/src/shared/interfaces/field.interface.ts @@ -2,8 +2,5 @@ import { Input } from '.'; export interface Field { name?: string; - index?: number; - type?: string; - inputType?: string; inputs: Input[]; } \ No newline at end of file diff --git a/src/shared/interfaces/index.ts b/src/shared/interfaces/index.ts index b12650d69d451fdb0ec1b784468e6310db01e113..0ae5d48d5c6ad87d56612c0163be9d930277ad39 100644 --- a/src/shared/interfaces/index.ts +++ b/src/shared/interfaces/index.ts @@ -1,6 +1,5 @@ export * from './ePocProject.interface'; export * from './sideAction.interface'; -export * from './screen.interface'; export * from './input.interface'; export * from './form.interface'; export * from './nodeElement.interface'; diff --git a/src/shared/interfaces/screen.interface.ts b/src/shared/interfaces/screen.interface.ts deleted file mode 100644 index 5dc91edc06ad66b058a7812af33f95d0655c46c8..0000000000000000000000000000000000000000 --- a/src/shared/interfaces/screen.interface.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { SideAction } from '.'; - -export interface Screen { - title: string; - actions: SideAction[]; -} \ No newline at end of file diff --git a/src/shared/services/editor.service.ts b/src/shared/services/editor.service.ts index 34845ecbbf0d6bdaa62d29faf68144bfb51b15d1..5342234cabb997dbc99937b4d3ac87f174161d74 100644 --- a/src/shared/services/editor.service.ts +++ b/src/shared/services/editor.service.ts @@ -171,8 +171,8 @@ function runPreview(): void { function runPreviewAtPage(): void { waitingToast('🔠Démarrage de la prévisualisation...'); - const openedNodeId = editorStore.openedParentId ? editorStore.openedParentId : editorStore.openedNodeId; - const openedNode = projectStore.elements.find(e => e.id === openedNodeId); + const openedElementId = editorStore.openedParentId ? editorStore.openedParentId : editorStore.openedElementId; + const openedNode = projectStore.elements.find(e => e.id === openedElementId); let contentPath; let error; if (openedNode) { diff --git a/src/shared/stores/editorStore.ts b/src/shared/stores/editorStore.ts index a192b578621c24b6f44098b2a3713ff40dca1cce..9d09c533fdf52430d6ecd89d7ec2040ef2f57119 100644 --- a/src/shared/stores/editorStore.ts +++ b/src/shared/stores/editorStore.ts @@ -1,7 +1,7 @@ import { defineStore } from 'pinia'; -import { ePocProject, Form, FormButton, NodeElement, PageModel, Screen, SideAction } from '@/src/shared/interfaces'; +import { ePocProject, Form, FormButton, NodeElement, PageModel, SideAction } from '@/src/shared/interfaces'; import { nextTick, toRaw, watch } from 'vue'; -import { applyEdgeChanges, applyNodeChanges, useVueFlow } from '@vue-flow/core'; +import { applyEdgeChanges, applyNodeChanges, GraphNode, useVueFlow } from '@vue-flow/core'; import { formsModel, questions, standardScreen } from '@/src/shared/data/form.data'; @@ -16,12 +16,12 @@ interface EditorState { saving: boolean; loadingPreview: boolean; exporting:boolean; - floatingMenu: boolean; + questionMenu: boolean; modelMenu: boolean; pageModels: PageModel[]; validationModal: boolean; formPanel: Form; - openedNodeId: uid | null; + openedElementId: uid | null; openedParentId: uid | null; questions: SideAction[]; standardScreens: SideAction[]; @@ -37,12 +37,12 @@ export const useEditorStore = defineStore('editor', { saving: false, loadingPreview: false, exporting: false, - floatingMenu: false, + questionMenu: false, modelMenu: false, pageModels: [], validationModal: false, formPanel: null, - openedNodeId: null, + openedElementId: null, openedParentId: null, questions: questions, standardScreens: standardScreen, @@ -51,42 +51,36 @@ export const useEditorStore = defineStore('editor', { }), getters: { - //* These getters are used to place the separator - getSideActionsFirstPart(): SideAction[] { - return this.sideActions.slice(0, -1); - }, - getSideActionsLastPart(): SideAction[] { - return this.sideActions.slice(-1); - }, - //This function is a part of the one used in ePocStore - getSelectedScreens(): Screen[] { - return this.standardScreens; - }, + getCurrentGraphNode(): GraphNode | null { + const nodeId = this.openedParentId ?? this.openedElementId; + return findNode(nodeId); + } }, actions: { dismissModals(): void { - this.floatingMenu = false; + this.questionMenu = false; this.modelMenu = false; }, + openFormPanel(id: string, formType: string, formValues, parentId?: string): void { - this.openedNodeId = id; + this.openedElementId = id; this.openedParentId = parentId; - this.formPanel = null; + //? To be sure the view is notified of closing / reopening + this.formPanel = null; setTimeout(() => { this.formPanel = structuredClone(formsModel.find(form => form.type === formType)); }); - nodes.value.forEach((node) => { - if(node.id !== this.openedNodeId) { - node.selected = false; - } - }); + + nodes.value.forEach(node => node.selected = node.id === this.openedElementId); }, + closeFormPanel(): void { this.formPanel = null; - this.openedNodeId = null; + this.openedElementId = null; }, + //generate id of format 'aaaaaaaa'-'aaaa'-'aaaa'-'aaaa'-'aaaaaaaaaaaa' generateId(): uid { const s4 = () => { @@ -96,183 +90,187 @@ export const useEditorStore = defineStore('editor', { }; return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4(); }, + //return a copy of the form linked to the type getForm(type: string): Form { return structuredClone(toRaw(formsModel.find(form => form.type === type))); }, + deleteElement(id: string, parentId?: string): void { const nodeToDelete = findNode(id); - if(parentId || !nodeToDelete){ - const parentNode = findNode(parentId ? parentId : this.openedParentId); + + if(parentId || !nodeToDelete) { + const parentNode = findNode(parentId ?? this.openedParentId); if(parentNode) { parentNode.data.elements.forEach((value, index) => { if(value.id === id) { - this.removeElementFromScreen(index, parentId ? parentId : this.openedParentId); + this.removeElementFromScreen(index, parentId ?? this.openedParentId); } }); } } else { - applyNodeChanges( - [{ id: nodeToDelete.id, type: 'remove' }], - nodes.value - ); + applyNodeChanges([{ id: nodeToDelete.id, type: 'remove' }], nodes.value); + if(nodeToDelete.type === 'chapter') { const chapters = nodes.value.filter(node => node.type === 'chapter'); - for(const chapter of chapters) { - if(chapter.id > nodeToDelete.id) { - chapter.position = {x: 0, y: chapter.position.y - 200}; - chapter.data.title = 'Chapitre ' + (Number(chapter.data.title.split(' ')[1]) - 1); - } - } + chapters.forEach(chapter => { + if(chapter.id <= nodeToDelete.id) return; + + chapter.position = { x: 0, y: chapter.position.y - 200 }; + chapter.data.title = `Chapitre ${Number(chapter.data.title.split(' ')[1]) - 1}`; + }); } } this.closeFormPanel(); }, - isNodeDeletable(id: string) { - return id !== '1' && id !== '2'; + + isNodeDeletable(id: string): boolean { + const undeletableIds= ['1', '2']; + return !undeletableIds.includes(id); }, + deleteSelectedNodes(): void { - const selectedNodes = nodes.value.filter(node => node.selected && node.id !== '1' && node.id !== '2'); + const selectedNodes = nodes.value.filter(node => node.selected && this.isNodeDeletable(node.id)); const isChild = Boolean(this.openedParentId); - - //? ChapterNode can't be selected but can be active - const activeNode = isChild ? findNode(this.openedParentId) : findNode(this.openedNodeId); - if (activeNode && (activeNode.id !== '1' && activeNode.id !== '2')) { - selectedNodes.push(activeNode); - } + //? Chapter nodes can't be selected but can be active + const activeNode = isChild ? findNode(this.openedParentId) : findNode(this.openedElementId); + if(activeNode && this.isNodeDeletable(activeNode.id)) selectedNodes.push(activeNode); + if(isChild) { - this.deleteElement(this.openedNodeId, this.openedParentId); + this.deleteElement(this.openedElementId, this.openedParentId); } else { - for(const node of selectedNodes) { - this.deleteElement(node.id); - } + selectedNodes.forEach(node => this.deleteElement(node.id)); } this.validationModal = false; }, + deleteValidation(): void { - const selectedNodes = nodes.value.filter(node => node.selected && node.id !== '1' && node.id !== '2'); + const selectedNodes = nodes.value.filter(node => node.selected && this.isNodeDeletable(node.id)); const isChild = Boolean(this.openedParentId); - - //? ChapterNode can't be selected but can be active - const activeNode = isChild ? findNode(this.openedParentId) : findNode(this.openedNodeId); - if (activeNode && (activeNode.id !== '1' && activeNode.id !== '2')) { - selectedNodes.push(activeNode); - } - + + //? Chapter nodes can't be selected but can be active + const activeNode = isChild ? findNode(this.openedParentId) : findNode(this.openedElementId); + if(activeNode && this.isNodeDeletable(activeNode.id)) selectedNodes.push(activeNode); + + const selectedEdges = edges.value.filter(edge => edge.selected); if(selectedNodes.length > 0 || isChild) { this.validationModal = true; - } else { - const selectedEdges = edges.value.filter(edge => edge.selected); - for(const edge of selectedEdges) { - applyEdgeChanges( - [{ id: edge.id, type: 'remove' }], - edges.value - ); - } + } else if(selectedEdges.length > 0) { + selectedEdges.forEach(edge => { + applyEdgeChanges([{ id: edge.id, type: 'remove' }], edges.value); + }); } }, - addElementToScreen(nodeId: string, action: SideAction, index?: number) { + + addElementToPage(nodeId: string, action: SideAction, index?: number): void { const node = findNode(nodeId); - //? can be 0 + + if(!node.data.formValues.components) node.data.formValues.components = []; + if(index !== undefined) { - node.data.formValues.components.splice(index, 0, { action: action }); - } - else { - if(!node.data.formValues.components) { - node.data.formValues.components = []; - } - node.data.formValues.components.push({ action: action }); + node.data.formValues.components.splice(index, 0, { action }); + } else { + node.data.formValues.components.push({ action }); } }, + //? The parameter nodeMoved is used when openedParentId is not usable removeElementFromScreen(index: number, parentNodeId, nodeMoved?: boolean): void { this.closeFormPanel(); + const node = findNode(parentNodeId); - node.data.elements.splice(index, 1); if(this.openedParentId || nodeMoved) { node.data.formValues.components.splice(index, 1); } - if(node.data.elements.length === 0) { + if(!node.data.elements.length) { this.deleteElement(parentNodeId); } }, + changeElementOrder(startIndex: number, finalIndex: number, parentNodeId: string): void { const node = findNode(parentNodeId); - const tmpElem = node.data.elements[startIndex]; - node.data.elements.splice(startIndex, 1); + const [tmpElem] = node.data.elements.splice(startIndex, 1); node.data.elements.splice(finalIndex, 0, tmpElem); - const tmpValues = node.data.formValues.components[startIndex]; - node.data.formValues.components.splice(startIndex, 1); + const [tmpValues] = node.data.formValues.components.splice(startIndex, 1); node.data.formValues.components.splice(finalIndex, 0, tmpValues); }, + undo() { // @todo console.log('todo undo', this.undoStack.length); }, + redo() { // @todo console.log('todo redo'); }, - generateContentId() { + + generateContentId(): string { const firstNumber = (Math.random() * 46656) | 0; const secondNumber = (Math.random() * 46656) | 0; const firstPart = ('000' + firstNumber.toString(36)).slice(-3); const secondPart = ('000' + secondNumber.toString(36)).slice(-3); return firstPart + secondPart; }, + getFormButtons(): FormButton[] { const buttons: FormButton[] = []; - if(this.formPanel.type !== 'epoc') { - buttons.push({ label: 'Supprimer',icon: 'icon-supprimer',action: 'delete'}); - if(this.formPanel.type !== 'chapter') { - const isChild = Boolean(this.openedParentId); - if(!isChild) { - buttons.push({ label: 'Dupliquer la page', icon: 'icon-plus', action: 'duplicate-screen' }); - buttons.push({ label: 'Lancer l\'aperçu ici', icon: 'icon-play', action: 'launch-preview' }); - buttons.push({ label: 'Sauvegarder le modèle', icon: 'icon-modele', action: 'save-model' }); - } else { - buttons.push({ label: 'Revenir à la page', icon: 'icon-ecran', action: 'open-page' }); - - //? This is temporary - if(this.formPanel.type !== 'text' && this.formPanel.type !== 'video') { - buttons.push({ label: 'Dupliquer l\'élément', icon: 'icon-plus', action: 'duplicate-element' }); - } - } - } + + if(this.formPanel.type === 'epoc') return buttons; + + buttons.push({ label: 'Supprimer',icon: 'icon-supprimer',action: 'delete' }); + + if(this.formPanel.type === 'chapter') return buttons; + + const isChild = Boolean(this.openedParentId); + + if(!isChild) { + buttons.push( + { label: 'Dupliquer la page', icon: 'icon-plus', action: 'duplicate-screen' }, + { label: 'Lancer l\'aperçu ici', icon: 'icon-play', action: 'launch-preview' }, + { label: 'Sauvegarder le modèle', icon: 'icon-modele', action: 'save-model' } + ); + } else { + buttons.push( + { label: 'Revenir à la page', icon: 'icon-back', action: 'back-to-screen' }, + { label : 'Dupliquer l\'élément', icon: 'icon-plus', action: 'duplicate-element' }, + ); } + return buttons; }, + savePageModel(model: SideAction[]): boolean { const modelExist = this.pageModels.some(pageModel => JSON.stringify(pageModel.actions) === JSON.stringify(model)); if(modelExist) return false; this.pageModels.push({ actions: model }); return true; }, - openPage() { + + openPage(): void { const parentNode = findNode(this.openedParentId); this.openFormPanel(parentNode.id, parentNode.data.formType, parentNode.data.formValues); }, - duplicateScreen() { - const node = findNode(this.openedNodeId); + duplicateScreen(): void { + const node = findNode(this.openedElementId); const newElements = []; const nodeId = this.generateId(); - for(const element of node.data.elements) { + node.data.elements.forEach(element => { const newElement = structuredClone(toRaw(element)); newElement.id = this.generateId(); newElement.parentId = nodeId; newElements.push(newElement); - } - + }); const newNode = { id: nodeId, @@ -280,63 +278,51 @@ export const useEditorStore = defineStore('editor', { position: { x: node.position.x + 150, y: node.position.y }, data: { elements: newElements, - readyToDrop: false, formType: 'screen', formValues: structuredClone(toRaw(node.data.formValues)), type: node.data.type, contentId: this.generateContentId(), - deletable: false - }, + // deletable: false, + } }; addNodes([newNode]); this.closeFormPanel(); }, - duplicateElement() { + + duplicateElement(): void { const node = findNode(this.openedParentId); + const element = node.data.elements.find(element => element.id === this.openedElementId); - const element = node.data.elements.find(element => element.id === this.openedNodeId); - - //! Structured Clone create error - // const newElement = structuredClone(toRaw(element)); const newElement = JSON.parse(JSON.stringify(toRaw(element))); - - console.log('new Element', newElement); - newElement.id = this.generateId(); newElement.parentId = node.id; node.data.elements.push(newElement); - this.addElementToScreen(node.id, newElement.action); + this.addElementToPage(node.id, newElement.action); }, - addNewPage(type: string, pos: { x: number, y: number }) { - const types = standardScreen.concat(questions); - const elements: NodeElement[] = []; + addNewPage(type: string, pos: { x: number, y: number }): void { + const types = standardScreen.concat(questions); const id = this.generateId(); - elements.push({ + const elements: NodeElement[] = [{ id: this.generateId(), action: types.find((value) => value.type === type), formType: type, formValues: {}, parentId: id, contentId: this.generateContentId(), - }); + }]; const { left, top } = vueFlowRef.value.getBoundingClientRect(); - - const position = project({ - x: pos.x - left, - y: pos.y - top, - }); + const position = project({ x: pos.x - left, y: pos.y - top }); const newNode = { - id: id, + id, type: 'content', - data: { type: type, readyToDrop: false, elements: elements, formType: 'screen', formValues: {}, contentId: id }, - position: position, - deletable: false + position, + data: { type, elements, formType: 'screen', formValues: {}, contentId: id } , }; addNodes([newNode]); @@ -355,7 +341,7 @@ export const useEditorStore = defineStore('editor', { ); }); - this.addElementToScreen(id, elements[0].action); + this.addElementToPage(id, elements[0].action); } - } + }, }); \ No newline at end of file diff --git a/src/shared/stores/epocStore.ts b/src/shared/stores/epocStore.ts index 2e3d7d106bb0c31617427723518e8d941c0b3cb4..4b92e319dda1091a417d4defe330eb05ddc388fb 100644 --- a/src/shared/stores/epocStore.ts +++ b/src/shared/stores/epocStore.ts @@ -1,9 +1,9 @@ // import { defineStore } from 'pinia'; -// import { Screen } from '@/src/shared/interfaces'; +// import { PageModel } from '@/src/shared/interfaces'; // interface EpocState { // screensModel: { -// personnal: Screen[], +// personnal: PageModel[], // personnalSelected: boolean // }, // } @@ -16,10 +16,4 @@ // personnal: [] // }, // }), - -// getters: { -// getSelectedScreens() { -// return this.screensModel.personnalSelected ? this.screensModel.personnal : this.screensModel.standard; -// }, -// }, // }); \ No newline at end of file diff --git a/src/shared/stores/projectStore.ts b/src/shared/stores/projectStore.ts index 443b31cc5f9672682d4ffde10afef1d0130a4442..c9434c06eb6e3963c78005d49598d63d20c01545 100644 --- a/src/shared/stores/projectStore.ts +++ b/src/shared/stores/projectStore.ts @@ -93,7 +93,6 @@ export const useProjectStore = defineStore('project', { type: 'content', data: { elements: contentElements, - readyToDrop: false, formType: 'screen', formValues: {}, type: 'template', @@ -126,7 +125,6 @@ export const useProjectStore = defineStore('project', { type: 'content', data: { elements: [element], - readyToDrop: false, formType: 'screen', formValues: {}, type: 'question', @@ -138,7 +136,7 @@ export const useProjectStore = defineStore('project', { addNodes([newNode]); - editorStore.addElementToScreen(id, element.action); + editorStore.addElementToPage(id, element.action); // align node position after drop, so it's centered to the mouse nextTick(() => { @@ -193,7 +191,6 @@ export const useProjectStore = defineStore('project', { type: 'content', data: { type: finalType, - readyToDrop: false, elements: elements, contentId: uid(), formType: 'screen', @@ -206,7 +203,7 @@ export const useProjectStore = defineStore('project', { addNodes([newNode]); actions.forEach((action) => { - editorStore.addElementToScreen(id, action); + editorStore.addElementToPage(id, action); }); // align node position after drop, so it's centered to the mouse