From 98e02718909ebf87562af44a16b47f35a9021d60 Mon Sep 17 00:00:00 2001
From: VIAUD Nathan <nathan.viaud@inria.fr>
Date: Thu, 16 Mar 2023 15:14:26 +0000
Subject: [PATCH] Issue #156: nodes selection refactor

---
 electron/components/contextMenu.js          | 38 ++++++++++++++
 electron/electron.js                        |  6 +++
 src/features/ePocFlow/ePocFlow.vue          |  1 +
 src/features/ePocFlow/nodes/ChapterNode.vue |  1 +
 src/features/ePocFlow/nodes/ScreenNode.vue  | 21 ++++----
 src/features/ePocFlow/nodes/ePocNode.vue    |  1 +
 src/global.scss                             | 12 +++--
 src/shared/services/editor.service.ts       |  5 ++
 src/shared/stores/editorStore.ts            | 58 +++++++++++++++++++--
 9 files changed, 122 insertions(+), 21 deletions(-)
 create mode 100644 electron/components/contextMenu.js

diff --git a/electron/components/contextMenu.js b/electron/components/contextMenu.js
new file mode 100644
index 00000000..6ad6bcb6
--- /dev/null
+++ b/electron/components/contextMenu.js
@@ -0,0 +1,38 @@
+const { Menu } = require('electron');
+const { BrowserWindow } = require('electron');
+const { sendToFrontend } = require('./ipc');
+
+const { screen } = require('electron');
+
+const contextTemplate = [
+    {
+        label: 'Ajouter',
+        submenu: [
+            {
+                label: 'Ajouter du texte',
+                click: () => { sendToFrontend(BrowserWindow.getFocusedWindow(),'addPage', { type: 'text', pos: getRelativeCursorPosition() }); }
+            },
+            {
+                label: 'Ajouter une vidéo',
+                click: () => { sendToFrontend(BrowserWindow.getFocusedWindow(), 'addPage', { type: 'video', pos: getRelativeCursorPosition() }); }
+            }
+        ]
+    },
+];
+
+function getRelativeCursorPosition() {
+    const cursorPosition = screen.getCursorScreenPoint();
+    // Get the position of the current window
+    const windowPosition = BrowserWindow.getFocusedWindow().getPosition();
+
+    // Calculate the cursor position relative to the current window
+    const cursorPositionRelativeToWindow = {
+        x: cursorPosition.x - windowPosition[0],
+        y: cursorPosition.y - windowPosition[1]
+    };
+
+    return cursorPositionRelativeToWindow;  
+}
+
+
+module.exports.popupMenu = Menu.buildFromTemplate(contextTemplate);
\ No newline at end of file
diff --git a/electron/electron.js b/electron/electron.js
index f0bb0e1c..4c5741f1 100644
--- a/electron/electron.js
+++ b/electron/electron.js
@@ -10,6 +10,8 @@ const { cleanPreview } = require('./components/preview');
 const path = require('path');
 const store = require('./components/store');
 
+const { popupMenu } = require('./components/contextMenu');
+
 let mainWindow;
 let splashWindow;
 // Open file with editor, on windows : using argv | on macOS using open-file event (see below)
@@ -78,4 +80,8 @@ app.whenReady().then(() => {
         cleanPreview();
         app.quit();
     });
+
+    mainWindow.webContents.on('context-menu', () => {
+        popupMenu.popup(mainWindow.webContents);
+    });
 });
