Browse Source

feat(editor): add zoom in and out

Ahmad Kholid 3 years ago
parent
commit
e6d321b9f3

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

@@ -25,7 +25,7 @@
 .drawflow {
 .drawflow {
   width: 100%;
   width: 100%;
   height: 100%;
   height: 100%;
-  user-select: none
+  user-select: none;
 }
 }
 
 
 .drawflow .drawflow-node {
 .drawflow .drawflow-node {
@@ -99,7 +99,8 @@
   fill: none;
   fill: none;
   stroke-width: 5px;
   stroke-width: 5px;
   stroke: theme('colors.accent');
   stroke: theme('colors.accent');
-  transform: translate(-9999px, -9999px)
+  transform: translate(-9999px, -9999px);
+  transition: stroke 100ms ease-in-out;
 }
 }
 
 
 .drawflow .connection .main-path:hover {
 .drawflow .connection .main-path:hover {
@@ -116,7 +117,7 @@
   stroke: #000;
   stroke: #000;
   stroke-width: 2;
   stroke-width: 2;
   fill: #fff;
   fill: #fff;
-  transform: translate(-9999px, -9999px)
+  transform: translate(-9999px, -9999px);
 }
 }
 
 
 .drawflow .connection .point.selected,
 .drawflow .connection .point.selected,

+ 85 - 0
src/components/block/BlockBase.vue

@@ -0,0 +1,85 @@
+<template>
+  <div ref="rootRef" class="group relative">
+    <div
+      class="
+        z-10
+        flex
+        items-center
+        bg-white
+        relative
+        rounded-lg
+        overflow-hidden
+        p-4
+      "
+    >
+      <span
+        :class="categories[state.blockData.category]?.color"
+        class="inline-block p-2 mr-2 rounded-lg bg-green-200"
+      >
+        <v-remixicon :path="icons[state.blockData.icon]" />
+      </span>
+      <div style="max-width: 220px">
+        <p class="font-semibold leading-none">
+          {{ state.blockData.name }}
+        </p>
+        <p class="text-gray-600 text-overflow leading-tight">
+          {{ state.blockData.description }}
+        </p>
+      </div>
+    </div>
+    <div
+      class="
+        absolute
+        top-0
+        transition-transform
+        duration-300
+        group-hover:translate-y-16
+        pt-4
+        ml-1
+        menu
+      "
+    >
+      <div class="bg-accent px-4 py-2 text-white rounded-lg flex items-center">
+        <button class="-ml-1">
+          <v-remixicon size="20" :path="icons.riPencilLine" />
+        </button>
+        <hr class="border-r border-gray-600 h-5 mx-3" />
+        <button class="-mr-1">
+          <v-remixicon size="20" :path="icons.riDeleteBin7Line" />
+        </button>
+      </div>
+    </div>
+  </div>
+</template>
+<script setup>
+import { ref, nextTick, shallowReactive } from 'vue';
+import { VRemixIcon as VRemixicon } from 'v-remixicon';
+import { icons } from '@/lib/v-remixicon';
+import { tasks, categories } from '@/utils/shared';
+
+const props = defineProps({
+  editor: {
+    type: Object,
+    default: () => ({}),
+  },
+});
+
+const rootRef = ref(null);
+const state = shallowReactive({
+  blockId: '',
+  blockData: {},
+});
+
+nextTick(() => {
+  state.blockId = rootRef.value?.parentElement.parentElement.id.replace(
+    'node-',
+    ''
+  );
+
+  if (state.blockId) {
+    const { name } = props.editor.getNodeFromId(state.blockId);
+
+    state.blockData = tasks[name];
+  }
+});
+</script>

+ 0 - 1
src/components/node/NodeStart.vue → src/components/block/BlockStart.vue

@@ -1,6 +1,5 @@
 <template>
 <template>
   <div class="flex items-center relative p-4 overflow-hidden rounded-lg">
   <div class="flex items-center relative p-4 overflow-hidden rounded-lg">
-    <div class="absolute top-0 left-0 w-full h-2 bg-yellow-200"></div>
     <span class="inline-block p-2 mr-4 rounded-lg bg-yellow-200">
     <span class="inline-block p-2 mr-4 rounded-lg bg-yellow-200">
       <v-remixicon :path="riFlagLine" />
       <v-remixicon :path="riFlagLine" />
     </span>
     </span>

+ 38 - 3
src/components/newtab/workflow/WorkflowBuilder.vue

@@ -1,5 +1,26 @@
 <template>
 <template>
-  <div id="drawflow" @drop="dropHandler" @dragover.prevent></div>
+  <div class="relative">
+    <div
+      id="drawflow"
+      class="h-full w-full"
+      @drop="dropHandler"
+      @dragover.prevent
+    ></div>
+    <div class="absolute m-4 bottom-0 left-0">
+      <button class="p-2 rounded-lg bg-white mr-2" @click="editor.zoom_reset()">
+        <v-remixicon name="riFullscreenLine" />
+      </button>
+      <div class="rounded-lg bg-white inline-block">
+        <button class="p-2 rounded-lg relative z-10" @click="editor.zoom_out()">
+          <v-remixicon name="riSubtractLine" />
+        </button>
+        <hr class="h-6 border-r inline-block" />
+        <button class="p-2 rounded-lg" @click="editor.zoom_in()">
+          <v-remixicon name="riAddLine" />
+        </button>
+      </div>
+    </div>
+  </div>
 </template>
 </template>
 <script>
 <script>
 import { onMounted, shallowRef } from 'vue';
 import { onMounted, shallowRef } from 'vue';
@@ -32,8 +53,8 @@ export default {
 
 
       editor.value.addNode(
       editor.value.addNode(
         block.id,
         block.id,
-        1,
-        1,
+        block.inputs,
+        block.outputs,
         xPosition,
         xPosition,
         yPosition,
         yPosition,
         block.id,
         block.id,
@@ -47,9 +68,23 @@ export default {
       const element = document.querySelector('#drawflow');
       const element = document.querySelector('#drawflow');
 
 
       editor.value = drawflow(element);
       editor.value = drawflow(element);
+      console.log(editor.value);
+      editor.value.start();
+      editor.value.addNode(
+        'trigger',
+        0,
+        1,
+        50,
+        300,
+        'trigger',
+        {},
+        'BlockBase',
+        'vue'
+      );
     });
     });
 
 
     return {
     return {
+      editor,
       dropHandler,
       dropHandler,
     };
     };
   },
   },

+ 0 - 69
src/components/node/NodeBase.vue

@@ -1,69 +0,0 @@
-<template>
-  <div ref="rootRef" class="group relative">
-    <div
-      class="
-        z-10
-        flex
-        items-center
-        bg-white
-        relative
-        rounded-lg
-        overflow-hidden
-        p-4
-      "
-    >
-      <span class="inline-block p-2 mr-2 rounded-lg bg-green-200">
-        <v-remixicon :path="riCursorLine" />
-      </span>
-      <p>Click element</p>
-    </div>
-    <div
-      class="
-        absolute
-        top-0
-        transition-transform
-        duration-300
-        group-hover:translate-y-16
-        pt-4
-        menu
-      "
-    >
-      <div class="bg-accent px-4 py-2 text-white rounded-lg flex items-center">
-        <button class="-ml-1">
-          <v-remixicon size="20" :path="riPencilLine" />
-        </button>
-        <hr class="border-r border-gray-600 h-5 mx-3" />
-        <button class="-mr-1">
-          <v-remixicon size="20" :path="riDeleteBin7Line" />
-        </button>
-      </div>
-    </div>
-  </div>
-</template>
-<script setup>
-import { ref, nextTick } from 'vue';
-import { VRemixIcon as VRemixicon } from 'v-remixicon';
-import {
-  riCursorLine,
-  riDeleteBin7Line,
-  riPencilLine,
-} from 'v-remixicon/icons';
-
-const props = defineProps({
-  editor: {
-    type: Object,
-    default: () => ({}),
-  },
-});
-
-const rootRef = ref(null);
-const nodeId = ref('');
-
-nextTick(() => {
-  nodeId.value = rootRef.value?.parentElement.parentElement.id;
-  console.log(rootRef.value?.parentElement.parentElement);
-  if (nodeId.value) {
-    console.log(props.editor);
-  }
-});
-</script>

