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">
|
||||
import { computed, defineComponent, nextTick, ref, onMounted, watch } from 'vue';
|
||||
import type { PropType } from 'vue';
|
||||
<script lang="ts" setup>
|
||||
import { computed, nextTick, ref, onMounted, watch } from 'vue';
|
||||
|
||||
import { type ProjectSharingData, ProjectTypes } from '@/types/projects.types';
|
||||
import PageViewLayout from '@/components/layouts/PageViewLayout.vue';
|
||||
|
@ -15,7 +14,6 @@ import { useTelemetry } from '@/composables/useTelemetry';
|
|||
import { useRoute } from 'vue-router';
|
||||
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 { Scope } from '@n8n/permissions';
|
||||
|
||||
|
@ -32,365 +30,325 @@ export type IResource = {
|
|||
sharedWithProjects?: ProjectSharingData[];
|
||||
};
|
||||
|
||||
interface IFilters {
|
||||
export interface IFilters {
|
||||
search: string;
|
||||
homeProject: string;
|
||||
[key: string]: boolean | string | string[];
|
||||
}
|
||||
|
||||
type IResourceKeyType = 'credentials' | 'workflows';
|
||||
type IResourceKeyType = 'credentials' | 'workflows' | 'variables';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ResourcesListLayout',
|
||||
components: {
|
||||
PageViewLayout,
|
||||
PageViewLayoutList,
|
||||
ResourceFiltersDropdown,
|
||||
ResourceListHeader,
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
resourceKey: IResourceKeyType;
|
||||
displayName?: (resource: IResource) => string;
|
||||
resources: IResource[];
|
||||
disabled: boolean;
|
||||
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,
|
||||
default: '' as IResourceKeyType,
|
||||
},
|
||||
displayName: {
|
||||
type: Function as PropType<(resource: IResource) => string>,
|
||||
default: (resource: IResource) => resource.name,
|
||||
},
|
||||
resources: {
|
||||
type: Array as PropType<IResource[]>,
|
||||
default: (): IResource[] => [],
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
initialize: {
|
||||
type: Function as PropType<() => Promise<void>>,
|
||||
default: () => async () => {},
|
||||
},
|
||||
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,
|
||||
},
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:filters': [value: IFilters];
|
||||
'click:add': [event: Event];
|
||||
sort: [value: string];
|
||||
}>();
|
||||
|
||||
defineSlots<{
|
||||
header(): unknown;
|
||||
empty(): unknown;
|
||||
preamble(): unknown;
|
||||
postamble(): unknown;
|
||||
'add-button'(props: { disabled: boolean }): unknown;
|
||||
callout(): unknown;
|
||||
filters(props: {
|
||||
filters: Record<string, boolean | string | string[]>;
|
||||
setKeyValue: (key: string, value: unknown) => void;
|
||||
}): unknown;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
default(props: { data: any; updateItemSize: (data: any) => void }): unknown;
|
||||
}>();
|
||||
|
||||
const route = useRoute();
|
||||
const i18n = useI18n();
|
||||
const { callDebounced } = useDebounce();
|
||||
const usersStore = useUsersStore();
|
||||
const projectsStore = useProjectsStore();
|
||||
const telemetry = useTelemetry();
|
||||
|
||||
const sortBy = ref(props.sortOptions[0]);
|
||||
const hasFilters = ref(false);
|
||||
const filtersModel = ref(props.filters);
|
||||
const currentPage = ref(1);
|
||||
const rowsPerPage = ref<number>(10);
|
||||
const resettingFilters = ref(false);
|
||||
const search = ref<HTMLElement | null>(null);
|
||||
|
||||
//computed
|
||||
|
||||
const filterKeys = computed(() => {
|
||||
return Object.keys(filtersModel.value);
|
||||
});
|
||||
|
||||
const filteredAndSortedResources = computed(() => {
|
||||
const filtered = props.resources.filter((resource) => {
|
||||
let matches = true;
|
||||
|
||||
if (filtersModel.value.homeProject) {
|
||||
matches =
|
||||
matches &&
|
||||
!!(resource.homeProject && resource.homeProject.id === filtersModel.value.homeProject);
|
||||
}
|
||||
|
||||
if (filtersModel.value.search) {
|
||||
const searchString = filtersModel.value.search.toLowerCase();
|
||||
matches = matches && props.displayName(resource).toLowerCase().includes(searchString);
|
||||
}
|
||||
|
||||
if (props.additionalFiltersHandler) {
|
||||
matches = props.additionalFiltersHandler(resource, filtersModel.value, matches);
|
||||
}
|
||||
|
||||
return matches;
|
||||
});
|
||||
|
||||
return filtered.sort((a, b) => {
|
||||
switch (sortBy.value) {
|
||||
case 'lastUpdated':
|
||||
return props.sortFns.lastUpdated
|
||||
? props.sortFns.lastUpdated(a, b)
|
||||
: new Date(b.updatedAt ?? '').valueOf() - new Date(a.updatedAt ?? '').valueOf();
|
||||
case 'lastCreated':
|
||||
return props.sortFns.lastCreated
|
||||
? props.sortFns.lastCreated(a, b)
|
||||
: new Date(b.createdAt ?? '').valueOf() - new Date(a.createdAt ?? '').valueOf();
|
||||
case 'nameAsc':
|
||||
return props.sortFns.nameAsc
|
||||
? props.sortFns.nameAsc(a, b)
|
||||
: props.displayName(a).trim().localeCompare(props.displayName(b).trim());
|
||||
case 'nameDesc':
|
||||
return props.sortFns.nameDesc
|
||||
? props.sortFns.nameDesc(a, b)
|
||||
: props.displayName(b).trim().localeCompare(props.displayName(a).trim());
|
||||
default:
|
||||
return props.sortFns[sortBy.value] ? props.sortFns[sortBy.value](a, b) : 0;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
//methods
|
||||
|
||||
const focusSearchInput = () => {
|
||||
if (search.value) {
|
||||
search.value.focus();
|
||||
}
|
||||
};
|
||||
|
||||
const hasAppliedFilters = (): boolean => {
|
||||
return !!filterKeys.value.find(
|
||||
(key) =>
|
||||
key !== 'search' &&
|
||||
(Array.isArray(props.filters[key])
|
||||
? props.filters[key].length > 0
|
||||
: props.filters[key] !== ''),
|
||||
);
|
||||
};
|
||||
|
||||
const setRowsPerPage = (numberOfRowsPerPage: number) => {
|
||||
rowsPerPage.value = numberOfRowsPerPage;
|
||||
};
|
||||
|
||||
const setCurrentPage = (page: number) => {
|
||||
currentPage.value = page;
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
currentPage,
|
||||
setCurrentPage,
|
||||
});
|
||||
|
||||
const sendFiltersTelemetry = (source: string) => {
|
||||
// Prevent sending multiple telemetry events when resetting filters
|
||||
// Timeout is required to wait for search debounce to be over
|
||||
if (resettingFilters.value) {
|
||||
if (source !== 'reset') {
|
||||
return;
|
||||
}
|
||||
|
||||
setTimeout(() => (resettingFilters.value = false), 1500);
|
||||
}
|
||||
|
||||
const filters = filtersModel.value as Record<string, string[] | string | boolean>;
|
||||
const filtersSet: string[] = [];
|
||||
const filterValues: Array<string[] | string | boolean | null> = [];
|
||||
|
||||
Object.keys(filters).forEach((key) => {
|
||||
if (filters[key]) {
|
||||
filtersSet.push(key);
|
||||
filterValues.push(key === 'search' ? null : filters[key]);
|
||||
}
|
||||
});
|
||||
|
||||
telemetry.track(`User set filters in ${props.resourceKey} list`, {
|
||||
filters_set: filtersSet,
|
||||
filter_values: filterValues,
|
||||
[`${props.resourceKey}_total_in_view`]: props.resources.length,
|
||||
[`${props.resourceKey}_after_filtering`]: filteredAndSortedResources.value.length,
|
||||
});
|
||||
};
|
||||
|
||||
const onAddButtonClick = (e: Event) => {
|
||||
emit('click:add', e);
|
||||
};
|
||||
|
||||
const onUpdateFilters = (e: IFilters) => {
|
||||
emit('update:filters', e);
|
||||
};
|
||||
|
||||
const resetFilters = () => {
|
||||
Object.keys(filtersModel.value).forEach((key) => {
|
||||
filtersModel.value[key] = Array.isArray(filtersModel.value[key]) ? [] : '';
|
||||
});
|
||||
|
||||
resettingFilters.value = true;
|
||||
sendFiltersTelemetry('reset');
|
||||
emit('update:filters', filtersModel.value);
|
||||
};
|
||||
|
||||
const itemSize = () => {
|
||||
if ('itemSize' in props.typeProps) {
|
||||
return props.typeProps.itemSize;
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
|
||||
const getColumns = () => {
|
||||
if ('columns' in props.typeProps) {
|
||||
return props.typeProps.columns;
|
||||
}
|
||||
return {};
|
||||
};
|
||||
|
||||
const sendSortingTelemetry = () => {
|
||||
telemetry.track(`User changed sorting in ${props.resourceKey} list`, {
|
||||
sorting: sortBy.value,
|
||||
});
|
||||
};
|
||||
|
||||
const onUpdateFiltersLength = (length: number) => {
|
||||
hasFilters.value = length > 0;
|
||||
};
|
||||
|
||||
const onSearch = (s: string) => {
|
||||
filtersModel.value.search = s;
|
||||
emit('update:filters', filtersModel.value);
|
||||
};
|
||||
|
||||
//watchers
|
||||
|
||||
watch(
|
||||
() => props.filters,
|
||||
(value) => {
|
||||
filtersModel.value = value;
|
||||
},
|
||||
emits: ['update:filters', 'click:add', 'sort'],
|
||||
setup(props, { emit }) {
|
||||
const route = useRoute();
|
||||
const i18n = useI18n();
|
||||
const { callDebounced } = useDebounce();
|
||||
const usersStore = useUsersStore();
|
||||
const projectsStore = useProjectsStore();
|
||||
const telemetry = useTelemetry();
|
||||
);
|
||||
|
||||
const sortBy = ref(props.sortOptions[0]);
|
||||
const hasFilters = ref(false);
|
||||
const filtersModel = ref(props.filters);
|
||||
const currentPage = ref(1);
|
||||
const rowsPerPage = ref<number>(10);
|
||||
const resettingFilters = ref(false);
|
||||
const search = ref<HTMLElement | null>(null);
|
||||
|
||||
//computed
|
||||
|
||||
const filterKeys = computed(() => {
|
||||
return Object.keys(filtersModel.value);
|
||||
});
|
||||
|
||||
const filteredAndSortedResources = computed(() => {
|
||||
const filtered = props.resources.filter((resource) => {
|
||||
let matches = true;
|
||||
|
||||
if (filtersModel.value.homeProject) {
|
||||
matches =
|
||||
matches &&
|
||||
!!(resource.homeProject && resource.homeProject.id === filtersModel.value.homeProject);
|
||||
}
|
||||
|
||||
if (filtersModel.value.search) {
|
||||
const searchString = filtersModel.value.search.toLowerCase();
|
||||
matches = matches && props.displayName(resource).toLowerCase().includes(searchString);
|
||||
}
|
||||
|
||||
if (props.additionalFiltersHandler) {
|
||||
matches = props.additionalFiltersHandler(resource, filtersModel.value, matches);
|
||||
}
|
||||
|
||||
return matches;
|
||||
});
|
||||
|
||||
return filtered.sort((a, b) => {
|
||||
switch (sortBy.value) {
|
||||
case 'lastUpdated':
|
||||
return props.sortFns.lastUpdated
|
||||
? props.sortFns.lastUpdated(a, b)
|
||||
: new Date(b.updatedAt ?? '').valueOf() - new Date(a.updatedAt ?? '').valueOf();
|
||||
case 'lastCreated':
|
||||
return props.sortFns.lastCreated
|
||||
? props.sortFns.lastCreated(a, b)
|
||||
: new Date(b.createdAt ?? '').valueOf() - new Date(a.createdAt ?? '').valueOf();
|
||||
case 'nameAsc':
|
||||
return props.sortFns.nameAsc
|
||||
? props.sortFns.nameAsc(a, b)
|
||||
: props.displayName(a).trim().localeCompare(props.displayName(b).trim());
|
||||
case 'nameDesc':
|
||||
return props.sortFns.nameDesc
|
||||
? props.sortFns.nameDesc(a, b)
|
||||
: props.displayName(b).trim().localeCompare(props.displayName(a).trim());
|
||||
default:
|
||||
return props.sortFns[sortBy.value] ? props.sortFns[sortBy.value](a, b) : 0;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
//methods
|
||||
|
||||
const focusSearchInput = () => {
|
||||
if (search.value) {
|
||||
search.value.focus();
|
||||
}
|
||||
};
|
||||
|
||||
const hasAppliedFilters = (): boolean => {
|
||||
return !!filterKeys.value.find(
|
||||
(key) =>
|
||||
key !== 'search' &&
|
||||
(Array.isArray(props.filters[key])
|
||||
? props.filters[key].length > 0
|
||||
: props.filters[key] !== ''),
|
||||
);
|
||||
};
|
||||
|
||||
const setRowsPerPage = (numberOfRowsPerPage: number) => {
|
||||
rowsPerPage.value = numberOfRowsPerPage;
|
||||
};
|
||||
|
||||
const setCurrentPage = (page: number) => {
|
||||
currentPage.value = page;
|
||||
};
|
||||
|
||||
const sendFiltersTelemetry = (source: string) => {
|
||||
// Prevent sending multiple telemetry events when resetting filters
|
||||
// Timeout is required to wait for search debounce to be over
|
||||
if (resettingFilters.value) {
|
||||
if (source !== 'reset') {
|
||||
return;
|
||||
}
|
||||
|
||||
setTimeout(() => (resettingFilters.value = false), 1500);
|
||||
}
|
||||
|
||||
const filters = filtersModel.value as Record<string, string[] | string | boolean>;
|
||||
const filtersSet: string[] = [];
|
||||
const filterValues: Array<string[] | string | boolean | null> = [];
|
||||
|
||||
Object.keys(filters).forEach((key) => {
|
||||
if (filters[key]) {
|
||||
filtersSet.push(key);
|
||||
filterValues.push(key === 'search' ? null : filters[key]);
|
||||
}
|
||||
});
|
||||
|
||||
telemetry.track(`User set filters in ${props.resourceKey} list`, {
|
||||
filters_set: filtersSet,
|
||||
filter_values: filterValues,
|
||||
[`${props.resourceKey}_total_in_view`]: props.resources.length,
|
||||
[`${props.resourceKey}_after_filtering`]: filteredAndSortedResources.value.length,
|
||||
});
|
||||
};
|
||||
|
||||
const onAddButtonClick = (e: Event) => {
|
||||
emit('click:add', e);
|
||||
};
|
||||
|
||||
const onUpdateFilters = (e: Event) => {
|
||||
emit('update:filters', e);
|
||||
};
|
||||
|
||||
const resetFilters = () => {
|
||||
Object.keys(filtersModel.value).forEach((key) => {
|
||||
filtersModel.value[key] = Array.isArray(filtersModel.value[key]) ? [] : '';
|
||||
});
|
||||
|
||||
resettingFilters.value = true;
|
||||
sendFiltersTelemetry('reset');
|
||||
emit('update:filters', filtersModel.value);
|
||||
};
|
||||
|
||||
const itemSize = () => {
|
||||
if ('itemSize' in props.typeProps) {
|
||||
return props.typeProps.itemSize;
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
|
||||
const getColumns = () => {
|
||||
if ('columns' in props.typeProps) {
|
||||
return props.typeProps.columns;
|
||||
}
|
||||
return {};
|
||||
};
|
||||
|
||||
const sendSortingTelemetry = () => {
|
||||
telemetry.track(`User changed sorting in ${props.resourceKey} list`, {
|
||||
sorting: sortBy.value,
|
||||
});
|
||||
};
|
||||
|
||||
const onUpdateFiltersLength = (length: number) => {
|
||||
hasFilters.value = length > 0;
|
||||
};
|
||||
|
||||
const onSearch = (s: string) => {
|
||||
filtersModel.value.search = s;
|
||||
emit('update:filters', filtersModel.value);
|
||||
};
|
||||
|
||||
//watchers
|
||||
|
||||
watch(
|
||||
() => props.filters,
|
||||
(value) => {
|
||||
filtersModel.value = value;
|
||||
},
|
||||
);
|
||||
|
||||
watch(
|
||||
() => filtersModel.value.homeProject,
|
||||
() => {
|
||||
sendFiltersTelemetry('homeProject');
|
||||
},
|
||||
);
|
||||
|
||||
watch(
|
||||
() => filtersModel.value.tags,
|
||||
() => {
|
||||
sendFiltersTelemetry('tags');
|
||||
},
|
||||
);
|
||||
|
||||
watch(
|
||||
() => filtersModel.value.type,
|
||||
() => {
|
||||
sendFiltersTelemetry('type');
|
||||
},
|
||||
);
|
||||
|
||||
watch(
|
||||
() => filtersModel.value.search,
|
||||
() => callDebounced(sendFiltersTelemetry, { debounceTime: 1000, trailing: true }, 'search'),
|
||||
);
|
||||
|
||||
watch(
|
||||
() => sortBy.value,
|
||||
(newValue) => {
|
||||
emit('sort', newValue);
|
||||
sendSortingTelemetry();
|
||||
},
|
||||
);
|
||||
|
||||
watch(
|
||||
() => route?.params?.projectId,
|
||||
() => {
|
||||
resetFilters();
|
||||
},
|
||||
);
|
||||
|
||||
onMounted(async () => {
|
||||
await props.initialize();
|
||||
await nextTick();
|
||||
|
||||
focusSearchInput();
|
||||
|
||||
if (hasAppliedFilters()) {
|
||||
hasFilters.value = true;
|
||||
}
|
||||
});
|
||||
|
||||
const headerIcon = computed(() => {
|
||||
if (projectsStore.currentProject?.type === ProjectTypes.Personal) {
|
||||
return 'user';
|
||||
} else if (projectsStore.currentProject?.name) {
|
||||
return 'layer-group';
|
||||
} else {
|
||||
return 'home';
|
||||
}
|
||||
});
|
||||
|
||||
const projectName = computed(() => {
|
||||
if (!projectsStore.currentProject) {
|
||||
return i18n.baseText('projects.menu.home');
|
||||
} else if (projectsStore.currentProject.type === ProjectTypes.Personal) {
|
||||
return i18n.baseText('projects.menu.personal');
|
||||
} else {
|
||||
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,
|
||||
};
|
||||
watch(
|
||||
() => filtersModel.value.homeProject,
|
||||
() => {
|
||||
sendFiltersTelemetry('homeProject');
|
||||
},
|
||||
);
|
||||
|
||||
watch(
|
||||
() => filtersModel.value.tags,
|
||||
() => {
|
||||
sendFiltersTelemetry('tags');
|
||||
},
|
||||
);
|
||||
|
||||
watch(
|
||||
() => filtersModel.value.type,
|
||||
() => {
|
||||
sendFiltersTelemetry('type');
|
||||
},
|
||||
);
|
||||
|
||||
watch(
|
||||
() => filtersModel.value.search,
|
||||
() => callDebounced(sendFiltersTelemetry, { debounceTime: 1000, trailing: true }, 'search'),
|
||||
);
|
||||
|
||||
watch(
|
||||
() => sortBy.value,
|
||||
(newValue) => {
|
||||
emit('sort', newValue);
|
||||
sendSortingTelemetry();
|
||||
},
|
||||
);
|
||||
|
||||
watch(
|
||||
() => route?.params?.projectId,
|
||||
() => {
|
||||
resetFilters();
|
||||
},
|
||||
);
|
||||
|
||||
onMounted(async () => {
|
||||
await props.initialize();
|
||||
await nextTick();
|
||||
|
||||
focusSearchInput();
|
||||
|
||||
if (hasAppliedFilters()) {
|
||||
hasFilters.value = true;
|
||||
}
|
||||
});
|
||||
|
||||
const headerIcon = computed(() => {
|
||||
if (projectsStore.currentProject?.type === ProjectTypes.Personal) {
|
||||
return 'user';
|
||||
} else if (projectsStore.currentProject?.name) {
|
||||
return 'layer-group';
|
||||
} else {
|
||||
return 'home';
|
||||
}
|
||||
});
|
||||
|
||||
const projectName = computed(() => {
|
||||
if (!projectsStore.currentProject) {
|
||||
return i18n.baseText('projects.menu.home');
|
||||
} else if (projectsStore.currentProject.type === ProjectTypes.Personal) {
|
||||
return i18n.baseText('projects.menu.personal');
|
||||
} else {
|
||||
return projectsStore.currentProject.name;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
|
|
|
@ -2,8 +2,10 @@
|
|||
import { ref, computed, onMounted, watch } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import type { ICredentialsResponse, ICredentialTypeMap } from '@/Interface';
|
||||
import type { IResource } from '@/components/layouts/ResourcesListLayout.vue';
|
||||
import ResourcesListLayout from '@/components/layouts/ResourcesListLayout.vue';
|
||||
import ResourcesListLayout, {
|
||||
type IResource,
|
||||
type IFilters,
|
||||
} from '@/components/layouts/ResourcesListLayout.vue';
|
||||
import CredentialCard from '@/components/CredentialCard.vue';
|
||||
import type { ICredentialType } from 'n8n-workflow';
|
||||
import {
|
||||
|
@ -43,10 +45,10 @@ const router = useRouter();
|
|||
const telemetry = useTelemetry();
|
||||
const i18n = useI18n();
|
||||
|
||||
const filters = ref({
|
||||
const filters = ref<IFilters>({
|
||||
search: '',
|
||||
homeProject: '',
|
||||
type: '',
|
||||
type: [],
|
||||
});
|
||||
|
||||
const loading = ref(false);
|
||||
|
@ -123,13 +125,11 @@ watch(
|
|||
},
|
||||
);
|
||||
|
||||
const onFilter = (
|
||||
resource: ICredentialsResponse,
|
||||
filtersToApply: { type: string[]; search: string },
|
||||
matches: boolean,
|
||||
): boolean => {
|
||||
const onFilter = (resource: IResource, newFilters: IFilters, matches: boolean): boolean => {
|
||||
const iResource = resource as ICredentialsResponse;
|
||||
const filtersToApply = newFilters as IFilters & { type: string[] };
|
||||
if (filtersToApply.type.length > 0) {
|
||||
matches = matches && filtersToApply.type.includes(resource.type);
|
||||
matches = matches && filtersToApply.type.includes(iResource.type);
|
||||
}
|
||||
|
||||
if (filtersToApply.search) {
|
||||
|
@ -137,8 +137,8 @@ const onFilter = (
|
|||
|
||||
matches =
|
||||
matches ||
|
||||
(credentialTypesById.value[resource.type] &&
|
||||
credentialTypesById.value[resource.type].displayName.toLowerCase().includes(searchString));
|
||||
(credentialTypesById.value[iResource.type] &&
|
||||
credentialTypesById.value[iResource.type].displayName.toLowerCase().includes(searchString));
|
||||
}
|
||||
|
||||
return matches;
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
<script lang="ts" setup>
|
||||
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 WorkflowTagsDropdown from '@/components/WorkflowTagsDropdown.vue';
|
||||
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 { useSettingsStore } from '@/stores/settings.store';
|
||||
import { useUsersStore } from '@/stores/users.store';
|
||||
|
@ -49,9 +52,7 @@ const uiStore = useUIStore();
|
|||
const tagsStore = useTagsStore();
|
||||
const documentTitle = useDocumentTitle();
|
||||
|
||||
interface Filters {
|
||||
search: string;
|
||||
homeProject: string;
|
||||
interface Filters extends IFilters {
|
||||
status: string | boolean;
|
||||
tags: string[];
|
||||
}
|
||||
|
@ -137,16 +138,13 @@ const emptyListDescription = computed(() => {
|
|||
}
|
||||
});
|
||||
|
||||
const onFilter = (
|
||||
resource: IWorkflowDb,
|
||||
newFilters: { tags: string[]; search: string; status: string | boolean },
|
||||
matches: boolean,
|
||||
): boolean => {
|
||||
if (settingsStore.areTagsEnabled && newFilters.tags.length > 0) {
|
||||
const onFilter = (resource: IResource, newFilters: IFilters, matches: boolean): boolean => {
|
||||
const iFilters = newFilters as Filters;
|
||||
if (settingsStore.areTagsEnabled && iFilters.tags.length > 0) {
|
||||
matches =
|
||||
matches &&
|
||||
newFilters.tags.every((tag) =>
|
||||
(resource.tags as ITag[])?.find((resourceTag) =>
|
||||
iFilters.tags.every((tag) =>
|
||||
(resource as IWorkflowDb).tags?.find((resourceTag) =>
|
||||
typeof resourceTag === 'object'
|
||||
? `${resourceTag.id}` === `${tag}`
|
||||
: `${resourceTag}` === `${tag}`,
|
||||
|
@ -155,14 +153,14 @@ const onFilter = (
|
|||
}
|
||||
|
||||
if (newFilters.status !== '') {
|
||||
matches = matches && resource.active === newFilters.status;
|
||||
matches = matches && (resource as IWorkflowDb).active === newFilters.status;
|
||||
}
|
||||
|
||||
return matches;
|
||||
};
|
||||
|
||||
// Methods
|
||||
const onFiltersUpdated = (newFilters: Filters) => {
|
||||
const onFiltersUpdated = (newFilters: IFilters) => {
|
||||
Object.assign(filters.value, newFilters);
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in a new issue