mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-17 18:12:04 +00:00
fix(editor): Addressing internal testing feedback for folders (no-changelog) (#13997)
This commit is contained in:
committed by
GitHub
parent
305ea0fb32
commit
1f56a24bbd
@@ -15,6 +15,7 @@ const props = defineProps<{
|
||||
modalName: string;
|
||||
data?: {
|
||||
closeCallback?: () => void;
|
||||
customHeading?: string;
|
||||
};
|
||||
}>();
|
||||
|
||||
@@ -89,7 +90,7 @@ const confirm = async () => {
|
||||
<N8nBadge>{{ i18n.baseText('communityPlusModal.badge') }}</N8nBadge>
|
||||
</p>
|
||||
<N8nText tag="h1" align="center" size="xlarge" class="mb-m">{{
|
||||
i18n.baseText('communityPlusModal.title')
|
||||
data?.customHeading ?? i18n.baseText('communityPlusModal.title')
|
||||
}}</N8nText>
|
||||
<N8nText tag="p">{{ i18n.baseText('communityPlusModal.description') }}</N8nText>
|
||||
<ul :class="$style.features">
|
||||
@@ -114,6 +115,13 @@ const confirm = async () => {
|
||||
{{ i18n.baseText('communityPlusModal.features.third.description') }}
|
||||
</N8nText>
|
||||
</li>
|
||||
<li>
|
||||
<i> 📁</i>
|
||||
<N8nText>
|
||||
<strong>{{ i18n.baseText('communityPlusModal.features.fourth.title') }}</strong>
|
||||
{{ i18n.baseText('communityPlusModal.features.fourth.description') }}
|
||||
</N8nText>
|
||||
</li>
|
||||
</ul>
|
||||
<N8nFormInput
|
||||
id="email"
|
||||
|
||||
@@ -6,8 +6,8 @@ import { createEventBus, type EventBus } from '@n8n/utils/event-bus';
|
||||
import { useI18n } from '@/composables/useI18n';
|
||||
import { useFoldersStore } from '@/stores/folders.store';
|
||||
import { useRoute } from 'vue-router';
|
||||
import type { FolderListItem } from '@/Interface';
|
||||
import { useProjectsStore } from '@/stores/projects.store';
|
||||
import { ProjectTypes } from '@/types/projects.types';
|
||||
|
||||
const props = defineProps<{
|
||||
modalName: string;
|
||||
@@ -32,12 +32,7 @@ const projectsStore = useProjectsStore();
|
||||
const loading = ref(false);
|
||||
const operation = ref('');
|
||||
const deleteConfirmText = ref('');
|
||||
const selectedFolder = ref<{ id: string; name: string } | null>(null);
|
||||
const projectFolders = ref<FolderListItem[]>([]);
|
||||
|
||||
const currentFolder = computed(() => {
|
||||
return projectFolders.value.find((folder) => folder.id === props.activeId);
|
||||
});
|
||||
const selectedFolder = ref<{ id: string; name: string; type: 'folder' | 'project' } | null>(null);
|
||||
|
||||
const folderToDelete = computed(() => {
|
||||
if (!props.activeId) return null;
|
||||
@@ -72,6 +67,14 @@ const enabled = computed(() => {
|
||||
return false;
|
||||
});
|
||||
|
||||
const currentProjectName = computed(() => {
|
||||
const currentProject = projectsStore.currentProject;
|
||||
if (currentProject?.type === ProjectTypes.Personal) {
|
||||
return i18n.baseText('projects.menu.personal');
|
||||
}
|
||||
return currentProject?.name;
|
||||
});
|
||||
|
||||
const folderContentWarningMessage = computed(() => {
|
||||
const folderCount = props.data.content.subFolderCount ?? 0;
|
||||
const workflowCount = props.data.content.workflowCount ?? 0;
|
||||
@@ -102,11 +105,10 @@ async function onSubmit() {
|
||||
try {
|
||||
loading.value = true;
|
||||
|
||||
await foldersStore.deleteFolder(
|
||||
route.params.projectId as string,
|
||||
props.activeId,
|
||||
selectedFolder.value?.id ?? undefined,
|
||||
);
|
||||
const newParentId =
|
||||
selectedFolder.value?.type === 'project' ? '0' : (selectedFolder.value?.id ?? undefined);
|
||||
|
||||
await foldersStore.deleteFolder(route.params.projectId as string, props.activeId, newParentId);
|
||||
|
||||
let message = '';
|
||||
if (selectedFolder.value) {
|
||||
@@ -132,7 +134,7 @@ async function onSubmit() {
|
||||
}
|
||||
}
|
||||
|
||||
const onFolderSelected = (payload: { id: string; name: string }) => {
|
||||
const onFolderSelected = (payload: { id: string; name: string; type: 'folder' | 'project' }) => {
|
||||
selectedFolder.value = payload;
|
||||
};
|
||||
</script>
|
||||
@@ -142,7 +144,7 @@ const onFolderSelected = (payload: { id: string; name: string }) => {
|
||||
:name="modalName"
|
||||
:title="title"
|
||||
:center="true"
|
||||
width="520"
|
||||
width="600"
|
||||
:event-bus="modalBus"
|
||||
@enter="onSubmit"
|
||||
>
|
||||
@@ -163,7 +165,14 @@ const onFolderSelected = (payload: { id: string; name: string }) => {
|
||||
label="transfer"
|
||||
@update:model-value="operation = 'transfer'"
|
||||
>
|
||||
<n8n-text color="text-dark">{{ i18n.baseText('folders.transfer.action') }}</n8n-text>
|
||||
<n8n-text v-if="currentProjectName">{{
|
||||
i18n.baseText('folders.transfer.action', {
|
||||
interpolate: { projectName: currentProjectName },
|
||||
})
|
||||
}}</n8n-text>
|
||||
<n8n-text v-else color="text-dark">{{
|
||||
i18n.baseText('folders.transfer.action.noProject')
|
||||
}}</n8n-text>
|
||||
</el-radio>
|
||||
<div v-if="operation === 'transfer'" :class="$style.optionInput">
|
||||
<n8n-text color="text-dark">{{
|
||||
@@ -173,8 +182,8 @@ const onFolderSelected = (payload: { id: string; name: string }) => {
|
||||
v-if="projectsStore.currentProject"
|
||||
:current-folder-id="props.activeId"
|
||||
:current-project-id="projectsStore.currentProject?.id"
|
||||
:parent-folder-id="currentFolder?.parentFolder?.id"
|
||||
@folder:selected="onFolderSelected"
|
||||
:parent-folder-id="folderToDelete?.parentFolder"
|
||||
@location:selected="onFolderSelected"
|
||||
/>
|
||||
</div>
|
||||
<el-radio
|
||||
|
||||
@@ -65,6 +65,7 @@ const onAction = (action: string) => {
|
||||
<n8n-action-toggle
|
||||
v-if="breadcrumbs.visibleItems"
|
||||
:actions="actions"
|
||||
:class="$style['action-toggle']"
|
||||
theme="dark"
|
||||
data-test-id="folder-breadcrumbs-actions"
|
||||
@action="onAction"
|
||||
@@ -78,6 +79,12 @@ const onAction = (action: string) => {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.action-toggle {
|
||||
span[role='button'] {
|
||||
color: var(--color-text-base);
|
||||
}
|
||||
}
|
||||
|
||||
.home-project {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import { useI18n } from '@/composables/useI18n';
|
||||
import type { FolderListItem } from '@/Interface';
|
||||
import type { ChangeLocationSearchResult } from '@/Interface';
|
||||
import { useFoldersStore } from '@/stores/folders.store';
|
||||
import { useProjectsStore } from '@/stores/projects.store';
|
||||
import { type ProjectIcon as ItemProjectIcon, ProjectTypes } from '@/types/projects.types';
|
||||
import { N8nSelect } from '@n8n/design-system';
|
||||
import { ref } from 'vue';
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
/**
|
||||
* This component is used to select a folder to move a resource (folder or workflow) to.
|
||||
@@ -24,21 +26,43 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
'folder:selected': [value: { id: string; name: string }];
|
||||
'location:selected': [value: { id: string; name: string; type: 'folder' | 'project' }];
|
||||
}>();
|
||||
|
||||
const i18n = useI18n();
|
||||
|
||||
const foldersStore = useFoldersStore();
|
||||
const projectsStore = useProjectsStore();
|
||||
|
||||
const moveFolderDropdown = ref<InstanceType<typeof N8nSelect>>();
|
||||
const selectedFolderId = ref<string | null>(null);
|
||||
const availableFolders = ref<FolderListItem[]>([]);
|
||||
const availableLocations = ref<ChangeLocationSearchResult[]>([]);
|
||||
const loading = ref(false);
|
||||
|
||||
const fetchAvailableFolders = async (query?: string) => {
|
||||
const currentProject = computed(() => {
|
||||
return projectsStore.currentProject;
|
||||
});
|
||||
|
||||
const projectName = computed(() => {
|
||||
if (currentProject.value?.type === ProjectTypes.Personal) {
|
||||
return i18n.baseText('projects.menu.personal');
|
||||
}
|
||||
return currentProject.value?.name;
|
||||
});
|
||||
|
||||
const projectIcon = computed<ItemProjectIcon>(() => {
|
||||
const defaultIcon: ItemProjectIcon = { type: 'icon', value: 'layer-group' };
|
||||
if (currentProject.value?.type === ProjectTypes.Personal) {
|
||||
return { type: 'icon', value: 'user' };
|
||||
} else if (currentProject.value?.type === ProjectTypes.Team) {
|
||||
return currentProject.value.icon ?? defaultIcon;
|
||||
}
|
||||
return defaultIcon;
|
||||
});
|
||||
|
||||
const fetchAvailableLocations = async (query?: string) => {
|
||||
if (!query) {
|
||||
availableFolders.value = [];
|
||||
availableLocations.value = [];
|
||||
return;
|
||||
}
|
||||
loading.value = true;
|
||||
@@ -48,19 +72,41 @@ const fetchAvailableFolders = async (query?: string) => {
|
||||
{ name: query ?? undefined },
|
||||
);
|
||||
if (!props.parentFolderId) {
|
||||
availableFolders.value = folders;
|
||||
availableLocations.value = folders;
|
||||
} else {
|
||||
availableFolders.value = folders.filter((folder) => folder.id !== props.parentFolderId);
|
||||
availableLocations.value = folders.filter((folder) => folder.id !== props.parentFolderId);
|
||||
}
|
||||
// Finally add project root if project name contains query (only if folder is not already in root)
|
||||
if (
|
||||
projectName.value &&
|
||||
projectName.value.toLowerCase().includes(query.toLowerCase()) &&
|
||||
props.parentFolderId !== ''
|
||||
) {
|
||||
availableLocations.value.unshift({
|
||||
id: props.currentProjectId,
|
||||
name: i18n.baseText('folders.move.project.root.name', {
|
||||
interpolate: { projectName: projectName.value },
|
||||
}),
|
||||
resource: 'project',
|
||||
createdAt: '',
|
||||
updatedAt: '',
|
||||
workflowCount: 0,
|
||||
subFolderCount: 0,
|
||||
});
|
||||
}
|
||||
loading.value = false;
|
||||
};
|
||||
|
||||
const onFolderSelected = (folderId: string) => {
|
||||
const selectedFolder = availableFolders.value.find((folder) => folder.id === folderId);
|
||||
const selectedFolder = availableLocations.value.find((folder) => folder.id === folderId);
|
||||
if (!selectedFolder) {
|
||||
return;
|
||||
}
|
||||
emit('folder:selected', { id: folderId, name: selectedFolder.name });
|
||||
emit('location:selected', {
|
||||
id: folderId,
|
||||
name: selectedFolder.name,
|
||||
type: selectedFolder.resource,
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -71,22 +117,35 @@ const onFolderSelected = (folderId: string) => {
|
||||
v-model="selectedFolderId"
|
||||
:filterable="true"
|
||||
:remote="true"
|
||||
:remote-method="fetchAvailableFolders"
|
||||
:remote-method="fetchAvailableLocations"
|
||||
:loading="loading"
|
||||
:placeholder="i18n.baseText('folders.move.modal.select.placeholder')"
|
||||
:no-data-text="i18n.baseText('folders.move.modal.no.data.label')"
|
||||
option-label="name"
|
||||
option-value="id"
|
||||
@update:model-value="onFolderSelected"
|
||||
>
|
||||
<template #prefix>
|
||||
<N8nIcon icon="search" />
|
||||
</template>
|
||||
<N8nOption
|
||||
v-for="folder in availableFolders"
|
||||
:key="folder.id"
|
||||
:value="folder.id"
|
||||
:label="folder.name"
|
||||
v-for="location in availableLocations"
|
||||
:key="location.id"
|
||||
:value="location.id"
|
||||
:label="location.name"
|
||||
data-test-id="move-to-folder-option"
|
||||
>
|
||||
<div :class="$style['folder-select-item']">
|
||||
<n8n-icon :class="$style['folder-icon']" icon="folder" />
|
||||
<span :class="$style['folder-name']"> {{ folder.name }}</span>
|
||||
<ProjectIcon
|
||||
v-if="location.resource === 'project' && currentProject"
|
||||
:class="$style['folder-icon']"
|
||||
:icon="projectIcon"
|
||||
:border-less="true"
|
||||
size="mini"
|
||||
color="text-dark"
|
||||
/>
|
||||
<n8n-icon v-else :class="$style['folder-icon']" icon="folder" />
|
||||
<span :class="$style['folder-name']"> {{ location.name }}</span>
|
||||
</div>
|
||||
</N8nOption>
|
||||
</N8nSelect>
|
||||
|
||||
@@ -48,7 +48,7 @@ const currentFolder = computed(() => {
|
||||
};
|
||||
});
|
||||
|
||||
const onFolderSelected = (payload: { id: string; name: string }) => {
|
||||
const onFolderSelected = (payload: { id: string; name: string; type: string }) => {
|
||||
selectedFolder.value = payload;
|
||||
};
|
||||
|
||||
@@ -81,7 +81,7 @@ const onSubmit = () => {
|
||||
:current-project-id="projectsStore.currentProject?.id"
|
||||
:parent-folder-id="props.data.resource.parentFolderId"
|
||||
:exclude-only-parent="props.data.resourceType === 'workflow'"
|
||||
@folder:selected="onFolderSelected"
|
||||
@location:selected="onFolderSelected"
|
||||
/>
|
||||
<p
|
||||
v-if="props.data.resourceType === 'folder'"
|
||||
|
||||
@@ -55,6 +55,7 @@ import { useTelemetry } from '@/composables/useTelemetry';
|
||||
import type { BaseTextKey } from '@/plugins/i18n';
|
||||
import { useNpsSurveyStore } from '@/stores/npsSurvey.store';
|
||||
import { usePageRedirectionHelper } from '@/composables/usePageRedirectionHelper';
|
||||
import { ProjectTypes } from '@/types/projects.types';
|
||||
|
||||
const props = defineProps<{
|
||||
readOnly?: boolean;
|
||||
@@ -200,6 +201,26 @@ const workflowTagIds = computed(() => {
|
||||
return (props.tags ?? []).map((tag) => (typeof tag === 'string' ? tag : tag.id));
|
||||
});
|
||||
|
||||
const currentFolder = computed(() => {
|
||||
if (props.id === PLACEHOLDER_EMPTY_WORKFLOW_ID) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const workflow = workflowsStore.getWorkflowById(props.id);
|
||||
if (!workflow) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return workflow.parentFolder;
|
||||
});
|
||||
|
||||
const currentProjectName = computed(() => {
|
||||
if (projectsStore.currentProject?.type === ProjectTypes.Personal) {
|
||||
return locale.baseText('projects.menu.personal');
|
||||
}
|
||||
return projectsStore.currentProject?.name;
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.id,
|
||||
() => {
|
||||
@@ -533,16 +554,22 @@ function showCreateWorkflowSuccessToast(id?: string) {
|
||||
let toastTitle = locale.baseText('workflows.create.personal.toast.title');
|
||||
let toastText = locale.baseText('workflows.create.personal.toast.text');
|
||||
|
||||
if (
|
||||
projectsStore.currentProject &&
|
||||
projectsStore.currentProject.id !== projectsStore.personalProject?.id
|
||||
) {
|
||||
toastTitle = locale.baseText('workflows.create.project.toast.title', {
|
||||
interpolate: { projectName: projectsStore.currentProject.name ?? '' },
|
||||
});
|
||||
if (projectsStore.currentProject) {
|
||||
if (currentFolder.value) {
|
||||
toastTitle = locale.baseText('workflows.create.folder.toast.title', {
|
||||
interpolate: {
|
||||
projectName: currentProjectName.value ?? '',
|
||||
folderName: currentFolder.value.name ?? '',
|
||||
},
|
||||
});
|
||||
} else if (projectsStore.currentProject.id !== projectsStore.personalProject?.id) {
|
||||
toastTitle = locale.baseText('workflows.create.project.toast.title', {
|
||||
interpolate: { projectName: currentProjectName.value ?? '' },
|
||||
});
|
||||
}
|
||||
|
||||
toastText = locale.baseText('workflows.create.project.toast.text', {
|
||||
interpolate: { projectName: projectsStore.currentProject.name ?? '' },
|
||||
interpolate: { projectName: currentProjectName.value ?? '' },
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -567,6 +567,7 @@ const closeDialog = () => {
|
||||
name: COMMUNITY_PLUS_ENROLLMENT_MODAL,
|
||||
data: {
|
||||
closeCallback,
|
||||
customHeading: undefined,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
|
||||
@@ -56,6 +56,7 @@ const showSettings = computed(
|
||||
);
|
||||
|
||||
const homeProject = computed(() => projectsStore.currentProject ?? projectsStore.personalProject);
|
||||
|
||||
const showFolders = computed(() => {
|
||||
return settingsStore.isFoldersFeatureEnabled && route.name !== VIEWS.WORKFLOWS;
|
||||
});
|
||||
@@ -189,7 +190,7 @@ const onSelect = (action: string) => {
|
||||
}
|
||||
|
||||
.actions {
|
||||
padding: var(--spacing-2xs) 0 var(--spacing-l);
|
||||
padding: var(--spacing-2xs) 0 var(--spacing-xs);
|
||||
}
|
||||
|
||||
@include mixins.breakpoint('xs-only') {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import { computed, ref } from 'vue';
|
||||
import {
|
||||
DUPLICATE_MODAL_KEY,
|
||||
MODAL_CONFIRM,
|
||||
@@ -26,6 +26,9 @@ import { ResourceType } from '@/utils/projects.utils';
|
||||
import type { EventBus } from '@n8n/utils/event-bus';
|
||||
import type { WorkflowResource } from './layouts/ResourcesListLayout.vue';
|
||||
import type { IUser } from 'n8n-workflow';
|
||||
import { type ProjectIcon as CardProjectIcon, ProjectTypes } from '@/types/projects.types';
|
||||
import type { PathItem } from '@n8n/design-system/components/N8nBreadcrumbs/Breadcrumbs.vue';
|
||||
import { useFoldersStore } from '@/stores/folders.store';
|
||||
|
||||
const WORKFLOW_LIST_ITEM_ACTIONS = {
|
||||
OPEN: 'open',
|
||||
@@ -68,15 +71,56 @@ const uiStore = useUIStore();
|
||||
const usersStore = useUsersStore();
|
||||
const workflowsStore = useWorkflowsStore();
|
||||
const projectsStore = useProjectsStore();
|
||||
const foldersStore = useFoldersStore();
|
||||
|
||||
const hiddenBreadcrumbsItemsAsync = ref<Promise<PathItem[]>>(new Promise(() => {}));
|
||||
|
||||
const resourceTypeLabel = computed(() => locale.baseText('generic.workflow').toLowerCase());
|
||||
const currentUser = computed(() => usersStore.currentUser ?? ({} as IUser));
|
||||
const workflowPermissions = computed(() => getResourcePermissions(props.data.scopes).workflow);
|
||||
const isOverviewPage = computed(() => route.name === VIEWS.WORKFLOWS);
|
||||
|
||||
const showFolders = computed(() => {
|
||||
return settingsStore.isFoldersFeatureEnabled && route.name !== VIEWS.WORKFLOWS;
|
||||
});
|
||||
|
||||
const projectIcon = computed<CardProjectIcon>(() => {
|
||||
const defaultIcon: CardProjectIcon = { type: 'icon', value: 'layer-group' };
|
||||
if (props.data.homeProject?.type === ProjectTypes.Personal) {
|
||||
return { type: 'icon', value: 'user' };
|
||||
} else if (props.data.homeProject?.type === ProjectTypes.Team) {
|
||||
return props.data.homeProject.icon ?? defaultIcon;
|
||||
}
|
||||
return defaultIcon;
|
||||
});
|
||||
|
||||
const projectName = computed(() => {
|
||||
if (props.data.homeProject?.type === ProjectTypes.Personal) {
|
||||
return locale.baseText('projects.menu.personal');
|
||||
}
|
||||
return props.data.homeProject?.name;
|
||||
});
|
||||
|
||||
const cardBreadcrumbs = computed<PathItem[]>(() => {
|
||||
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 = [
|
||||
{
|
||||
@@ -236,6 +280,17 @@ async function deleteWorkflow() {
|
||||
emit('workflow:deleted');
|
||||
}
|
||||
|
||||
const fetchHiddenBreadCrumbsItems = async () => {
|
||||
if (!props.data.homeProject?.id || !projectName.value || !props.data.parentFolder) {
|
||||
hiddenBreadcrumbsItemsAsync.value = Promise.resolve([]);
|
||||
} else {
|
||||
hiddenBreadcrumbsItemsAsync.value = foldersStore.getHiddenBreadcrumbsItems(
|
||||
{ id: props.data.homeProject.id, name: projectName.value },
|
||||
props.data.parentFolder.id,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
function moveResource() {
|
||||
uiStore.openModalWithData({
|
||||
name: PROJECT_MOVE_RESOURCE_MODAL,
|
||||
@@ -251,6 +306,12 @@ function moveResource() {
|
||||
const emitWorkflowActiveToggle = (value: { id: string; active: boolean }) => {
|
||||
emit('workflow:active-toggle', value);
|
||||
};
|
||||
|
||||
const onBreadcrumbItemClick = async (item: PathItem) => {
|
||||
if (item.href) {
|
||||
await router.push(item.href);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -289,7 +350,33 @@ const emitWorkflowActiveToggle = (value: { id: string; active: boolean }) => {
|
||||
</div>
|
||||
<template #append>
|
||||
<div :class="$style.cardActions" @click.stop>
|
||||
<div v-if="isOverviewPage" :class="$style.breadcrumbs">
|
||||
<n8n-breadcrumbs
|
||||
:items="cardBreadcrumbs"
|
||||
:hidden-items="hiddenBreadcrumbsItemsAsync"
|
||||
:path-truncated="true"
|
||||
:show-border="true"
|
||||
:highlight-last-item="false"
|
||||
hidden-items-trigger="hover"
|
||||
theme="small"
|
||||
data-test-id="workflow-card-breadcrumbs"
|
||||
@tooltip-opened="fetchHiddenBreadCrumbsItems"
|
||||
@item-selected="onBreadcrumbItemClick"
|
||||
>
|
||||
<template v-if="data.homeProject" #prepend>
|
||||
<div :class="$style['home-project']">
|
||||
<n8n-link :to="`/projects/${data.homeProject.id}`">
|
||||
<ProjectIcon :icon="projectIcon" :border-less="true" size="mini" />
|
||||
<n8n-text size="small" :compact="true" :bold="true" color="text-base">{{
|
||||
projectName
|
||||
}}</n8n-text>
|
||||
</n8n-link>
|
||||
</div>
|
||||
</template>
|
||||
</n8n-breadcrumbs>
|
||||
</div>
|
||||
<ProjectCardBadge
|
||||
v-else
|
||||
:class="$style.cardBadge"
|
||||
:resource="data"
|
||||
:resource-type="ResourceType.Workflow"
|
||||
|
||||
@@ -113,6 +113,7 @@ onBeforeMount(async () => {
|
||||
<n8n-button
|
||||
icon="filter"
|
||||
type="tertiary"
|
||||
size="small"
|
||||
:active="hasFilters"
|
||||
:class="{
|
||||
[$style['filter-button']]: true,
|
||||
@@ -165,7 +166,8 @@ onBeforeMount(async () => {
|
||||
|
||||
<style lang="scss" module>
|
||||
.filter-button {
|
||||
height: 40px;
|
||||
height: 30px;
|
||||
width: 30px;
|
||||
align-items: center;
|
||||
|
||||
&.no-label {
|
||||
|
||||
@@ -543,6 +543,7 @@ const loadPaginationFromQueryString = async () => {
|
||||
:model-value="filtersModel.search"
|
||||
:class="$style.search"
|
||||
:placeholder="i18n.baseText(`${resourceKey}.search.placeholder` as BaseTextKey)"
|
||||
size="small"
|
||||
clearable
|
||||
data-test-id="resources-list-search"
|
||||
@update:model-value="onSearch"
|
||||
@@ -552,7 +553,7 @@ const loadPaginationFromQueryString = async () => {
|
||||
</template>
|
||||
</n8n-input>
|
||||
<div :class="$style['sort-and-filter']">
|
||||
<n8n-select v-model="sortBy" data-test-id="resources-list-sort">
|
||||
<n8n-select v-model="sortBy" size="small" data-test-id="resources-list-sort">
|
||||
<n8n-option
|
||||
v-for="sortOption in sortOptions"
|
||||
:key="sortOption"
|
||||
@@ -660,7 +661,12 @@ const loadPaginationFromQueryString = async () => {
|
||||
</n8n-datatable>
|
||||
</div>
|
||||
|
||||
<n8n-text v-else color="text-base" size="medium" data-test-id="resources-list-empty">
|
||||
<n8n-text
|
||||
v-else-if="hasAppliedFilters() || filtersModel.search !== ''"
|
||||
color="text-base"
|
||||
size="medium"
|
||||
data-test-id="resources-list-empty"
|
||||
>
|
||||
{{ i18n.baseText(`${resourceKey}.noResults` as BaseTextKey) }}
|
||||
</n8n-text>
|
||||
|
||||
@@ -684,14 +690,14 @@ const loadPaginationFromQueryString = async () => {
|
||||
display: grid;
|
||||
grid-auto-flow: column;
|
||||
grid-auto-columns: 1fr max-content max-content max-content;
|
||||
gap: var(--spacing-2xs);
|
||||
gap: var(--spacing-4xs);
|
||||
align-items: center;
|
||||
justify-content: end;
|
||||
width: 100%;
|
||||
|
||||
.sort-and-filter {
|
||||
display: flex;
|
||||
gap: var(--spacing-2xs);
|
||||
gap: var(--spacing-4xs);
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
@@ -707,7 +713,7 @@ const loadPaginationFromQueryString = async () => {
|
||||
justify-self: end;
|
||||
|
||||
input {
|
||||
height: 42px;
|
||||
height: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user