feat(editor): Add move resources option to workflows and credentials on (#9654)

This commit is contained in:
Csaba Tuncsik
2024-06-11 14:21:16 +02:00
committed by GitHub
parent dda7901398
commit bc35e8c33d
22 changed files with 960 additions and 495 deletions

View File

@@ -1,3 +1,132 @@
<script setup lang="ts">
import { computed } from 'vue';
import dateformat from 'dateformat';
import type { ICredentialsResponse } from '@/Interface';
import { MODAL_CONFIRM, PROJECT_MOVE_RESOURCE_MODAL } from '@/constants';
import { useMessage } from '@/composables/useMessage';
import CredentialIcon from '@/components/CredentialIcon.vue';
import { getCredentialPermissions } from '@/permissions';
import { useUIStore } from '@/stores/ui.store';
import { useCredentialsStore } from '@/stores/credentials.store';
import TimeAgo from '@/components/TimeAgo.vue';
import type { ProjectSharingData } from '@/types/projects.types';
import { useProjectsStore } from '@/stores/projects.store';
import ProjectCardBadge from '@/components/Projects/ProjectCardBadge.vue';
import { useI18n } from '@/composables/useI18n';
const CREDENTIAL_LIST_ITEM_ACTIONS = {
OPEN: 'open',
DELETE: 'delete',
MOVE: 'move',
};
const props = withDefaults(
defineProps<{
data: ICredentialsResponse;
readOnly: boolean;
}>(),
{
data: () => ({
id: '',
createdAt: '',
updatedAt: '',
type: '',
name: '',
sharedWithProjects: [],
homeProject: {} as ProjectSharingData,
}),
readOnly: false,
},
);
const locale = useI18n();
const message = useMessage();
const uiStore = useUIStore();
const credentialsStore = useCredentialsStore();
const projectsStore = useProjectsStore();
const credentialType = computed(() => credentialsStore.getCredentialTypeByName(props.data.type));
const credentialPermissions = computed(() => getCredentialPermissions(props.data));
const actions = computed(() => {
const items = [
{
label: locale.baseText('credentials.item.open'),
value: CREDENTIAL_LIST_ITEM_ACTIONS.OPEN,
},
];
if (credentialPermissions.value.delete) {
items.push({
label: locale.baseText('credentials.item.delete'),
value: CREDENTIAL_LIST_ITEM_ACTIONS.DELETE,
});
}
if (credentialPermissions.value.move) {
items.push({
label: locale.baseText('credentials.item.move'),
value: CREDENTIAL_LIST_ITEM_ACTIONS.MOVE,
});
}
return items;
});
const formattedCreatedAtDate = computed(() => {
const currentYear = new Date().getFullYear().toString();
return dateformat(
props.data.createdAt,
`d mmmm${props.data.createdAt.startsWith(currentYear) ? '' : ', yyyy'}`,
);
});
function onClick() {
uiStore.openExistingCredential(props.data.id);
}
async function onAction(action: string) {
switch (action) {
case CREDENTIAL_LIST_ITEM_ACTIONS.OPEN:
onClick();
break;
case CREDENTIAL_LIST_ITEM_ACTIONS.DELETE:
await deleteResource();
break;
case CREDENTIAL_LIST_ITEM_ACTIONS.MOVE:
moveResource();
break;
}
}
async function deleteResource() {
const deleteConfirmed = await message.confirm(
locale.baseText('credentialEdit.credentialEdit.confirmMessage.deleteCredential.message', {
interpolate: { savedCredentialName: props.data.name },
}),
locale.baseText('credentialEdit.credentialEdit.confirmMessage.deleteCredential.headline'),
{
confirmButtonText: locale.baseText(
'credentialEdit.credentialEdit.confirmMessage.deleteCredential.confirmButtonText',
),
},
);
if (deleteConfirmed === MODAL_CONFIRM) {
await credentialsStore.deleteCredential({ id: props.data.id });
}
}
function moveResource() {
uiStore.openModalWithData({
name: PROJECT_MOVE_RESOURCE_MODAL,
data: {
resource: props.data,
resourceType: locale.baseText('generic.credential').toLocaleLowerCase(),
},
});
}
</script>
<template>
<n8n-card :class="$style.cardLink" @click="onClick">
<template #prepend>
@@ -20,151 +149,19 @@
</n8n-text>
</div>
<template #append>
<ProjectCardBadge :resource="data" :personal-project="projectsStore.personalProject" />
<div ref="cardActions" :class="$style.cardActions">
<n8n-action-toggle :actions="actions" theme="dark" @action="onAction" @click.stop />
<div :class="$style.cardActions" @click.stop>
<ProjectCardBadge :resource="data" :personal-project="projectsStore.personalProject" />
<n8n-action-toggle
data-test-id="credential-card-actions"
:actions="actions"
theme="dark"
@action="onAction"
/>
</div>
</template>
</n8n-card>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import type { PropType } from 'vue';
import type { ICredentialsResponse, IUser } from '@/Interface';
import type { ICredentialType } from 'n8n-workflow';
import { MODAL_CONFIRM } from '@/constants';
import { useMessage } from '@/composables/useMessage';
import CredentialIcon from '@/components/CredentialIcon.vue';
import type { PermissionsMap } from '@/permissions';
import { getCredentialPermissions } from '@/permissions';
import dateformat from 'dateformat';
import { mapStores } from 'pinia';
import { useUIStore } from '@/stores/ui.store';
import { useUsersStore } from '@/stores/users.store';
import { useCredentialsStore } from '@/stores/credentials.store';
import TimeAgo from '@/components/TimeAgo.vue';
import type { ProjectSharingData } from '@/types/projects.types';
import { useProjectsStore } from '@/stores/projects.store';
import type { CredentialScope } from '@n8n/permissions';
import ProjectCardBadge from '@/components/Projects/ProjectCardBadge.vue';
export const CREDENTIAL_LIST_ITEM_ACTIONS = {
OPEN: 'open',
DELETE: 'delete',
};
export default defineComponent({
components: {
TimeAgo,
CredentialIcon,
ProjectCardBadge,
},
props: {
data: {
type: Object as PropType<ICredentialsResponse>,
required: true,
default: (): ICredentialsResponse => ({
id: '',
createdAt: '',
updatedAt: '',
type: '',
name: '',
sharedWithProjects: [],
homeProject: {} as ProjectSharingData,
}),
},
readonly: {
type: Boolean,
default: false,
},
},
setup() {
return {
...useMessage(),
};
},
computed: {
...mapStores(useCredentialsStore, useUIStore, useUsersStore, useProjectsStore),
currentUser(): IUser | null {
return this.usersStore.currentUser;
},
credentialType(): ICredentialType | undefined {
return this.credentialsStore.getCredentialTypeByName(this.data.type);
},
credentialPermissions(): PermissionsMap<CredentialScope> | null {
return !this.currentUser ? null : getCredentialPermissions(this.data);
},
actions(): Array<{ label: string; value: string }> {
if (!this.credentialPermissions) {
return [];
}
return [
{
label: this.$locale.baseText('credentials.item.open'),
value: CREDENTIAL_LIST_ITEM_ACTIONS.OPEN,
},
].concat(
this.credentialPermissions.delete
? [
{
label: this.$locale.baseText('credentials.item.delete'),
value: CREDENTIAL_LIST_ITEM_ACTIONS.DELETE,
},
]
: [],
);
},
formattedCreatedAtDate(): string {
const currentYear = new Date().getFullYear().toString();
return dateformat(
this.data.createdAt,
`d mmmm${this.data.createdAt.startsWith(currentYear) ? '' : ', yyyy'}`,
);
},
},
methods: {
async onClick(event: Event) {
const cardActionsEl = this.$refs.cardActions as HTMLDivElement | undefined;
const clickTarget = event.target as HTMLElement | null;
if (cardActionsEl === clickTarget || (clickTarget && cardActionsEl?.contains(clickTarget))) {
return;
}
this.uiStore.openExistingCredential(this.data.id);
},
async onAction(action: string) {
if (action === CREDENTIAL_LIST_ITEM_ACTIONS.OPEN) {
await this.onClick(new Event('click'));
} else if (action === CREDENTIAL_LIST_ITEM_ACTIONS.DELETE) {
const deleteConfirmed = await this.confirm(
this.$locale.baseText(
'credentialEdit.credentialEdit.confirmMessage.deleteCredential.message',
{
interpolate: { savedCredentialName: this.data.name },
},
),
this.$locale.baseText(
'credentialEdit.credentialEdit.confirmMessage.deleteCredential.headline',
),
{
confirmButtonText: this.$locale.baseText(
'credentialEdit.credentialEdit.confirmMessage.deleteCredential.confirmButtonText',
),
},
);
if (deleteConfirmed === MODAL_CONFIRM) {
await this.credentialsStore.deleteCredential({ id: this.data.id });
}
}
},
},
});
</script>
<style lang="scss" module>
.cardLink {
transition: box-shadow 0.3s ease;