\ No newline at end of file
diff --git a/src/features/ePocFlow/ePocFlow.vue b/src/features/ePocFlow/ePocFlow.vue
index c1c0a7a3..79c25410 100644
--- a/src/features/ePocFlow/ePocFlow.vue
+++ b/src/features/ePocFlow/ePocFlow.vue
@@ -151,6 +151,7 @@ function onEdgeclick (event) {
         @dragover.prevent
         @dragenter.prevent
         @edge-click="onEdgeclick"
+        @pane-click="editorStore.closeFormPanel()"
     >
         <template #node-custom="{ id, data }">
             <ScreenNode :id="id" :data="data" />
diff --git a/src/features/ePocFlow/nodes/ChapterNode.vue b/src/features/ePocFlow/nodes/ChapterNode.vue
index 8f08d419..6b368fc6 100644
--- a/src/features/ePocFlow/nodes/ChapterNode.vue
+++ b/src/features/ePocFlow/nodes/ChapterNode.vue
@@ -42,6 +42,7 @@ const subtitle = computed(() => {
             :class-list="{ 'btn-content-blue' : false, 'clickable': true, 'btn-content-node': true, 'btn-content-large': true }"
             :subtitle="subtitle"
             @click="openForm()"
+            @mousedown="editorStore.closeFormPanel()"
         />
     </div>
     <Handle
diff --git a/src/features/ePocFlow/nodes/ScreenNode.vue b/src/features/ePocFlow/nodes/ScreenNode.vue
index 24a35cdf..45ef4d94 100644
--- a/src/features/ePocFlow/nodes/ScreenNode.vue
+++ b/src/features/ePocFlow/nodes/ScreenNode.vue
@@ -1,7 +1,7 @@
 <script setup lang="ts">
 import { Handle, Position, useVueFlow } from '@vue-flow/core';
 import ContentButton from '@/src/components/ContentButton.vue';
-import { computed, onMounted, ref } from 'vue';
+import { onMounted, ref } from 'vue';
 import { useEditorStore } from '@/src/shared/stores';
 import { NodeElement } from '@/src/shared/interfaces';
 
@@ -21,7 +21,7 @@ const props = defineProps<{
 }>();
 
 
-const { findNode, getSelectedNodes } = useVueFlow({ id: 'main' });
+const { findNode } = useVueFlow({ id: 'main' });
 
 const node = findNode(props.id);
 const dropped = ref(false);
@@ -116,14 +116,10 @@ function dragStart(event, element: NodeElement, index: number) {
     event.dataTransfer.setData('source', JSON.stringify({ parent: props.id, index: index}));
 }
 
-function cmdClick() {
+function closeFormPanel() {
     editorStore.closeFormPanel();
 }
 
-const isSelected = computed(() => {
-    return getSelectedNodes.value.find((selectedNode) => selectedNode.id === node.id);
-});
-
 </script>
 
 <template>
@@ -138,11 +134,12 @@ const isSelected = computed(() => {
             />
             <div
                 :id="'node'+ props.id"
-                :class=" { 'active': editorStore.openedNodeId ? editorStore.openedNodeId === props.id : false, 'selected': isSelected }"
+                :class=" { 'active': editorStore.openedNodeId ? editorStore.openedNodeId === props.id : false }"
                 class="node"
                 @click.exact="openPageForm(node.id, node.data.formType, node.data.formValues)"
-                @click.meta="cmdClick"
-                @click.ctrl="cmdClick"  
+                @click.meta="closeFormPanel"
+                @click.ctrl="closeFormPanel"
+                @mousedown="closeFormPanel"
                 @dragover="dragOver($event)"
                 @dragleave="dragLeave($event)"
                 @dragenter="dragEnter($event)"
@@ -166,8 +163,8 @@ const isSelected = computed(() => {
                                 :is-draggable="isQuestion"
                                 :class-list="{ 'btn-content-blue' : false, 'clickable': true, 'btn-content-node': true }"
                                 @click.exact="openForm(element)"
-                                @click.meta="cmdClick"
-                                @click.ctrl="cmdClick"
+                                @click.meta="closeFormPanel"
+                                @click.ctrl="closeFormPanel"
                                 @dragstart="dragStart($event, element, index)"
                             />
                         </div>
diff --git a/src/features/ePocFlow/nodes/ePocNode.vue b/src/features/ePocFlow/nodes/ePocNode.vue
index 613b6df5..3bc2a294 100644
--- a/src/features/ePocFlow/nodes/ePocNode.vue
+++ b/src/features/ePocFlow/nodes/ePocNode.vue
@@ -31,6 +31,7 @@ function openForm() {
             :class-list="{ 'btn-content-blue' : false, 'clickable': true, 'btn-content-node': true, 'btn-content-large': true }"
             subtitle="ePoc"
             @click="openForm()"
+            @mousedown="editorStore.closeFormPanel()"
         />
     </div>
 </template>
\ No newline at end of file
diff --git a/src/global.scss b/src/global.scss
index 8858dfee..5b47d1c8 100644
--- a/src/global.scss
+++ b/src/global.scss
@@ -229,11 +229,6 @@ hr {
     box-shadow: 0 1px 8px 0 var(--editor-blue-shadow);
     background-color: var(--node-active);
   } 
-  
-  &.selected {
-    border: 1px solid var(--editor-blue);
-    box-shadow: 0 1px 8px 0 var(--editor-blue-shadow);  
-  }
 
   &-animate {
       padding: 1.5rem;
@@ -472,4 +467,11 @@ g.selected {
 .vue-flow__nodesselection-rect {
   padding: 2rem 1rem 1rem 1rem;
   transform: translate(-1rem, -2rem);
+}
+
+.vue-flow__node.selected {
+  .node {
+    border: 1px solid var(--editor-blue);
+    box-shadow: 0 1px 8px 0 var(--editor-blue-shadow);  
+  }
 }
\ No newline at end of file
diff --git a/src/shared/services/editor.service.ts b/src/shared/services/editor.service.ts
index 63658171..5a111780 100644
--- a/src/shared/services/editor.service.ts
+++ b/src/shared/services/editor.service.ts
@@ -122,6 +122,11 @@ const setup = function () {
         editorStore.exporting = false;
     });
 
+    api.receive('addPage', (data: string) => {
+        const page = JSON.parse(data);
+        editorStore.addNewPage(page.type, page.pos);
+    });
+
     initialized = true;
 };
 function newEpocProject(): void {
diff --git a/src/shared/stores/editorStore.ts b/src/shared/stores/editorStore.ts
index bb3d894b..b448b0a0 100644
--- a/src/shared/stores/editorStore.ts
+++ b/src/shared/stores/editorStore.ts
@@ -1,11 +1,11 @@
 import { defineStore } from 'pinia';
-import { ePocProject, Form, FormButton, Screen, SideAction } from '@/src/shared/interfaces';
-import { toRaw } from 'vue';
+import { ePocProject, Form, FormButton, NodeElement, Screen, SideAction } from '@/src/shared/interfaces';
+import { nextTick, toRaw, watch } from 'vue';
 import { applyNodeChanges, getConnectedEdges, useVueFlow } from '@vue-flow/core';
 
 import { formsModel, questions, standardScreen } from '@/src/shared/data/form.data';
 
-const { findNode, nodes, edges, addNodes } = useVueFlow({ id: 'main' });
+const { findNode, nodes, edges, addNodes, project, vueFlowRef } = useVueFlow({ id: 'main' });
 
 type uid = string;
 
@@ -72,7 +72,9 @@ export const useEditorStore = defineStore('editor', {
             //? To be sure the view is notified of closing / reopening
             setTimeout(() => { 
                 this.formPanel = structuredClone(formsModel.find(form => form.type === formType));
-                document.querySelectorAll('.node.selected').forEach(node => node.classList.remove('selected'));
+            });
+            nodes.value.forEach((node) => {
+                node.selected = false;
             });
         },
         closeFormPanel(): void {
@@ -248,6 +250,54 @@ export const useEditorStore = defineStore('editor', {
 
             node.data.elements.push(newElement);
             this.addElementToScreen(node.id, newElement.action);
+        },
+        addNewPage(type: string, pos: { x: number, y: number }) {
+            const types = standardScreen.concat(questions);
+
+            const elements: NodeElement[] = [];
+            const id = this.generateId();
+
+            elements.push({
+                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 newNode = {
+                id: id,
+                type: 'content',
+                data: { type: type, readyToDrop: false, animated: false, elements: elements, formType: 'screen', formValues: {}, contentId: id },
+                position: position,
+                deletable: false
+            };
+
+            addNodes([newNode]);
+
+            nextTick(() => {
+                const node = findNode(newNode.id);
+                const stop = watch(
+                    () => node.dimensions,
+                    (dimensions) => {
+                        if (dimensions.width > 0 && dimensions.height > 0) {
+                            node.position = { x: node.position.x - node.dimensions.width / 2, y: node.position.y - node.dimensions.height / 2 };
+                            stop();
+                        }
+                    },
+                    { deep: true, flush: 'post' },
+                );
+            });
+
+            this.addElementToScreen(id, elements[0].action);
         }
     }
 });
-- 
GitLab