Browse Source

feat: add context menu to the editor

Ahmad Kholid 3 years ago
parent
commit
2ebc866160

+ 4 - 0
src/assets/css/drawflow.css

@@ -124,6 +124,10 @@
   stroke: theme('colors.accent');
 }
 
+.drawflow-node .drawflow-delete {
+  display: none !important;
+}
+
 .drawflow-delete {
   position: absolute;
   display: block;

+ 1 - 1
src/background/collection-engine/flow-handler.js

@@ -36,7 +36,7 @@ export function workflow(flow) {
         workflowId: currentWorkflow.id,
         workflowName: currentWorkflow.name,
       });
-      console.log(engine);
+
       resolve({
         id,
         message,

+ 91 - 4
src/components/newtab/workflow/WorkflowBuilder.vue

@@ -32,11 +32,29 @@
         </button>
       </div>
     </div>
+    <ui-popover
+      v-model="contextMenu.show"
+      :options="contextMenu.position"
+      padding="p-3"
+    >
+      <ui-list class="w-36 space-y-1">
+        <ui-list-item
+          v-for="item in contextMenu.items"
+          :key="item.id"
+          v-close-popover
+          class="cursor-pointer"
+          @click="contextMenuHandler[item.event]"
+        >
+          <v-remixicon :name="item.icon" class="mr-2 -ml-1" />
+          <span>{{ item.name }}</span>
+        </ui-list-item>
+      </ui-list>
+    </ui-popover>
   </div>
 </template>
 <script>
 /* eslint-disable camelcase */
-import { onMounted, shallowRef, getCurrentInstance } from 'vue';
+import { onMounted, shallowRef, reactive, getCurrentInstance } from 'vue';
 import emitter from 'tiny-emitter/instance';
 import { tasks } from '@/utils/shared';
 import { useGroupTooltip } from '@/composable/groupTooltip';
@@ -53,7 +71,30 @@ export default {
   setup(props, { emit }) {
     useGroupTooltip();
 
+    const contextMenuItems = {
+      block: [
+        {
+          id: 'duplicate',
+          name: 'Duplicate',
+          icon: 'riFileCopyLine',
+          event: 'duplicateBlock',
+        },
+        {
+          id: 'delete',
+          name: 'Delete',
+          icon: 'riDeleteBin7Line',
+          event: 'deleteBlock',
+        },
+      ],
+    };
+
     const editor = shallowRef(null);
+    const contextMenu = reactive({
+      items: [],
+      data: null,
+      show: false,
+      position: {},
+    });
 
     function dropHandler({ dataTransfer, clientX, clientY }) {
       const block = JSON.parse(dataTransfer.getData('block') || null);
@@ -102,9 +143,32 @@ export default {
         return item === input;
       });
     }
+    function deleteBlock() {
+      editor.value.removeNodeId(contextMenu.data);
+    }
+    function duplicateBlock() {
+      const { name, pos_x, pos_y, data, html } = editor.value.getNodeFromId(
+        contextMenu.data.substr(5)
+      );
+
+      if (name === 'trigger') return;
+
+      const { outputs, inputs } = tasks[name];
+
+      editor.value.addNode(
+        name,
+        inputs,
+        outputs,
+        pos_x + 50,
+        pos_y + 100,
+        name,
+        data,
+        html,
+        'vue'
+      );
+    }
 
     onMounted(() => {
-      /* eslint-disable-next-line */
       const context = getCurrentInstance().appContext.app._context;
       const element = document.querySelector('#drawflow');
 
@@ -162,8 +226,26 @@ export default {
       editor.value.on('connectionRemoved', () => {
         emitter.emit('editor:data-changed');
       });
-      editor.value.on('contextmenu', (event) => {
-        console.log(event);
+      editor.value.on('contextmenu', ({ clientY, clientX, target }) => {
+        const isBlock = target.closest('.drawflow .drawflow-node');
+
+        if (isBlock) {
+          const virtualEl = {
+            getReferenceClientRect: () => ({
+              width: 0,
+              height: 0,
+              top: clientY,
+              right: clientX,
+              bottom: clientY,
+              left: clientX,
+            }),
+          };
+
+          contextMenu.data = isBlock.id;
+          contextMenu.position = virtualEl;
+          contextMenu.items = contextMenuItems.block;
+          contextMenu.show = true;
+        }
       });
 
       setTimeout(() => {
@@ -173,7 +255,12 @@ export default {
 
     return {
       editor,
+      contextMenu,
       dropHandler,
+      contextMenuHandler: {
+        deleteBlock,
+        duplicateBlock,
+      },
     };
   },
 };

+ 14 - 1
src/components/ui/UiPopover.vue

@@ -41,6 +41,10 @@ export default {
       type: [String, Object, HTMLElement],
       default: '',
     },
+    options: {
+      type: Object,
+      default: () => ({}),
+    },
     disabled: {
       type: Boolean,
       default: false,
@@ -50,13 +54,20 @@ export default {
       default: false,
     },
   },
-  emits: ['show', 'trigger', 'close'],
+  emits: ['show', 'trigger', 'close', 'update:modelValue'],
   setup(props, { emit }) {
     const targetEl = ref(null);
     const content = ref(null);
     const isShow = ref(false);
     const instance = shallowRef(null);
 
+    watch(
+      () => props.options,
+      (value) => {
+        instance.value.setProps(value);
+      },
+      { deep: true }
+    );
     watch(
       () => props.disabled,
       (value) => {
@@ -102,9 +113,11 @@ export default {
         },
         onHide: () => {
           emit('close');
+          emit('update:modelValue', false);
           isShow.value = false;
         },
         onTrigger: () => emit('trigger'),
+        ...props.options,
       });
     });
     onUnmounted(() => {

+ 1 - 1
src/content/element-selector/ElementSelector.ce.vue

@@ -73,7 +73,7 @@ import {
   riEyeOffLine,
 } from 'v-remixicon/icons';
 
-/* to-do get list of attribute value */
+/* to-do get list of attribute value, add test for each of the web interation block */
 
 const element = reactive({
   hide: window.self !== window.top,

+ 2 - 0
src/lib/v-remixicon.js

@@ -1,6 +1,7 @@
 import vRemixicon from 'v-remixicon';
 import {
   riHome5Line,
+  riFileCopyLine,
   riFolderLine,
   riInformationLine,
   riWindow2Line,
@@ -72,6 +73,7 @@ import {
 
 export const icons = {
   riHome5Line,
+  riFileCopyLine,
   riFolderLine,
   riInformationLine,
   riWindow2Line,