+ 0 - 0
src/components/node/index.js


+ 3 - 30
src/lib/drawflow.js

@@ -2,7 +2,7 @@ import { createApp, h } from 'vue';
 import Drawflow from 'drawflow';
 import Drawflow from 'drawflow';
 import '@/assets/css/drawflow.css';
 import '@/assets/css/drawflow.css';
 
 
-const nodeComponents = require.context('../components/node', false, /\.vue$/);
+const blockComponents = require.context('../components/block', false, /\.vue$/);
 
 
 export default function (element, ctx) {
 export default function (element, ctx) {
   const editor = new Drawflow(element, { createApp, version: 3, h }, ctx);
   const editor = new Drawflow(element, { createApp, version: 3, h }, ctx);
@@ -10,37 +10,10 @@ export default function (element, ctx) {
   editor.useuuid = true;
   editor.useuuid = true;
   editor.reroute = true;
   editor.reroute = true;
 
 
-  nodeComponents.keys().forEach((key) => {
+  blockComponents.keys().forEach((key) => {
     const name = key.replace(/(.\/)|\.vue$/g, '');
     const name = key.replace(/(.\/)|\.vue$/g, '');
 
 
-    editor.registerNode(name, nodeComponents(key).default, { editor }, {});
-  });
-
-  editor.start();
-  editor.addNode(
-    'NodeStart',
-    0,
-    1,
-    150,
-    300,
-    'node-start',
-    {},
-    'NodeStart',
-    'vue'
-  );
-  editor.on('nodeCreated', (id) => {
-    const { name } = editor.getNodeFromId(id);
-
-    console.log(name, id, editor.getNodeFromId(id));
-
-    // Node.insert({
-    //   data: {
-    //     id,
-    //     data: nodeData,
-    //     storyId,
-    //     type: name,
-    //   },
-    // });
+    editor.registerNode(name, blockComponents(key).default, { editor }, {});
   });
   });
 
 
   return editor;
   return editor;

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

@@ -1,6 +1,7 @@
 import vRemixicon from 'v-remixicon';
 import vRemixicon from 'v-remixicon';
 import {
 import {
   riHome5Line,
   riHome5Line,
+  riSubtractLine,
   riPlayLine,
   riPlayLine,
   riPauseLine,
   riPauseLine,
   riSearch2Line,
   riSearch2Line,
@@ -17,6 +18,7 @@ import {
   riHistoryLine,
   riHistoryLine,
   riArrowDropDownLine,
   riArrowDropDownLine,
   riAddLine,
   riAddLine,
+  riFullscreenLine,
   riSortAsc,
   riSortAsc,
   riSortDesc,
   riSortDesc,
   riGlobalLine,
   riGlobalLine,
@@ -41,6 +43,7 @@ import {
 
 
 export const icons = {
 export const icons = {
   riHome5Line,
   riHome5Line,
+  riSubtractLine,
   riPlayLine,
   riPlayLine,
   riPauseLine,
   riPauseLine,
   riSearch2Line,
   riSearch2Line,
@@ -57,6 +60,7 @@ export const icons = {
   riHistoryLine,
   riHistoryLine,
   riArrowDropDownLine,
   riArrowDropDownLine,
   riAddLine,
   riAddLine,
+  riFullscreenLine,
   riSortAsc,
   riSortAsc,
   riSortDesc,
   riSortDesc,
   riGlobalLine,
   riGlobalLine,

+ 49 - 5
src/utils/shared.js

@@ -2,68 +2,112 @@ export const tasks = {
   trigger: {
   trigger: {
     name: 'Trigger',
     name: 'Trigger',
     icon: 'riFlashlightLine',
     icon: 'riFlashlightLine',
-    component: 'task-',
+    component: 'BlockBase',
     category: 'general',
     category: 'general',
+    inputs: 0,
+    outputs: 1,
+    allowedInputs: [],
+    maxConnection: 1,
   },
   },
   'event-click': {
   'event-click': {
     name: 'Click element',
     name: 'Click element',
     icon: 'riCursorLine',
     icon: 'riCursorLine',
-    component: 'NodeBase',
+    component: 'BlockBase',
     category: 'interaction',
     category: 'interaction',
+    inputs: 1,
+    outputs: 1,
+    allowedInputs: [],
+    maxConnection: 1,
   },
   },
   delay: {
   delay: {
     name: 'Delay',
     name: 'Delay',
     icon: 'riTimerLine',
     icon: 'riTimerLine',
-    component: 'NodeBase',
+    component: 'BlockBase',
     category: 'general',
     category: 'general',
+    inputs: 1,
+    outputs: 1,
+    allowedInputs: [],
+    maxConnection: 1,
   },
   },
   'get-text': {
   'get-text': {
     name: 'Get text',
     name: 'Get text',
     icon: 'riParagraph',
     icon: 'riParagraph',
-    component: 'NodeBase',
+    component: 'BlockBase',
     category: 'interaction',
     category: 'interaction',
+    inputs: 1,
+    outputs: 1,
+    allowedInputs: [],
+    maxConnection: 1,
   },
   },
   'export-data': {
   'export-data': {
     name: 'Export data',
     name: 'Export data',
     icon: 'riDownloadLine',
     icon: 'riDownloadLine',
     component: 'task-',
     component: 'task-',
     category: 'general',
     category: 'general',
+    inputs: 1,
+    outputs: 1,
+    allowedInputs: [],
+    maxConnection: 1,
   },
   },
   'element-scroll': {
   'element-scroll': {
     name: 'Scroll element',
     name: 'Scroll element',
     icon: 'riMouseLine',
     icon: 'riMouseLine',
-    component: 'NodeBase',
+    component: 'BlockBase',
     category: 'interaction',
     category: 'interaction',
+    inputs: 1,
+    outputs: 1,
+    allowedInputs: [],
+    maxConnection: 1,
   },
   },
   'get-attribute': {
   'get-attribute': {
     name: 'Get attribute',
     name: 'Get attribute',
     icon: 'riBracketsLine',
     icon: 'riBracketsLine',
     component: 'task-',
     component: 'task-',
     category: 'interaction',
     category: 'interaction',
+    inputs: 1,
+    outputs: 1,
+    allowedInputs: [],
+    maxConnection: 1,
   },
   },
   'open-website': {
   'open-website': {
     name: 'Open website',
     name: 'Open website',
     icon: 'riGlobalLine',
     icon: 'riGlobalLine',
     component: 'task-',
     component: 'task-',
     category: 'general',
     category: 'general',
+    inputs: 1,
+    outputs: 1,
+    allowedInputs: [],
+    maxConnection: 1,
   },
   },
   'text-input': {
   'text-input': {
     name: 'Text input',
     name: 'Text input',
     icon: 'riInputCursorMove',
     icon: 'riInputCursorMove',
     component: 'task-',
     component: 'task-',
     category: 'interaction',
     category: 'interaction',
+    inputs: 1,
+    outputs: 1,
+    allowedInputs: [],
+    maxConnection: 1,
   },
   },
   'repeat-task': {
   'repeat-task': {
     name: 'Repeat tasks',
     name: 'Repeat tasks',
     icon: 'riRepeat2Line',
     icon: 'riRepeat2Line',
     component: 'task-',
     component: 'task-',
     category: 'general',
     category: 'general',
+    inputs: 1,
+    outputs: 1,
+    allowedInputs: [],
+    maxConnection: 1,
   },
   },
   'trigger-element-events': {
   'trigger-element-events': {
     name: 'Trigger element events',
     name: 'Trigger element events',
     icon: 'riLightbulbFlashLine',
     icon: 'riLightbulbFlashLine',
     component: 'task-',
     component: 'task-',
     category: 'interaction',
     category: 'interaction',
+    inputs: 1,
+    outputs: 1,
+    allowedInputs: [],
+    maxConnection: 1,
   },
   },
 };
 };