feat(editor): Show workflow breadcrumbs in canvas (#14710)

This commit is contained in:
Milorad FIlipović
2025-04-28 13:37:55 +02:00
committed by GitHub
parent be53453def
commit 46df8b47d6
26 changed files with 911 additions and 238 deletions

View File

@@ -32,7 +32,6 @@ import InsightsSummary from '@/features/insights/components/InsightsSummary.vue'
import { useInsightsStore } from '@/features/insights/insights.store';
import type {
FolderListItem,
FolderPathItem,
IUser,
UserAction,
WorkflowListItem,
@@ -136,6 +135,8 @@ const currentFolderId = ref<string | null>(null);
const showCardsBadge = ref(false);
const isNameEditEnabled = ref(false);
/**
* Folder actions
* These can appear on the list header, and then they are applied to current folder
@@ -213,6 +214,12 @@ const currentFolder = computed(() => {
return currentFolderId.value ? foldersStore.breadcrumbsCache[currentFolderId.value] : null;
});
const currentFolderParent = computed(() => {
return currentFolder.value?.parentFolder
? foldersStore.breadcrumbsCache[currentFolder.value.parentFolder]
: null;
});
const isDragging = computed(() => {
return foldersStore.draggedElement !== null;
});
@@ -504,6 +511,7 @@ const fetchWorkflows = async () => {
.map((r) => ({ id: r.id, name: r.name, parentFolder: r.parentFolder?.id })),
);
// This is for the case when user lands straight on a folder page
const isCurrentFolderCached = foldersStore.breadcrumbsCache[parentFolder ?? ''] !== undefined;
const needToFetchFolderPath = parentFolder && !isCurrentFolderCached && routeProjectId;
@@ -857,63 +865,6 @@ const moveResourceOnDrop = async (draggedResource: DragTarget, dropTarget: DropT
// Breadcrumbs methods
/**
* Breadcrumbs: Calculate visible and hidden items for both main breadcrumbs and card breadcrumbs
* We do this here and pass to each component to avoid recalculating in each card
*/
const visibleBreadcrumbsItems = computed<FolderPathItem[]>(() => {
if (!currentFolder.value) return [];
const items: FolderPathItem[] = [];
const parent = foldersStore.getCachedFolder(currentFolder.value.parentFolder ?? '');
if (parent) {
items.push({
id: parent.id,
label: parent.name,
href: `/projects/${route.params.projectId}/folders/${parent.id}/workflows`,
parentFolder: parent.parentFolder,
});
}
items.push({
id: currentFolder.value.id,
label: currentFolder.value.name,
parentFolder: parent?.parentFolder,
});
return items;
});
const hiddenBreadcrumbsItems = computed<FolderPathItem[]>(() => {
const lastVisibleParent: FolderPathItem =
visibleBreadcrumbsItems.value[visibleBreadcrumbsItems.value.length - 1];
if (!lastVisibleParent) return [];
const items: FolderPathItem[] = [];
// Go through all the parent folders and add them to the hidden items
let parentFolder = lastVisibleParent.parentFolder;
while (parentFolder) {
const parent = foldersStore.getCachedFolder(parentFolder);
if (!parent) break;
items.unshift({
id: parent.id,
label: parent.name,
href: `/projects/${route.params.projectId}/folders/${parent.id}/workflows`,
parentFolder: parent.parentFolder,
});
parentFolder = parent.parentFolder;
}
return items;
});
/**
* Main breadcrumbs items that show on top of the list
* These show path to the current folder with up to 2 parents visible
*/
const mainBreadcrumbs = computed(() => {
return {
visibleItems: visibleBreadcrumbsItems.value,
hiddenItems: hiddenBreadcrumbsItems.value,
};
});
const onBreadcrumbItemClick = (item: PathItem) => {
if (item.href) {
loading.value = true;
@@ -958,8 +909,7 @@ const onBreadCrumbsAction = async (action: string) => {
);
break;
case FOLDER_LIST_ITEM_ACTIONS.RENAME:
if (!route.params.folderId) return;
await renameFolder(route.params.folderId as string);
onNameToggle();
break;
case FOLDER_LIST_ITEM_ACTIONS.MOVE:
if (!currentFolder.value) return;
@@ -1307,6 +1257,69 @@ const onCreateWorkflowClick = () => {
},
});
};
const onNameToggle = () => {
isNameEditEnabled.value = !isNameEditEnabled.value;
};
const onNameSubmit = async ({
name,
onSubmit,
}: {
name: string;
onSubmit: (saved: boolean) => void;
}) => {
if (!currentFolder.value || !currentProject.value) return;
const newName = name.trim();
if (!newName) {
toast.showMessage({
title: i18n.baseText('renameAction.emptyName.title'),
message: i18n.baseText('renameAction.emptyName.message'),
type: 'error',
});
onSubmit(false);
return;
}
if (newName === currentFolder.value.name) {
isNameEditEnabled.value = false;
onSubmit(true);
return;
}
const validationResult = folderHelpers.validateFolderName(newName);
if (typeof validationResult === 'string') {
toast.showMessage({
title: i18n.baseText('renameAction.invalidName.title'),
message: validationResult,
type: 'error',
});
onSubmit(false);
return;
} else {
try {
await foldersStore.renameFolder(currentProject.value?.id, currentFolder.value.id, newName);
foldersStore.breadcrumbsCache[currentFolder.value.id].name = newName;
toast.showMessage({
title: i18n.baseText('folders.rename.success.message', {
interpolate: { folderName: newName },
}),
type: 'success',
});
telemetry.track('User renamed folder', {
folder_id: currentFolder.value.id,
});
isNameEditEnabled.value = false;
onSubmit(true);
} catch (error) {
toast.showError(error, i18n.baseText('folders.rename.error.title'));
onSubmit(false);
}
}
};
</script>
<template>
@@ -1417,14 +1430,31 @@ const onCreateWorkflowClick = () => {
data-test-id="main-breadcrumbs"
>
<FolderBreadcrumbs
:breadcrumbs="mainBreadcrumbs"
:current-folder="currentFolderParent"
:actions="mainBreadcrumbsActions"
:hidden-items-trigger="isDragging ? 'hover' : 'click'"
:current-folder-as-link="true"
@item-selected="onBreadcrumbItemClick"
@action="onBreadCrumbsAction"
@item-drop="onBreadCrumbsItemDrop"
@project-drop="moveFolderToProjectRoot"
/>
>
<template #append>
<span :class="$style['path-separator']">/</span>
<InlineTextEdit
data-test-id="breadcrumbs-item-current"
:model-value="currentFolder.name"
:preview-value="currentFolder.name"
:is-edit-enabled="isNameEditEnabled"
:max-length="30"
:disabled="readOnlyEnv || !hasPermissionToUpdateFolders"
:class="{ [$style.name]: true, [$style['pointer-disabled']]: isDragging }"
:placeholder="i18n.baseText('folders.rename.placeholder')"
@toggle="onNameToggle"
@submit="onNameSubmit"
/>
</template>
</FolderBreadcrumbs>
</div>
</template>
<template #item="{ item: data, index }">
@@ -1716,6 +1746,21 @@ const onCreateWorkflowClick = () => {
background-color: var(--color-callout-secondary-background);
}
}
.path-separator {
font-size: var(--font-size-xl);
color: var(--color-foreground-base);
margin: var(--spacing-4xs);
}
.name {
color: $custom-font-dark;
font-size: var(--font-size-s);
}
.pointer-disabled {
pointer-events: none;
}
</style>
<style lang="scss">