mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-18 02:21:13 +00:00
feat(editor): Combine 'Move to Folder' and 'Change owner' modals (#15756)
This new modal also allows transferring entire folders to other projects and users.
This commit is contained in:
@@ -1,8 +1,7 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue';
|
||||
import { useI18n } from '@/composables/useI18n';
|
||||
import { ResourceType } from '@/utils/projects.utils';
|
||||
import { splitName } from '@/utils/projects.utils';
|
||||
import { ResourceType, splitName } from '@/utils/projects.utils';
|
||||
import type { Project, ProjectIcon as BadgeIcon } from '@/types/projects.types';
|
||||
import { ProjectTypes } from '@/types/projects.types';
|
||||
import type {
|
||||
@@ -36,6 +35,10 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
|
||||
const i18n = useI18n();
|
||||
|
||||
const isShared = computed(() => {
|
||||
return 'sharedWithProjects' in props.resource && props.resource.sharedWithProjects?.length;
|
||||
});
|
||||
|
||||
const projectState = computed(() => {
|
||||
if (
|
||||
(props.resource.homeProject &&
|
||||
@@ -43,17 +46,17 @@ const projectState = computed(() => {
|
||||
props.resource.homeProject.id === props.personalProject.id) ||
|
||||
!props.resource.homeProject
|
||||
) {
|
||||
if (props.resource.sharedWithProjects?.length) {
|
||||
if (isShared.value) {
|
||||
return ProjectState.SharedOwned;
|
||||
}
|
||||
return ProjectState.Owned;
|
||||
} else if (props.resource.homeProject?.type !== ProjectTypes.Team) {
|
||||
if (props.resource.sharedWithProjects?.length) {
|
||||
if (isShared.value) {
|
||||
return ProjectState.SharedPersonal;
|
||||
}
|
||||
return ProjectState.Personal;
|
||||
} else if (props.resource.homeProject?.type === ProjectTypes.Team) {
|
||||
if (props.resource.sharedWithProjects?.length) {
|
||||
if (isShared.value) {
|
||||
return ProjectState.SharedTeam;
|
||||
}
|
||||
return ProjectState.Team;
|
||||
@@ -61,8 +64,8 @@ const projectState = computed(() => {
|
||||
return ProjectState.Unknown;
|
||||
});
|
||||
|
||||
const numberOfMembersInHomeTeamProject = computed(
|
||||
() => props.resource.sharedWithProjects?.length ?? 0,
|
||||
const numberOfMembersInHomeTeamProject = computed(() =>
|
||||
'sharedWithProjects' in props.resource ? (props.resource.sharedWithProjects?.length ?? 0) : 0,
|
||||
);
|
||||
|
||||
const badgeText = computed(() => {
|
||||
|
||||
@@ -235,6 +235,7 @@ describe('ProjectMoveResourceModal', () => {
|
||||
'workflow',
|
||||
movedWorkflow.id,
|
||||
destinationProject.id,
|
||||
undefined,
|
||||
['1', '2'],
|
||||
);
|
||||
});
|
||||
|
||||
@@ -7,7 +7,12 @@ import { useUIStore } from '@/stores/ui.store';
|
||||
import { useProjectsStore } from '@/stores/projects.store';
|
||||
import Modal from '@/components/Modal.vue';
|
||||
import { VIEWS } from '@/constants';
|
||||
import { ResourceType, splitName } from '@/utils/projects.utils';
|
||||
import {
|
||||
splitName,
|
||||
getTruncatedProjectName,
|
||||
ResourceType,
|
||||
MAX_NAME_LENGTH,
|
||||
} from '@/utils/projects.utils';
|
||||
import { useTelemetry } from '@/composables/useTelemetry';
|
||||
import { ProjectTypes } from '@/types/projects.types';
|
||||
import ProjectMoveSuccessToastMessage from '@/components/Projects/ProjectMoveSuccessToastMessage.vue';
|
||||
@@ -88,10 +93,9 @@ const selectedProject = computed(() =>
|
||||
const isResourceInTeamProject = computed(() => isHomeProjectTeam(props.data.resource));
|
||||
const isResourceWorkflow = computed(() => props.data.resourceType === ResourceType.Workflow);
|
||||
const targetProjectName = computed(() => {
|
||||
const { name, email } = splitName(selectedProject.value?.name ?? '');
|
||||
return truncate(name ?? email ?? '', 25);
|
||||
return getTruncatedProjectName(selectedProject.value?.name);
|
||||
});
|
||||
const resourceName = computed(() => truncate(props.data.resource.name, 25));
|
||||
const resourceName = computed(() => truncate(props.data.resource.name, MAX_NAME_LENGTH));
|
||||
|
||||
const isHomeProjectTeam = (resource: IWorkflowDb | ICredentialsResponse) =>
|
||||
resource.homeProject?.type === ProjectTypes.Team;
|
||||
@@ -120,6 +124,7 @@ const moveResource = async () => {
|
||||
props.data.resourceType,
|
||||
props.data.resource.id,
|
||||
selectedProject.value.id,
|
||||
undefined,
|
||||
shareUsedCredentials.value ? shareableCredentials.value.map((c) => c.id) : undefined,
|
||||
);
|
||||
closeModal();
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue';
|
||||
import { truncate } from '@n8n/utils/string/truncate';
|
||||
import { ResourceType, splitName } from '@/utils/projects.utils';
|
||||
import { ResourceType, getTruncatedProjectName } from '@/utils/projects.utils';
|
||||
import type { ProjectListItem } from '@/types/projects.types';
|
||||
import { ProjectTypes } from '@/types/projects.types';
|
||||
import { useI18n } from '@/composables/useI18n';
|
||||
@@ -19,8 +18,7 @@ const i18n = useI18n();
|
||||
const isWorkflow = computed(() => props.resourceType === ResourceType.Workflow);
|
||||
const isTargetProjectTeam = computed(() => props.targetProject.type === ProjectTypes.Team);
|
||||
const targetProjectName = computed(() => {
|
||||
const { name, email } = splitName(props.targetProject?.name ?? '');
|
||||
return truncate(name ?? email ?? '', 25);
|
||||
return getTruncatedProjectName(props.targetProject?.name);
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
|
||||
@@ -2,7 +2,12 @@
|
||||
import type { AllRolesMap } from '@n8n/permissions';
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { useI18n } from '@/composables/useI18n';
|
||||
import type { ProjectListItem, ProjectSharingData } from '@/types/projects.types';
|
||||
import {
|
||||
ProjectTypes,
|
||||
type ProjectIcon as ProjectIconItem,
|
||||
type ProjectListItem,
|
||||
type ProjectSharingData,
|
||||
} from '@/types/projects.types';
|
||||
import ProjectSharingInfo from '@/components/Projects/ProjectSharingInfo.vue';
|
||||
import { sortByProperty } from '@n8n/utils/sort/sortByProperty';
|
||||
|
||||
@@ -46,6 +51,19 @@ const filteredProjects = computed(() =>
|
||||
),
|
||||
);
|
||||
|
||||
const projectIcon = computed<ProjectIconItem>(() => {
|
||||
const defaultIcon: ProjectIconItem = { type: 'icon', value: 'layer-group' };
|
||||
const project = props.projects.find((p) => p.id === selectedProject.value);
|
||||
|
||||
if (project?.type === ProjectTypes.Personal) {
|
||||
return { type: 'icon', value: 'user' };
|
||||
} else if (project?.type === ProjectTypes.Team) {
|
||||
return project.icon ?? defaultIcon;
|
||||
}
|
||||
|
||||
return defaultIcon;
|
||||
});
|
||||
|
||||
const setFilter = (query: string) => {
|
||||
filter.value = query;
|
||||
};
|
||||
@@ -109,7 +127,10 @@ watch(
|
||||
@update:model-value="onProjectSelected"
|
||||
>
|
||||
<template #prefix>
|
||||
<n8n-icon icon="search" />
|
||||
<N8nIcon v-if="projectIcon.type === 'icon'" :icon="projectIcon.value" color="text-dark" />
|
||||
<N8nText v-else-if="projectIcon.type === 'emoji'" color="text-light" :class="$style.emoji">
|
||||
{{ projectIcon.value }}
|
||||
</N8nText>
|
||||
</template>
|
||||
<N8nOption
|
||||
v-for="project in filteredProjects"
|
||||
@@ -187,4 +208,8 @@ watch(
|
||||
.projectRoleSelect {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.emoji {
|
||||
font-size: var(--font-size-s);
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user