@@ -230,6 +231,7 @@ import ActivityBar from "@/components/ActivityBar/ActivityBar.vue";
import MarkdownEditor from "@/components/Markdown/MarkdownEditor.vue";
import FlexPanel from "@/components/Panels/FlexPanel.vue";
import ToolPanel from "@/components/Panels/ToolPanel.vue";
+import WorkflowPanel from "@/components/Panels/WorkflowPanel.vue";
import UndoRedoStack from "@/components/UndoRedo/UndoRedoStack.vue";
import FormDefault from "@/components/Workflow/Editor/Forms/FormDefault.vue";
import FormTool from "@/components/Workflow/Editor/Forms/FormTool.vue";
@@ -254,6 +256,7 @@ export default {
WorkflowGraph,
FontAwesomeIcon,
UndoRedoStack,
+ WorkflowPanel,
},
props: {
workflowId: {
diff --git a/client/src/components/Workflow/List/WorkflowActions.vue b/client/src/components/Workflow/List/WorkflowActions.vue
index 91c5524c1ac4..a80fbcab2f02 100644
--- a/client/src/components/Workflow/List/WorkflowActions.vue
+++ b/client/src/components/Workflow/List/WorkflowActions.vue
@@ -1,48 +1,48 @@
@@ -112,7 +76,7 @@ async function onDelete() {
title="Add to bookmarks"
tooltip="Add to bookmarks. This workflow will appear in the left tool panel."
size="sm"
- @click="onToggleBookmark(true)">
+ @click="toggleBookmark">
@@ -123,7 +87,7 @@ async function onDelete() {
variant="link"
title="Remove bookmark"
size="sm"
- @click="onToggleBookmark(false)">
+ @click="toggleBookmark">
@@ -141,13 +105,63 @@ async function onDelete() {
+
+
+
+ Run
+
+
+
+
+ Link to Workflow
+
+
+
+
+ Copy
+
+
+
+
+
+ @click="deleteWorkflow">
Delete
diff --git a/client/src/components/Workflow/List/WorkflowActionsExtend.vue b/client/src/components/Workflow/List/WorkflowActionsExtend.vue
index ae5878a44e7f..f896fe89ceff 100644
--- a/client/src/components/Workflow/List/WorkflowActionsExtend.vue
+++ b/client/src/components/Workflow/List/WorkflowActionsExtend.vue
@@ -1,56 +1,54 @@
-
+
-
-
- Link to Workflow
-
+
+
+
+ Link to Workflow
+
+
+
+
+ Copy
+
+
+
+
+ Download
+
+
+
+
+ Share
+
+
-
- Copy
-
-
-
-
- Download
+ @click="onRestore">
+
+ Restore
+
+
-
- Share
+ :to="`/workflows/edit?id=${workflow.id}`">
+
+ Edit
-
-
- Restore
-
-
+ :action="importWorkflow">
+ Import
+
+
+
+
+
+
+
+
+
+
+
+ Insert
+
+
+
diff --git a/client/src/components/Workflow/List/useWorkflowActions.ts b/client/src/components/Workflow/List/useWorkflowActions.ts
new file mode 100644
index 000000000000..8b0855fb4bc0
--- /dev/null
+++ b/client/src/components/Workflow/List/useWorkflowActions.ts
@@ -0,0 +1,110 @@
+import { computed, type Ref, ref } from "vue";
+
+import {
+ copyWorkflow as copyWorkflowService,
+ deleteWorkflow as deleteWorkflowService,
+ updateWorkflow as updateWorkflowService,
+} from "@/components/Workflow/workflows.services";
+import { useConfig } from "@/composables/config";
+import { useConfirmDialog } from "@/composables/confirmDialog";
+import { useToast } from "@/composables/toast";
+import { copy } from "@/utils/clipboard";
+import { withPrefix } from "@/utils/redirect";
+import { getFullAppUrl } from "@/utils/utils";
+
+// TODO: replace me with a more accurate type
+type Workflow = any;
+
+export function useWorkflowActions(workflow: Ref
, refreshCallback: () => void) {
+ const toast = useToast();
+ const { config } = useConfig() as { config: Record };
+
+ const bookmarkLoading = ref(false);
+
+ const toggleBookmark = async (checked: boolean) => {
+ try {
+ bookmarkLoading.value = true;
+
+ await updateWorkflowService(workflow.value.id, {
+ show_in_tool_panel: checked,
+ });
+
+ toast.info(`Workflow ${checked ? "added to" : "removed from"} bookmarks`);
+
+ if (checked) {
+ config.stored_workflow_menu_entries.push({
+ id: workflow.value.id,
+ name: workflow.value.name,
+ });
+ } else {
+ const indexToRemove = config.stored_workflow_menu_entries.findIndex(
+ (w: Workflow) => w.id === workflow.value.id
+ );
+ config.stored_workflow_menu_entries.splice(indexToRemove, 1);
+ }
+ } catch (error) {
+ toast.error("Failed to update workflow bookmark status");
+ } finally {
+ refreshCallback();
+ bookmarkLoading.value = false;
+ }
+ };
+
+ const { confirm } = useConfirmDialog();
+
+ const deleteWorkflow = async () => {
+ const confirmed = await confirm("Are you sure you want to delete this workflow?", {
+ title: "Delete workflow",
+ okTitle: "Delete",
+ okVariant: "danger",
+ });
+
+ if (confirmed) {
+ await deleteWorkflowService(workflow.value.id);
+ refreshCallback();
+ toast.info("Workflow deleted");
+ }
+ };
+
+ const relativeLink = computed(() => {
+ return `/published/workflow?id=${workflow.value.id}`;
+ });
+
+ const fullLink = computed(() => {
+ return getFullAppUrl(relativeLink.value.substring(1));
+ });
+
+ function copyPublicLink() {
+ copy(fullLink.value);
+ toast.success("Link to workflow copied");
+ }
+
+ async function copyWorkflow() {
+ const confirmed = await confirm("Are you sure you want to make a copy of this workflow?", "Copy workflow");
+
+ if (confirmed) {
+ await copyWorkflowService(workflow.value.id, workflow.value.owner);
+ refreshCallback();
+ toast.success("Workflow copied");
+ }
+ }
+
+ const downloadUrl = computed(() => {
+ return withPrefix(`/api/workflows/${workflow.value.id}/download?format=json-download`);
+ });
+
+ async function importWorkflow() {
+ await copyWorkflowService(workflow.value.id, workflow.value.owner);
+ toast.success("Workflow imported successfully");
+ }
+
+ return {
+ bookmarkLoading,
+ toggleBookmark,
+ deleteWorkflow,
+ copyPublicLink,
+ copyWorkflow,
+ importWorkflow,
+ downloadUrl,
+ };
+}
diff --git a/client/src/components/Workflow/WorkflowInvocationsCount.vue b/client/src/components/Workflow/WorkflowInvocationsCount.vue
index 275173da3eb9..e9101f5def7a 100644
--- a/client/src/components/Workflow/WorkflowInvocationsCount.vue
+++ b/client/src/components/Workflow/WorkflowInvocationsCount.vue
@@ -41,7 +41,7 @@ onMounted(initCounts);
-
+
never run
diff --git a/client/src/components/Workflow/workflows.services.ts b/client/src/components/Workflow/workflows.services.ts
index 1a39e98d57f0..7c611620c807 100644
--- a/client/src/components/Workflow/workflows.services.ts
+++ b/client/src/components/Workflow/workflows.services.ts
@@ -1,10 +1,14 @@
import axios from "axios";
+import { GalaxyApi } from "@/api";
import { useUserStore } from "@/stores/userStore";
import { withPrefix } from "@/utils/redirect";
+import { rethrowSimple } from "@/utils/simple-error";
export type Workflow = Record;
+type SortBy = "create_time" | "update_time" | "name";
+
interface LoadWorkflowsOptions {
sortBy: SortBy;
sortDesc: boolean;
@@ -15,7 +19,6 @@ interface LoadWorkflowsOptions {
skipStepCounts: boolean;
}
-const getWorkflows = fetcher.path("/api/workflows").method("get").create();
export async function loadWorkflows({
sortBy = "update_time",
sortDesc = true,
@@ -24,17 +27,28 @@ export async function loadWorkflows({
filterText = "",
showPublished = false,
skipStepCounts = true,
-}: LoadWorkflowsOptions): Promise<{ data: Workflow[]; headers: Headers }> {
- const { data, headers } = await getWorkflows({
- sort_by: sortBy,
- sort_desc: sortDesc,
- limit,
- offset,
- search: filterText,
- show_published: showPublished,
- skip_step_counts: skipStepCounts,
+}: LoadWorkflowsOptions): Promise<{ data: Workflow[]; totalMatches: number }> {
+ const { response, data, error } = await GalaxyApi().GET("/api/workflows", {
+ params: {
+ query: {
+ sort_by: sortBy,
+ sort_desc: sortDesc,
+ limit,
+ offset,
+ search: filterText,
+ show_published: showPublished,
+ skip_step_counts: skipStepCounts,
+ },
+ },
});
- return { data, headers };
+
+ if (error) {
+ rethrowSimple(error);
+ }
+
+ const totalMatches = parseInt(response.headers.get("Total_matches") || "0", 10) || 0;
+
+ return { data, totalMatches };
}
export async function updateWorkflow(id: string, changes: object): Promise {
From 2cd580fad58ccf574198693259ccfc524737fe43 Mon Sep 17 00:00:00 2001
From: Laila Los <44241786+ElectronicBlueberry@users.noreply.github.com>
Date: Fri, 6 Sep 2024 14:27:44 +0200
Subject: [PATCH 015/132] fix z-indexing for dropdown add missing actions to
dropdown
---
.../Workflow/List/WorkflowActions.vue | 55 ++++++++-----------
.../components/Workflow/List/WorkflowCard.vue | 13 ++++-
2 files changed, 33 insertions(+), 35 deletions(-)
diff --git a/client/src/components/Workflow/List/WorkflowActions.vue b/client/src/components/Workflow/List/WorkflowActions.vue
index a80fbcab2f02..bbedf649ab0a 100644
--- a/client/src/components/Workflow/List/WorkflowActions.vue
+++ b/client/src/components/Workflow/List/WorkflowActions.vue
@@ -3,10 +3,12 @@ import { faStar as farStar } from "@fortawesome/free-regular-svg-icons";
import {
faCaretDown,
faCopy,
+ faDownload,
faExternalLinkAlt,
faFileExport,
faLink,
faPlay,
+ faShareAlt,
faSpinner,
faStar,
faTrash,
@@ -33,12 +35,14 @@ const props = withDefaults(defineProps(), {
const emit = defineEmits<{
(e: "refreshList", overlayLoading?: boolean): void;
+ (e: "dropdown", open: boolean): void;
}>();
-const { bookmarkLoading, deleteWorkflow, toggleBookmark, copyPublicLink, copyWorkflow } = useWorkflowActions(
- computed(() => props.workflow),
- () => emit("refreshList", true)
-);
+const { bookmarkLoading, deleteWorkflow, toggleBookmark, copyPublicLink, copyWorkflow, downloadUrl } =
+ useWorkflowActions(
+ computed(() => props.workflow),
+ () => emit("refreshList", true)
+ );
const userStore = useUserStore();
const { isAnonymous } = storeToRefs(userStore);
@@ -100,7 +104,9 @@ const runPath = computed(
class="workflow-actions-dropdown"
title="Workflow actions"
toggle-class="inline-icon-button"
- variant="link">
+ variant="link"
+ @show="() => emit('dropdown', true)"
+ @hide="() => emit('dropdown', false)">
@@ -115,44 +121,33 @@ const runPath = computed(
v-if="props.workflow.published"
size="sm"
title="Copy link to workflow"
- variant="link"
@click="copyPublicLink">
Link to Workflow
-
+
Copy
-
+ Share
+
- Delete
+ Delete
- View on Dockstore
+ View on Dockstore
- View external link
+ View external link
- Export
+ Export
diff --git a/client/src/components/Workflow/List/WorkflowCard.vue b/client/src/components/Workflow/List/WorkflowCard.vue
index 953570a7019c..5fbf335957b2 100644
--- a/client/src/components/Workflow/List/WorkflowCard.vue
+++ b/client/src/components/Workflow/List/WorkflowCard.vue
@@ -3,7 +3,7 @@ import { faPen } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
import { BButton, BLink } from "bootstrap-vue";
import { storeToRefs } from "pinia";
-import { computed } from "vue";
+import { computed, ref } from "vue";
import { updateWorkflow } from "@/components/Workflow/workflows.services";
import { useUserStore } from "@/stores/userStore";
@@ -67,10 +67,12 @@ async function onTagsUpdate(tags: string[]) {
async function onTagClick(tag: string) {
emit("tagClick", tag);
}
+
+const dropdownOpen = ref(false);
-
+
+ @refreshList="emit('refreshList', true)"
+ @dropdown="(open) => (dropdownOpen = open)" />
@@ -154,6 +157,10 @@ async function onTagClick(tag: string) {
container: workflow-card / inline-size;
padding: 0 0.25rem 0.5rem 0.25rem;
+ &.dropdown-open {
+ z-index: 10;
+ }
+
.workflow-rename {
opacity: 0;
}
From c9e0483b266764a5da0cb817666d52a1e9d645f6 Mon Sep 17 00:00:00 2001
From: Laila Los <44241786+ElectronicBlueberry@users.noreply.github.com>
Date: Fri, 6 Sep 2024 14:40:07 +0200
Subject: [PATCH 016/132] connect insert and insert steps events
---
client/src/components/Panels/WorkflowPanel.vue | 13 ++++++++++++-
client/src/components/Workflow/Editor/Index.vue | 5 ++++-
.../Workflow/List/WorkflowActionsExtend.vue | 4 ++--
.../src/components/Workflow/List/WorkflowCard.vue | 6 +++++-
.../components/Workflow/List/WorkflowCardList.vue | 15 ++++++++++++++-
5 files changed, 37 insertions(+), 6 deletions(-)
diff --git a/client/src/components/Panels/WorkflowPanel.vue b/client/src/components/Panels/WorkflowPanel.vue
index ec20955e0745..b301f01a5bc6 100644
--- a/client/src/components/Panels/WorkflowPanel.vue
+++ b/client/src/components/Panels/WorkflowPanel.vue
@@ -11,6 +11,11 @@ import DelayedInput from "@/components/Common/DelayedInput.vue";
import ScrollToTopButton from "@/components/ToolsList/ScrollToTopButton.vue";
import WorkflowCardList from "@/components/Workflow/List/WorkflowCardList.vue";
+const emit = defineEmits<{
+ (e: "insertWorkflow", id: string, name: string): void;
+ (e: "insertWorkflowSteps", id: string, stepCount: number): void;
+}>();
+
const scrollable = ref(null);
const { arrived, scrollTop } = useAnimationFrameScroll(scrollable);
@@ -115,7 +120,13 @@ function scrollToTop() {
:loading="loading">
diff --git a/client/src/components/Workflow/List/WorkflowCardList.vue b/client/src/components/Workflow/List/WorkflowCardList.vue
index 86c9ff8eeb73..7b4aaf0cc61f 100644
--- a/client/src/components/Workflow/List/WorkflowCardList.vue
+++ b/client/src/components/Workflow/List/WorkflowCardList.vue
@@ -23,6 +23,8 @@ const emit = defineEmits<{
(e: "tagClick", tag: string): void;
(e: "refreshList", overlayLoading?: boolean, silent?: boolean): void;
(e: "updateFilter", key: string, value: any): void;
+ (e: "insertWorkflow", id: string, name: string): void;
+ (e: "insertWorkflowSteps", id: string, stepCount: number): void;
}>();
const modalOptions = reactive({
@@ -54,6 +56,15 @@ function onPreview(id: string) {
modalOptions.preview.id = id;
showPreview.value = true;
}
+
+// TODO: clean-up types, as soon as better Workflow type is available
+function onInsert(workflow: Workflow) {
+ emit("insertWorkflow", workflow.id as any, workflow.name as any);
+}
+
+function onInsertSteps(workflow: Workflow) {
+ emit("insertWorkflowSteps", workflow.id as any, workflow.number_of_steps as any);
+}
@@ -72,7 +83,9 @@ function onPreview(id: string) {
@refreshList="(...args) => emit('refreshList', ...args)"
@updateFilter="(...args) => emit('updateFilter', ...args)"
@rename="onRename"
- @preview="onPreview">
+ @preview="onPreview"
+ @insert="onInsert(workflow)"
+ @insertSteps="onInsertSteps(workflow)">
Date: Fri, 6 Sep 2024 15:14:14 +0200
Subject: [PATCH 017/132] move breakpoints to partial remove unused css fix
broken css add non-found indicator
---
.../src/components/Panels/WorkflowPanel.vue | 1 +
.../Workflow/List/WorkflowActionsExtend.vue | 2 +-
.../components/Workflow/List/WorkflowCard.vue | 39 +++++++++++--------
.../Workflow/List/WorkflowCardList.vue | 2 +-
.../Workflow/List/WorkflowIndicators.vue | 1 -
.../components/Workflow/List/WorkflowList.vue | 3 --
.../Workflow/List/_breakpoints.scss} | 1 +
7 files changed, 26 insertions(+), 23 deletions(-)
rename client/src/{style/scss/breakpoints.scss => components/Workflow/List/_breakpoints.scss} (83%)
diff --git a/client/src/components/Panels/WorkflowPanel.vue b/client/src/components/Panels/WorkflowPanel.vue
index b301f01a5bc6..4028c896682d 100644
--- a/client/src/components/Panels/WorkflowPanel.vue
+++ b/client/src/components/Panels/WorkflowPanel.vue
@@ -131,6 +131,7 @@ function scrollToTop() {
- 1 workflow loaded -
- All {{ workflows.length }} workflows loaded -
+ - No workflows found -
- loading -
diff --git a/client/src/components/Workflow/List/WorkflowActionsExtend.vue b/client/src/components/Workflow/List/WorkflowActionsExtend.vue
index 9992e4c33bcd..abe6ed959ea7 100644
--- a/client/src/components/Workflow/List/WorkflowActionsExtend.vue
+++ b/client/src/components/Workflow/List/WorkflowActionsExtend.vue
@@ -217,7 +217,7 @@ const { copyPublicLink, copyWorkflow, downloadUrl, importWorkflow } = useWorkflo
diff --git a/client/src/components/Workflow/List/WorkflowCardList.vue b/client/src/components/Workflow/List/WorkflowCardList.vue
index 7b4aaf0cc61f..91a3b9f91151 100644
--- a/client/src/components/Workflow/List/WorkflowCardList.vue
+++ b/client/src/components/Workflow/List/WorkflowCardList.vue
@@ -117,7 +117,7 @@ function onInsertSteps(workflow: Workflow) {
diff --git a/client/src/components/Panels/FlexPanel.vue b/client/src/components/Panels/FlexPanel.vue
index 9d0031af395f..a01a8612b097 100644
--- a/client/src/components/Panels/FlexPanel.vue
+++ b/client/src/components/Panels/FlexPanel.vue
@@ -2,14 +2,9 @@
import { library } from "@fortawesome/fontawesome-svg-core";
import { faChevronLeft, faChevronRight } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
-import { useDebounce, useDraggable } from "@vueuse/core";
import { computed, ref, watch } from "vue";
-import { useTimeoutThrottle } from "@/composables/throttle";
-
-import { determineWidth } from "./utilities";
-
-const { throttle } = useTimeoutThrottle(10);
+import DraggableSeparator from "../Common/DraggableSeparator.vue";
library.add(faChevronLeft, faChevronRight);
@@ -28,26 +23,18 @@ const props = withDefaults(defineProps(), {
defaultWidth: 300,
});
-const draggable = ref(null);
-const root = ref(null);
-
const panelWidth = ref(props.defaultWidth);
-const show = ref(true);
-
-const { position, isDragging } = useDraggable(draggable, {
- preventDefault: true,
- exact: true,
-});
-const hoverDraggable = ref(false);
-const hoverDraggableDebounced = useDebounce(hoverDraggable, 100);
-const showHover = computed(() => (hoverDraggable.value && hoverDraggableDebounced.value) || isDragging.value);
+const root = ref(null);
+const show = ref(true);
const showToggle = ref(false);
const hoverToggle = ref(false);
-const hoverDraggableOrToggle = computed(
- () => (hoverDraggableDebounced.value || hoverToggle.value) && !isDragging.value
-);
+
+const isHoveringDragHandle = ref(false);
+const isDragging = ref(false);
+
+const hoverDraggableOrToggle = computed(() => (isHoveringDragHandle.value || hoverToggle.value) && !isDragging.value);
const toggleLinger = 500;
const toggleShowDelay = 600;
@@ -70,72 +57,6 @@ watch(
}
);
-/** Watch position changes and adjust width accordingly */
-watch(position, () => {
- throttle(() => {
- if (!root.value || !draggable.value) {
- return;
- }
-
- const rectRoot = root.value.getBoundingClientRect();
- const rectDraggable = draggable.value.getBoundingClientRect();
- panelWidth.value = determineWidth(
- rectRoot,
- rectDraggable,
- props.minWidth,
- props.maxWidth,
- props.side,
- position.value.x
- );
- });
-});
-
-/** If the `maxWidth` changes, prevent the panel from exceeding it */
-watch(
- () => props.maxWidth,
- (newVal) => {
- if (newVal && panelWidth.value > newVal) {
- panelWidth.value = props.maxWidth;
- }
- },
- { immediate: true }
-);
-
-/** If the `minWidth` changes, ensure the panel width is at least the `minWidth` */
-watch(
- () => props.minWidth,
- (newVal) => {
- if (newVal && panelWidth.value < newVal) {
- panelWidth.value = newVal;
- }
- },
- { immediate: true }
-);
-
-function onKeyLeft() {
- if (props.side === "left") {
- decreaseWidth();
- } else {
- increaseWidth();
- }
-}
-
-function onKeyRight() {
- if (props.side === "left") {
- increaseWidth();
- } else {
- decreaseWidth();
- }
-}
-
-function increaseWidth(by = 50) {
- panelWidth.value = Math.min(panelWidth.value + by, props.maxWidth);
-}
-
-function decreaseWidth(by = 50) {
- panelWidth.value = Math.max(panelWidth.value - by, props.minWidth);
-}
-
const sideClasses = computed(() => ({
left: props.side === "left",
right: props.side === "right",
@@ -148,19 +69,9 @@ const sideClasses = computed(() => ({
:id="side"
ref="root"
class="flex-panel"
- :class="{ ...sideClasses, 'show-hover': showHover }"
+ :class="{ ...sideClasses }"
:style="`--width: ${panelWidth}px`">
-
+
-
+
@@ -216,6 +217,7 @@ import reportDefault from "./reportDefault";
import WorkflowLint from "./Lint.vue";
import MessagesModal from "./MessagesModal.vue";
+import NodeInspector from "./NodeInspector.vue";
import RefactorConfirmationModal from "./RefactorConfirmationModal.vue";
import SaveChangesModal from "./SaveChangesModal.vue";
import StateUpgradeModal from "./StateUpgradeModal.vue";
@@ -224,12 +226,9 @@ import WorkflowGraph from "./WorkflowGraph.vue";
import ActivityBar from "@/components/ActivityBar/ActivityBar.vue";
import MarkdownEditor from "@/components/Markdown/MarkdownEditor.vue";
import MarkdownToolBox from "@/components/Markdown/MarkdownToolBox.vue";
-import FlexPanel from "@/components/Panels/FlexPanel.vue";
import ToolPanel from "@/components/Panels/ToolPanel.vue";
import WorkflowPanel from "@/components/Panels/WorkflowPanel.vue";
import UndoRedoStack from "@/components/UndoRedo/UndoRedoStack.vue";
-import FormDefault from "@/components/Workflow/Editor/Forms/FormDefault.vue";
-import FormTool from "@/components/Workflow/Editor/Forms/FormTool.vue";
library.add(faArrowLeft, faArrowRight, faHistory);
@@ -237,12 +236,9 @@ export default {
components: {
ActivityBar,
MarkdownEditor,
- FlexPanel,
SaveChangesModal,
StateUpgradeModal,
ToolPanel,
- FormDefault,
- FormTool,
WorkflowAttributes,
WorkflowLint,
RefactorConfirmationModal,
@@ -252,6 +248,7 @@ export default {
UndoRedoStack,
WorkflowPanel,
MarkdownToolBox,
+ NodeInspector,
},
props: {
workflowId: {
diff --git a/client/src/components/Workflow/Editor/NodeInspector.vue b/client/src/components/Workflow/Editor/NodeInspector.vue
new file mode 100644
index 000000000000..4b506626242f
--- /dev/null
+++ b/client/src/components/Workflow/Editor/NodeInspector.vue
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
diff --git a/client/src/components/Workflow/Editor/WorkflowGraph.vue b/client/src/components/Workflow/Editor/WorkflowGraph.vue
index 14c83d2a8cbf..b0a27f1b6235 100644
--- a/client/src/components/Workflow/Editor/WorkflowGraph.vue
+++ b/client/src/components/Workflow/Editor/WorkflowGraph.vue
@@ -64,6 +64,7 @@
:viewport-bounding-box="viewportBoundingBox"
@panBy="panBy"
@moveTo="moveTo" />
+