3172 lines
126 KiB
JavaScript
Executable File
3172 lines
126 KiB
JavaScript
Executable File
import { d as defineComponent, a2 as useRoute, b as useRouter, av as useFoldersStore, r as ref, x as computed, c as useI18n, aF as ProjectTypes, V as VIEWS, a8 as resolveComponent, h as createElementBlock, g as openBlock, i as createVNode, w as withCtx, s as N8nCard, n as normalizeClass, j as createBaseVNode, f as createCommentVNode, e as createBlock, fY as ProjectCardBadge, l as unref, aS as ResourceType, e6 as N8nActionToggle, B as withModifiers, p as N8nText, k as createTextVNode, t as toDisplayString, eD as _sfc_main$7, m as N8nHeading, aK as N8nBadge, N as N8nIcon, _ as _export_sfc, a as useToast, v as useSettingsStore, Q as useUIStore, u as useUsersStore, a1 as useWorkflowsStore, au as useProjectsStore, aB as getResourcePermissions, fQ as dateFormat, b2 as withDirectives, b3 as vShow, aN as WORKFLOW_SHARE_MODAL_KEY, al as useTelemetry, aR as DUPLICATE_MODAL_KEY, am as useMessage, an as MODAL_CONFIRM, aT as PROJECT_MOVE_RESOURCE_MODAL, fZ as FOLDER_NAME_ILLEGAL_CHARACTERS_REGEX, f_ as ILLEGAL_FOLDER_CHARACTERS, f$ as FOLDER_NAME_ONLY_DOTS_REGEX, g0 as FOLDER_NAME_MAX_LENGTH, $ as defineStore, d7 as usePostHog, bb as useCloudPlanStore, bk as useTemplatesStore, bc as useStorage, g1 as LOCAL_STORAGE_EXPERIMENTAL_DISMISSED_SUGGESTED_WORKFLOWS, de as jsonParse, a7 as watch, fh as TEMPLATE_ONBOARDING_EXPERIMENT, a4 as STORES, z as N8nCallout, C as N8nLink, X as renderSlot, ad as useNodeTypesStore, bl as usePersonalizedTemplatesV2Store, o as onMounted, bW as NodeIcon, bu as EXPERIMENT_TEMPLATE_RECO_V2_KEY, F as Fragment, A as renderList, af as useSourceControlStore, ar as useTagsStore, eU as useUsageStore, g2 as useInsightsStore, ax as useDocumentTitle, P as useDebounce, aE as EnterpriseEditionFeature, g3 as isExtraTemplateLinksExperimentEnabled, aq as createEventBus, W as onBeforeUnmount, ao as debounce, g4 as PROJECT_ROOT, aG as useTemplateRef, bq as createSlots, a9 as Tooltip, q as N8nButton, cK as InfoTip, dR as N8nActionBox, d_ as N8nInputLabel, aJ as _sfc_main$9, e1 as N8nSelect, e2 as _sfc_main$a, eS as N8nCheckbox, g5 as Draggable, dD as N8nLoading, aH as InlineRename, g6 as DEFAULT_WORKFLOW_PAGE_SIZE, bv as trackTemplatesClick, bw as TemplateClickSource, g7 as getEasyAiWorkflowJson, eW as COMMUNITY_PLUS_ENROLLMENT_MODAL } from "./index-CeNA_ukL.js";
|
|
import { _ as _sfc_main$8 } from "./EmptySharedSectionActionBox.vue_vue_type_script_setup_true_lang-CHWBnByP.js";
|
|
import { B as Breadcrumbs } from "./ProjectBreadcrumb-CcYMzyYS.js";
|
|
import { W as WorkflowActivator, _ as __unplugin_components_0 } from "./WorkflowActivator-BxX80zGr.js";
|
|
import { R as ResourcesListLayout } from "./ResourcesListLayout-CHxMfd0o.js";
|
|
import { P as ProjectHeader } from "./ProjectHeader-BGjOBBZs.js";
|
|
import { T as Tags } from "./Tags-DxSdhB5Q.js";
|
|
import { u as useProjectPages } from "./useProjectPages-xv6Eq2Y5.js";
|
|
import { u as useAITemplatesStarterCollectionStore, a as useReadyToRunWorkflowsStore } from "./readyToRunWorkflows.store-DGBtTmGX.js";
|
|
import { I as InsightsSummary } from "./InsightsSummary-Bypgyn1_.js";
|
|
import "./useWorkflowActivate-CuNKq-WZ.js";
|
|
import "./TableBase-DmNxoh-V.js";
|
|
import "./Tag-ZyDcgFEj.js";
|
|
const FOLDER_LIST_ITEM_ACTIONS = {
|
|
OPEN: "open",
|
|
CREATE: "create",
|
|
CREATE_WORKFLOW: "create_workflow",
|
|
RENAME: "rename",
|
|
MOVE: "move",
|
|
DELETE: "delete"
|
|
};
|
|
const _hoisted_1$3 = { "data-test-id": "folder-card" };
|
|
const _hoisted_2$1 = { key: 0 };
|
|
const _sfc_main$6 = /* @__PURE__ */ defineComponent({
|
|
__name: "FolderCard",
|
|
props: {
|
|
data: {},
|
|
personalProject: {},
|
|
actions: { default: () => [] },
|
|
readOnly: { type: Boolean, default: true },
|
|
showOwnershipBadge: { type: Boolean, default: false }
|
|
},
|
|
emits: ["action", "folderOpened"],
|
|
setup(__props, { emit: __emit }) {
|
|
const props = __props;
|
|
const i18n = useI18n();
|
|
const route = useRoute();
|
|
const router = useRouter();
|
|
const foldersStore = useFoldersStore();
|
|
const emit = __emit;
|
|
const hiddenBreadcrumbsItemsAsync = ref(new Promise(() => {
|
|
}));
|
|
const cachedHiddenBreadcrumbsItems = ref([]);
|
|
const resourceTypeLabel = computed(() => i18n.baseText("generic.folder").toLowerCase());
|
|
const cardUrl = computed(() => {
|
|
return getFolderUrl(props.data.id);
|
|
});
|
|
const projectName = computed(() => {
|
|
if (props.data.homeProject?.type === ProjectTypes.Personal) {
|
|
return i18n.baseText("projects.menu.personal");
|
|
}
|
|
return props.data.homeProject?.name;
|
|
});
|
|
const cardBreadcrumbs = computed(() => {
|
|
if (props.data.parentFolder) {
|
|
return [
|
|
{
|
|
id: props.data.parentFolder.id,
|
|
name: props.data.parentFolder.name,
|
|
label: props.data.parentFolder.name,
|
|
href: router.resolve({
|
|
name: VIEWS.PROJECTS_FOLDERS,
|
|
params: {
|
|
projectId: props.data.homeProject?.id,
|
|
folderId: props.data.parentFolder.id
|
|
}
|
|
}).href
|
|
}
|
|
];
|
|
}
|
|
return [];
|
|
});
|
|
const showCardBreadcrumbs = computed(() => {
|
|
return props.showOwnershipBadge && cardBreadcrumbs.value.length;
|
|
});
|
|
const getFolderUrl = (folderId) => {
|
|
return router.resolve({
|
|
name: VIEWS.PROJECTS_FOLDERS,
|
|
params: {
|
|
projectId: route.params.projectId,
|
|
folderId
|
|
},
|
|
query: route.query
|
|
}).href;
|
|
};
|
|
const onAction = async (action) => {
|
|
if (action === FOLDER_LIST_ITEM_ACTIONS.OPEN) {
|
|
emit("folderOpened", { folder: props.data });
|
|
await router.push(cardUrl.value);
|
|
return;
|
|
}
|
|
emit("action", { action, folderId: props.data.id });
|
|
};
|
|
const fetchHiddenBreadCrumbsItems = async () => {
|
|
if (!props.data.homeProject?.id || !projectName.value || !props.data.parentFolder) {
|
|
hiddenBreadcrumbsItemsAsync.value = Promise.resolve([]);
|
|
} else {
|
|
if (cachedHiddenBreadcrumbsItems.value.length) {
|
|
hiddenBreadcrumbsItemsAsync.value = Promise.resolve(cachedHiddenBreadcrumbsItems.value);
|
|
return;
|
|
}
|
|
const loadedItem = foldersStore.getHiddenBreadcrumbsItems(
|
|
{ id: props.data.homeProject.id, name: projectName.value },
|
|
props.data.parentFolder.id
|
|
);
|
|
hiddenBreadcrumbsItemsAsync.value = loadedItem;
|
|
cachedHiddenBreadcrumbsItems.value = await loadedItem;
|
|
}
|
|
};
|
|
const onBreadcrumbItemClick = async (item) => {
|
|
if (item.href) {
|
|
await router.push(item.href);
|
|
}
|
|
};
|
|
return (_ctx, _cache) => {
|
|
const _component_n8n_icon = N8nIcon;
|
|
const _component_n8n_heading = N8nHeading;
|
|
const _component_N8nBadge = N8nBadge;
|
|
const _component_n8n_text = N8nText;
|
|
const _component_TimeAgo = _sfc_main$7;
|
|
const _component_n8n_breadcrumbs = Breadcrumbs;
|
|
const _component_ProjectCardBadge = ProjectCardBadge;
|
|
const _component_n8n_action_toggle = N8nActionToggle;
|
|
const _component_n8n_card = N8nCard;
|
|
const _component_router_link = resolveComponent("router-link");
|
|
return openBlock(), createElementBlock("div", _hoisted_1$3, [
|
|
createVNode(_component_router_link, {
|
|
to: cardUrl.value,
|
|
onClick: _cache[1] || (_cache[1] = () => emit("folderOpened", { folder: props.data }))
|
|
}, {
|
|
default: withCtx(() => [
|
|
createVNode(_component_n8n_card, {
|
|
class: normalizeClass(_ctx.$style.card)
|
|
}, {
|
|
prepend: withCtx(() => [
|
|
createVNode(_component_n8n_icon, {
|
|
"data-test-id": "folder-card-icon",
|
|
class: normalizeClass(_ctx.$style["folder-icon"]),
|
|
icon: "folder",
|
|
size: "xlarge",
|
|
"stroke-width": 1
|
|
}, null, 8, ["class"])
|
|
]),
|
|
header: withCtx(() => [
|
|
createBaseVNode("div", {
|
|
class: normalizeClass(_ctx.$style["card-header"])
|
|
}, [
|
|
createVNode(_component_n8n_heading, {
|
|
tag: "h2",
|
|
bold: "",
|
|
size: "small",
|
|
"data-test-id": "folder-card-name"
|
|
}, {
|
|
default: withCtx(() => [
|
|
createTextVNode(toDisplayString(_ctx.data.name), 1)
|
|
]),
|
|
_: 1
|
|
}),
|
|
_ctx.readOnly ? (openBlock(), createBlock(_component_N8nBadge, {
|
|
key: 0,
|
|
class: "ml-3xs",
|
|
theme: "tertiary",
|
|
bold: ""
|
|
}, {
|
|
default: withCtx(() => [
|
|
createTextVNode(toDisplayString(unref(i18n).baseText("workflows.item.readonly")), 1)
|
|
]),
|
|
_: 1
|
|
})) : createCommentVNode("", true)
|
|
], 2)
|
|
]),
|
|
footer: withCtx(() => [
|
|
createBaseVNode("div", {
|
|
class: normalizeClass(_ctx.$style["card-footer"])
|
|
}, [
|
|
_ctx.data.workflowCount > 0 ? (openBlock(), createBlock(_component_n8n_text, {
|
|
key: 0,
|
|
size: "small",
|
|
color: "text-light",
|
|
class: normalizeClass([_ctx.$style["info-cell"], _ctx.$style["info-cell--workflow-count"]]),
|
|
"data-test-id": "folder-card-folder-count"
|
|
}, {
|
|
default: withCtx(() => [
|
|
createTextVNode(toDisplayString(unref(i18n).baseText("generic.workflow", { interpolate: { count: _ctx.data.workflowCount } })), 1)
|
|
]),
|
|
_: 1
|
|
}, 8, ["class"])) : createCommentVNode("", true),
|
|
_ctx.data.subFolderCount > 0 ? (openBlock(), createBlock(_component_n8n_text, {
|
|
key: 1,
|
|
size: "small",
|
|
color: "text-light",
|
|
class: normalizeClass([_ctx.$style["info-cell"], _ctx.$style["info-cell--workflow-count"]]),
|
|
"data-test-id": "folder-card-workflow-count"
|
|
}, {
|
|
default: withCtx(() => [
|
|
createTextVNode(toDisplayString(unref(i18n).baseText("generic.folderCount", {
|
|
interpolate: { count: _ctx.data.subFolderCount }
|
|
})), 1)
|
|
]),
|
|
_: 1
|
|
}, 8, ["class"])) : createCommentVNode("", true),
|
|
createVNode(_component_n8n_text, {
|
|
size: "small",
|
|
color: "text-light",
|
|
class: normalizeClass([_ctx.$style["info-cell"], _ctx.$style["info-cell--updated"]]),
|
|
"data-test-id": "folder-card-last-updated"
|
|
}, {
|
|
default: withCtx(() => [
|
|
createTextVNode(toDisplayString(unref(i18n).baseText("workerList.item.lastUpdated")) + " ", 1),
|
|
createVNode(_component_TimeAgo, {
|
|
date: String(_ctx.data.updatedAt)
|
|
}, null, 8, ["date"])
|
|
]),
|
|
_: 1
|
|
}, 8, ["class"]),
|
|
createVNode(_component_n8n_text, {
|
|
size: "small",
|
|
color: "text-light",
|
|
class: normalizeClass([_ctx.$style["info-cell"], _ctx.$style["info-cell--created"]]),
|
|
"data-test-id": "folder-card-created"
|
|
}, {
|
|
default: withCtx(() => [
|
|
createTextVNode(toDisplayString(unref(i18n).baseText("workflows.item.created")) + " ", 1),
|
|
createVNode(_component_TimeAgo, {
|
|
date: String(_ctx.data.createdAt)
|
|
}, null, 8, ["date"])
|
|
]),
|
|
_: 1
|
|
}, 8, ["class"])
|
|
], 2)
|
|
]),
|
|
append: withCtx(() => [
|
|
createBaseVNode("div", {
|
|
class: normalizeClass(_ctx.$style["card-actions"]),
|
|
onClick: _cache[0] || (_cache[0] = withModifiers(() => {
|
|
}, ["prevent"]))
|
|
}, [
|
|
_ctx.data.homeProject && _ctx.showOwnershipBadge ? (openBlock(), createElementBlock("div", _hoisted_2$1, [
|
|
createVNode(_component_ProjectCardBadge, {
|
|
class: normalizeClass({
|
|
[_ctx.$style.cardBadge]: true,
|
|
[_ctx.$style["with-breadcrumbs"]]: showCardBreadcrumbs.value
|
|
}),
|
|
resource: _ctx.data,
|
|
"resource-type": unref(ResourceType).Workflow,
|
|
"resource-type-label": resourceTypeLabel.value,
|
|
"personal-project": _ctx.personalProject,
|
|
"show-badge-border": false
|
|
}, {
|
|
default: withCtx(() => [
|
|
showCardBreadcrumbs.value ? (openBlock(), createElementBlock("div", {
|
|
key: 0,
|
|
class: normalizeClass(_ctx.$style.breadcrumbs)
|
|
}, [
|
|
createVNode(_component_n8n_breadcrumbs, {
|
|
items: cardBreadcrumbs.value,
|
|
"hidden-items": _ctx.data.parentFolder?.parentFolderId !== null ? hiddenBreadcrumbsItemsAsync.value : void 0,
|
|
"path-truncated": _ctx.data.parentFolder?.parentFolderId !== null,
|
|
"highlight-last-item": false,
|
|
"hidden-items-trigger": "hover",
|
|
theme: "small",
|
|
"data-test-id": "folder-card-breadcrumbs",
|
|
onTooltipOpened: fetchHiddenBreadCrumbsItems,
|
|
onItemSelected: onBreadcrumbItemClick
|
|
}, {
|
|
prepend: withCtx(() => _cache[2] || (_cache[2] = [])),
|
|
_: 1
|
|
}, 8, ["items", "hidden-items", "path-truncated"])
|
|
], 2)) : createCommentVNode("", true)
|
|
]),
|
|
_: 1
|
|
}, 8, ["class", "resource", "resource-type", "resource-type-label", "personal-project"])
|
|
])) : createCommentVNode("", true),
|
|
_ctx.actions.length ? (openBlock(), createBlock(_component_n8n_action_toggle, {
|
|
key: 1,
|
|
actions: _ctx.actions,
|
|
theme: "dark",
|
|
"data-test-id": "folder-card-actions",
|
|
onAction
|
|
}, null, 8, ["actions"])) : createCommentVNode("", true)
|
|
], 2)
|
|
]),
|
|
_: 1
|
|
}, 8, ["class"])
|
|
]),
|
|
_: 1
|
|
}, 8, ["to"])
|
|
]);
|
|
};
|
|
}
|
|
});
|
|
const card = "_card_pt4ir_123";
|
|
const cardBadge$1 = "_cardBadge_pt4ir_157";
|
|
const style0$6 = {
|
|
card,
|
|
"folder-icon": "_folder-icon_pt4ir_131",
|
|
"card-header": "_card-header_pt4ir_140",
|
|
"card-footer": "_card-footer_pt4ir_148",
|
|
"info-cell": "_info-cell_pt4ir_152",
|
|
cardBadge: cardBadge$1,
|
|
"with-breadcrumbs": "_with-breadcrumbs_pt4ir_157",
|
|
"card-actions": "_card-actions_pt4ir_164",
|
|
"info-cell--created": "_info-cell--created_pt4ir_182"
|
|
};
|
|
const cssModules$6 = {
|
|
"$style": style0$6
|
|
};
|
|
const __unplugin_components_4 = /* @__PURE__ */ _export_sfc(_sfc_main$6, [["__cssModules", cssModules$6]]);
|
|
const _sfc_main$5 = /* @__PURE__ */ defineComponent({
|
|
__name: "WorkflowCard",
|
|
props: {
|
|
data: {},
|
|
readOnly: { type: Boolean, default: false },
|
|
workflowListEventBus: { default: void 0 },
|
|
showOwnershipBadge: { type: Boolean, default: false }
|
|
},
|
|
emits: ["expand:tags", "click:tag", "workflow:deleted", "workflow:archived", "workflow:unarchived", "workflow:active-toggle", "action:move-to-folder"],
|
|
setup(__props, { emit: __emit }) {
|
|
const WORKFLOW_LIST_ITEM_ACTIONS = {
|
|
OPEN: "open",
|
|
SHARE: "share",
|
|
DUPLICATE: "duplicate",
|
|
DELETE: "delete",
|
|
ARCHIVE: "archive",
|
|
UNARCHIVE: "unarchive",
|
|
MOVE: "move",
|
|
MOVE_TO_FOLDER: "moveToFolder"
|
|
};
|
|
const props = __props;
|
|
const emit = __emit;
|
|
const toast = useToast();
|
|
const message = useMessage();
|
|
const locale = useI18n();
|
|
const router = useRouter();
|
|
const route = useRoute();
|
|
const telemetry = useTelemetry();
|
|
const settingsStore = useSettingsStore();
|
|
const uiStore = useUIStore();
|
|
const usersStore = useUsersStore();
|
|
const workflowsStore = useWorkflowsStore();
|
|
const projectsStore = useProjectsStore();
|
|
const foldersStore = useFoldersStore();
|
|
const hiddenBreadcrumbsItemsAsync = ref(new Promise(() => {
|
|
}));
|
|
const cachedHiddenBreadcrumbsItems = ref([]);
|
|
const resourceTypeLabel = computed(() => locale.baseText("generic.workflow").toLowerCase());
|
|
const currentUser = computed(() => usersStore.currentUser ?? {});
|
|
const workflowPermissions = computed(() => getResourcePermissions(props.data.scopes).workflow);
|
|
const showFolders = computed(() => {
|
|
return settingsStore.isFoldersFeatureEnabled && route.name !== VIEWS.WORKFLOWS;
|
|
});
|
|
const showCardBreadcrumbs = computed(() => {
|
|
return props.showOwnershipBadge && !isSomeoneElsesWorkflow.value && cardBreadcrumbs.value.length;
|
|
});
|
|
const projectName = computed(() => {
|
|
if (props.data.homeProject?.type === ProjectTypes.Personal) {
|
|
return locale.baseText("projects.menu.personal");
|
|
}
|
|
return props.data.homeProject?.name;
|
|
});
|
|
const cardBreadcrumbs = computed(() => {
|
|
if (props.data.parentFolder) {
|
|
return [
|
|
{
|
|
id: props.data.parentFolder.id,
|
|
name: props.data.parentFolder.name,
|
|
label: props.data.parentFolder.name,
|
|
href: router.resolve({
|
|
name: VIEWS.PROJECTS_FOLDERS,
|
|
params: {
|
|
projectId: props.data.homeProject?.id,
|
|
folderId: props.data.parentFolder.id
|
|
}
|
|
}).href
|
|
}
|
|
];
|
|
}
|
|
return [];
|
|
});
|
|
const actions = computed(() => {
|
|
const items = [
|
|
{
|
|
label: locale.baseText("workflows.item.open"),
|
|
value: WORKFLOW_LIST_ITEM_ACTIONS.OPEN
|
|
},
|
|
{
|
|
label: locale.baseText("workflows.item.share"),
|
|
value: WORKFLOW_LIST_ITEM_ACTIONS.SHARE
|
|
}
|
|
];
|
|
if (workflowPermissions.value.create && !props.readOnly && !props.data.isArchived) {
|
|
items.push({
|
|
label: locale.baseText("workflows.item.duplicate"),
|
|
value: WORKFLOW_LIST_ITEM_ACTIONS.DUPLICATE
|
|
});
|
|
}
|
|
if ((workflowPermissions.value.update && !props.readOnly || workflowPermissions.value.move && projectsStore.isTeamProjectFeatureEnabled) && showFolders.value && route.name !== VIEWS.SHARED_WORKFLOWS) {
|
|
items.push({
|
|
label: locale.baseText("folders.actions.moveToFolder"),
|
|
value: WORKFLOW_LIST_ITEM_ACTIONS.MOVE_TO_FOLDER
|
|
});
|
|
}
|
|
if (workflowPermissions.value.delete && !props.readOnly) {
|
|
if (!props.data.isArchived) {
|
|
items.push({
|
|
label: locale.baseText("workflows.item.archive"),
|
|
value: WORKFLOW_LIST_ITEM_ACTIONS.ARCHIVE
|
|
});
|
|
} else {
|
|
items.push({
|
|
label: locale.baseText("workflows.item.delete"),
|
|
value: WORKFLOW_LIST_ITEM_ACTIONS.DELETE
|
|
});
|
|
items.push({
|
|
label: locale.baseText("workflows.item.unarchive"),
|
|
value: WORKFLOW_LIST_ITEM_ACTIONS.UNARCHIVE
|
|
});
|
|
}
|
|
}
|
|
return items;
|
|
});
|
|
const formattedCreatedAtDate = computed(() => {
|
|
const currentYear = (/* @__PURE__ */ new Date()).getFullYear().toString();
|
|
return dateFormat(
|
|
props.data.createdAt,
|
|
`d mmmm${String(props.data.createdAt).startsWith(currentYear) ? "" : ", yyyy"}`
|
|
);
|
|
});
|
|
const isSomeoneElsesWorkflow = computed(
|
|
() => props.data.homeProject?.type !== ProjectTypes.Team && props.data.homeProject?.id !== projectsStore.personalProject?.id
|
|
);
|
|
async function onClick(event) {
|
|
if (event?.ctrlKey || event?.metaKey) {
|
|
const route2 = router.resolve({
|
|
name: VIEWS.WORKFLOW,
|
|
params: { name: props.data.id }
|
|
});
|
|
window.open(route2.href, "_blank");
|
|
return;
|
|
}
|
|
await router.push({
|
|
name: VIEWS.WORKFLOW,
|
|
params: { name: props.data.id }
|
|
});
|
|
}
|
|
function onClickTag(tagId, event) {
|
|
event.stopPropagation();
|
|
emit("click:tag", tagId, event);
|
|
}
|
|
function onExpandTags() {
|
|
emit("expand:tags");
|
|
}
|
|
async function onAction(action) {
|
|
switch (action) {
|
|
case WORKFLOW_LIST_ITEM_ACTIONS.OPEN:
|
|
await onClick();
|
|
break;
|
|
case WORKFLOW_LIST_ITEM_ACTIONS.DUPLICATE:
|
|
uiStore.openModalWithData({
|
|
name: DUPLICATE_MODAL_KEY,
|
|
data: {
|
|
id: props.data.id,
|
|
name: props.data.name,
|
|
tags: (props.data.tags ?? []).map(
|
|
(tag) => typeof tag !== "string" && "id" in tag ? tag.id : tag
|
|
),
|
|
externalEventBus: props.workflowListEventBus,
|
|
parentFolderId: props.data.parentFolder?.id
|
|
}
|
|
});
|
|
break;
|
|
case WORKFLOW_LIST_ITEM_ACTIONS.SHARE:
|
|
uiStore.openModalWithData({
|
|
name: WORKFLOW_SHARE_MODAL_KEY,
|
|
data: { id: props.data.id }
|
|
});
|
|
telemetry.track("User opened sharing modal", {
|
|
workflow_id: props.data.id,
|
|
user_id_sharer: currentUser.value.id,
|
|
sub_view: "Workflows listing"
|
|
});
|
|
break;
|
|
case WORKFLOW_LIST_ITEM_ACTIONS.DELETE:
|
|
await deleteWorkflow();
|
|
break;
|
|
case WORKFLOW_LIST_ITEM_ACTIONS.ARCHIVE:
|
|
await archiveWorkflow();
|
|
break;
|
|
case WORKFLOW_LIST_ITEM_ACTIONS.UNARCHIVE:
|
|
await unarchiveWorkflow();
|
|
break;
|
|
case WORKFLOW_LIST_ITEM_ACTIONS.MOVE:
|
|
moveResource();
|
|
break;
|
|
case WORKFLOW_LIST_ITEM_ACTIONS.MOVE_TO_FOLDER:
|
|
emit("action:move-to-folder", {
|
|
id: props.data.id,
|
|
name: props.data.name,
|
|
parentFolderId: props.data.parentFolder?.id,
|
|
sharedWithProjects: props.data.sharedWithProjects
|
|
});
|
|
break;
|
|
}
|
|
}
|
|
async function deleteWorkflow() {
|
|
const deleteConfirmed = await message.confirm(
|
|
locale.baseText("mainSidebar.confirmMessage.workflowDelete.message", {
|
|
interpolate: { workflowName: props.data.name }
|
|
}),
|
|
locale.baseText("mainSidebar.confirmMessage.workflowDelete.headline"),
|
|
{
|
|
type: "warning",
|
|
confirmButtonText: locale.baseText(
|
|
"mainSidebar.confirmMessage.workflowDelete.confirmButtonText"
|
|
),
|
|
cancelButtonText: locale.baseText(
|
|
"mainSidebar.confirmMessage.workflowDelete.cancelButtonText"
|
|
)
|
|
}
|
|
);
|
|
if (deleteConfirmed !== MODAL_CONFIRM) {
|
|
return;
|
|
}
|
|
try {
|
|
await workflowsStore.deleteWorkflow(props.data.id);
|
|
} catch (error) {
|
|
toast.showError(error, locale.baseText("generic.deleteWorkflowError"));
|
|
return;
|
|
}
|
|
toast.showMessage({
|
|
title: locale.baseText("mainSidebar.showMessage.handleSelect1.title", {
|
|
interpolate: { workflowName: props.data.name }
|
|
}),
|
|
type: "success"
|
|
});
|
|
emit("workflow:deleted");
|
|
}
|
|
async function archiveWorkflow() {
|
|
if (props.data.active) {
|
|
const archiveConfirmed = await message.confirm(
|
|
locale.baseText("mainSidebar.confirmMessage.workflowArchive.message", {
|
|
interpolate: { workflowName: props.data.name }
|
|
}),
|
|
locale.baseText("mainSidebar.confirmMessage.workflowArchive.headline"),
|
|
{
|
|
type: "warning",
|
|
confirmButtonText: locale.baseText(
|
|
"mainSidebar.confirmMessage.workflowArchive.confirmButtonText"
|
|
),
|
|
cancelButtonText: locale.baseText(
|
|
"mainSidebar.confirmMessage.workflowArchive.cancelButtonText"
|
|
)
|
|
}
|
|
);
|
|
if (archiveConfirmed !== MODAL_CONFIRM) {
|
|
return;
|
|
}
|
|
}
|
|
try {
|
|
await workflowsStore.archiveWorkflow(props.data.id);
|
|
} catch (error) {
|
|
toast.showError(error, locale.baseText("generic.archiveWorkflowError"));
|
|
return;
|
|
}
|
|
toast.showMessage({
|
|
title: locale.baseText("mainSidebar.showMessage.handleArchive.title", {
|
|
interpolate: { workflowName: props.data.name }
|
|
}),
|
|
type: "success"
|
|
});
|
|
emit("workflow:archived");
|
|
}
|
|
async function unarchiveWorkflow() {
|
|
try {
|
|
await workflowsStore.unarchiveWorkflow(props.data.id);
|
|
} catch (error) {
|
|
toast.showError(error, locale.baseText("generic.unarchiveWorkflowError"));
|
|
return;
|
|
}
|
|
toast.showMessage({
|
|
title: locale.baseText("mainSidebar.showMessage.handleUnarchive.title", {
|
|
interpolate: { workflowName: props.data.name }
|
|
}),
|
|
type: "success"
|
|
});
|
|
emit("workflow:unarchived");
|
|
}
|
|
const fetchHiddenBreadCrumbsItems = async () => {
|
|
if (!props.data.homeProject?.id || !projectName.value || !props.data.parentFolder) {
|
|
hiddenBreadcrumbsItemsAsync.value = Promise.resolve([]);
|
|
} else {
|
|
if (cachedHiddenBreadcrumbsItems.value.length) {
|
|
hiddenBreadcrumbsItemsAsync.value = Promise.resolve(cachedHiddenBreadcrumbsItems.value);
|
|
return;
|
|
}
|
|
const loadedItem = foldersStore.getHiddenBreadcrumbsItems(
|
|
{ id: props.data.homeProject.id, name: projectName.value },
|
|
props.data.parentFolder.id
|
|
);
|
|
hiddenBreadcrumbsItemsAsync.value = loadedItem;
|
|
cachedHiddenBreadcrumbsItems.value = await loadedItem;
|
|
}
|
|
};
|
|
function moveResource() {
|
|
uiStore.openModalWithData({
|
|
name: PROJECT_MOVE_RESOURCE_MODAL,
|
|
data: {
|
|
resource: props.data,
|
|
resourceType: ResourceType.Workflow,
|
|
resourceTypeLabel: resourceTypeLabel.value,
|
|
eventBus: props.workflowListEventBus
|
|
}
|
|
});
|
|
}
|
|
const emitWorkflowActiveToggle = (value) => {
|
|
emit("workflow:active-toggle", value);
|
|
};
|
|
const onBreadcrumbItemClick = async (item) => {
|
|
if (item.href) {
|
|
await router.push(item.href);
|
|
}
|
|
};
|
|
const tags = computed(
|
|
() => props.data.tags?.map((tag) => typeof tag === "string" ? { id: tag, name: tag } : tag) ?? []
|
|
);
|
|
return (_ctx, _cache) => {
|
|
const _component_N8nBadge = N8nBadge;
|
|
const _component_n8n_text = N8nText;
|
|
const _component_n8n_tags = Tags;
|
|
const _component_n8n_breadcrumbs = Breadcrumbs;
|
|
const _component_n8n_action_toggle = N8nActionToggle;
|
|
const _component_n8n_card = N8nCard;
|
|
return openBlock(), createBlock(_component_n8n_card, {
|
|
class: normalizeClass({
|
|
[_ctx.$style.cardLink]: true,
|
|
[_ctx.$style.cardArchived]: _ctx.data.isArchived
|
|
}),
|
|
"data-test-id": "workflow-card",
|
|
onClick
|
|
}, {
|
|
header: withCtx(() => [
|
|
createVNode(_component_n8n_text, {
|
|
tag: "h2",
|
|
bold: "",
|
|
class: normalizeClass({
|
|
[_ctx.$style.cardHeading]: true,
|
|
[_ctx.$style.cardHeadingArchived]: _ctx.data.isArchived
|
|
}),
|
|
"data-test-id": "workflow-card-name"
|
|
}, {
|
|
default: withCtx(() => [
|
|
createTextVNode(toDisplayString(_ctx.data.name) + " ", 1),
|
|
!workflowPermissions.value.update ? (openBlock(), createBlock(_component_N8nBadge, {
|
|
key: 0,
|
|
class: "ml-3xs",
|
|
theme: "tertiary",
|
|
bold: ""
|
|
}, {
|
|
default: withCtx(() => [
|
|
createTextVNode(toDisplayString(unref(locale).baseText("workflows.item.readonly")), 1)
|
|
]),
|
|
_: 1
|
|
})) : createCommentVNode("", true)
|
|
]),
|
|
_: 1
|
|
}, 8, ["class"])
|
|
]),
|
|
append: withCtx(() => [
|
|
createBaseVNode("div", {
|
|
class: normalizeClass(_ctx.$style.cardActions),
|
|
onClick: _cache[0] || (_cache[0] = withModifiers(() => {
|
|
}, ["stop"]))
|
|
}, [
|
|
_ctx.showOwnershipBadge ? (openBlock(), createBlock(ProjectCardBadge, {
|
|
key: 0,
|
|
class: normalizeClass({ [_ctx.$style.cardBadge]: true, [_ctx.$style["with-breadcrumbs"]]: showCardBreadcrumbs.value }),
|
|
resource: _ctx.data,
|
|
"resource-type": unref(ResourceType).Workflow,
|
|
"resource-type-label": resourceTypeLabel.value,
|
|
"personal-project": unref(projectsStore).personalProject,
|
|
"show-badge-border": false
|
|
}, {
|
|
default: withCtx(() => [
|
|
showCardBreadcrumbs.value ? (openBlock(), createElementBlock("div", {
|
|
key: 0,
|
|
class: normalizeClass(_ctx.$style.breadcrumbs)
|
|
}, [
|
|
createVNode(_component_n8n_breadcrumbs, {
|
|
items: cardBreadcrumbs.value,
|
|
"hidden-items": _ctx.data.parentFolder?.parentFolderId !== null ? hiddenBreadcrumbsItemsAsync.value : void 0,
|
|
"path-truncated": _ctx.data.parentFolder?.parentFolderId !== null,
|
|
"highlight-last-item": false,
|
|
"hidden-items-trigger": "hover",
|
|
theme: "small",
|
|
"data-test-id": "workflow-card-breadcrumbs",
|
|
onTooltipOpened: fetchHiddenBreadCrumbsItems,
|
|
onItemSelected: onBreadcrumbItemClick
|
|
}, {
|
|
prepend: withCtx(() => _cache[2] || (_cache[2] = [])),
|
|
_: 1
|
|
}, 8, ["items", "hidden-items", "path-truncated"])
|
|
], 2)) : createCommentVNode("", true)
|
|
]),
|
|
_: 1
|
|
}, 8, ["class", "resource", "resource-type", "resource-type-label", "personal-project"])) : createCommentVNode("", true),
|
|
_ctx.data.isArchived ? (openBlock(), createBlock(_component_n8n_text, {
|
|
key: 1,
|
|
color: "text-light",
|
|
size: "small",
|
|
bold: "",
|
|
class: "ml-s mr-s",
|
|
"data-test-id": "workflow-card-archived"
|
|
}, {
|
|
default: withCtx(() => [
|
|
createTextVNode(toDisplayString(unref(locale).baseText("workflows.item.archived")), 1)
|
|
]),
|
|
_: 1
|
|
})) : (openBlock(), createBlock(WorkflowActivator, {
|
|
key: 2,
|
|
class: "mr-s",
|
|
"is-archived": _ctx.data.isArchived,
|
|
"workflow-active": _ctx.data.active,
|
|
"workflow-id": _ctx.data.id,
|
|
"workflow-permissions": workflowPermissions.value,
|
|
"data-test-id": "workflow-card-activator",
|
|
"onUpdate:workflowActive": emitWorkflowActiveToggle
|
|
}, null, 8, ["is-archived", "workflow-active", "workflow-id", "workflow-permissions"])),
|
|
createVNode(_component_n8n_action_toggle, {
|
|
actions: actions.value,
|
|
theme: "dark",
|
|
"data-test-id": "workflow-card-actions",
|
|
onAction
|
|
}, null, 8, ["actions"])
|
|
], 2)
|
|
]),
|
|
default: withCtx(() => [
|
|
createBaseVNode("div", {
|
|
class: normalizeClass(_ctx.$style.cardDescription)
|
|
}, [
|
|
createVNode(_component_n8n_text, {
|
|
color: "text-light",
|
|
size: "small"
|
|
}, {
|
|
default: withCtx(() => [
|
|
withDirectives(createBaseVNode("span", null, [
|
|
createTextVNode(toDisplayString(unref(locale).baseText("workflows.item.updated")) + " ", 1),
|
|
createVNode(_sfc_main$7, {
|
|
date: String(_ctx.data.updatedAt)
|
|
}, null, 8, ["date"]),
|
|
_cache[1] || (_cache[1] = createTextVNode(" | "))
|
|
], 512), [
|
|
[vShow, _ctx.data]
|
|
]),
|
|
withDirectives(createBaseVNode("span", { class: "mr-2xs" }, toDisplayString(unref(locale).baseText("workflows.item.created")) + " " + toDisplayString(formattedCreatedAtDate.value), 513), [
|
|
[vShow, _ctx.data]
|
|
]),
|
|
unref(settingsStore).areTagsEnabled && _ctx.data.tags && _ctx.data.tags.length > 0 ? withDirectives((openBlock(), createElementBlock("span", {
|
|
key: 0,
|
|
class: normalizeClass(_ctx.$style.cardTags)
|
|
}, [
|
|
createVNode(_component_n8n_tags, {
|
|
tags: tags.value,
|
|
"truncate-at": 3,
|
|
truncate: "",
|
|
"data-test-id": "workflow-card-tags",
|
|
"onClick:tag": onClickTag,
|
|
onExpand: onExpandTags
|
|
}, null, 8, ["tags"])
|
|
], 2)), [
|
|
[vShow, _ctx.data]
|
|
]) : createCommentVNode("", true)
|
|
]),
|
|
_: 1
|
|
})
|
|
], 2)
|
|
]),
|
|
_: 1
|
|
}, 8, ["class"]);
|
|
};
|
|
}
|
|
});
|
|
const cardLink = "_cardLink_9y0d7_123";
|
|
const cardHeading = "_cardHeading_9y0d7_133";
|
|
const cardHeadingArchived = "_cardHeadingArchived_9y0d7_142";
|
|
const cardDescription = "_cardDescription_9y0d7_146";
|
|
const cardTags = "_cardTags_9y0d7_153";
|
|
const cardActions = "_cardActions_9y0d7_158";
|
|
const cardBadge = "_cardBadge_9y0d7_169";
|
|
const cardArchived = "_cardArchived_9y0d7_180";
|
|
const breadcrumbs = "_breadcrumbs_9y0d7_198";
|
|
const style0$5 = {
|
|
cardLink,
|
|
cardHeading,
|
|
cardHeadingArchived,
|
|
cardDescription,
|
|
cardTags,
|
|
cardActions,
|
|
cardBadge,
|
|
"with-breadcrumbs": "_with-breadcrumbs_9y0d7_173",
|
|
cardArchived,
|
|
breadcrumbs
|
|
};
|
|
const cssModules$5 = {
|
|
"$style": style0$5
|
|
};
|
|
const WorkflowCard = /* @__PURE__ */ _export_sfc(_sfc_main$5, [["__cssModules", cssModules$5]]);
|
|
function isDropTarget(target) {
|
|
return target.type === "folder" || target.type === "project";
|
|
}
|
|
function isValidResourceType(value) {
|
|
return ["folder", "workflow", "project"].includes(value);
|
|
}
|
|
function useFolders() {
|
|
const i18n = useI18n();
|
|
const foldersStore = useFoldersStore();
|
|
const isDragging = computed(() => {
|
|
return foldersStore.draggedElement !== null;
|
|
});
|
|
function validateFolderName(folderName) {
|
|
if (FOLDER_NAME_ILLEGAL_CHARACTERS_REGEX.test(folderName)) {
|
|
return i18n.baseText("folders.invalidName.invalidCharacters.message", {
|
|
interpolate: {
|
|
illegalChars: ILLEGAL_FOLDER_CHARACTERS.join(" ")
|
|
}
|
|
});
|
|
}
|
|
if (FOLDER_NAME_ONLY_DOTS_REGEX.test(folderName)) {
|
|
return i18n.baseText("folders.invalidName.only.dots.message");
|
|
}
|
|
if (folderName.startsWith(".")) {
|
|
return i18n.baseText("folders.invalidName.starts.with.dot..message");
|
|
}
|
|
if (folderName.trim() === "") {
|
|
return i18n.baseText("folders.invalidName.empty.message");
|
|
}
|
|
if (folderName.length > FOLDER_NAME_MAX_LENGTH) {
|
|
return i18n.baseText("folders.invalidName.tooLong.message", {
|
|
interpolate: {
|
|
maxLength: FOLDER_NAME_MAX_LENGTH
|
|
}
|
|
});
|
|
}
|
|
return true;
|
|
}
|
|
function onDragStart(el) {
|
|
const eventTarget = el.closest("[data-target]");
|
|
if (!eventTarget) return;
|
|
const dragTarget = getDragAndDropTarget(eventTarget);
|
|
if (!dragTarget) return;
|
|
if (dragTarget.type === "folder" || dragTarget.type === "workflow") {
|
|
foldersStore.draggedElement = {
|
|
type: dragTarget.type,
|
|
id: dragTarget.id,
|
|
name: dragTarget.name
|
|
};
|
|
}
|
|
}
|
|
function onDragEnd() {
|
|
foldersStore.draggedElement = null;
|
|
foldersStore.activeDropTarget = null;
|
|
}
|
|
function onDragEnter(event) {
|
|
const eventTarget = event.target;
|
|
if (!eventTarget || !isDragging.value) return;
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
const dragTarget = getDragAndDropTarget(eventTarget);
|
|
if (!dragTarget || dragTarget.type !== "folder") return;
|
|
foldersStore.activeDropTarget = {
|
|
type: dragTarget.type,
|
|
id: dragTarget.id,
|
|
name: dragTarget.name
|
|
};
|
|
}
|
|
function resetDropTarget() {
|
|
foldersStore.activeDropTarget = null;
|
|
}
|
|
function getDragAndDropTarget(el) {
|
|
const dragTarget = el.closest("[data-target]");
|
|
if (!dragTarget) return null;
|
|
const targetResource = dragTarget.dataset.target;
|
|
const targetId = dragTarget.dataset.resourceid;
|
|
const targetName = dragTarget.dataset.resourcename;
|
|
if (!targetResource || !targetId || !targetName || !isValidResourceType(targetResource))
|
|
return null;
|
|
return {
|
|
type: targetResource,
|
|
id: targetId,
|
|
name: targetName
|
|
};
|
|
}
|
|
function handleDrop(event) {
|
|
const eventTarget = event.target;
|
|
if (!eventTarget || !isDragging.value) return {};
|
|
event.preventDefault();
|
|
const draggedResourceId = foldersStore.draggedElement?.id;
|
|
const draggedResourceType = foldersStore.draggedElement?.type;
|
|
const draggedResourceName = foldersStore.draggedElement?.name;
|
|
if (!draggedResourceId || !draggedResourceType || !draggedResourceName) return {};
|
|
onDragEnd();
|
|
const dropTarget = getDragAndDropTarget(eventTarget);
|
|
if (!dropTarget || !isDropTarget(dropTarget)) return {};
|
|
return {
|
|
draggedResource: {
|
|
type: draggedResourceType,
|
|
id: draggedResourceId,
|
|
name: draggedResourceName
|
|
},
|
|
dropTarget: {
|
|
type: dropTarget.type,
|
|
id: dropTarget.id,
|
|
name: dropTarget.name
|
|
}
|
|
};
|
|
}
|
|
return {
|
|
validateFolderName,
|
|
onDragStart,
|
|
onDragEnd,
|
|
onDragEnter,
|
|
resetDropTarget,
|
|
handleDrop
|
|
};
|
|
}
|
|
const SIMPLE_TEMPLATES = [6270, 5271, 2178];
|
|
const PREDEFINED_TEMPLATES_BY_NODE = {
|
|
"n8n-nodes-base.gmail": [5678, 4722, 5694],
|
|
"n8n-nodes-base.googleSheets": [5694, 5690, 5906],
|
|
"n8n-nodes-base.telegram": [5626, 2114, 4875],
|
|
"@n8n/n8n-nodes-langchain.openAi": [2462, 2722, 2178],
|
|
"@n8n/n8n-nodes-langchain.googleGemini": [5993, 6270, 5677],
|
|
"n8n-nodes-base.googleCalendar": [2328, 3393, 2110],
|
|
"n8n-nodes-base.youTube": [3188, 4846, 4506],
|
|
"n8n-nodes-base.airtable": [3053, 2700, 2579]
|
|
};
|
|
function getPredefinedFromSelected(selectedApps) {
|
|
const predefinedNodes = Object.keys(PREDEFINED_TEMPLATES_BY_NODE);
|
|
const predefinedSelected = predefinedNodes.filter((node) => selectedApps.includes(node));
|
|
return predefinedSelected.reduce(
|
|
(acc, app) => [
|
|
...acc,
|
|
...PREDEFINED_TEMPLATES_BY_NODE[app]
|
|
],
|
|
[]
|
|
);
|
|
}
|
|
function getSuggestedTemplatesForLowCodingSkill(selectedApps) {
|
|
if (selectedApps.length === 0) {
|
|
return SIMPLE_TEMPLATES;
|
|
}
|
|
const predefinedSelected = getPredefinedFromSelected(selectedApps);
|
|
if (predefinedSelected.length > 0) {
|
|
return predefinedSelected;
|
|
}
|
|
return [];
|
|
}
|
|
function keepTop3Templates(templates) {
|
|
if (templates.length <= 3) {
|
|
return templates;
|
|
}
|
|
return Array.from(new Map(templates.map((t) => [t.id, t])).values()).sort((a, b) => b.totalViews - a.totalViews).slice(0, 3);
|
|
}
|
|
const usePersonalizedTemplatesStore = defineStore(STORES.PERSONALIZED_TEMPLATES, () => {
|
|
const telemetry = useTelemetry();
|
|
const posthogStore = usePostHog();
|
|
const cloudPlanStore = useCloudPlanStore();
|
|
const templatesStore = useTemplatesStore();
|
|
const allSuggestedWorkflows = ref([]);
|
|
const dismissedSuggestedWorkflowsStorage = useStorage(
|
|
LOCAL_STORAGE_EXPERIMENTAL_DISMISSED_SUGGESTED_WORKFLOWS
|
|
);
|
|
const dismissedSuggestedWorkflows = computed(() => {
|
|
return dismissedSuggestedWorkflowsStorage.value ? jsonParse(dismissedSuggestedWorkflowsStorage.value, { fallbackValue: [] }) : [];
|
|
});
|
|
const suggestedWorkflows = computed(
|
|
() => allSuggestedWorkflows.value.filter(({ id }) => !dismissedSuggestedWorkflows.value.includes(id))
|
|
);
|
|
const dismissSuggestedWorkflow = (id) => {
|
|
dismissedSuggestedWorkflowsStorage.value = JSON.stringify([
|
|
...dismissedSuggestedWorkflows.value ?? [],
|
|
id
|
|
]);
|
|
};
|
|
const isFeatureEnabled = () => {
|
|
return posthogStore.getVariant(TEMPLATE_ONBOARDING_EXPERIMENT.name) === TEMPLATE_ONBOARDING_EXPERIMENT.variantSuggestedTemplates && cloudPlanStore.userIsTrialing;
|
|
};
|
|
const trackUserWasRecommendedTemplates = (templateIds) => {
|
|
telemetry.track("User was recommended personalized templates", {
|
|
templateIds
|
|
});
|
|
};
|
|
const trackUserClickedOnPersonalizedTemplate = (templateId) => {
|
|
telemetry.track("User clicked on personalized template callout", {
|
|
templateId
|
|
});
|
|
};
|
|
const trackUserDismissedCallout = (templateId) => {
|
|
telemetry.track("User dismissed personalized template callout", {
|
|
templateId
|
|
});
|
|
};
|
|
const fetchSuggestedWorkflows = async (codingSkill, selectedApps) => {
|
|
if (!isFeatureEnabled()) {
|
|
return;
|
|
}
|
|
try {
|
|
if (codingSkill === 1) {
|
|
const predefinedSelected = getSuggestedTemplatesForLowCodingSkill(selectedApps);
|
|
if (predefinedSelected.length > 0) {
|
|
const suggestedWorkflowsPromises2 = predefinedSelected.map(
|
|
async (id) => await templatesStore.fetchTemplateById(id.toString())
|
|
);
|
|
const allWorkflows2 = await Promise.all(suggestedWorkflowsPromises2);
|
|
const top3Templates2 = keepTop3Templates(allWorkflows2);
|
|
allSuggestedWorkflows.value = top3Templates2;
|
|
trackUserWasRecommendedTemplates(top3Templates2.map((t) => t.id));
|
|
return;
|
|
}
|
|
}
|
|
const topWorkflowsByApp = await templatesStore.getWorkflows({
|
|
categories: [],
|
|
search: "",
|
|
sort: "rank:desc",
|
|
nodes: selectedApps.length > 0 ? selectedApps : void 0,
|
|
combineWith: "or"
|
|
});
|
|
const topWorkflowsIds = topWorkflowsByApp.slice(0, 3).map((workflow) => workflow.id);
|
|
const suggestedWorkflowsPromises = topWorkflowsIds.map(
|
|
async (id) => await templatesStore.fetchTemplateById(id.toString())
|
|
);
|
|
const allWorkflows = await Promise.all(suggestedWorkflowsPromises);
|
|
const top3Templates = keepTop3Templates(allWorkflows);
|
|
allSuggestedWorkflows.value = top3Templates;
|
|
trackUserWasRecommendedTemplates(top3Templates.map((t) => t.id));
|
|
} catch (error) {
|
|
}
|
|
};
|
|
const getTemplateRoute = (id) => {
|
|
return { name: VIEWS.TEMPLATE, params: { id } };
|
|
};
|
|
watch(
|
|
() => cloudPlanStore.currentUserCloudInfo,
|
|
async (userInfo) => {
|
|
if (!userInfo) return;
|
|
const codingSkill = cloudPlanStore.codingSkill;
|
|
const selectedApps = cloudPlanStore.selectedApps ?? [];
|
|
await fetchSuggestedWorkflows(codingSkill, selectedApps);
|
|
}
|
|
);
|
|
return {
|
|
isFeatureEnabled,
|
|
suggestedWorkflows,
|
|
dismissSuggestedWorkflow,
|
|
trackUserClickedOnPersonalizedTemplate,
|
|
trackUserDismissedCallout,
|
|
getTemplateRoute
|
|
};
|
|
});
|
|
const _sfc_main$4 = /* @__PURE__ */ defineComponent({
|
|
__name: "SuggestedWorkflowCard",
|
|
props: {
|
|
data: {}
|
|
},
|
|
setup(__props) {
|
|
const props = __props;
|
|
const { data } = props;
|
|
const {
|
|
dismissSuggestedWorkflow,
|
|
getTemplateRoute,
|
|
trackUserClickedOnPersonalizedTemplate,
|
|
trackUserDismissedCallout
|
|
} = usePersonalizedTemplatesStore();
|
|
const locale = useI18n();
|
|
const onDismissCallout = () => {
|
|
trackUserDismissedCallout(data.id);
|
|
dismissSuggestedWorkflow(data.id);
|
|
};
|
|
const onTryTemplate = () => {
|
|
trackUserClickedOnPersonalizedTemplate(data.id);
|
|
dismissSuggestedWorkflow(data.id);
|
|
};
|
|
return (_ctx, _cache) => {
|
|
const _component_N8nLink = N8nLink;
|
|
const _component_N8nIcon = N8nIcon;
|
|
const _component_N8nCallout = N8nCallout;
|
|
return openBlock(), createBlock(_component_N8nCallout, {
|
|
theme: "secondary",
|
|
iconless: true,
|
|
class: normalizeClass(_ctx.$style["suggested-workflow-callout"]),
|
|
slim: true
|
|
}, {
|
|
trailingContent: withCtx(() => [
|
|
createBaseVNode("div", {
|
|
class: normalizeClass(_ctx.$style["callout-trailing-content"])
|
|
}, [
|
|
createVNode(_component_N8nLink, {
|
|
"data-test-id": "suggested-workflow-button",
|
|
size: "small",
|
|
to: unref(getTemplateRoute)(unref(data).id),
|
|
onClick: onTryTemplate
|
|
}, {
|
|
default: withCtx(() => [
|
|
createTextVNode(toDisplayString(unref(locale).baseText("workflows.itemSuggestion.try")), 1)
|
|
]),
|
|
_: 1
|
|
}, 8, ["to"]),
|
|
createVNode(_component_N8nIcon, {
|
|
size: "small",
|
|
icon: "x",
|
|
title: unref(locale).baseText("generic.dismiss"),
|
|
class: "clickable",
|
|
onClick: onDismissCallout
|
|
}, null, 8, ["title"])
|
|
], 2)
|
|
]),
|
|
default: withCtx(() => [
|
|
createBaseVNode("div", {
|
|
class: normalizeClass(_ctx.$style["callout-content"])
|
|
}, toDisplayString(unref(data).name), 3)
|
|
]),
|
|
_: 1
|
|
}, 8, ["class"]);
|
|
};
|
|
}
|
|
});
|
|
const style0$4 = {
|
|
"suggested-workflow-callout": "_suggested-workflow-callout_1alzo_123",
|
|
"callout-content": "_callout-content_1alzo_129",
|
|
"callout-trailing-content": "_callout-trailing-content_1alzo_133"
|
|
};
|
|
const cssModules$4 = {
|
|
"$style": style0$4
|
|
};
|
|
const SuggestedWorkflowCard = /* @__PURE__ */ _export_sfc(_sfc_main$4, [["__cssModules", cssModules$4]]);
|
|
const style0$3 = {
|
|
"suggested-workflows": "_suggested-workflows_t3ed6_123"
|
|
};
|
|
const _sfc_main$3 = {};
|
|
const _hoisted_1$2 = {
|
|
class: "suggested-workflows",
|
|
"data-test-id": "suggested-workflows"
|
|
};
|
|
function _sfc_render(_ctx, _cache) {
|
|
return openBlock(), createElementBlock("div", _hoisted_1$2, [
|
|
renderSlot(_ctx.$slots, "default")
|
|
]);
|
|
}
|
|
const cssModules$3 = {
|
|
"$style": style0$3
|
|
};
|
|
const SuggestedWorkflows = /* @__PURE__ */ _export_sfc(_sfc_main$3, [["render", _sfc_render], ["__cssModules", cssModules$3]]);
|
|
const _sfc_main$2 = /* @__PURE__ */ defineComponent({
|
|
__name: "NodeRecommendationCard",
|
|
props: {
|
|
nodeName: {}
|
|
},
|
|
setup(__props) {
|
|
const props = __props;
|
|
const uiStore = useUIStore();
|
|
const nodeTypesStore = useNodeTypesStore();
|
|
const { trackMinicardClick } = usePersonalizedTemplatesV2Store();
|
|
const nodeType = computed(() => nodeTypesStore.getNodeType(props.nodeName));
|
|
const openModal = () => {
|
|
trackMinicardClick(nodeType.value?.displayName ?? props.nodeName);
|
|
uiStore.openModalWithData({
|
|
name: EXPERIMENT_TEMPLATE_RECO_V2_KEY,
|
|
data: { nodeName: props.nodeName }
|
|
});
|
|
};
|
|
onMounted(async () => {
|
|
await nodeTypesStore.loadNodeTypesIfNotLoaded();
|
|
});
|
|
return (_ctx, _cache) => {
|
|
const _component_NodeIcon = NodeIcon;
|
|
const _component_N8nText = N8nText;
|
|
const _component_N8nCard = N8nCard;
|
|
return openBlock(), createElementBlock("div", null, [
|
|
createVNode(_component_N8nCard, {
|
|
class: normalizeClass(_ctx.$style.nodeCard),
|
|
hoverable: "",
|
|
onClick: openModal
|
|
}, {
|
|
default: withCtx(() => [
|
|
createBaseVNode("div", {
|
|
class: normalizeClass(_ctx.$style.emptyStateCardContent)
|
|
}, [
|
|
createVNode(_component_NodeIcon, {
|
|
"node-type": nodeType.value,
|
|
class: normalizeClass(_ctx.$style.nodeIcon),
|
|
"stroke-width": 1.5
|
|
}, null, 8, ["node-type", "class"]),
|
|
createVNode(_component_N8nText, {
|
|
size: "xsmall",
|
|
class: "mt-xs pl-2xs pr-2xs",
|
|
bold: true
|
|
}, {
|
|
default: withCtx(() => [
|
|
createTextVNode(toDisplayString(nodeType.value?.displayName), 1)
|
|
]),
|
|
_: 1
|
|
})
|
|
], 2)
|
|
]),
|
|
_: 1
|
|
}, 8, ["class"])
|
|
]);
|
|
};
|
|
}
|
|
});
|
|
const nodeCard = "_nodeCard_1ub18_123";
|
|
const nodeIcon = "_nodeIcon_1ub18_133";
|
|
const emptyStateCardContent$1 = "_emptyStateCardContent_1ub18_137";
|
|
const style0$2 = {
|
|
nodeCard,
|
|
nodeIcon,
|
|
emptyStateCardContent: emptyStateCardContent$1
|
|
};
|
|
const cssModules$2 = {
|
|
"$style": style0$2
|
|
};
|
|
const NodeRecommendationCard = /* @__PURE__ */ _export_sfc(_sfc_main$2, [["__cssModules", cssModules$2]]);
|
|
const _hoisted_1$1 = {
|
|
key: 0,
|
|
class: "text-center mt-3xl",
|
|
"data-test-id": "list-empty-state"
|
|
};
|
|
const _sfc_main$1 = /* @__PURE__ */ defineComponent({
|
|
__name: "TemplateRecommendationV2",
|
|
setup(__props) {
|
|
const templateRecoV2Store = usePersonalizedTemplatesV2Store();
|
|
const locale = useI18n();
|
|
return (_ctx, _cache) => {
|
|
const _component_N8nHeading = N8nHeading;
|
|
return unref(templateRecoV2Store).nodes.length ? (openBlock(), createElementBlock("div", _hoisted_1$1, [
|
|
createVNode(_component_N8nHeading, {
|
|
tag: "h2",
|
|
size: "medium",
|
|
class: "mb-2xs",
|
|
color: "text-light"
|
|
}, {
|
|
default: withCtx(() => [
|
|
createTextVNode(toDisplayString(unref(locale).baseText("workflows.templateRecoV2.exploreTemplates")), 1)
|
|
]),
|
|
_: 1
|
|
}),
|
|
createBaseVNode("div", {
|
|
class: normalizeClass(_ctx.$style.nodeCardsContainer)
|
|
}, [
|
|
(openBlock(true), createElementBlock(Fragment, null, renderList(unref(templateRecoV2Store).nodes, (node) => {
|
|
return openBlock(), createBlock(NodeRecommendationCard, {
|
|
key: node,
|
|
"node-name": node
|
|
}, null, 8, ["node-name"]);
|
|
}), 128))
|
|
], 2)
|
|
])) : createCommentVNode("", true);
|
|
};
|
|
}
|
|
});
|
|
const nodeCardsContainer = "_nodeCardsContainer_7g2he_123";
|
|
const style0$1 = {
|
|
nodeCardsContainer
|
|
};
|
|
const cssModules$1 = {
|
|
"$style": style0$1
|
|
};
|
|
const TemplateRecommendationV2 = /* @__PURE__ */ _export_sfc(_sfc_main$1, [["__cssModules", cssModules$1]]);
|
|
const _hoisted_1 = { key: 0 };
|
|
const _hoisted_2 = { key: 0 };
|
|
const _hoisted_3 = { key: 1 };
|
|
const _hoisted_4 = { key: 1 };
|
|
const _hoisted_5 = { key: 1 };
|
|
const _hoisted_6 = {
|
|
class: "text-center mt-s",
|
|
"data-test-id": "list-empty-state"
|
|
};
|
|
const _hoisted_7 = {
|
|
key: 0,
|
|
class: "mb-s"
|
|
};
|
|
const _hoisted_8 = { class: "mb-s" };
|
|
const _hoisted_9 = { class: "mb-s" };
|
|
const SEARCH_DEBOUNCE_TIME = 300;
|
|
const FILTERS_DEBOUNCE_TIME = 100;
|
|
const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
__name: "WorkflowsView",
|
|
setup(__props) {
|
|
const StatusFilter = {
|
|
ALL: "",
|
|
ACTIVE: "active",
|
|
DEACTIVATED: "deactivated"
|
|
};
|
|
const WORKFLOWS_SORT_MAP = {
|
|
lastUpdated: "updatedAt:desc",
|
|
lastCreated: "createdAt:desc",
|
|
nameAsc: "name:asc",
|
|
nameDesc: "name:desc"
|
|
};
|
|
const i18n = useI18n();
|
|
const route = useRoute();
|
|
const router = useRouter();
|
|
const message = useMessage();
|
|
const toast = useToast();
|
|
const folderHelpers = useFolders();
|
|
const sourceControlStore = useSourceControlStore();
|
|
const usersStore = useUsersStore();
|
|
const workflowsStore = useWorkflowsStore();
|
|
const settingsStore = useSettingsStore();
|
|
const projectsStore = useProjectsStore();
|
|
const telemetry = useTelemetry();
|
|
const uiStore = useUIStore();
|
|
const tagsStore = useTagsStore();
|
|
const foldersStore = useFoldersStore();
|
|
const usageStore = useUsageStore();
|
|
const insightsStore = useInsightsStore();
|
|
const templatesStore = useTemplatesStore();
|
|
const aiStarterTemplatesStore = useAITemplatesStarterCollectionStore();
|
|
const personalizedTemplatesStore = usePersonalizedTemplatesStore();
|
|
const readyToRunWorkflowsStore = useReadyToRunWorkflowsStore();
|
|
const personalizedTemplatesV2Store = usePersonalizedTemplatesV2Store();
|
|
const documentTitle = useDocumentTitle();
|
|
const { callDebounced } = useDebounce();
|
|
const projectPages = useProjectPages();
|
|
const loading = ref(true);
|
|
const breadcrumbsLoading = ref(false);
|
|
const filters = ref({
|
|
search: "",
|
|
homeProject: "",
|
|
status: StatusFilter.ALL,
|
|
showArchived: false,
|
|
tags: []
|
|
});
|
|
const workflowListEventBus = createEventBus();
|
|
const workflowsAndFolders = ref([]);
|
|
const easyAICalloutVisible = ref(true);
|
|
const currentPage = ref(1);
|
|
const pageSize = ref(DEFAULT_WORKFLOW_PAGE_SIZE);
|
|
const currentSort = ref("updatedAt:desc");
|
|
const currentFolderId = ref(null);
|
|
const showCardsBadge = ref(false);
|
|
const folderActions = computed(() => [
|
|
{
|
|
label: i18n.baseText("generic.open"),
|
|
value: FOLDER_LIST_ITEM_ACTIONS.OPEN,
|
|
disabled: false,
|
|
onlyAvailableOn: "card"
|
|
},
|
|
{
|
|
label: i18n.baseText("folders.actions.create"),
|
|
value: FOLDER_LIST_ITEM_ACTIONS.CREATE,
|
|
disabled: readOnlyEnv.value || !hasPermissionToCreateFolders.value
|
|
},
|
|
{
|
|
label: i18n.baseText("folders.actions.create.workflow"),
|
|
value: FOLDER_LIST_ITEM_ACTIONS.CREATE_WORKFLOW,
|
|
disabled: readOnlyEnv.value || !hasPermissionToCreateWorkflows.value
|
|
},
|
|
{
|
|
label: i18n.baseText("generic.rename"),
|
|
value: FOLDER_LIST_ITEM_ACTIONS.RENAME,
|
|
disabled: readOnlyEnv.value || !hasPermissionToUpdateFolders.value
|
|
},
|
|
{
|
|
label: i18n.baseText("folders.actions.moveToFolder"),
|
|
value: FOLDER_LIST_ITEM_ACTIONS.MOVE,
|
|
disabled: readOnlyEnv.value || !hasPermissionToUpdateFolders.value
|
|
},
|
|
{
|
|
label: i18n.baseText("generic.delete"),
|
|
value: FOLDER_LIST_ITEM_ACTIONS.DELETE,
|
|
disabled: readOnlyEnv.value || !hasPermissionToDeleteFolders.value
|
|
}
|
|
]);
|
|
const folderCardActions = computed(
|
|
() => folderActions.value.filter(
|
|
(action) => !action.onlyAvailableOn || action.onlyAvailableOn === "card"
|
|
)
|
|
);
|
|
const mainBreadcrumbsActions = computed(
|
|
() => folderActions.value.filter(
|
|
(action) => !action.onlyAvailableOn || action.onlyAvailableOn === "mainBreadcrumbs"
|
|
)
|
|
);
|
|
const readOnlyEnv = computed(() => sourceControlStore.preferences.branchReadOnly);
|
|
const currentUser = computed(() => usersStore.currentUser ?? {});
|
|
const isShareable = computed(
|
|
() => settingsStore.isEnterpriseFeatureEnabled[EnterpriseEditionFeature.Sharing]
|
|
);
|
|
const foldersEnabled = computed(() => {
|
|
return settingsStore.isFoldersFeatureEnabled;
|
|
});
|
|
const teamProjectsEnabled = computed(() => {
|
|
return projectsStore.isTeamProjectFeatureEnabled;
|
|
});
|
|
const showFolders = computed(() => {
|
|
return foldersEnabled.value && !projectPages.isOverviewSubPage && !projectPages.isSharedSubPage;
|
|
});
|
|
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;
|
|
});
|
|
const isDragNDropEnabled = computed(() => {
|
|
return !readOnlyEnv.value && hasPermissionToUpdateFolders.value;
|
|
});
|
|
const hasPermissionToCreateFolders = computed(() => {
|
|
if (!currentProject.value) return false;
|
|
return getResourcePermissions(currentProject.value.scopes).folder.create === true;
|
|
});
|
|
const hasPermissionToUpdateFolders = computed(() => {
|
|
if (!currentProject.value) return false;
|
|
return getResourcePermissions(currentProject.value.scopes).folder.update === true;
|
|
});
|
|
const hasPermissionToDeleteFolders = computed(() => {
|
|
if (!currentProject.value) return false;
|
|
return getResourcePermissions(currentProject.value.scopes).folder.delete === true;
|
|
});
|
|
const hasPermissionToCreateWorkflows = computed(() => {
|
|
if (!currentProject.value) return false;
|
|
return getResourcePermissions(currentProject.value.scopes).workflow.create === true;
|
|
});
|
|
const currentProject = computed(() => projectsStore.currentProject);
|
|
const projectName = computed(() => {
|
|
if (currentProject.value?.type === ProjectTypes.Personal) {
|
|
return i18n.baseText("projects.menu.personal");
|
|
}
|
|
return currentProject.value?.name;
|
|
});
|
|
const currentParentName = computed(() => {
|
|
if (currentFolder.value) {
|
|
return currentFolder.value.name;
|
|
}
|
|
return projectName.value;
|
|
});
|
|
const personalProject = computed(() => {
|
|
return projectsStore.personalProject;
|
|
});
|
|
const workflowListResources = computed(() => {
|
|
const resources = (workflowsAndFolders.value || []).map((resource) => {
|
|
if (resource.resource === "folder") {
|
|
return {
|
|
resourceType: "folder",
|
|
id: resource.id,
|
|
name: resource.name,
|
|
createdAt: resource.createdAt.toString(),
|
|
updatedAt: resource.updatedAt.toString(),
|
|
homeProject: resource.homeProject,
|
|
workflowCount: resource.workflowCount,
|
|
subFolderCount: resource.subFolderCount,
|
|
parentFolder: resource.parentFolder
|
|
};
|
|
} else {
|
|
return {
|
|
resourceType: "workflow",
|
|
id: resource.id,
|
|
name: resource.name,
|
|
active: resource.active ?? false,
|
|
isArchived: resource.isArchived,
|
|
updatedAt: resource.updatedAt.toString(),
|
|
createdAt: resource.createdAt.toString(),
|
|
homeProject: resource.homeProject,
|
|
scopes: resource.scopes,
|
|
sharedWithProjects: resource.sharedWithProjects,
|
|
readOnly: !getResourcePermissions(resource.scopes).workflow.update,
|
|
tags: resource.tags,
|
|
parentFolder: resource.parentFolder
|
|
};
|
|
}
|
|
});
|
|
return resources;
|
|
});
|
|
const statusFilterOptions = computed(() => [
|
|
{
|
|
label: i18n.baseText("workflows.filters.status.all"),
|
|
value: StatusFilter.ALL
|
|
},
|
|
{
|
|
label: i18n.baseText("workflows.filters.status.active"),
|
|
value: StatusFilter.ACTIVE
|
|
},
|
|
{
|
|
label: i18n.baseText("workflows.filters.status.deactivated"),
|
|
value: StatusFilter.DEACTIVATED
|
|
}
|
|
]);
|
|
const showEasyAIWorkflowCallout = computed(() => {
|
|
const easyAIWorkflowOnboardingDone = usersStore.isEasyAIWorkflowOnboardingDone;
|
|
return !easyAIWorkflowOnboardingDone;
|
|
});
|
|
const templatesCardEnabled = computed(() => {
|
|
return isExtraTemplateLinksExperimentEnabled() && settingsStore.isTemplatesEnabled;
|
|
});
|
|
const projectPermissions = computed(() => {
|
|
return getResourcePermissions(
|
|
projectsStore.currentProject?.scopes ?? personalProject.value?.scopes
|
|
);
|
|
});
|
|
const showReadyToRunWorkflowsCallout = computed(() => {
|
|
const isEnabled = readyToRunWorkflowsStore.isFeatureEnabled;
|
|
const isDismissed = readyToRunWorkflowsStore.isCalloutDismissed;
|
|
return isEnabled && !isDismissed && !loading.value && !readOnlyEnv.value && (projectPages.isOverviewSubPage || hasPermissionToCreateFolders.value && hasPermissionToCreateWorkflows.value);
|
|
});
|
|
const emptyListDescription = computed(() => {
|
|
if (readOnlyEnv.value) {
|
|
return i18n.baseText("workflows.empty.description.readOnlyEnv");
|
|
} else if (!projectPermissions.value.workflow.create) {
|
|
return i18n.baseText("workflows.empty.description.noPermission");
|
|
} else {
|
|
return i18n.baseText("workflows.empty.description");
|
|
}
|
|
});
|
|
const hasFilters = computed(() => {
|
|
return !!(filters.value.search || filters.value.status !== StatusFilter.ALL || filters.value.showArchived || filters.value.tags.length);
|
|
});
|
|
const showArchivedOnlyHint = computed(() => {
|
|
return workflowsAndFolders.value.length === 0 && !hasFilters.value && !filters.value.showArchived && foldersStore.totalWorkflowCount > 0;
|
|
});
|
|
const isSelfHostedDeployment = computed(() => settingsStore.deploymentType === "default");
|
|
const canUserRegisterCommunityPlus = computed(
|
|
() => getResourcePermissions(usersStore.currentUser?.globalScopes).community.register
|
|
);
|
|
const showRegisteredCommunityCTA = computed(
|
|
() => isSelfHostedDeployment.value && !foldersEnabled.value && canUserRegisterCommunityPlus.value
|
|
);
|
|
const showAIStarterCollectionCallout = computed(() => {
|
|
return !loading.value && aiStarterTemplatesStore.isFeatureEnabled && !aiStarterTemplatesStore.calloutDismissed && !readOnlyEnv.value && // We want to show the callout only if the user has permissions to create folders and workflows
|
|
// but also on the overview page
|
|
(projectPages.isOverviewSubPage || hasPermissionToCreateFolders.value && hasPermissionToCreateWorkflows.value);
|
|
});
|
|
const showPersonalizedTemplates = computed(
|
|
() => !loading.value && personalizedTemplatesStore.isFeatureEnabled()
|
|
);
|
|
watch([() => route.params?.projectId, () => route.name], async () => {
|
|
loading.value = true;
|
|
});
|
|
watch(
|
|
() => route.params?.folderId,
|
|
async (newVal) => {
|
|
currentFolderId.value = newVal;
|
|
filters.value.search = "";
|
|
saveFiltersOnQueryString();
|
|
await fetchWorkflows();
|
|
}
|
|
);
|
|
sourceControlStore.$onAction(({ name: name2, after }) => {
|
|
if (name2 !== "pullWorkfolder") return;
|
|
after(async () => await initialize());
|
|
});
|
|
const refreshWorkflows = async () => {
|
|
await Promise.all([
|
|
fetchWorkflows(),
|
|
foldersStore.fetchTotalWorkflowsAndFoldersCount(route.params.projectId)
|
|
]);
|
|
};
|
|
const onFolderDeleted = async (payload) => {
|
|
const folderInfo = foldersStore.getCachedFolder(payload.folderId);
|
|
foldersStore.deleteFoldersFromCache([payload.folderId, folderInfo?.parentFolder ?? ""]);
|
|
await foldersStore.fetchTotalWorkflowsAndFoldersCount(
|
|
route.params.projectId
|
|
);
|
|
if (currentFolderId.value === payload.folderId) {
|
|
void router.push({
|
|
name: VIEWS.PROJECTS_FOLDERS,
|
|
params: { projectId: route.params.projectId, folderId: folderInfo?.parentFolder ?? "" }
|
|
});
|
|
} else {
|
|
await fetchWorkflows();
|
|
}
|
|
telemetry.track("User deleted folder", {
|
|
folder_id: payload.folderId,
|
|
deleted_sub_folders: payload.folderCount,
|
|
deleted_sub_workflows: payload.workflowCount
|
|
});
|
|
};
|
|
const showInsights = computed(() => {
|
|
return projectPages.isOverviewSubPage && insightsStore.isSummaryEnabled && (!personalizedTemplatesV2Store.isFeatureEnabled() || workflowListResources.value.length > 0);
|
|
});
|
|
const showTemplateRecommendationV2 = computed(() => {
|
|
return personalizedTemplatesV2Store.isFeatureEnabled() && !loading.value;
|
|
});
|
|
onMounted(async () => {
|
|
documentTitle.set(i18n.baseText("workflows.heading"));
|
|
void usersStore.showPersonalizationSurvey();
|
|
workflowListEventBus.on("resource-moved", fetchWorkflows);
|
|
workflowListEventBus.on("workflow-duplicated", fetchWorkflows);
|
|
workflowListEventBus.on("folder-deleted", onFolderDeleted);
|
|
workflowListEventBus.on("folder-moved", moveFolder);
|
|
workflowListEventBus.on("folder-transferred", onFolderTransferred);
|
|
workflowListEventBus.on("workflow-moved", onWorkflowMoved);
|
|
workflowListEventBus.on("workflow-transferred", onWorkflowTransferred);
|
|
});
|
|
onBeforeUnmount(() => {
|
|
workflowListEventBus.off("resource-moved", fetchWorkflows);
|
|
workflowListEventBus.off("workflow-duplicated", fetchWorkflows);
|
|
workflowListEventBus.off("folder-deleted", onFolderDeleted);
|
|
workflowListEventBus.off("folder-moved", moveFolder);
|
|
workflowListEventBus.off("folder-transferred", onFolderTransferred);
|
|
workflowListEventBus.off("workflow-moved", onWorkflowMoved);
|
|
workflowListEventBus.off("workflow-transferred", onWorkflowTransferred);
|
|
});
|
|
const initialize = async () => {
|
|
loading.value = true;
|
|
await setFiltersFromQueryString();
|
|
currentFolderId.value = route.params.folderId;
|
|
const [, resourcesPage] = await Promise.all([
|
|
usersStore.fetchUsers(),
|
|
fetchWorkflows(),
|
|
workflowsStore.fetchActiveWorkflows(),
|
|
usageStore.getLicenseInfo(),
|
|
foldersStore.fetchTotalWorkflowsAndFoldersCount(route.params.projectId)
|
|
]);
|
|
breadcrumbsLoading.value = false;
|
|
workflowsAndFolders.value = resourcesPage;
|
|
loading.value = false;
|
|
};
|
|
const fetchWorkflows = async () => {
|
|
const delayedLoading = debounce(() => {
|
|
loading.value = true;
|
|
}, 300);
|
|
const routeProjectId = route.params?.projectId;
|
|
const homeProjectFilter = filters.value.homeProject || void 0;
|
|
const parentFolder = route.params?.folderId || void 0;
|
|
const tags = filters.value.tags.length ? filters.value.tags.map((tagId) => tagsStore.tagsById[tagId]?.name) : [];
|
|
const activeFilter = filters.value.status === StatusFilter.ALL ? void 0 : filters.value.status === StatusFilter.ACTIVE;
|
|
const archivedFilter = filters.value.showArchived ? void 0 : false;
|
|
const fetchFolders = showFolders.value && !tags.length && activeFilter === void 0;
|
|
try {
|
|
const fetchedResources = await workflowsStore.fetchWorkflowsPage(
|
|
routeProjectId ?? homeProjectFilter,
|
|
currentPage.value,
|
|
pageSize.value,
|
|
currentSort.value,
|
|
{
|
|
name: filters.value.search || void 0,
|
|
active: activeFilter,
|
|
isArchived: archivedFilter,
|
|
tags: tags.length ? tags : void 0,
|
|
parentFolderId: getParentFolderId(parentFolder)
|
|
},
|
|
fetchFolders,
|
|
projectPages.isSharedSubPage
|
|
);
|
|
foldersStore.cacheFolders(
|
|
fetchedResources.filter((resource) => resource.resource === "folder").map((r) => ({ id: r.id, name: r.name, parentFolder: r.parentFolder?.id }))
|
|
);
|
|
const isCurrentFolderCached = foldersStore.breadcrumbsCache[parentFolder ?? ""] !== void 0;
|
|
const needToFetchFolderPath = parentFolder && !isCurrentFolderCached && routeProjectId;
|
|
if (needToFetchFolderPath) {
|
|
breadcrumbsLoading.value = true;
|
|
await foldersStore.getFolderPath(routeProjectId, parentFolder);
|
|
breadcrumbsLoading.value = false;
|
|
}
|
|
workflowsAndFolders.value = fetchedResources;
|
|
showCardsBadge.value = projectPages.isOverviewSubPage || projectPages.isSharedSubPage || filters.value.search !== "";
|
|
return fetchedResources;
|
|
} catch (error) {
|
|
toast.showError(error, i18n.baseText("workflows.list.error.fetching"));
|
|
void router.push({ name: VIEWS.PROJECTS_FOLDERS, params: { projectId: routeProjectId } });
|
|
return [];
|
|
} finally {
|
|
delayedLoading.cancel();
|
|
loading.value = false;
|
|
if (breadcrumbsLoading.value) {
|
|
breadcrumbsLoading.value = false;
|
|
}
|
|
}
|
|
};
|
|
const getParentFolderId = (routeId) => {
|
|
if (routeId !== null && routeId !== void 0) {
|
|
return routeId;
|
|
}
|
|
if (projectPages.isOverviewSubPage || projectPages.isSharedSubPage || filters?.value.search) {
|
|
return void 0;
|
|
}
|
|
return PROJECT_ROOT;
|
|
};
|
|
const onFiltersUpdated = async () => {
|
|
currentPage.value = 1;
|
|
saveFiltersOnQueryString();
|
|
if (!loading.value) {
|
|
await callDebounced(fetchWorkflows, { debounceTime: FILTERS_DEBOUNCE_TIME, trailing: true });
|
|
}
|
|
};
|
|
const onSearchUpdated = async (search) => {
|
|
currentPage.value = 1;
|
|
saveFiltersOnQueryString();
|
|
if (search) {
|
|
await callDebounced(fetchWorkflows, { debounceTime: SEARCH_DEBOUNCE_TIME, trailing: true });
|
|
} else {
|
|
await fetchWorkflows();
|
|
}
|
|
};
|
|
const setPaginationAndSort = async (payload) => {
|
|
if (payload.page) {
|
|
currentPage.value = payload.page;
|
|
}
|
|
if (payload.pageSize) {
|
|
pageSize.value = payload.pageSize;
|
|
}
|
|
if (payload.sort) {
|
|
currentSort.value = WORKFLOWS_SORT_MAP[payload.sort] ?? "updatedAt:desc";
|
|
}
|
|
if (!loading.value) {
|
|
await callDebounced(fetchWorkflows, { debounceTime: FILTERS_DEBOUNCE_TIME, trailing: true });
|
|
}
|
|
};
|
|
const onClickTag = async (tagId) => {
|
|
if (!filters.value.tags.includes(tagId)) {
|
|
filters.value.tags.push(tagId);
|
|
currentPage.value = 1;
|
|
saveFiltersOnQueryString();
|
|
await fetchWorkflows();
|
|
}
|
|
};
|
|
const saveFiltersOnQueryString = () => {
|
|
const currentQuery = { ...route.query };
|
|
if (filters.value.search) {
|
|
currentQuery.search = filters.value.search;
|
|
} else {
|
|
delete currentQuery.search;
|
|
}
|
|
if (filters.value.status !== StatusFilter.ALL) {
|
|
currentQuery.status = (filters.value.status === StatusFilter.ACTIVE).toString();
|
|
} else {
|
|
delete currentQuery.status;
|
|
}
|
|
if (filters.value.showArchived) {
|
|
currentQuery.showArchived = "true";
|
|
} else {
|
|
delete currentQuery.showArchived;
|
|
}
|
|
if (filters.value.tags.length) {
|
|
currentQuery.tags = filters.value.tags.join(",");
|
|
} else {
|
|
delete currentQuery.tags;
|
|
}
|
|
if (filters.value.homeProject) {
|
|
currentQuery.homeProject = filters.value.homeProject;
|
|
} else {
|
|
delete currentQuery.homeProject;
|
|
}
|
|
void router.replace({
|
|
query: Object.keys(currentQuery).length ? currentQuery : void 0
|
|
});
|
|
};
|
|
const setFiltersFromQueryString = async () => {
|
|
const newQuery = { ...route.query };
|
|
const { tags, status, search, homeProject, sort, showArchived } = route.query ?? {};
|
|
const isValidString = (value) => typeof value === "string" && value.trim().length > 0;
|
|
if (isValidString(homeProject)) {
|
|
await projectsStore.getAvailableProjects();
|
|
if (isValidProjectId(homeProject)) {
|
|
newQuery.homeProject = homeProject;
|
|
filters.value.homeProject = homeProject;
|
|
} else {
|
|
delete newQuery.homeProject;
|
|
}
|
|
} else {
|
|
delete newQuery.homeProject;
|
|
}
|
|
if (isValidString(search)) {
|
|
newQuery.search = search;
|
|
filters.value.search = search;
|
|
} else {
|
|
delete newQuery.search;
|
|
}
|
|
if (isValidString(tags)) {
|
|
await tagsStore.fetchAll();
|
|
const validTags = tags.split(",").filter((tag) => tagsStore.allTags.map((t) => t.id).includes(tag));
|
|
if (validTags.length) {
|
|
newQuery.tags = validTags.join(",");
|
|
filters.value.tags = validTags;
|
|
} else {
|
|
delete newQuery.tags;
|
|
}
|
|
} else {
|
|
delete newQuery.tags;
|
|
}
|
|
if (isValidString(status)) {
|
|
newQuery.status = status;
|
|
filters.value.status = status === "true" ? StatusFilter.ACTIVE : StatusFilter.DEACTIVATED;
|
|
} else {
|
|
delete newQuery.status;
|
|
}
|
|
if (isValidString(sort)) {
|
|
const newSort = WORKFLOWS_SORT_MAP[sort] ?? "updatedAt:desc";
|
|
newQuery.sort = sort;
|
|
currentSort.value = newSort;
|
|
} else {
|
|
delete newQuery.sort;
|
|
}
|
|
if (isValidString(showArchived)) {
|
|
newQuery.showArchived = showArchived;
|
|
filters.value.showArchived = showArchived === "true";
|
|
} else {
|
|
delete newQuery.showArchived;
|
|
filters.value.showArchived = false;
|
|
}
|
|
void router.replace({ query: newQuery });
|
|
};
|
|
const addWorkflow = () => {
|
|
uiStore.nodeViewInitialized = false;
|
|
void router.push({
|
|
name: VIEWS.NEW_WORKFLOW,
|
|
query: { projectId: route.params?.projectId, parentFolderId: route.params?.folderId }
|
|
});
|
|
telemetry.track("User clicked add workflow button", {
|
|
source: "Workflows list"
|
|
});
|
|
trackEmptyCardClick("blank");
|
|
};
|
|
const openTemplatesRepository = async () => {
|
|
trackTemplatesClick(TemplateClickSource.emptyInstanceCard);
|
|
if (templatesStore.hasCustomTemplatesHost) {
|
|
await router.push({ name: VIEWS.TEMPLATES });
|
|
return;
|
|
}
|
|
window.open(templatesStore.websiteTemplateRepositoryURL, "_blank");
|
|
};
|
|
const trackEmptyCardClick = (option) => {
|
|
telemetry.track("User clicked empty page option", {
|
|
option
|
|
});
|
|
};
|
|
function isValidProjectId(projectId) {
|
|
return projectsStore.availableProjects.some((project) => project.id === projectId);
|
|
}
|
|
const createAIStarterWorkflows = async (source) => {
|
|
try {
|
|
const projectId = projectPages.isOverviewSubPage ? personalProject.value?.id : route.params.projectId;
|
|
if (typeof projectId !== "string") {
|
|
toast.showError(new Error(), i18n.baseText("workflows.ai.starter.collection.error"));
|
|
return;
|
|
}
|
|
const newFolder = await aiStarterTemplatesStore.createStarterWorkflows(
|
|
projectId,
|
|
currentFolderId.value ?? void 0
|
|
);
|
|
if (projectPages.isOverviewSubPage) {
|
|
await router.push({
|
|
name: VIEWS.PROJECTS_FOLDERS,
|
|
params: { projectId, folderId: newFolder.id }
|
|
});
|
|
} else {
|
|
workflowsAndFolders.value.unshift({
|
|
id: newFolder.id,
|
|
name: newFolder.name,
|
|
resource: "folder",
|
|
createdAt: newFolder.createdAt,
|
|
updatedAt: newFolder.updatedAt,
|
|
subFolderCount: 0,
|
|
workflowCount: 3,
|
|
parentFolder: newFolder.parentFolder
|
|
});
|
|
}
|
|
aiStarterTemplatesStore.trackUserCreatedStarterCollection(source);
|
|
} catch (error) {
|
|
toast.showError(error, i18n.baseText("workflows.ai.starter.collection.error"));
|
|
return;
|
|
}
|
|
};
|
|
const handleCreateReadyToRunWorkflows = async (source) => {
|
|
try {
|
|
const projectId = projectPages.isOverviewSubPage ? personalProject.value?.id : route.params.projectId;
|
|
if (typeof projectId !== "string") {
|
|
toast.showError(new Error(), i18n.baseText("workflows.readyToRunWorkflows.error"));
|
|
return;
|
|
}
|
|
const newFolder = await readyToRunWorkflowsStore.createWorkflows(
|
|
projectId,
|
|
currentFolderId.value ?? void 0
|
|
);
|
|
readyToRunWorkflowsStore.trackCreateWorkflows(source);
|
|
if (projectPages.isOverviewSubPage) {
|
|
await router.push({
|
|
name: VIEWS.PROJECTS_FOLDERS,
|
|
params: { projectId, folderId: newFolder.id }
|
|
});
|
|
} else {
|
|
workflowsAndFolders.value.unshift({
|
|
id: newFolder.id,
|
|
name: newFolder.name,
|
|
resource: "folder",
|
|
createdAt: newFolder.createdAt,
|
|
updatedAt: newFolder.updatedAt,
|
|
subFolderCount: 0,
|
|
workflowCount: 4,
|
|
parentFolder: newFolder.parentFolder
|
|
});
|
|
}
|
|
} catch (error) {
|
|
toast.showError(error, i18n.baseText("workflows.readyToRunWorkflows.error"));
|
|
return;
|
|
}
|
|
};
|
|
const dismissStarterCollectionCallout = () => {
|
|
aiStarterTemplatesStore.dismissCallout();
|
|
aiStarterTemplatesStore.trackUserDismissedCallout();
|
|
};
|
|
const dismissEasyAICallout = () => {
|
|
easyAICalloutVisible.value = false;
|
|
};
|
|
const openAIWorkflow = async (source) => {
|
|
dismissEasyAICallout();
|
|
telemetry.track("User clicked test AI workflow", {
|
|
source
|
|
});
|
|
const easyAiWorkflowJson = getEasyAiWorkflowJson();
|
|
await router.push({
|
|
name: VIEWS.TEMPLATE_IMPORT,
|
|
params: { id: easyAiWorkflowJson.meta.templateId },
|
|
query: { fromJson: "true", parentFolderId: route.params.folderId }
|
|
});
|
|
};
|
|
const onShowArchived = async () => {
|
|
filters.value.showArchived = true;
|
|
await onFiltersUpdated();
|
|
};
|
|
const handleDismissReadyToRunCallout = () => {
|
|
readyToRunWorkflowsStore.dismissCallout();
|
|
readyToRunWorkflowsStore.trackDismissCallout();
|
|
};
|
|
const onWorkflowActiveToggle = (data) => {
|
|
const workflow = workflowsAndFolders.value.find(
|
|
(w) => w.id === data.id
|
|
);
|
|
if (!workflow) return;
|
|
workflow.active = data.active;
|
|
};
|
|
const getFolderListItem = (folderId) => {
|
|
return workflowsAndFolders.value.find(
|
|
(resource) => resource.resource === "folder" && resource.id === folderId
|
|
);
|
|
};
|
|
const getFolderContent = async (folderId) => {
|
|
const folderListItem = getFolderListItem(folderId);
|
|
if (folderListItem) {
|
|
return {
|
|
workflowCount: folderListItem.workflowCount,
|
|
subFolderCount: folderListItem.subFolderCount
|
|
};
|
|
}
|
|
try {
|
|
const content = await foldersStore.fetchFolderContent(currentProject.value?.id ?? "", folderId);
|
|
return { workflowCount: content.totalWorkflows, subFolderCount: content.totalSubFolders };
|
|
} catch (error) {
|
|
toast.showMessage({
|
|
title: i18n.baseText("folders.delete.error.message"),
|
|
message: i18n.baseText("folders.not.found.message"),
|
|
type: "error"
|
|
});
|
|
return { workflowCount: 0, subFolderCount: 0 };
|
|
}
|
|
};
|
|
const onFolderCardDrop = async (event) => {
|
|
const { draggedResource, dropTarget } = folderHelpers.handleDrop(event);
|
|
if (!draggedResource || !dropTarget) return;
|
|
await moveResourceOnDrop(draggedResource, dropTarget);
|
|
};
|
|
const onBreadCrumbsItemDrop = async (item) => {
|
|
if (!foldersStore.draggedElement) return;
|
|
await moveResourceOnDrop(
|
|
{
|
|
id: foldersStore.draggedElement.id,
|
|
type: foldersStore.draggedElement.type,
|
|
name: foldersStore.draggedElement.name
|
|
},
|
|
{
|
|
id: item.id,
|
|
type: "folder",
|
|
name: item.label
|
|
}
|
|
);
|
|
folderHelpers.onDragEnd();
|
|
};
|
|
const moveFolderToProjectRoot = async (id, name2) => {
|
|
if (!foldersStore.draggedElement) return;
|
|
await moveResourceOnDrop(
|
|
{
|
|
id: foldersStore.draggedElement.id,
|
|
type: foldersStore.draggedElement.type,
|
|
name: foldersStore.draggedElement.name
|
|
},
|
|
{
|
|
id,
|
|
type: "project",
|
|
name: name2
|
|
}
|
|
);
|
|
folderHelpers.onDragEnd();
|
|
};
|
|
const moveResourceOnDrop = async (draggedResource, dropTarget) => {
|
|
if (draggedResource.type === "folder") {
|
|
await moveFolder({
|
|
folder: { id: draggedResource.id, name: draggedResource.name },
|
|
newParent: { id: dropTarget.id, name: dropTarget.name, type: dropTarget.type },
|
|
options: { skipFetch: true, skipNavigation: true }
|
|
});
|
|
workflowsAndFolders.value = workflowsAndFolders.value.filter(
|
|
(folder) => folder.id !== draggedResource.id
|
|
);
|
|
const targetFolder = getFolderListItem(dropTarget.id);
|
|
if (targetFolder) {
|
|
targetFolder.subFolderCount += 1;
|
|
}
|
|
} else if (draggedResource.type === "workflow") {
|
|
await onWorkflowMoved({
|
|
workflow: {
|
|
id: draggedResource.id,
|
|
name: draggedResource.name,
|
|
oldParentId: currentFolderId.value ?? ""
|
|
},
|
|
newParent: { id: dropTarget.id, name: dropTarget.name, type: dropTarget.type },
|
|
options: { skipFetch: true }
|
|
});
|
|
workflowsAndFolders.value = workflowsAndFolders.value.filter(
|
|
(workflow) => workflow.id !== draggedResource.id
|
|
);
|
|
const targetFolder = getFolderListItem(dropTarget.id);
|
|
if (targetFolder) {
|
|
targetFolder.workflowCount += 1;
|
|
}
|
|
}
|
|
};
|
|
const onBreadcrumbItemClick = (item) => {
|
|
if (item.href) {
|
|
loading.value = true;
|
|
void router.push(item.href).then(() => {
|
|
currentFolderId.value = item.id;
|
|
loading.value = false;
|
|
}).catch((error) => {
|
|
toast.showError(error, i18n.baseText("folders.open.error.title"));
|
|
});
|
|
}
|
|
};
|
|
const onBreadCrumbsAction = async (action) => {
|
|
switch (action) {
|
|
case FOLDER_LIST_ITEM_ACTIONS.CREATE:
|
|
if (!route.params.projectId) return;
|
|
const currentParent = currentFolder.value?.name || projectName.value;
|
|
if (!currentParent) return;
|
|
await createFolder({
|
|
id: route.params.folderId ?? "-1",
|
|
name: currentParent,
|
|
type: currentFolder.value ? "folder" : "project"
|
|
});
|
|
break;
|
|
case FOLDER_LIST_ITEM_ACTIONS.CREATE_WORKFLOW:
|
|
addWorkflow();
|
|
break;
|
|
case FOLDER_LIST_ITEM_ACTIONS.DELETE:
|
|
if (!route.params.folderId) return;
|
|
const content = await getFolderContent(route.params.folderId);
|
|
await deleteFolder(
|
|
route.params.folderId,
|
|
content.workflowCount,
|
|
content.subFolderCount
|
|
);
|
|
break;
|
|
case FOLDER_LIST_ITEM_ACTIONS.RENAME:
|
|
onNameToggle();
|
|
break;
|
|
case FOLDER_LIST_ITEM_ACTIONS.MOVE:
|
|
if (!currentFolder.value) return;
|
|
uiStore.openMoveToFolderModal(
|
|
"folder",
|
|
{
|
|
id: currentFolder.value?.id,
|
|
name: currentFolder.value?.name,
|
|
parentFolderId: currentFolder.value?.parentFolder
|
|
},
|
|
workflowListEventBus
|
|
);
|
|
break;
|
|
}
|
|
};
|
|
const onFolderCardAction = async (payload) => {
|
|
const clickedFolder = foldersStore.getCachedFolder(payload.folderId);
|
|
if (!clickedFolder) return;
|
|
switch (payload.action) {
|
|
case FOLDER_LIST_ITEM_ACTIONS.CREATE:
|
|
await createFolder(
|
|
{
|
|
id: clickedFolder.id,
|
|
name: clickedFolder.name,
|
|
type: "folder"
|
|
},
|
|
{ openAfterCreate: true }
|
|
);
|
|
break;
|
|
case FOLDER_LIST_ITEM_ACTIONS.CREATE_WORKFLOW:
|
|
currentFolderId.value = clickedFolder.id;
|
|
void router.push({
|
|
name: VIEWS.NEW_WORKFLOW,
|
|
query: { projectId: route.params?.projectId, parentFolderId: clickedFolder.id }
|
|
});
|
|
break;
|
|
case FOLDER_LIST_ITEM_ACTIONS.DELETE: {
|
|
const content = await getFolderContent(clickedFolder.id);
|
|
await deleteFolder(clickedFolder.id, content.workflowCount, content.subFolderCount);
|
|
break;
|
|
}
|
|
case FOLDER_LIST_ITEM_ACTIONS.RENAME:
|
|
await renameFolder(clickedFolder.id);
|
|
break;
|
|
case FOLDER_LIST_ITEM_ACTIONS.MOVE:
|
|
uiStore.openMoveToFolderModal(
|
|
"folder",
|
|
{
|
|
id: clickedFolder.id,
|
|
name: clickedFolder.name,
|
|
parentFolderId: clickedFolder.parentFolder
|
|
},
|
|
workflowListEventBus
|
|
);
|
|
break;
|
|
}
|
|
};
|
|
const createFolder = async (parent, options = { openAfterCreate: false }) => {
|
|
const promptResponsePromise = message.prompt(
|
|
i18n.baseText("folders.add.to.parent.message", { interpolate: { parent: parent.name } }),
|
|
{
|
|
confirmButtonText: i18n.baseText("generic.create"),
|
|
cancelButtonText: i18n.baseText("generic.cancel"),
|
|
inputValidator: folderHelpers.validateFolderName,
|
|
customClass: "add-folder-modal"
|
|
}
|
|
);
|
|
const promptResponse = await promptResponsePromise;
|
|
if (promptResponse.action === MODAL_CONFIRM) {
|
|
const folderName = promptResponse.value;
|
|
try {
|
|
const newFolder = await foldersStore.createFolder(
|
|
folderName,
|
|
route.params.projectId,
|
|
parent.type === "folder" ? parent.id : void 0
|
|
);
|
|
const newFolderURL = router.resolve({
|
|
name: VIEWS.PROJECTS_FOLDERS,
|
|
params: { projectId: route.params.projectId, folderId: newFolder.id }
|
|
}).href;
|
|
toast.showToast({
|
|
title: i18n.baseText("folders.add.success.title"),
|
|
message: i18n.baseText("folders.add.success.message", {
|
|
interpolate: {
|
|
link: newFolderURL,
|
|
folderName: newFolder.name
|
|
}
|
|
}),
|
|
onClick: (event) => {
|
|
if (event?.target instanceof HTMLAnchorElement) {
|
|
event.preventDefault();
|
|
void router.push(newFolderURL);
|
|
}
|
|
},
|
|
type: "success"
|
|
});
|
|
telemetry.track("User created folder", {
|
|
folder_id: newFolder.id
|
|
});
|
|
if (options.openAfterCreate) {
|
|
await router.push({
|
|
name: VIEWS.PROJECTS_FOLDERS,
|
|
params: { projectId: route.params.projectId, folderId: parent.id }
|
|
});
|
|
} else {
|
|
if (!workflowsAndFolders.value.length) {
|
|
workflowsAndFolders.value = [
|
|
{
|
|
id: newFolder.id,
|
|
name: newFolder.name,
|
|
resource: "folder",
|
|
createdAt: newFolder.createdAt,
|
|
updatedAt: newFolder.updatedAt,
|
|
homeProject: projectsStore.currentProject,
|
|
workflowCount: 0,
|
|
subFolderCount: 0
|
|
}
|
|
];
|
|
foldersStore.cacheFolders([
|
|
{ id: newFolder.id, name: newFolder.name, parentFolder: currentFolder.value?.id }
|
|
]);
|
|
} else {
|
|
await fetchWorkflows();
|
|
}
|
|
}
|
|
} catch (error) {
|
|
toast.showError(error, i18n.baseText("folders.create.error.title"));
|
|
}
|
|
}
|
|
};
|
|
const renameFolder = async (folderId) => {
|
|
const folder = foldersStore.getCachedFolder(folderId);
|
|
if (!folder || !currentProject.value) return;
|
|
const promptResponsePromise = message.prompt(
|
|
i18n.baseText("folders.rename.message", { interpolate: { folderName: folder.name } }),
|
|
{
|
|
confirmButtonText: i18n.baseText("generic.rename"),
|
|
cancelButtonText: i18n.baseText("generic.cancel"),
|
|
inputValue: folder.name,
|
|
customClass: "rename-folder-modal",
|
|
inputValidator: folderHelpers.validateFolderName
|
|
}
|
|
);
|
|
const promptResponse = await promptResponsePromise;
|
|
if (promptResponse.action === MODAL_CONFIRM) {
|
|
const newFolderName = promptResponse.value;
|
|
try {
|
|
await foldersStore.renameFolder(currentProject.value?.id, folderId, newFolderName);
|
|
foldersStore.breadcrumbsCache[folderId].name = newFolderName;
|
|
toast.showMessage({
|
|
title: i18n.baseText("folders.rename.success.message", {
|
|
interpolate: { folderName: newFolderName }
|
|
}),
|
|
type: "success"
|
|
});
|
|
await fetchWorkflows();
|
|
telemetry.track("User renamed folder", {
|
|
folder_id: folderId
|
|
});
|
|
} catch (error) {
|
|
toast.showError(error, i18n.baseText("folders.rename.error.title"));
|
|
}
|
|
}
|
|
};
|
|
const createFolderInCurrent = async () => {
|
|
if (showRegisteredCommunityCTA.value) {
|
|
uiStore.openModalWithData({
|
|
name: COMMUNITY_PLUS_ENROLLMENT_MODAL,
|
|
data: { customHeading: i18n.baseText("folders.registeredCommunity.cta.heading") }
|
|
});
|
|
return;
|
|
}
|
|
if (!route.params.projectId) return;
|
|
const currentParent = currentFolder.value?.name || projectName.value;
|
|
if (!currentParent) return;
|
|
await createFolder({
|
|
id: route.params.folderId ?? "-1",
|
|
name: currentParent,
|
|
type: currentFolder.value ? "folder" : "project"
|
|
});
|
|
};
|
|
const deleteFolder = async (folderId, workflowCount, subFolderCount) => {
|
|
if (subFolderCount || workflowCount) {
|
|
uiStore.openDeleteFolderModal(folderId, workflowListEventBus, {
|
|
workflowCount,
|
|
subFolderCount
|
|
});
|
|
} else {
|
|
await foldersStore.deleteFolder(route.params.projectId, folderId);
|
|
toast.showMessage({
|
|
title: i18n.baseText("folders.delete.success.message"),
|
|
type: "success"
|
|
});
|
|
await onFolderDeleted({ folderId, workflowCount, folderCount: subFolderCount });
|
|
}
|
|
};
|
|
const moveFolder = async (payload) => {
|
|
if (!route.params.projectId) return;
|
|
try {
|
|
await foldersStore.moveFolder(
|
|
route.params.projectId,
|
|
payload.folder.id,
|
|
payload.newParent.type === "folder" ? payload.newParent.id : "0"
|
|
);
|
|
const isCurrentFolder = currentFolderId.value === payload.folder.id;
|
|
const newFolderURL = router.resolve({
|
|
name: VIEWS.PROJECTS_FOLDERS,
|
|
params: {
|
|
projectId: route.params.projectId,
|
|
folderId: payload.newParent.type === "folder" ? payload.newParent.id : void 0
|
|
}
|
|
}).href;
|
|
if (isCurrentFolder && !payload.options?.skipNavigation) {
|
|
void router.push(newFolderURL);
|
|
} else {
|
|
toast.showToast({
|
|
title: i18n.baseText("folders.move.success.title"),
|
|
message: i18n.baseText("folders.move.success.message", {
|
|
interpolate: {
|
|
folderName: payload.folder.name,
|
|
newFolderName: payload.newParent.name
|
|
}
|
|
}),
|
|
onClick: (event) => {
|
|
if (event?.target instanceof HTMLAnchorElement) {
|
|
event.preventDefault();
|
|
void router.push(newFolderURL);
|
|
}
|
|
},
|
|
type: "success"
|
|
});
|
|
if (!payload.options?.skipFetch) {
|
|
await fetchWorkflows();
|
|
}
|
|
}
|
|
} catch (error) {
|
|
toast.showError(error, i18n.baseText("folders.move.error.title"));
|
|
}
|
|
};
|
|
const onFolderTransferred = async (payload) => {
|
|
try {
|
|
await foldersStore.moveFolderToProject(
|
|
payload.source.projectId,
|
|
payload.source.folder.id,
|
|
payload.destination.projectId,
|
|
payload.destination.parentFolder.id,
|
|
payload.shareCredentials
|
|
);
|
|
const isCurrentFolder = currentFolderId.value === payload.source.folder.id;
|
|
const newFolderURL = router.resolve({
|
|
name: VIEWS.PROJECTS_FOLDERS,
|
|
params: {
|
|
projectId: payload.destination.canAccess ? payload.destination.projectId : payload.source.projectId,
|
|
folderId: payload.destination.canAccess ? payload.source.folder.id : void 0
|
|
}
|
|
}).href;
|
|
if (isCurrentFolder) {
|
|
if (payload.destination.canAccess) {
|
|
void router.push(newFolderURL);
|
|
} else {
|
|
void router.push({
|
|
name: VIEWS.PROJECTS_WORKFLOWS,
|
|
params: {
|
|
projectId: payload.source.projectId
|
|
}
|
|
});
|
|
}
|
|
} else {
|
|
await refreshWorkflows();
|
|
if (payload.destination.canAccess) {
|
|
toast.showToast({
|
|
title: i18n.baseText("folders.move.success.title"),
|
|
message: i18n.baseText("folders.move.success.message", {
|
|
interpolate: {
|
|
folderName: payload.source.folder.name,
|
|
newFolderName: payload.destination.parentFolder.name
|
|
}
|
|
}),
|
|
onClick: (event) => {
|
|
if (event?.target instanceof HTMLAnchorElement) {
|
|
event.preventDefault();
|
|
void router.push(newFolderURL);
|
|
}
|
|
},
|
|
type: "success"
|
|
});
|
|
} else {
|
|
toast.showToast({
|
|
title: i18n.baseText("folders.move.success.title"),
|
|
message: i18n.baseText("folders.move.success.messageNoAccess", {
|
|
interpolate: {
|
|
folderName: payload.source.folder.name,
|
|
newFolderName: payload.destination.parentFolder.name
|
|
}
|
|
}),
|
|
type: "success"
|
|
});
|
|
}
|
|
}
|
|
} catch (error) {
|
|
toast.showError(error, i18n.baseText("folders.move.error.title"));
|
|
}
|
|
};
|
|
const moveWorkflowToFolder = async (payload) => {
|
|
if (showRegisteredCommunityCTA.value) {
|
|
uiStore.openModalWithData({
|
|
name: COMMUNITY_PLUS_ENROLLMENT_MODAL,
|
|
data: { customHeading: i18n.baseText("folders.registeredCommunity.cta.heading") }
|
|
});
|
|
return;
|
|
}
|
|
uiStore.openMoveToFolderModal(
|
|
"workflow",
|
|
{
|
|
id: payload.id,
|
|
name: payload.name,
|
|
parentFolderId: payload.parentFolderId,
|
|
sharedWithProjects: payload.sharedWithProjects
|
|
},
|
|
workflowListEventBus
|
|
);
|
|
};
|
|
const onWorkflowTransferred = async (payload) => {
|
|
try {
|
|
await projectsStore.moveResourceToProject(
|
|
"workflow",
|
|
payload.source.workflow.id,
|
|
payload.destination.projectId,
|
|
payload.destination.parentFolder.id,
|
|
payload.shareCredentials
|
|
);
|
|
await refreshWorkflows();
|
|
if (payload.destination.canAccess) {
|
|
toast.showToast({
|
|
title: i18n.baseText("folders.move.workflow.success.title"),
|
|
message: i18n.baseText("folders.move.workflow.success.message", {
|
|
interpolate: {
|
|
workflowName: payload.source.workflow.name,
|
|
newFolderName: payload.destination.parentFolder.name
|
|
}
|
|
}),
|
|
onClick: (event) => {
|
|
if (event?.target instanceof HTMLAnchorElement) {
|
|
event.preventDefault();
|
|
void router.push({
|
|
name: VIEWS.PROJECTS_FOLDERS,
|
|
params: {
|
|
projectId: payload.destination.projectId,
|
|
folderId: payload.destination.parentFolder.id
|
|
}
|
|
});
|
|
}
|
|
},
|
|
type: "success"
|
|
});
|
|
} else {
|
|
toast.showToast({
|
|
title: i18n.baseText("folders.move.workflow.success.title"),
|
|
message: i18n.baseText("folders.move.workflow.success.messageNoAccess", {
|
|
interpolate: {
|
|
workflowName: payload.source.workflow.name,
|
|
newFolderName: payload.destination.parentFolder.name
|
|
}
|
|
}),
|
|
type: "success"
|
|
});
|
|
}
|
|
} catch (error) {
|
|
toast.showError(error, i18n.baseText("folders.move.workflow.error.title"));
|
|
}
|
|
};
|
|
const onWorkflowMoved = async (payload) => {
|
|
if (!route.params.projectId) return;
|
|
try {
|
|
const newFolderURL = router.resolve({
|
|
name: VIEWS.PROJECTS_FOLDERS,
|
|
params: {
|
|
projectId: route.params.projectId,
|
|
folderId: payload.newParent.type === "folder" ? payload.newParent.id : void 0
|
|
}
|
|
}).href;
|
|
const workflowResource = workflowsAndFolders.value.find(
|
|
(resource) => resource.id === payload.workflow.id
|
|
);
|
|
await workflowsStore.updateWorkflow(payload.workflow.id, {
|
|
parentFolderId: payload.newParent.type === "folder" ? payload.newParent.id : "0",
|
|
versionId: workflowResource?.versionId
|
|
});
|
|
if (!payload.options?.skipFetch) {
|
|
await fetchWorkflows();
|
|
}
|
|
toast.showToast({
|
|
title: i18n.baseText("folders.move.workflow.success.title"),
|
|
message: i18n.baseText("folders.move.workflow.success.message", {
|
|
interpolate: {
|
|
workflowName: payload.workflow.name,
|
|
newFolderName: payload.newParent.name
|
|
}
|
|
}),
|
|
onClick: (event) => {
|
|
if (event?.target instanceof HTMLAnchorElement) {
|
|
event.preventDefault();
|
|
void router.push(newFolderURL);
|
|
}
|
|
},
|
|
type: "success"
|
|
});
|
|
telemetry.track("User moved content", {
|
|
workflow_id: payload.workflow.id,
|
|
source_folder_id: payload.workflow.oldParentId,
|
|
destination_folder_id: payload.newParent.id
|
|
});
|
|
} catch (error) {
|
|
toast.showError(error, i18n.baseText("folders.move.workflow.error.title"));
|
|
}
|
|
};
|
|
const onCreateWorkflowClick = () => {
|
|
void router.push({
|
|
name: VIEWS.NEW_WORKFLOW,
|
|
query: {
|
|
projectId: currentProject.value?.id,
|
|
parentFolderId: route.params.folderId
|
|
}
|
|
});
|
|
};
|
|
const renameInput = useTemplateRef("renameInput");
|
|
function onNameToggle() {
|
|
setTimeout(() => {
|
|
if (renameInput.value?.forceFocus) {
|
|
renameInput.value.forceFocus();
|
|
}
|
|
}, 0);
|
|
}
|
|
const onNameSubmit = async (name2) => {
|
|
if (!currentFolder.value || !currentProject.value) return;
|
|
const newName = name2.trim();
|
|
if (!newName) {
|
|
toast.showMessage({
|
|
title: i18n.baseText("renameAction.emptyName.title"),
|
|
message: i18n.baseText("renameAction.emptyName.message"),
|
|
type: "error"
|
|
});
|
|
return;
|
|
}
|
|
if (newName === currentFolder.value.name) {
|
|
renameInput.value?.forceCancel();
|
|
return;
|
|
}
|
|
const validationResult = folderHelpers.validateFolderName(newName);
|
|
if (typeof validationResult === "string") {
|
|
toast.showMessage({
|
|
title: i18n.baseText("renameAction.invalidName.title"),
|
|
message: validationResult,
|
|
type: "error"
|
|
});
|
|
renameInput.value?.forceCancel();
|
|
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
|
|
});
|
|
} catch (error) {
|
|
toast.showError(error, i18n.baseText("folders.rename.error.title"));
|
|
renameInput.value?.forceCancel();
|
|
}
|
|
}
|
|
};
|
|
return (_ctx, _cache) => {
|
|
const _component_N8nTooltip = Tooltip;
|
|
const _component_N8nCallout = N8nCallout;
|
|
const _component_n8n_loading = N8nLoading;
|
|
const _component_FolderBreadcrumbs = __unplugin_components_0;
|
|
const _component_FolderCard = __unplugin_components_4;
|
|
const _component_EmptySharedSectionActionBox = _sfc_main$8;
|
|
const _component_N8nCheckbox = N8nCheckbox;
|
|
const _component_N8nLink = N8nLink;
|
|
const _component_N8nInfoTip = InfoTip;
|
|
const _component_N8nActionBox = N8nActionBox;
|
|
return openBlock(), createBlock(ResourcesListLayout, {
|
|
filters: filters.value,
|
|
"onUpdate:filters": [
|
|
_cache[6] || (_cache[6] = ($event) => filters.value = $event),
|
|
onFiltersUpdated
|
|
],
|
|
"resource-key": "workflows",
|
|
type: "list-paginated",
|
|
resources: workflowListResources.value,
|
|
"type-props": { itemSize: 80 },
|
|
shareable: isShareable.value,
|
|
initialize,
|
|
disabled: readOnlyEnv.value || !projectPermissions.value.workflow.create,
|
|
loading: false,
|
|
"resources-refreshing": loading.value,
|
|
"custom-page-size": unref(DEFAULT_WORKFLOW_PAGE_SIZE),
|
|
"total-items": unref(workflowsStore).totalWorkflowCount,
|
|
"dont-perform-sorting-and-filtering": true,
|
|
"has-empty-state": unref(foldersStore).totalWorkflowCount === 0 && !currentFolderId.value,
|
|
"onClick:add": addWorkflow,
|
|
"onUpdate:search": onSearchUpdated,
|
|
"onUpdate:paginationAndSort": setPaginationAndSort,
|
|
onMouseleave: unref(folderHelpers).resetDropTarget
|
|
}, createSlots({
|
|
header: withCtx(() => [
|
|
createVNode(ProjectHeader, { onCreateFolder: createFolderInCurrent }, {
|
|
default: withCtx(() => [
|
|
showInsights.value ? (openBlock(), createBlock(InsightsSummary, {
|
|
key: 0,
|
|
loading: unref(insightsStore).weeklySummary.isLoading,
|
|
summary: unref(insightsStore).weeklySummary.state,
|
|
"time-range": "week"
|
|
}, null, 8, ["loading", "summary"])) : createCommentVNode("", true)
|
|
]),
|
|
_: 1
|
|
})
|
|
]),
|
|
callout: withCtx(() => [
|
|
showAIStarterCollectionCallout.value ? (openBlock(), createBlock(_component_N8nCallout, {
|
|
key: 0,
|
|
theme: "secondary",
|
|
icon: "gift",
|
|
class: normalizeClass(_ctx.$style["easy-ai-workflow-callout"])
|
|
}, {
|
|
trailingContent: withCtx(() => [
|
|
createBaseVNode("div", {
|
|
class: normalizeClass(_ctx.$style["callout-trailing-content"])
|
|
}, [
|
|
createVNode(unref(N8nButton), {
|
|
"data-test-id": "easy-ai-button",
|
|
size: "small",
|
|
type: "secondary",
|
|
onClick: _cache[0] || (_cache[0] = ($event) => createAIStarterWorkflows("callout"))
|
|
}, {
|
|
default: withCtx(() => [
|
|
createTextVNode(toDisplayString(unref(i18n).baseText("generic.startNow")), 1)
|
|
]),
|
|
_: 1
|
|
}),
|
|
createVNode(unref(N8nIcon), {
|
|
size: "small",
|
|
icon: "x",
|
|
title: unref(i18n).baseText("generic.dismiss"),
|
|
class: "clickable",
|
|
onClick: dismissStarterCollectionCallout
|
|
}, null, 8, ["title"])
|
|
], 2)
|
|
]),
|
|
default: withCtx(() => [
|
|
createTextVNode(toDisplayString(unref(i18n).baseText("workflows.ai.starter.collection.callout")) + " ", 1)
|
|
]),
|
|
_: 1
|
|
}, 8, ["class"])) : showPersonalizedTemplates.value ? (openBlock(), createBlock(SuggestedWorkflows, { key: 1 }, {
|
|
default: withCtx(() => [
|
|
(openBlock(true), createElementBlock(Fragment, null, renderList(unref(personalizedTemplatesStore).suggestedWorkflows, (workflow) => {
|
|
return openBlock(), createBlock(SuggestedWorkflowCard, {
|
|
key: workflow.id,
|
|
"data-test-id": "resource-list-item-suggested-workflow",
|
|
data: {
|
|
id: workflow.id,
|
|
name: workflow.name
|
|
}
|
|
}, null, 8, ["data"]);
|
|
}), 128))
|
|
]),
|
|
_: 1
|
|
})) : createCommentVNode("", true),
|
|
showReadyToRunWorkflowsCallout.value ? (openBlock(), createBlock(_component_N8nCallout, {
|
|
key: 2,
|
|
theme: "secondary",
|
|
icon: "bolt-filled",
|
|
class: normalizeClass(_ctx.$style["easy-ai-workflow-callout"])
|
|
}, {
|
|
trailingContent: withCtx(() => [
|
|
createBaseVNode("div", {
|
|
class: normalizeClass(_ctx.$style["callout-trailing-content"])
|
|
}, [
|
|
createVNode(unref(N8nButton), {
|
|
"data-test-id": "easy-ai-button",
|
|
size: "small",
|
|
type: "secondary",
|
|
onClick: _cache[1] || (_cache[1] = ($event) => handleCreateReadyToRunWorkflows("callout"))
|
|
}, {
|
|
default: withCtx(() => [
|
|
createTextVNode(toDisplayString(unref(i18n).baseText("generic.startNow")), 1)
|
|
]),
|
|
_: 1
|
|
}),
|
|
createVNode(unref(N8nIcon), {
|
|
size: "small",
|
|
icon: "x",
|
|
title: unref(i18n).baseText("generic.dismiss"),
|
|
class: "clickable",
|
|
onClick: handleDismissReadyToRunCallout
|
|
}, null, 8, ["title"])
|
|
], 2)
|
|
]),
|
|
default: withCtx(() => [
|
|
createTextVNode(toDisplayString(unref(readyToRunWorkflowsStore).getCalloutText()) + " ", 1)
|
|
]),
|
|
_: 1
|
|
}, 8, ["class"])) : createCommentVNode("", true)
|
|
]),
|
|
breadcrumbs: withCtx(() => [
|
|
breadcrumbsLoading.value ? (openBlock(), createElementBlock("div", {
|
|
key: 0,
|
|
class: normalizeClass(_ctx.$style["breadcrumbs-loading"])
|
|
}, [
|
|
createVNode(_component_n8n_loading, {
|
|
loading: breadcrumbsLoading.value,
|
|
rows: 1,
|
|
variant: "p"
|
|
}, null, 8, ["loading"])
|
|
], 2)) : showFolders.value && currentFolder.value ? (openBlock(), createElementBlock("div", {
|
|
key: 1,
|
|
class: normalizeClass(_ctx.$style["breadcrumbs-container"]),
|
|
"data-test-id": "main-breadcrumbs"
|
|
}, [
|
|
createVNode(_component_FolderBreadcrumbs, {
|
|
"current-folder": currentFolderParent.value,
|
|
actions: mainBreadcrumbsActions.value,
|
|
"hidden-items-trigger": isDragging.value ? "hover" : "click",
|
|
"current-folder-as-link": true,
|
|
onItemSelected: onBreadcrumbItemClick,
|
|
onAction: onBreadCrumbsAction,
|
|
onItemDrop: onBreadCrumbsItemDrop,
|
|
onProjectDrop: moveFolderToProjectRoot
|
|
}, {
|
|
append: withCtx(() => [
|
|
createBaseVNode("span", {
|
|
class: normalizeClass(_ctx.$style["path-separator"])
|
|
}, "/", 2),
|
|
(openBlock(), createBlock(unref(InlineRename), {
|
|
ref_key: "renameInput",
|
|
ref: renameInput,
|
|
key: currentFolder.value?.id,
|
|
"data-test-id": "breadcrumbs-item-current",
|
|
placeholder: unref(i18n).baseText("folders.rename.placeholder"),
|
|
"model-value": currentFolder.value.name,
|
|
"max-length": 30,
|
|
"read-only": readOnlyEnv.value || !hasPermissionToUpdateFolders.value,
|
|
class: normalizeClass({ [_ctx.$style.name]: true, [_ctx.$style["pointer-disabled"]]: isDragging.value }),
|
|
"onUpdate:modelValue": onNameSubmit
|
|
}, null, 8, ["placeholder", "model-value", "read-only", "class"]))
|
|
]),
|
|
_: 1
|
|
}, 8, ["current-folder", "actions", "hidden-items-trigger"])
|
|
], 2)) : createCommentVNode("", true)
|
|
]),
|
|
item: withCtx(({ item: data, index }) => [
|
|
data.resourceType === "folder" ? (openBlock(), createBlock(Draggable, {
|
|
key: `folder-${index}`,
|
|
disabled: !isDragNDropEnabled.value,
|
|
type: "move",
|
|
"target-data-key": "folder",
|
|
onDragstart: unref(folderHelpers).onDragStart,
|
|
onDragend: unref(folderHelpers).onDragEnd
|
|
}, {
|
|
preview: withCtx(() => [
|
|
createVNode(unref(N8nCard), null, {
|
|
default: withCtx(() => [
|
|
createVNode(unref(N8nText), {
|
|
tag: "h2",
|
|
bold: ""
|
|
}, {
|
|
default: withCtx(() => [
|
|
createTextVNode(toDisplayString(data.name), 1)
|
|
]),
|
|
_: 2
|
|
}, 1024)
|
|
]),
|
|
_: 2
|
|
}, 1024)
|
|
]),
|
|
default: withCtx(() => [
|
|
createVNode(_component_FolderCard, {
|
|
data,
|
|
actions: folderCardActions.value,
|
|
"read-only": readOnlyEnv.value || !hasPermissionToDeleteFolders.value && !hasPermissionToCreateFolders.value,
|
|
"personal-project": personalProject.value,
|
|
"data-resourceid": data.id,
|
|
"data-resourcename": data.name,
|
|
class: normalizeClass([{
|
|
["mb-2xs"]: true,
|
|
[_ctx.$style["drag-active"]]: isDragging.value,
|
|
[_ctx.$style.dragging]: unref(foldersStore).draggedElement?.type === "folder" && unref(foldersStore).draggedElement?.id === data.id,
|
|
[_ctx.$style["drop-active"]]: unref(foldersStore).activeDropTarget?.id === data.id
|
|
}, "mb-2xs"]),
|
|
"show-ownership-badge": showCardsBadge.value,
|
|
"data-target": "folder",
|
|
onAction: onFolderCardAction,
|
|
onMouseenter: unref(folderHelpers).onDragEnter,
|
|
onMouseup: onFolderCardDrop
|
|
}, null, 8, ["data", "actions", "read-only", "personal-project", "data-resourceid", "data-resourcename", "class", "show-ownership-badge", "onMouseenter"])
|
|
]),
|
|
_: 2
|
|
}, 1032, ["disabled", "onDragstart", "onDragend"])) : (openBlock(), createBlock(Draggable, {
|
|
key: `workflow-${index}`,
|
|
disabled: !isDragNDropEnabled.value,
|
|
type: "move",
|
|
"target-data-key": "workflow",
|
|
onDragstart: unref(folderHelpers).onDragStart,
|
|
onDragend: unref(folderHelpers).onDragEnd
|
|
}, {
|
|
preview: withCtx(() => [
|
|
createVNode(unref(N8nCard), null, {
|
|
default: withCtx(() => [
|
|
createVNode(unref(N8nText), {
|
|
tag: "h2",
|
|
bold: ""
|
|
}, {
|
|
default: withCtx(() => [
|
|
createTextVNode(toDisplayString(data.name), 1)
|
|
]),
|
|
_: 2
|
|
}, 1024)
|
|
]),
|
|
_: 2
|
|
}, 1024)
|
|
]),
|
|
default: withCtx(() => [
|
|
createVNode(WorkflowCard, {
|
|
"data-test-id": "resources-list-item-workflow",
|
|
class: normalizeClass({
|
|
["mb-2xs"]: true,
|
|
[_ctx.$style["drag-active"]]: isDragging.value,
|
|
[_ctx.$style.dragging]: unref(foldersStore).draggedElement?.type === "workflow" && unref(foldersStore).draggedElement?.id === data.id
|
|
}),
|
|
data,
|
|
"workflow-list-event-bus": unref(workflowListEventBus),
|
|
"read-only": readOnlyEnv.value,
|
|
"data-resourceid": data.id,
|
|
"data-resourcename": data.name,
|
|
"show-ownership-badge": showCardsBadge.value,
|
|
"data-target": "workflow",
|
|
"onClick:tag": onClickTag,
|
|
"onWorkflow:deleted": refreshWorkflows,
|
|
"onWorkflow:archived": refreshWorkflows,
|
|
"onWorkflow:unarchived": refreshWorkflows,
|
|
"onWorkflow:moved": fetchWorkflows,
|
|
"onWorkflow:duplicated": fetchWorkflows,
|
|
"onWorkflow:activeToggle": onWorkflowActiveToggle,
|
|
"onAction:moveToFolder": moveWorkflowToFolder,
|
|
onMouseenter: _cache[2] || (_cache[2] = ($event) => isDragging.value ? unref(folderHelpers).resetDropTarget() : {})
|
|
}, null, 8, ["class", "data", "workflow-list-event-bus", "read-only", "data-resourceid", "data-resourcename", "show-ownership-badge"])
|
|
]),
|
|
_: 2
|
|
}, 1032, ["disabled", "onDragstart", "onDragend"]))
|
|
]),
|
|
empty: withCtx(() => [
|
|
unref(projectPages).isSharedSubPage && personalProject.value ? (openBlock(), createBlock(_component_EmptySharedSectionActionBox, {
|
|
key: 0,
|
|
"personal-project": personalProject.value,
|
|
"resource-type": "workflows"
|
|
}, null, 8, ["personal-project"])) : (openBlock(), createElementBlock("div", _hoisted_5, [
|
|
createBaseVNode("div", _hoisted_6, [
|
|
createVNode(unref(N8nHeading), {
|
|
tag: "h2",
|
|
size: "xlarge",
|
|
class: "mb-2xs"
|
|
}, {
|
|
default: withCtx(() => [
|
|
createTextVNode(toDisplayString(currentUser.value.firstName ? unref(i18n).baseText("workflows.empty.heading", {
|
|
interpolate: { name: currentUser.value.firstName }
|
|
}) : unref(i18n).baseText("workflows.empty.heading.userNotSetup")), 1)
|
|
]),
|
|
_: 1
|
|
}),
|
|
createVNode(unref(N8nText), {
|
|
size: "large",
|
|
color: "text-base"
|
|
}, {
|
|
default: withCtx(() => [
|
|
createTextVNode(toDisplayString(emptyListDescription.value), 1)
|
|
]),
|
|
_: 1
|
|
})
|
|
]),
|
|
!readOnlyEnv.value && projectPermissions.value.workflow.create ? (openBlock(), createElementBlock("div", {
|
|
key: 0,
|
|
class: normalizeClass(["text-center", "mt-2xl", _ctx.$style.actionsContainer])
|
|
}, [
|
|
createVNode(unref(N8nCard), {
|
|
class: normalizeClass(_ctx.$style.emptyStateCard),
|
|
hoverable: "",
|
|
"data-test-id": "new-workflow-card",
|
|
onClick: addWorkflow
|
|
}, {
|
|
default: withCtx(() => [
|
|
createBaseVNode("div", {
|
|
class: normalizeClass(_ctx.$style.emptyStateCardContent)
|
|
}, [
|
|
createVNode(unref(N8nIcon), {
|
|
class: normalizeClass(_ctx.$style.emptyStateCardIcon),
|
|
icon: "file",
|
|
color: "foreground-dark",
|
|
"stroke-width": 1.5
|
|
}, null, 8, ["class"]),
|
|
createVNode(unref(N8nText), {
|
|
size: "large",
|
|
class: "mt-xs"
|
|
}, {
|
|
default: withCtx(() => [
|
|
createTextVNode(toDisplayString(unref(i18n).baseText("workflows.empty.startFromScratch")), 1)
|
|
]),
|
|
_: 1
|
|
})
|
|
], 2)
|
|
]),
|
|
_: 1
|
|
}, 8, ["class"]),
|
|
showAIStarterCollectionCallout.value ? (openBlock(), createBlock(unref(N8nCard), {
|
|
key: 0,
|
|
class: normalizeClass(_ctx.$style.emptyStateCard),
|
|
hoverable: "",
|
|
"data-test-id": "easy-ai-workflow-card",
|
|
onClick: _cache[3] || (_cache[3] = ($event) => createAIStarterWorkflows("card"))
|
|
}, {
|
|
default: withCtx(() => [
|
|
createBaseVNode("div", {
|
|
class: normalizeClass(_ctx.$style.emptyStateCardContent)
|
|
}, [
|
|
createVNode(unref(N8nIcon), {
|
|
class: normalizeClass(_ctx.$style.emptyStateCardIcon),
|
|
"stroke-width": 1.5,
|
|
icon: "gift",
|
|
color: "foreground-dark"
|
|
}, null, 8, ["class"]),
|
|
createVNode(unref(N8nText), {
|
|
size: "large",
|
|
class: "mt-xs pl-2xs pr-2xs"
|
|
}, {
|
|
default: withCtx(() => [
|
|
createTextVNode(toDisplayString(unref(i18n).baseText("workflows.ai.starter.collection.card")), 1)
|
|
]),
|
|
_: 1
|
|
})
|
|
], 2)
|
|
]),
|
|
_: 1
|
|
}, 8, ["class"])) : showEasyAIWorkflowCallout.value ? (openBlock(), createBlock(unref(N8nCard), {
|
|
key: 1,
|
|
class: normalizeClass(_ctx.$style.emptyStateCard),
|
|
hoverable: "",
|
|
"data-test-id": "easy-ai-workflow-card",
|
|
onClick: _cache[4] || (_cache[4] = ($event) => openAIWorkflow("empty"))
|
|
}, {
|
|
default: withCtx(() => [
|
|
createBaseVNode("div", {
|
|
class: normalizeClass(_ctx.$style.emptyStateCardContent)
|
|
}, [
|
|
createVNode(unref(N8nIcon), {
|
|
class: normalizeClass(_ctx.$style.emptyStateCardIcon),
|
|
"stroke-width": 1.5,
|
|
icon: "bot",
|
|
color: "foreground-dark"
|
|
}, null, 8, ["class"]),
|
|
createVNode(unref(N8nText), {
|
|
size: "large",
|
|
class: "mt-xs pl-2xs pr-2xs"
|
|
}, {
|
|
default: withCtx(() => [
|
|
createTextVNode(toDisplayString(unref(i18n).baseText("workflows.empty.easyAI")), 1)
|
|
]),
|
|
_: 1
|
|
})
|
|
], 2)
|
|
]),
|
|
_: 1
|
|
}, 8, ["class"])) : createCommentVNode("", true),
|
|
showReadyToRunWorkflowsCallout.value ? (openBlock(), createBlock(unref(N8nCard), {
|
|
key: 2,
|
|
class: normalizeClass(_ctx.$style.emptyStateCard),
|
|
hoverable: "",
|
|
"data-test-id": "ready-to-run-workflows-card",
|
|
onClick: _cache[5] || (_cache[5] = ($event) => handleCreateReadyToRunWorkflows("card"))
|
|
}, {
|
|
default: withCtx(() => [
|
|
createBaseVNode("div", {
|
|
class: normalizeClass(_ctx.$style.emptyStateCardContent)
|
|
}, [
|
|
createVNode(unref(N8nIcon), {
|
|
class: normalizeClass(_ctx.$style.emptyStateCardIcon),
|
|
"stroke-width": 1.5,
|
|
icon: "package-open",
|
|
color: "foreground-dark"
|
|
}, null, 8, ["class"]),
|
|
createVNode(unref(N8nText), {
|
|
size: "large",
|
|
class: "mt-xs pl-2xs pr-2xs"
|
|
}, {
|
|
default: withCtx(() => [
|
|
createTextVNode(toDisplayString(unref(readyToRunWorkflowsStore).getCardText()), 1)
|
|
]),
|
|
_: 1
|
|
})
|
|
], 2)
|
|
]),
|
|
_: 1
|
|
}, 8, ["class"])) : createCommentVNode("", true),
|
|
templatesCardEnabled.value ? (openBlock(), createBlock(unref(N8nCard), {
|
|
key: 3,
|
|
class: normalizeClass(_ctx.$style.emptyStateCard),
|
|
hoverable: "",
|
|
"data-test-id": "new-workflow-from-template-card",
|
|
onClick: openTemplatesRepository
|
|
}, {
|
|
default: withCtx(() => [
|
|
createBaseVNode("div", {
|
|
class: normalizeClass(_ctx.$style.emptyStateCardContent)
|
|
}, [
|
|
createVNode(unref(N8nIcon), {
|
|
class: normalizeClass(_ctx.$style.emptyStateCardIcon),
|
|
"stroke-width": 1.5,
|
|
icon: "package-open",
|
|
color: "foreground-dark"
|
|
}, null, 8, ["class"]),
|
|
createVNode(unref(N8nText), {
|
|
size: "large",
|
|
class: "mt-xs pl-2xs pr-2xs"
|
|
}, {
|
|
default: withCtx(() => [
|
|
createTextVNode(toDisplayString(unref(i18n).baseText("workflows.empty.startWithTemplate")), 1)
|
|
]),
|
|
_: 1
|
|
})
|
|
], 2)
|
|
]),
|
|
_: 1
|
|
}, 8, ["class"])) : createCommentVNode("", true)
|
|
], 2)) : createCommentVNode("", true),
|
|
showTemplateRecommendationV2.value ? (openBlock(), createBlock(TemplateRecommendationV2, { key: 1 })) : createCommentVNode("", true)
|
|
]))
|
|
]),
|
|
filters: withCtx(({ setKeyValue }) => [
|
|
unref(settingsStore).areTagsEnabled ? (openBlock(), createElementBlock("div", _hoisted_7, [
|
|
createVNode(unref(N8nInputLabel), {
|
|
label: unref(i18n).baseText("workflows.filters.tags"),
|
|
bold: false,
|
|
size: "small",
|
|
color: "text-base",
|
|
class: "mb-3xs"
|
|
}, null, 8, ["label"]),
|
|
createVNode(_sfc_main$9, {
|
|
placeholder: unref(i18n).baseText("workflowOpen.filterWorkflows"),
|
|
"model-value": filters.value.tags,
|
|
"create-enabled": false,
|
|
"onUpdate:modelValue": ($event) => setKeyValue("tags", $event)
|
|
}, null, 8, ["placeholder", "model-value", "onUpdate:modelValue"])
|
|
])) : createCommentVNode("", true),
|
|
createBaseVNode("div", _hoisted_8, [
|
|
createVNode(unref(N8nInputLabel), {
|
|
label: unref(i18n).baseText("workflows.filters.status"),
|
|
bold: false,
|
|
size: "small",
|
|
color: "text-base",
|
|
class: "mb-3xs"
|
|
}, null, 8, ["label"]),
|
|
createVNode(unref(N8nSelect), {
|
|
"data-test-id": "status-dropdown",
|
|
"model-value": filters.value.status,
|
|
"onUpdate:modelValue": ($event) => setKeyValue("status", $event)
|
|
}, {
|
|
default: withCtx(() => [
|
|
(openBlock(true), createElementBlock(Fragment, null, renderList(statusFilterOptions.value, (option) => {
|
|
return openBlock(), createBlock(unref(_sfc_main$a), {
|
|
key: option.label,
|
|
label: option.label,
|
|
value: option.value,
|
|
"data-test-id": "status"
|
|
}, null, 8, ["label", "value"]);
|
|
}), 128))
|
|
]),
|
|
_: 2
|
|
}, 1032, ["model-value", "onUpdate:modelValue"])
|
|
]),
|
|
createBaseVNode("div", _hoisted_9, [
|
|
createVNode(_component_N8nCheckbox, {
|
|
label: unref(i18n).baseText("workflows.filters.showArchived"),
|
|
"model-value": filters.value.showArchived || false,
|
|
"data-test-id": "show-archived-checkbox",
|
|
"onUpdate:modelValue": ($event) => setKeyValue("showArchived", $event)
|
|
}, null, 8, ["label", "model-value", "onUpdate:modelValue"])
|
|
])
|
|
]),
|
|
postamble: withCtx(() => [
|
|
workflowsAndFolders.value.length === 0 && !hasFilters.value ? (openBlock(), createElementBlock("div", {
|
|
key: 0,
|
|
class: normalizeClass(_ctx.$style["empty-folder-container"]),
|
|
"data-test-id": "empty-folder-container"
|
|
}, [
|
|
showArchivedOnlyHint.value ? (openBlock(), createBlock(_component_N8nInfoTip, {
|
|
key: 0,
|
|
bold: false
|
|
}, {
|
|
default: withCtx(() => [
|
|
createTextVNode(toDisplayString(unref(i18n).baseText("workflows.archivedOnly.hint")) + " ", 1),
|
|
createVNode(_component_N8nLink, {
|
|
size: "small",
|
|
"data-test-id": "show-archived-link",
|
|
onClick: onShowArchived
|
|
}, {
|
|
default: withCtx(() => [
|
|
createTextVNode(toDisplayString(unref(i18n).baseText("workflows.archivedOnly.hint.link")), 1)
|
|
]),
|
|
_: 1
|
|
})
|
|
]),
|
|
_: 1
|
|
})) : createCommentVNode("", true),
|
|
unref(projectPages).isSharedSubPage && personalProject.value ? (openBlock(), createBlock(_component_EmptySharedSectionActionBox, {
|
|
key: 1,
|
|
"personal-project": personalProject.value,
|
|
"resource-type": "workflows"
|
|
}, null, 8, ["personal-project"])) : currentFolder.value ? (openBlock(), createBlock(_component_N8nActionBox, {
|
|
key: 2,
|
|
"data-test-id": "empty-folder-action-box",
|
|
heading: unref(i18n).baseText("folders.empty.actionbox.title", {
|
|
interpolate: { folderName: currentFolder.value.name }
|
|
}),
|
|
"button-text": unref(i18n).baseText("generic.create.workflow"),
|
|
"button-type": "secondary",
|
|
"button-disabled": readOnlyEnv.value || !projectPermissions.value.workflow.create,
|
|
"onClick:button": onCreateWorkflowClick
|
|
}, {
|
|
disabledButtonTooltip: withCtx(() => [
|
|
createTextVNode(toDisplayString(readOnlyEnv.value ? unref(i18n).baseText("readOnlyEnv.cantAdd.workflow") : unref(i18n).baseText("generic.missing.permissions")), 1)
|
|
]),
|
|
_: 1
|
|
}, 8, ["heading", "button-text", "button-disabled"])) : createCommentVNode("", true)
|
|
], 2)) : createCommentVNode("", true)
|
|
]),
|
|
_: 2
|
|
}, [
|
|
foldersEnabled.value || showRegisteredCommunityCTA.value ? {
|
|
name: "add-button",
|
|
fn: withCtx(() => [
|
|
createVNode(_component_N8nTooltip, {
|
|
placement: "top",
|
|
disabled: !(unref(projectPages).isOverviewSubPage || unref(projectPages).isSharedSubPage || !readOnlyEnv.value && hasPermissionToCreateFolders.value)
|
|
}, {
|
|
content: withCtx(() => [
|
|
(unref(projectPages).isOverviewSubPage || unref(projectPages).isSharedSubPage) && !showRegisteredCommunityCTA.value ? (openBlock(), createElementBlock("span", _hoisted_1, [
|
|
teamProjectsEnabled.value ? (openBlock(), createElementBlock("span", _hoisted_2, toDisplayString(unref(i18n).baseText("folders.add.overview.withProjects.message")), 1)) : (openBlock(), createElementBlock("span", _hoisted_3, toDisplayString(unref(i18n).baseText("folders.add.overview.community.message")), 1))
|
|
])) : (openBlock(), createElementBlock("span", _hoisted_4, toDisplayString(currentParentName.value ? unref(i18n).baseText("folders.add.to.parent.message", {
|
|
interpolate: { parent: currentParentName.value }
|
|
}) : unref(i18n).baseText("folders.add.here.message")), 1))
|
|
]),
|
|
default: withCtx(() => [
|
|
createVNode(unref(N8nButton), {
|
|
size: "small",
|
|
icon: "folder-plus",
|
|
type: "tertiary",
|
|
"data-test-id": "add-folder-button",
|
|
class: normalizeClass(_ctx.$style["add-folder-button"]),
|
|
disabled: !showRegisteredCommunityCTA.value && (readOnlyEnv.value || !hasPermissionToCreateFolders.value),
|
|
onClick: createFolderInCurrent
|
|
}, null, 8, ["class", "disabled"])
|
|
]),
|
|
_: 1
|
|
}, 8, ["disabled"])
|
|
]),
|
|
key: "0"
|
|
} : void 0
|
|
]), 1032, ["filters", "resources", "shareable", "disabled", "resources-refreshing", "custom-page-size", "total-items", "has-empty-state", "onMouseleave"]);
|
|
};
|
|
}
|
|
});
|
|
const actionsContainer = "_actionsContainer_1puyr_123";
|
|
const emptyStateCard = "_emptyStateCard_1puyr_139";
|
|
const emptyStateCardContent = "_emptyStateCardContent_1puyr_152";
|
|
const emptyStateCardIcon = "_emptyStateCardIcon_1puyr_159";
|
|
const dragging = "_dragging_1puyr_192";
|
|
const name = "_name_1puyr_210";
|
|
const style0 = {
|
|
actionsContainer,
|
|
"easy-ai-workflow-callout": "_easy-ai-workflow-callout_1puyr_128",
|
|
"callout-trailing-content": "_callout-trailing-content_1puyr_133",
|
|
emptyStateCard,
|
|
emptyStateCardContent,
|
|
emptyStateCardIcon,
|
|
"add-folder-button": "_add-folder-button_1puyr_166",
|
|
"breadcrumbs-container": "_breadcrumbs-container_1puyr_171",
|
|
"breadcrumbs-loading": "_breadcrumbs-loading_1puyr_177",
|
|
"empty-folder-container": "_empty-folder-container_1puyr_183",
|
|
"drag-active": "_drag-active_1puyr_187",
|
|
dragging,
|
|
"drop-active": "_drop-active_1puyr_199",
|
|
"path-separator": "_path-separator_1puyr_204",
|
|
name,
|
|
"pointer-disabled": "_pointer-disabled_1puyr_216"
|
|
};
|
|
const cssModules = {
|
|
"$style": style0
|
|
};
|
|
const WorkflowsView = /* @__PURE__ */ _export_sfc(_sfc_main, [["__cssModules", cssModules]]);
|
|
export {
|
|
WorkflowsView as default
|
|
};
|