123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980 |
- <template>
- <div
- v-bind="{ arrow: $store.state.settings.editor.arrow }"
- id="drawflow"
- class="parent-drawflow relative"
- @drop="dropHandler"
- @dragover.prevent="handleDragOver"
- >
- <div
- class="flex items-end absolute w-full p-4 left-0 bottom-0 justify-between z-10"
- >
- <div id="zoom">
- <button
- v-tooltip.group="t('workflow.editor.resetZoom')"
- class="p-2 rounded-lg bg-white dark:bg-gray-800 mr-2"
- @click="editor.zoom_reset()"
- >
- <v-remixicon name="riFullscreenLine" />
- </button>
- <div class="rounded-lg bg-white dark:bg-gray-800 inline-block">
- <button
- v-tooltip.group="t('workflow.editor.zoomOut')"
- 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
- v-tooltip.group="t('workflow.editor.zoomIn')"
- class="p-2 rounded-lg"
- @click="editor.zoom_in()"
- >
- <v-remixicon name="riAddLine" />
- </button>
- </div>
- <!-- <workflow-builder-search-blocks :editor="editor" /> -->
- </div>
- <slot v-bind="{ editor }"></slot>
- </div>
- <ui-popover
- v-model="contextMenu.show"
- :options="contextMenu.position"
- padding="p-3"
- @close="clearContextMenu"
- >
- <ui-list class="space-y-1 w-52">
- <ui-list-item
- v-for="item in contextMenu.items"
- :key="item.id"
- v-close-popover
- class="cursor-pointer justify-between"
- @click="contextMenuHandler[item.event]"
- >
- <span>
- {{ item.name }}
- </span>
- <span
- v-if="item.shortcut"
- class="text-sm capitalize text-gray-600 dark:text-gray-200"
- >
- {{ item.shortcut }}
- </span>
- </ui-list-item>
- </ui-list>
- </ui-popover>
- </div>
- </template>
- <script>
- /* eslint-disable camelcase */
- import {
- onMounted,
- shallowRef,
- reactive,
- getCurrentInstance,
- watch,
- onBeforeUnmount,
- } from 'vue';
- import { useStore } from 'vuex';
- import { useRoute } from 'vue-router';
- import { useI18n } from 'vue-i18n';
- import { compare } from 'compare-versions';
- import defu from 'defu';
- import SelectionArea from '@viselect/vanilla';
- import browser from 'webextension-polyfill';
- import emitter from '@/lib/mitt';
- import {
- useShortcut,
- getShortcut,
- getReadableShortcut,
- } from '@/composable/shortcut';
- import { tasks, excludeOnError } from '@/utils/shared';
- import { parseJSON } from '@/utils/helper';
- import { useGroupTooltip } from '@/composable/groupTooltip';
- import drawflow from '@/lib/drawflow';
- // import WorkflowBuilderSearchBlocks from './WorkflowBuilderSearchBlocks.vue';
- export default {
- // components: { WorkflowBuilderSearchBlocks },
- props: {
- data: {
- type: [Object, String],
- default: null,
- },
- isShared: {
- type: Boolean,
- default: false,
- },
- version: {
- type: [String, Boolean],
- default: '',
- },
- mode: {
- type: String,
- default: 'edit',
- },
- },
- emits: ['load', 'loaded', 'deleteBlock', 'update', 'save'],
- setup(props, { emit }) {
- useGroupTooltip();
- const { t } = useI18n();
- const route = useRoute();
- const store = useStore();
- const contextMenuItems = {
- common: [
- {
- id: 'paste',
- name: t('workflow.editor.paste'),
- icon: 'riFileCopyLine',
- event: 'pasteBlocks',
- shortcut: getReadableShortcut('mod+v'),
- },
- ],
- block: [
- {
- id: 'copy',
- name: t('workflow.editor.copy'),
- icon: 'riFileCopyLine',
- event: 'copyBlocks',
- shortcut: getReadableShortcut('mod+c'),
- },
- {
- id: 'duplicate',
- name: t('workflow.editor.duplicate'),
- icon: 'riFileCopyLine',
- event: 'duplicateBlock',
- shortcut: getShortcut('editor:duplicate-block').readable,
- },
- {
- id: 'delete',
- name: t('common.delete'),
- icon: 'riDeleteBin7Line',
- event: 'deleteBlock',
- shortcut: 'Del',
- },
- ],
- };
- let activeNode = null;
- let hasDragged = false;
- let isDragging = false;
- let selectedElements = [];
- const selection = shallowRef(null);
- const editor = shallowRef(null);
- const contextMenu = reactive({
- items: [],
- data: null,
- show: false,
- position: {},
- });
- const workflowId = route.params.id;
- const prevSelectedEl = {
- output: null,
- connection: null,
- nodeContent: null,
- };
- const isOutputEl = (el) => el.classList.contains('output');
- const isConnectionEl = (el) =>
- el.matches('path.main-path') ||
- el.parentElement.classList.contains('connection');
- function toggleHoverClass({ target, name, active, classes }) {
- const prev = prevSelectedEl[name];
- if (active) {
- if (prev === target) return;
- target.classList.toggle(classes, true);
- } else if (prev) {
- prev.classList.toggle(classes, false);
- }
- prevSelectedEl[name] = target;
- }
- function handleDragOver({ target }) {
- toggleHoverClass({
- target,
- name: 'connection',
- classes: 'selected',
- active: isConnectionEl(target),
- });
- toggleHoverClass({
- target,
- name: 'output',
- classes: 'ring-4',
- active: isOutputEl(target),
- });
- const nodeContent = target.closest(
- '.drawflow-node:not(.blocks-group) .drawflow_content_node'
- );
- toggleHoverClass({
- classes: 'ring-4',
- target: nodeContent,
- name: 'nodeContent',
- active: nodeContent,
- });
- }
- function getRelativePosToEditor(clientX, clientY) {
- const { x, y } = editor.value.precanvas.getBoundingClientRect();
- const { clientWidth, clientHeight } = editor.value.precanvas;
- const { zoom } = editor.value;
- const xPosition =
- clientX * (clientWidth / (clientWidth * zoom)) -
- x * (clientWidth / (clientWidth * zoom));
- const yPosition =
- clientY * (clientHeight / (clientHeight * zoom)) -
- y * (clientHeight / (clientHeight * zoom));
- return { xPosition, yPosition };
- }
- function dropHandler({ dataTransfer, clientX, clientY, target }) {
- const block = JSON.parse(dataTransfer.getData('block') || null);
- if (!block) return;
- const highlightedEls = document.querySelectorAll(
- '.drawflow_content_node.ring-4'
- );
- highlightedEls.forEach((el) => {
- el.classList.remove('ring-4');
- });
- const isTriggerExists =
- block.id === 'trigger' &&
- editor.value.getNodesFromName('trigger').length !== 0;
- if (isTriggerExists) return;
- if (target.closest('.drawflow_content_node')) {
- const targetNodeId = target
- .closest('.drawflow-node')
- .id.replace(/node-/, '');
- const targetNode = editor.value.getNodeFromId(targetNodeId);
- editor.value.removeNodeId(`node-${targetNodeId}`);
- if (targetNode.name === 'blocks-group') return;
- let targetBlock = block;
- if (block.fromBlockBasic) {
- targetBlock = { ...tasks[block.id], id: block.id };
- }
- const onErrorEnabled =
- targetNode.data?.onError?.enable &&
- !excludeOnError.includes(targetBlock.id);
- const newNodeData = onErrorEnabled
- ? { ...targetBlock.data, onError: targetNode.data.onError }
- : targetBlock.data;
- const newNodeId = editor.value.addNode(
- targetBlock.id,
- targetBlock.inputs,
- targetBlock.outputs,
- targetNode.pos_x,
- targetNode.pos_y,
- targetBlock.id,
- newNodeData,
- targetBlock.component,
- 'vue'
- );
- if (onErrorEnabled && targetNode.data.onError.toDo === 'fallback') {
- editor.value.addNodeOutput(newNodeId);
- }
- const duplicateConnections = (nodeIO, type) => {
- if (block[type] === 0) return;
- Object.keys(nodeIO).forEach((name) => {
- const { connections } = nodeIO[name];
- connections.forEach(({ node, input, output }) => {
- if (node === targetNodeId) return;
- if (type === 'inputs') {
- editor.value.addConnection(node, newNodeId, input, name);
- } else if (type === 'outputs') {
- editor.value.addConnection(newNodeId, node, name, output);
- }
- });
- });
- };
- duplicateConnections(targetNode.inputs, 'inputs');
- duplicateConnections(targetNode.outputs, 'outputs');
- emitter.emit('editor:data-changed');
- return;
- }
- if (block.fromBlockBasic) return;
- const { xPosition, yPosition } = getRelativePosToEditor(clientX, clientY);
- const blockId = editor.value.addNode(
- block.id,
- block.inputs,
- block.outputs,
- xPosition + 25,
- yPosition - 25,
- block.id,
- block.data,
- block.component,
- 'vue'
- );
- if (block.fromGroup) {
- const blockEl = document.getElementById(`node-${blockId}`);
- blockEl.setAttribute('group-item-id', block.itemId);
- }
- if (isConnectionEl(target)) {
- target.classList.remove('selected');
- const classes = target.parentElement.classList.toString();
- const result = {};
- const items = [
- { str: 'node_in_', key: 'inputId' },
- { str: 'input_', key: 'inputClass' },
- { str: 'node_out_', key: 'outputId' },
- { str: 'output_', key: 'outputClass' },
- ];
- items.forEach(({ key, str }) => {
- result[key] = classes
- .match(new RegExp(`${str}[^\\s]*`))[0]
- ?.replace(/node_in_node-|node_out_node-/, '');
- });
- try {
- editor.value.removeSingleConnection(
- result.outputId,
- result.inputId,
- result.outputClass,
- result.inputClass
- );
- editor.value.addConnection(
- result.outputId,
- blockId,
- result.outputClass,
- 'input_1'
- );
- editor.value.addConnection(
- blockId,
- result.inputId,
- 'output_1',
- result.inputClass
- );
- } catch (error) {
- console.error(error);
- }
- } else if (isOutputEl(target)) {
- prevSelectedEl.output?.classList.remove('ring-4');
- const targetNodeId = target
- .closest('.drawflow-node')
- .id.replace(/node-/, '');
- const outputClass = target.classList[1];
- editor.value.addConnection(
- targetNodeId,
- blockId,
- outputClass,
- 'input_1'
- );
- }
- emitter.emit('editor:data-changed');
- }
- function isInputAllowed(allowedInputs, input) {
- if (typeof allowedInputs === 'boolean') return allowedInputs;
- return allowedInputs.some((item) => {
- if (item.startsWith('#')) {
- return tasks[input].category === item.substr(1);
- }
- return item === input;
- });
- }
- function deleteBlock() {
- editor.value.removeNodeId(contextMenu.data);
- }
- function clearSelectedElements() {
- selection.value.clearSelection();
- selectedElements.forEach(({ el }) => {
- if (!el) return;
- el.classList.remove('selected-list');
- });
- selectedElements = [];
- activeNode = null;
- }
- function duplicateBlock(nodeId, isPaste = false) {
- let initialPos = null;
- const nodes = new Map();
- const addNode = (id) => {
- const node = editor.value.getNodeFromId(id);
- if (node.name === 'trigger') return;
- nodes.set(node.id, node);
- };
- if (isPaste) {
- store.state.copiedNodes.forEach((node) => {
- nodes.set(node.id, node);
- });
- const pos = contextMenu?.position?.getReferenceClientRect?.() ?? null;
- if (pos) {
- const { xPosition, yPosition } = getRelativePosToEditor(
- pos.left,
- pos.top
- );
- initialPos = { x: xPosition, y: yPosition };
- }
- } else {
- if (nodeId) addNode(nodeId);
- else if (activeNode) addNode(activeNode.id);
- selectedElements.forEach((node) => {
- if (activeNode?.id === node.id || nodeId === node.id) return;
- addNode(node.id);
- });
- }
- clearSelectedElements();
- const nodesOutputs = [];
- let firstNodePos = null;
- let index = 0;
- nodes.forEach((node) => {
- const { outputs, inputs } = tasks[node.name];
- const inputsLen = Object.keys(node.inputs).length;
- const outputsLen = Object.keys(node.outputs).length;
- const blockInputs = inputsLen || inputs;
- const blockOutputs = outputsLen || outputs;
- let nodePosX = node.pos_x;
- let nodePosY = node.pos_y;
- if (initialPos && index === 0) {
- firstNodePos = { x: nodePosX, y: nodePosY };
- nodePosX = initialPos.x;
- nodePosY = initialPos.y;
- } else if (firstNodePos) {
- const xDistance = nodePosX - firstNodePos.x;
- const yDistance = nodePosY - firstNodePos.y;
- nodePosX = initialPos.x + xDistance;
- nodePosY = initialPos.y + yDistance;
- }
- const newNodeId = editor.value.addNode(
- node.name,
- blockInputs,
- blockOutputs,
- nodePosX + 25,
- nodePosY + 70,
- node.name,
- node.data,
- node.html,
- 'vue'
- );
- nodes.set(node.id, { ...nodes.get(node.id), newId: newNodeId });
- const nodeElement = document.querySelector(`#node-${newNodeId}`);
- nodeElement.classList.add('selected-list');
- selectedElements.push({
- id: newNodeId,
- el: nodeElement,
- posY: parseInt(nodeElement.style.top, 10),
- posX: parseInt(nodeElement.style.left, 10),
- });
- emitter.emit('editor:data-changed');
- if (outputsLen > 0) {
- nodesOutputs.push({ id: newNodeId, outputs: node.outputs });
- }
- index += 1;
- });
- if (nodesOutputs.length < 1) return;
- nodesOutputs.forEach(({ id, outputs }) => {
- Object.keys(outputs).forEach((key) => {
- outputs[key].connections.forEach((connection) => {
- const node = nodes.get(connection.node);
- if (!node) return;
- editor.value.addConnection(id, node.newId, key, 'input_1');
- });
- });
- });
- }
- function checkWorkflowData() {
- if (!editor.value) return;
- editor.value.editor_mode = props.isShared ? 'fixed' : 'edit';
- editor.value.container.classList.toggle('is-shared', props.isShared);
- }
- function saveEditorState() {
- const editorStates =
- parseJSON(localStorage.getItem('editor-states'), {}) || {};
- editorStates[workflowId] = {
- zoom: editor.value.zoom,
- canvas_x: editor.value.canvas_x,
- canvas_y: editor.value.canvas_y,
- };
- localStorage.setItem('editor-states', JSON.stringify(editorStates));
- }
- function initSelectArea() {
- selection.value = new SelectionArea({
- container: '#drawflow',
- startareas: ['#drawflow'],
- boundaries: ['#drawflow'],
- selectables: ['.drawflow-node'],
- features: {
- singleTap: {
- allow: false,
- },
- },
- });
- selection.value.on('beforestart', ({ event }) => {
- if (!event.ctrlKey && !event.metaKey) return false;
- editor.value.editor_mode = 'fixed';
- editor.value.editor_selected = false;
- return true;
- });
- selection.value.on('move', () => {
- hasDragged = true;
- });
- selection.value.on('stop', (event) => {
- event.store.selected.forEach((el) => {
- const isExists = selectedElements.some((item) =>
- item.el.isEqualNode(el)
- );
- if (isExists) return;
- el.classList.toggle('selected-list', true);
- selectedElements.push({
- el,
- id: el.id.slice(5),
- posY: parseInt(el.style.top, 10),
- posX: parseInt(el.style.left, 10),
- });
- });
- setTimeout(() => {
- hasDragged = false;
- }, 500);
- });
- }
- function onMouseup({ target }) {
- editor.value.editor_mode = 'edit';
- const isNodeEl = target.closest('.drawflow-node');
- if (!isNodeEl) return;
- const getPosition = (el) => {
- return {
- posY: parseInt(el.style.top, 10),
- posX: parseInt(el.style.left, 10),
- };
- };
- selectedElements.forEach(({ el }, index) => {
- Object.assign(selectedElements[index], getPosition(el));
- });
- if (activeNode) Object.assign(activeNode, getPosition(activeNode.el));
- isDragging = false;
- }
- function onMousedown({ target }) {
- const nodeEl = target.closest('.drawflow-node');
- if (!nodeEl) return;
- if (nodeEl.classList.contains('selected-list')) {
- activeNode = {
- el: nodeEl,
- id: nodeEl.id.slice(5),
- posY: parseInt(nodeEl.style.top, 10),
- posX: parseInt(nodeEl.style.left, 10),
- };
- }
- isDragging = true;
- }
- function onClick({ ctrlKey, metaKey, target }) {
- const nodeEl = target.closest('.drawflow-node');
- if (!nodeEl) {
- if (!hasDragged) clearSelectedElements();
- return;
- }
- const nodeProperties = {
- el: nodeEl,
- id: nodeEl.id.slice(5),
- posY: parseInt(nodeEl.style.top, 10),
- posX: parseInt(nodeEl.style.left, 10),
- };
- if (!ctrlKey && !metaKey && !hasDragged) {
- clearSelectedElements();
- activeNode = nodeProperties;
- nodeEl.classList.add('selected-list');
- selectedElements = [nodeProperties];
- hasDragged = false;
- return;
- }
- hasDragged = false;
- if (!ctrlKey && !metaKey) return;
- const nodeIndex = selectedElements.findIndex(({ el }) =>
- nodeEl.isEqualNode(el)
- );
- if (nodeIndex !== -1) {
- setTimeout(() => {
- nodeEl.classList.remove('selected-list', 'selected');
- }, 400);
- selectedElements.splice(nodeIndex, 1);
- } else {
- nodeEl.classList.add('selected-list');
- selectedElements.push(nodeProperties);
- }
- }
- function clearContextMenu() {
- Object.assign(contextMenu, {
- items: [],
- data: null,
- show: false,
- position: {},
- });
- }
- function copyBlocks() {
- let nodes = selectedElements;
- if (nodes.length === 0) {
- const selectedEl = document.querySelector('.drawflow-node.selected');
- if (selectedEl) {
- nodes.push({ id: selectedEl.id.substr(5) });
- }
- }
- nodes = nodes.map((node) => editor.value.getNodeFromId(node.id));
- store.commit('updateState', {
- key: 'copiedNodes',
- value: nodes,
- });
- }
- function onKeyup({ key, target, ctrlKey, metaKey }) {
- if (ctrlKey || metaKey) {
- if (key === 'c') {
- copyBlocks();
- } else if (key === 'v') {
- duplicateBlock(null, true);
- }
- }
- const isAnInput =
- ['INPUT', 'TEXTAREA'].includes(target.tagName) ||
- target.isContentEditable;
- if (key !== 'Delete' || isAnInput) return;
- selectedElements.forEach(({ id }) => {
- const nodeId = `node-${id}`;
- const isNodeExists = document.querySelector(`#${nodeId}`);
- if (!isNodeExists) return;
- editor.value.removeNodeId(nodeId);
- });
- selectedElements = [];
- activeNode = null;
- }
- useShortcut('editor:duplicate-block', () => {
- if (!activeNode && selectedElements.length <= 0) return;
- duplicateBlock();
- });
- watch(() => props.isShared, checkWorkflowData);
- onMounted(() => {
- const context = getCurrentInstance().appContext.app._context;
- const element = document.querySelector('#drawflow');
- element.addEventListener('mousedown', onMousedown);
- element.addEventListener('mouseup', onMouseup);
- element.addEventListener('click', onClick);
- element.addEventListener('keyup', onKeyup);
- editor.value = drawflow(element, {
- context,
- options: {
- reroute: true,
- ...store.state.settings.editor,
- },
- });
- editor.value.start();
- emit('load', editor.value);
- if (props.data) {
- let data =
- typeof props.data === 'string'
- ? parseJSON(props.data, null)
- : props.data;
- if (!data || !data?.drawflow?.Home) return;
- const currentExtVersion = browser.runtime.getManifest().version;
- const isOldWorkflow = compare(
- currentExtVersion,
- props.version || '0.0.0',
- '>'
- );
- if (isOldWorkflow && typeof props.version !== 'boolean') {
- const newDrawflowData = Object.entries(
- data.drawflow.Home.data
- ).reduce((obj, [key, value]) => {
- obj[key] = {
- ...value,
- html: tasks[value.name].component,
- data: defu({}, value.data, tasks[value.name].data),
- };
- return obj;
- }, {});
- data = {
- drawflow: { Home: { data: newDrawflowData } },
- };
- emit('update', { version: currentExtVersion });
- }
- editor.value.import(data);
- if (isOldWorkflow) {
- setTimeout(() => {
- emit('save');
- }, 200);
- }
- } else if (!props.isShared) {
- editor.value.addNode(
- 'trigger',
- 0,
- 1,
- 50,
- 300,
- 'trigger',
- tasks.trigger.data,
- 'BlockBasic',
- 'vue'
- );
- }
- editor.value.on('mouseMove', () => {
- if (!activeNode || !isDragging) return;
- const xDistance =
- parseInt(activeNode.el.style.left, 10) - activeNode.posX;
- const yDistance =
- parseInt(activeNode.el.style.top, 10) - activeNode.posY;
- selectedElements.forEach(({ el, posX, posY }) => {
- if (el.isEqualNode(activeNode.el)) return;
- const nodeId = el.id.slice(5);
- const node = editor.value.drawflow.drawflow.Home.data[nodeId];
- const newPosX = posX + xDistance;
- const newPosY = posY + yDistance;
- node.pos_x = newPosX;
- node.pos_y = newPosY;
- el.style.top = `${newPosY}px`;
- el.style.left = `${newPosX}px`;
- editor.value.updateConnectionNodes(el.id);
- });
- hasDragged = true;
- });
- editor.value.on('nodeRemoved', (id) => {
- emit('deleteBlock', id);
- });
- editor.value.on(
- 'connectionCreated',
- ({ output_id, input_id, output_class, input_class }) => {
- const { name: inputName } = editor.value.getNodeFromId(input_id);
- const { allowedInputs } = tasks[inputName];
- const isAllowed = isInputAllowed(allowedInputs, inputName);
- if (!isAllowed) {
- editor.value.removeSingleConnection(
- output_id,
- input_id,
- output_class,
- input_class
- );
- }
- emitter.emit('editor:data-changed');
- }
- );
- editor.value.on('connectionRemoved', () => {
- emitter.emit('editor:data-changed');
- });
- editor.value.on('export', saveEditorState);
- editor.value.on('contextmenu', ({ clientY, clientX, target }) => {
- if (target.tagName === 'path' && target.classList.contains('main-path'))
- return;
- const isBlock = target.closest('.drawflow .drawflow-node');
- const virtualEl = {
- getReferenceClientRect: () => ({
- width: 0,
- height: 0,
- top: clientY,
- right: clientX,
- bottom: clientY,
- left: clientX,
- }),
- };
- if (isBlock) {
- contextMenu.data = isBlock.id;
- contextMenu.position = virtualEl;
- contextMenu.items = contextMenuItems.block;
- contextMenu.show = true;
- }
- const copiedNodesLen = store.state.copiedNodes.length;
- if (copiedNodesLen > 0) {
- if (isBlock) {
- contextMenuItems.common.forEach((item) => {
- const isExists = contextMenu.items.some(
- (menu) => menu.id === item.id
- );
- if (isExists) return;
- contextMenu.items.unshift(item);
- });
- } else {
- contextMenu.items = contextMenuItems.common;
- }
- contextMenu.position = virtualEl;
- contextMenu.show = true;
- }
- });
- const editorStates =
- parseJSON(localStorage.getItem('editor-states'), {}) || {};
- const editorState = editorStates[workflowId];
- if (editorState) {
- const { canvas_x, canvas_y, zoom } = editorState;
- editor.value.translate_to(canvas_x, canvas_y, zoom);
- }
- checkWorkflowData();
- initSelectArea();
- emit('loaded', editor.value);
- });
- onBeforeUnmount(() => {
- const element = document.querySelector('#drawflow');
- if (element) {
- element.removeEventListener('mousedown', onMousedown);
- element.removeEventListener('mouseup', onMouseup);
- element.removeEventListener('click', onClick);
- element.removeEventListener('keyup', onKeyup);
- }
- saveEditorState();
- });
- return {
- t,
- editor,
- contextMenu,
- dropHandler,
- handleDragOver,
- clearContextMenu,
- contextMenuHandler: {
- copyBlocks,
- deleteBlock,
- pasteBlocks: () => duplicateBlock(null, true),
- duplicateBlock: () => duplicateBlock(contextMenu.data.substr(5)),
- },
- };
- },
- };
- </script>
- <style>
- #drawflow {
- background-image: url('@/assets/images/tile.png');
- background-size: 35px;
- user-select: none;
- }
- .dark #drawflow {
- background-image: url('@/assets/images/tile-white.png');
- }
- .drawflow .drawflow-node {
- @apply dark:bg-gray-800;
- }
- #drawflow[arrow='true'] .drawflow-node .input {
- background-color: transparent !important;
- border-top: 10px solid transparent;
- border-radius: 0;
- border-left: 10px solid theme('colors.accent');
- border-right: 10px solid transparent;
- border-bottom: 10px solid transparent;
- }
- .selection-area {
- background: rgba(46, 115, 252, 0.11);
- border: 2px solid rgba(98, 155, 255, 0.81);
- border-radius: 0.1em;
- }
- .drawflow_content_node {
- @apply rounded-lg;
- }
- </style>
|