mirror of
https://github.com/n8n-io/n8n.git
synced 2025-03-05 20:50:17 -08:00
refactor(editor): ResourceListLayout to script setup (#11526)
This commit is contained in:
parent
e10968b26f
commit
5f3deea60f
|
@ -1,6 +1,5 @@
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { computed, defineComponent, nextTick, ref, onMounted, watch } from 'vue';
|
import { computed, nextTick, ref, onMounted, watch } from 'vue';
|
||||||
import type { PropType } from 'vue';
|
|
||||||
|
|
||||||
import { type ProjectSharingData, ProjectTypes } from '@/types/projects.types';
|
import { type ProjectSharingData, ProjectTypes } from '@/types/projects.types';
|
||||||
import PageViewLayout from '@/components/layouts/PageViewLayout.vue';
|
import PageViewLayout from '@/components/layouts/PageViewLayout.vue';
|
||||||
|
@ -15,7 +14,6 @@ import { useTelemetry } from '@/composables/useTelemetry';
|
||||||
import { useRoute } from 'vue-router';
|
import { useRoute } from 'vue-router';
|
||||||
import { useProjectsStore } from '@/stores/projects.store';
|
import { useProjectsStore } from '@/stores/projects.store';
|
||||||
|
|
||||||
// eslint-disable-next-line unused-imports/no-unused-imports, @typescript-eslint/no-unused-vars
|
|
||||||
import type { BaseTextKey } from '@/plugins/i18n';
|
import type { BaseTextKey } from '@/plugins/i18n';
|
||||||
import type { Scope } from '@n8n/permissions';
|
import type { Scope } from '@n8n/permissions';
|
||||||
|
|
||||||
|
@ -32,86 +30,71 @@ export type IResource = {
|
||||||
sharedWithProjects?: ProjectSharingData[];
|
sharedWithProjects?: ProjectSharingData[];
|
||||||
};
|
};
|
||||||
|
|
||||||
interface IFilters {
|
export interface IFilters {
|
||||||
search: string;
|
search: string;
|
||||||
homeProject: string;
|
homeProject: string;
|
||||||
[key: string]: boolean | string | string[];
|
[key: string]: boolean | string | string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
type IResourceKeyType = 'credentials' | 'workflows';
|
type IResourceKeyType = 'credentials' | 'workflows' | 'variables';
|
||||||
|
|
||||||
export default defineComponent({
|
const props = withDefaults(
|
||||||
name: 'ResourcesListLayout',
|
defineProps<{
|
||||||
components: {
|
resourceKey: IResourceKeyType;
|
||||||
PageViewLayout,
|
displayName?: (resource: IResource) => string;
|
||||||
PageViewLayoutList,
|
resources: IResource[];
|
||||||
ResourceFiltersDropdown,
|
disabled: boolean;
|
||||||
ResourceListHeader,
|
initialize: () => Promise<void>;
|
||||||
|
filters?: IFilters;
|
||||||
|
additionalFiltersHandler?: (
|
||||||
|
resource: IResource,
|
||||||
|
filters: IFilters,
|
||||||
|
matches: boolean,
|
||||||
|
) => boolean;
|
||||||
|
shareable?: boolean;
|
||||||
|
showFiltersDropdown?: boolean;
|
||||||
|
sortFns?: Record<string, (a: IResource, b: IResource) => number>;
|
||||||
|
sortOptions?: string[];
|
||||||
|
type?: 'datatable' | 'list';
|
||||||
|
typeProps: { itemSize: number } | { columns: DatatableColumn[] };
|
||||||
|
loading: boolean;
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
displayName: (resource: IResource) => resource.name,
|
||||||
|
initialize: async () => {},
|
||||||
|
filters: () => ({ search: '', homeProject: '' }),
|
||||||
|
sortFns: () => ({}),
|
||||||
|
sortOptions: () => ['lastUpdated', 'lastCreated', 'nameAsc', 'nameDesc'],
|
||||||
|
type: 'list',
|
||||||
|
typeProps: () => ({ itemSize: 80 }),
|
||||||
|
loading: true,
|
||||||
|
additionalFiltersHandler: undefined,
|
||||||
|
showFiltersDropdown: true,
|
||||||
|
shareable: true,
|
||||||
},
|
},
|
||||||
props: {
|
);
|
||||||
resourceKey: {
|
|
||||||
type: String,
|
const emit = defineEmits<{
|
||||||
default: '' as IResourceKeyType,
|
'update:filters': [value: IFilters];
|
||||||
},
|
'click:add': [event: Event];
|
||||||
displayName: {
|
sort: [value: string];
|
||||||
type: Function as PropType<(resource: IResource) => string>,
|
}>();
|
||||||
default: (resource: IResource) => resource.name,
|
|
||||||
},
|
defineSlots<{
|
||||||
resources: {
|
header(): unknown;
|
||||||
type: Array as PropType<IResource[]>,
|
empty(): unknown;
|
||||||
default: (): IResource[] => [],
|
preamble(): unknown;
|
||||||
},
|
postamble(): unknown;
|
||||||
disabled: {
|
'add-button'(props: { disabled: boolean }): unknown;
|
||||||
type: Boolean,
|
callout(): unknown;
|
||||||
default: false,
|
filters(props: {
|
||||||
},
|
filters: Record<string, boolean | string | string[]>;
|
||||||
initialize: {
|
setKeyValue: (key: string, value: unknown) => void;
|
||||||
type: Function as PropType<() => Promise<void>>,
|
}): unknown;
|
||||||
default: () => async () => {},
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
},
|
default(props: { data: any; updateItemSize: (data: any) => void }): unknown;
|
||||||
filters: {
|
}>();
|
||||||
type: Object,
|
|
||||||
default: (): IFilters => ({ search: '', homeProject: '' }),
|
|
||||||
},
|
|
||||||
additionalFiltersHandler: {
|
|
||||||
type: Function,
|
|
||||||
required: false,
|
|
||||||
default: undefined,
|
|
||||||
},
|
|
||||||
shareable: {
|
|
||||||
type: Boolean,
|
|
||||||
default: true,
|
|
||||||
},
|
|
||||||
showFiltersDropdown: {
|
|
||||||
type: Boolean,
|
|
||||||
default: true,
|
|
||||||
},
|
|
||||||
sortFns: {
|
|
||||||
type: Object as PropType<Record<string, (a: IResource, b: IResource) => number>>,
|
|
||||||
default: (): Record<string, (a: IResource, b: IResource) => number> => ({}),
|
|
||||||
},
|
|
||||||
sortOptions: {
|
|
||||||
type: Array as PropType<string[]>,
|
|
||||||
default: () => ['lastUpdated', 'lastCreated', 'nameAsc', 'nameDesc'],
|
|
||||||
},
|
|
||||||
type: {
|
|
||||||
type: String as PropType<'datatable' | 'list'>,
|
|
||||||
default: 'list',
|
|
||||||
},
|
|
||||||
typeProps: {
|
|
||||||
type: Object as PropType<{ itemSize: number } | { columns: DatatableColumn[] }>,
|
|
||||||
default: () => ({
|
|
||||||
itemSize: 80,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
loading: {
|
|
||||||
type: Boolean,
|
|
||||||
required: false,
|
|
||||||
default: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
emits: ['update:filters', 'click:add', 'sort'],
|
|
||||||
setup(props, { emit }) {
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const i18n = useI18n();
|
const i18n = useI18n();
|
||||||
const { callDebounced } = useDebounce();
|
const { callDebounced } = useDebounce();
|
||||||
|
@ -205,6 +188,11 @@ export default defineComponent({
|
||||||
currentPage.value = page;
|
currentPage.value = page;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
currentPage,
|
||||||
|
setCurrentPage,
|
||||||
|
});
|
||||||
|
|
||||||
const sendFiltersTelemetry = (source: string) => {
|
const sendFiltersTelemetry = (source: string) => {
|
||||||
// Prevent sending multiple telemetry events when resetting filters
|
// Prevent sending multiple telemetry events when resetting filters
|
||||||
// Timeout is required to wait for search debounce to be over
|
// Timeout is required to wait for search debounce to be over
|
||||||
|
@ -239,7 +227,7 @@ export default defineComponent({
|
||||||
emit('click:add', e);
|
emit('click:add', e);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onUpdateFilters = (e: Event) => {
|
const onUpdateFilters = (e: IFilters) => {
|
||||||
emit('update:filters', e);
|
emit('update:filters', e);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -362,36 +350,6 @@ export default defineComponent({
|
||||||
return projectsStore.currentProject.name;
|
return projectsStore.currentProject.name;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
|
||||||
i18n,
|
|
||||||
search,
|
|
||||||
usersStore,
|
|
||||||
projectsStore,
|
|
||||||
filterKeys,
|
|
||||||
currentPage,
|
|
||||||
rowsPerPage,
|
|
||||||
filteredAndSortedResources,
|
|
||||||
hasFilters,
|
|
||||||
sortBy,
|
|
||||||
resettingFilters,
|
|
||||||
filtersModel,
|
|
||||||
sendFiltersTelemetry,
|
|
||||||
getColumns,
|
|
||||||
itemSize,
|
|
||||||
onAddButtonClick,
|
|
||||||
onUpdateFiltersLength,
|
|
||||||
onUpdateFilters,
|
|
||||||
resetFilters,
|
|
||||||
callDebounced,
|
|
||||||
setCurrentPage,
|
|
||||||
setRowsPerPage,
|
|
||||||
onSearch,
|
|
||||||
headerIcon,
|
|
||||||
projectName,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
|
@ -2,8 +2,10 @@
|
||||||
import { ref, computed, onMounted, watch } from 'vue';
|
import { ref, computed, onMounted, watch } from 'vue';
|
||||||
import { useRoute, useRouter } from 'vue-router';
|
import { useRoute, useRouter } from 'vue-router';
|
||||||
import type { ICredentialsResponse, ICredentialTypeMap } from '@/Interface';
|
import type { ICredentialsResponse, ICredentialTypeMap } from '@/Interface';
|
||||||
import type { IResource } from '@/components/layouts/ResourcesListLayout.vue';
|
import ResourcesListLayout, {
|
||||||
import ResourcesListLayout from '@/components/layouts/ResourcesListLayout.vue';
|
type IResource,
|
||||||
|
type IFilters,
|
||||||
|
} from '@/components/layouts/ResourcesListLayout.vue';
|
||||||
import CredentialCard from '@/components/CredentialCard.vue';
|
import CredentialCard from '@/components/CredentialCard.vue';
|
||||||
import type { ICredentialType } from 'n8n-workflow';
|
import type { ICredentialType } from 'n8n-workflow';
|
||||||
import {
|
import {
|
||||||
|
@ -43,10 +45,10 @@ const router = useRouter();
|
||||||
const telemetry = useTelemetry();
|
const telemetry = useTelemetry();
|
||||||
const i18n = useI18n();
|
const i18n = useI18n();
|
||||||
|
|
||||||
const filters = ref({
|
const filters = ref<IFilters>({
|
||||||
search: '',
|
search: '',
|
||||||
homeProject: '',
|
homeProject: '',
|
||||||
type: '',
|
type: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
|
@ -123,13 +125,11 @@ watch(
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
const onFilter = (
|
const onFilter = (resource: IResource, newFilters: IFilters, matches: boolean): boolean => {
|
||||||
resource: ICredentialsResponse,
|
const iResource = resource as ICredentialsResponse;
|
||||||
filtersToApply: { type: string[]; search: string },
|
const filtersToApply = newFilters as IFilters & { type: string[] };
|
||||||
matches: boolean,
|
|
||||||
): boolean => {
|
|
||||||
if (filtersToApply.type.length > 0) {
|
if (filtersToApply.type.length > 0) {
|
||||||
matches = matches && filtersToApply.type.includes(resource.type);
|
matches = matches && filtersToApply.type.includes(iResource.type);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (filtersToApply.search) {
|
if (filtersToApply.search) {
|
||||||
|
@ -137,8 +137,8 @@ const onFilter = (
|
||||||
|
|
||||||
matches =
|
matches =
|
||||||
matches ||
|
matches ||
|
||||||
(credentialTypesById.value[resource.type] &&
|
(credentialTypesById.value[iResource.type] &&
|
||||||
credentialTypesById.value[resource.type].displayName.toLowerCase().includes(searchString));
|
credentialTypesById.value[iResource.type].displayName.toLowerCase().includes(searchString));
|
||||||
}
|
}
|
||||||
|
|
||||||
return matches;
|
return matches;
|
||||||
|
|
|
@ -1,10 +1,13 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, onMounted, watch, ref } from 'vue';
|
import { computed, onMounted, watch, ref } from 'vue';
|
||||||
import ResourcesListLayout, { type IResource } from '@/components/layouts/ResourcesListLayout.vue';
|
import ResourcesListLayout, {
|
||||||
|
type IResource,
|
||||||
|
type IFilters,
|
||||||
|
} from '@/components/layouts/ResourcesListLayout.vue';
|
||||||
import WorkflowCard from '@/components/WorkflowCard.vue';
|
import WorkflowCard from '@/components/WorkflowCard.vue';
|
||||||
import WorkflowTagsDropdown from '@/components/WorkflowTagsDropdown.vue';
|
import WorkflowTagsDropdown from '@/components/WorkflowTagsDropdown.vue';
|
||||||
import { EnterpriseEditionFeature, MORE_ONBOARDING_OPTIONS_EXPERIMENT, VIEWS } from '@/constants';
|
import { EnterpriseEditionFeature, MORE_ONBOARDING_OPTIONS_EXPERIMENT, VIEWS } from '@/constants';
|
||||||
import type { ITag, IUser, IWorkflowDb } from '@/Interface';
|
import type { IUser, IWorkflowDb } from '@/Interface';
|
||||||
import { useUIStore } from '@/stores/ui.store';
|
import { useUIStore } from '@/stores/ui.store';
|
||||||
import { useSettingsStore } from '@/stores/settings.store';
|
import { useSettingsStore } from '@/stores/settings.store';
|
||||||
import { useUsersStore } from '@/stores/users.store';
|
import { useUsersStore } from '@/stores/users.store';
|
||||||
|
@ -49,9 +52,7 @@ const uiStore = useUIStore();
|
||||||
const tagsStore = useTagsStore();
|
const tagsStore = useTagsStore();
|
||||||
const documentTitle = useDocumentTitle();
|
const documentTitle = useDocumentTitle();
|
||||||
|
|
||||||
interface Filters {
|
interface Filters extends IFilters {
|
||||||
search: string;
|
|
||||||
homeProject: string;
|
|
||||||
status: string | boolean;
|
status: string | boolean;
|
||||||
tags: string[];
|
tags: string[];
|
||||||
}
|
}
|
||||||
|
@ -137,16 +138,13 @@ const emptyListDescription = computed(() => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const onFilter = (
|
const onFilter = (resource: IResource, newFilters: IFilters, matches: boolean): boolean => {
|
||||||
resource: IWorkflowDb,
|
const iFilters = newFilters as Filters;
|
||||||
newFilters: { tags: string[]; search: string; status: string | boolean },
|
if (settingsStore.areTagsEnabled && iFilters.tags.length > 0) {
|
||||||
matches: boolean,
|
|
||||||
): boolean => {
|
|
||||||
if (settingsStore.areTagsEnabled && newFilters.tags.length > 0) {
|
|
||||||
matches =
|
matches =
|
||||||
matches &&
|
matches &&
|
||||||
newFilters.tags.every((tag) =>
|
iFilters.tags.every((tag) =>
|
||||||
(resource.tags as ITag[])?.find((resourceTag) =>
|
(resource as IWorkflowDb).tags?.find((resourceTag) =>
|
||||||
typeof resourceTag === 'object'
|
typeof resourceTag === 'object'
|
||||||
? `${resourceTag.id}` === `${tag}`
|
? `${resourceTag.id}` === `${tag}`
|
||||||
: `${resourceTag}` === `${tag}`,
|
: `${resourceTag}` === `${tag}`,
|
||||||
|
@ -155,14 +153,14 @@ const onFilter = (
|
||||||
}
|
}
|
||||||
|
|
||||||
if (newFilters.status !== '') {
|
if (newFilters.status !== '') {
|
||||||
matches = matches && resource.active === newFilters.status;
|
matches = matches && (resource as IWorkflowDb).active === newFilters.status;
|
||||||
}
|
}
|
||||||
|
|
||||||
return matches;
|
return matches;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Methods
|
// Methods
|
||||||
const onFiltersUpdated = (newFilters: Filters) => {
|
const onFiltersUpdated = (newFilters: IFilters) => {
|
||||||
Object.assign(filters.value, newFilters);
|
Object.assign(filters.value, newFilters);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue