refactor(editor): ResourceListLayout to script setup (#11526)

This commit is contained in:
Raúl Gómez Morales 2024-11-04 13:07:21 +01:00 committed by GitHub
parent e10968b26f
commit 5f3deea60f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 336 additions and 380 deletions

View file

@ -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>

View file

@ -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;

View file

@ -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);
}; };