1
1
mirror of https://github.com/n8n-io/n8n.git synced 2024-09-19 08:57:09 +03:00

refactor(editor): Fix TypeScript issues in views (no-changelog) (#9573)

This commit is contained in:
Elias Meire 2024-05-31 15:52:00 +02:00 committed by GitHub
parent 327794127e
commit e23420d89d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 89 additions and 70 deletions

View File

@ -250,7 +250,7 @@ export interface IWorkflowData {
export interface IWorkflowDataUpdate {
id?: string;
name?: string;
nodes?: INode[];
nodes?: Array<INode | IWorkflowTemplateNode>;
connections?: IConnections;
settings?: IWorkflowSettings;
active?: boolean;
@ -363,6 +363,7 @@ export interface ICredentialsResponse extends ICredentialsEncrypted {
homeProject?: ProjectSharingData;
currentUserHasAccess?: boolean;
scopes?: Scope[];
ownedBy?: Pick<IUserResponse, 'id' | 'firstName' | 'lastName' | 'email'>;
}
export interface ICredentialsBase {

View File

@ -64,7 +64,7 @@ export default function useCanvasMouseSelect() {
selectActive.value = false;
}
function _getSelectionBox(event: MouseEvent) {
function _getSelectionBox(event: MouseEvent | TouchEvent) {
const [x, y] = getMousePositionWithinNodeView(event);
return {
x: Math.min(x, selectBox.value.x),
@ -74,7 +74,7 @@ export default function useCanvasMouseSelect() {
};
}
function _getNodesInSelection(event: MouseEvent): INodeUi[] {
function _getNodesInSelection(event: MouseEvent | TouchEvent): INodeUi[] {
const returnNodes: INodeUi[] = [];
const selectionBox = _getSelectionBox(event);
@ -128,9 +128,9 @@ export default function useCanvasMouseSelect() {
_updateSelectBox(e);
}
function mouseUpMouseSelect(e: MouseEvent) {
function mouseUpMouseSelect(e: MouseEvent | TouchEvent) {
// Ignore right-click
if (e.button === 2 || isContextMenuOpen.value) return;
if (('button' in e && e.button === 2) || isContextMenuOpen.value) return;
if (!selectActive.value) {
if (isTouchDevice && e.target instanceof HTMLElement) {

View File

@ -212,7 +212,7 @@ export const useUIStore = defineStore(STORES.UI, {
const settingsStore = useSettingsStore();
const deploymentType = settingsStore.deploymentType;
let contextKey = '';
let contextKey: '' | '.cloud' | '.desktop' = '';
if (deploymentType === 'cloud') {
contextKey = '.cloud';
} else if (deploymentType === 'desktop_mac' || deploymentType === 'desktop_win') {
@ -266,7 +266,7 @@ export const useUIStore = defineStore(STORES.UI, {
},
},
},
};
} as const;
},
getLastSelectedNode(): INodeUi | null {
const workflowsStore = useWorkflowsStore();

View File

@ -16,6 +16,7 @@ import { NodeConnectionType } from 'n8n-workflow';
import type { BrowserJsPlumbInstance } from '@jsplumb/browser-ui';
import { EVENT_CONNECTION_MOUSEOUT, EVENT_CONNECTION_MOUSEOVER } from '@jsplumb/browser-ui';
import { useUIStore } from '@/stores/ui.store';
import type { StyleValue } from 'vue';
/*
Canvas constants and functions.
@ -609,7 +610,7 @@ export const getBackgroundStyles = (
scale: number,
offsetPosition: XYPosition,
executionPreview: boolean,
) => {
): StyleValue => {
const squareSize = GRID_SIZE * scale;
const dotSize = 1 * scale;
const dotPosition = (GRID_SIZE / 2) * scale;
@ -623,7 +624,7 @@ export const getBackgroundStyles = (
};
}
const styles: object = {
const styles: StyleValue = {
'background-size': `${squareSize}px ${squareSize}px`,
'background-position': `left ${offsetPosition[0]}px top ${offsetPosition[1]}px`,
};

View File

@ -22,10 +22,11 @@
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { type PropType, defineComponent } from 'vue';
import Logo from '@/components/Logo.vue';
import SSOLogin from '@/components/SSOLogin.vue';
import type { IFormBoxConfig } from '@/Interface';
export default defineComponent({
name: 'AuthView',
@ -34,7 +35,9 @@ export default defineComponent({
SSOLogin,
},
props: {
form: {},
form: {
type: Object as PropType<IFormBoxConfig>,
},
formLoading: {
type: Boolean,
default: false,

View File

@ -18,20 +18,22 @@
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import type { BaseTextKey } from '@/plugins/i18n';
import { type PropType, defineComponent } from 'vue';
export default defineComponent({
name: 'ErrorView',
props: {
messageKey: {
type: String,
type: String as PropType<BaseTextKey>,
required: true,
},
errorCode: {
type: Number,
},
redirectTextKey: {
type: String,
type: String as PropType<BaseTextKey>,
required: true,
},
redirectPage: {
type: String,

View File

@ -4,7 +4,7 @@
<n8n-heading size="2xlarge">
{{ $locale.baseText('settings.api') }}
<span :style="{ fontSize: 'var(--font-size-s)', color: 'var(--color-text-light)' }">
({{ $locale.baseText('beta') }})
({{ $locale.baseText('generic.beta') }})
</span>
</n8n-heading>
</div>

View File

@ -4,7 +4,7 @@
<n8n-heading size="2xlarge">{{
i18n.baseText('settings.personal.personalSettings')
}}</n8n-heading>
<div :class="$style.user">
<div v-if="currentUser" :class="$style.user">
<span :class="$style.username" data-test-id="current-user-name">
<n8n-text color="text-light">{{ currentUser.fullName }}</n8n-text>
</span>

View File

@ -219,7 +219,7 @@ const onSelectSshKeyType = async (sshKeyType: TupleToUnion<SshKeyTypes>) => {
:validation-rules="repoUrlValidationRules"
:disabled="isConnected"
:placeholder="locale.baseText('settings.sourceControl.repoUrlPlaceholder')"
@validate="(value) => onValidate('repoUrl', value)"
@validate="(value: boolean) => onValidate('repoUrl', value)"
/>
<n8n-button
v-if="isConnected"
@ -248,7 +248,7 @@ const onSelectSshKeyType = async (sshKeyType: TupleToUnion<SshKeyTypes>) => {
:validation-rules="keyGeneratorTypeValidationRules"
:options="sourceControlStore.sshKeyTypesWithLabel"
:model-value="sourceControlStore.preferences.keyGeneratorType"
@validate="(value) => onValidate('keyGeneratorType', value)"
@validate="(value: boolean) => onValidate('keyGeneratorType', value)"
@update:model-value="onSelectSshKeyType"
/>
<CopyInput
@ -309,7 +309,7 @@ const onSelectSshKeyType = async (sshKeyType: TupleToUnion<SshKeyTypes>) => {
:validation-rules="branchNameValidationRules"
:options="branchNameOptions"
:model-value="sourceControlStore.preferences.branchName"
@validate="(value) => onValidate('branchName', value)"
@validate="(value: boolean) => onValidate('branchName', value)"
@update:model-value="onSelect"
/>
<n8n-tooltip placement="top">

View File

@ -84,7 +84,7 @@ import { defineComponent } from 'vue';
import { mapStores } from 'pinia';
import { EnterpriseEditionFeature, INVITE_USER_MODAL_KEY, VIEWS, ROLE } from '@/constants';
import type { IUser, IUserListAction, InvitableRoleName } from '@/Interface';
import type { IRole, IUser, IUserListAction, InvitableRoleName } from '@/Interface';
import { useToast } from '@/composables/useToast';
import { useUIStore } from '@/stores/ui.store';
import { useSettingsStore } from '@/stores/settings.store';

View File

@ -15,7 +15,7 @@
<script lang="ts">
import { defineComponent } from 'vue';
import type { Route } from 'vue-router';
import type { RouteLocationPathRaw } from 'vue-router';
import { VIEWS } from '@/constants';
import SettingsSidebar from '@/components/SettingsSidebar.vue';
@ -32,7 +32,7 @@ const SettingsView = defineComponent({
},
data() {
return {
previousRoute: null as Route | null,
previousRoute: null as RouteLocationPathRaw | null,
};
},
methods: {

View File

@ -101,9 +101,9 @@ export default defineComponent({
},
computed: {
...mapStores(useUIStore, useUsersStore),
inviteMessage(): null | string {
inviteMessage(): string {
if (!this.inviter) {
return null;
return '';
}
return this.$locale.baseText('settings.signup.signUpInviterInfo', {

View File

@ -53,15 +53,18 @@ const canRender = ref(true);
const isListLoading = ref(true);
const requestNumberOfItems = ref(20);
const lastReceivedItemsLength = ref(0);
const editorRoute = computed(() => ({
name: VIEWS.WORKFLOW,
params: {
name: route.params.workflowId,
},
}));
const activeWorkflow = ref<IWorkflowDb | null>(null);
const workflowHistory = ref<WorkflowHistory[]>([]);
const activeWorkflowVersion = ref<WorkflowVersion | null>(null);
const workflowId = computed(() => normalizeSingleRouteParam('workflowId'));
const versionId = computed(() => normalizeSingleRouteParam('versionId'));
const editorRoute = computed(() => ({
name: VIEWS.WORKFLOW,
params: {
name: workflowId.value,
},
}));
const actions = computed<UserAction[]>(() =>
workflowHistoryActionTypes.map((value) => ({
label: i18n.baseText(`workflowHistory.item.actions.${value}`),
@ -70,23 +73,18 @@ const actions = computed<UserAction[]>(() =>
})),
);
const isFirstItemShown = computed(
() => workflowHistory.value[0]?.versionId === route.params.versionId,
);
const isFirstItemShown = computed(() => workflowHistory.value[0]?.versionId === versionId.value);
const evaluatedPruneTime = computed(() => Math.floor(workflowHistoryStore.evaluatedPruneTime / 24));
const sendTelemetry = (event: string) => {
telemetry.track(event, {
instance_id: useRootStore().instanceId,
workflow_id: route.params.workflowId,
workflow_id: workflowId.value,
});
};
const loadMore = async (queryParams: WorkflowHistoryRequestParams) => {
const history = await workflowHistoryStore.getWorkflowHistory(
route.params.workflowId,
queryParams,
);
const history = await workflowHistoryStore.getWorkflowHistory(workflowId.value, queryParams);
lastReceivedItemsLength.value = history.length;
workflowHistory.value = workflowHistory.value.concat(history);
};
@ -95,17 +93,17 @@ onBeforeMount(async () => {
sendTelemetry('User opened workflow history');
try {
const [workflow] = await Promise.all([
workflowsStore.fetchWorkflow(route.params.workflowId),
workflowsStore.fetchWorkflow(workflowId.value),
loadMore({ take: requestNumberOfItems.value }),
]);
activeWorkflow.value = workflow;
isListLoading.value = false;
if (!route.params.versionId && workflowHistory.value.length) {
if (!versionId.value && workflowHistory.value.length) {
await router.replace({
name: VIEWS.WORKFLOW_HISTORY,
params: {
workflowId: route.params.workflowId,
workflowId: workflowId.value,
versionId: workflowHistory.value[0].versionId,
},
});
@ -116,11 +114,17 @@ onBeforeMount(async () => {
}
});
const normalizeSingleRouteParam = (name: string): string => {
const param = route.params[name];
if (typeof param === 'string') return param;
return param?.[0] ?? '';
};
const openInNewTab = (id: WorkflowVersionId) => {
const { href } = router.resolve({
name: VIEWS.WORKFLOW_HISTORY,
params: {
workflowId: route.params.workflowId,
workflowId: workflowId.value,
versionId: id,
},
});
@ -183,7 +187,7 @@ const cloneWorkflowVersion = async (
data: { formattedCreatedAt: string },
) => {
const clonedWorkflow = await workflowHistoryStore.cloneIntoNewWorkflow(
route.params.workflowId,
workflowId.value,
id,
data,
);
@ -210,17 +214,17 @@ const restoreWorkflowVersion = async (
id: WorkflowVersionId,
data: { formattedCreatedAt: string },
) => {
const workflow = await workflowsStore.fetchWorkflow(route.params.workflowId);
const workflow = await workflowsStore.fetchWorkflow(workflowId.value);
const modalAction = await openRestorationModal(workflow.active, data.formattedCreatedAt);
if (modalAction === WorkflowHistoryVersionRestoreModalActions.cancel) {
return;
}
activeWorkflow.value = await workflowHistoryStore.restoreWorkflow(
route.params.workflowId,
workflowId.value,
id,
modalAction === WorkflowHistoryVersionRestoreModalActions.deactivateAndRestore,
);
const history = await workflowHistoryStore.getWorkflowHistory(route.params.workflowId, {
const history = await workflowHistoryStore.getWorkflowHistory(workflowId.value, {
take: 1,
});
workflowHistory.value = history.concat(workflowHistory.value);
@ -246,7 +250,7 @@ const onAction = async ({
sendTelemetry('User opened version in new tab');
break;
case WORKFLOW_HISTORY_ACTIONS.DOWNLOAD:
await workflowHistoryStore.downloadVersion(route.params.workflowId, id, data);
await workflowHistoryStore.downloadVersion(workflowId.value, id, data);
sendTelemetry('User downloaded version');
break;
case WORKFLOW_HISTORY_ACTIONS.CLONE:
@ -278,7 +282,7 @@ const onPreview = async ({ event, id }: { event: MouseEvent; id: WorkflowVersion
await router.push({
name: VIEWS.WORKFLOW_HISTORY,
params: {
workflowId: route.params.workflowId,
workflowId: workflowId.value,
versionId: id,
},
});
@ -290,24 +294,24 @@ const onUpgrade = () => {
};
watchEffect(async () => {
if (!route.params.versionId) {
if (!versionId.value) {
return;
}
try {
activeWorkflowVersion.value = await workflowHistoryStore.getWorkflowVersion(
route.params.workflowId,
route.params.versionId,
workflowId.value,
versionId.value,
);
sendTelemetry('User selected version');
} catch (error) {
toast.showError(
new Error(`${error.message} "${route.params.versionId}"&nbsp;`),
new Error(`${error.message} "${versionId.value}"&nbsp;`),
i18n.baseText('workflowHistory.title'),
);
}
try {
activeWorkflow.value = await workflowsStore.fetchWorkflow(route.params.workflowId);
activeWorkflow.value = await workflowsStore.fetchWorkflow(workflowId.value);
} catch (error) {
canRender.value = false;
toast.showError(error, i18n.baseText('workflowHistory.title'));

View File

@ -9,7 +9,7 @@ import { useRoute, useRouter } from 'vue-router';
const loadingService = useLoadingService();
const templateStore = useTemplatesStore();
const workfowStore = useWorkflowsStore();
const workflowsStore = useWorkflowsStore();
const router = useRouter();
const route = useRoute();
const i18n = useI18n();
@ -26,7 +26,7 @@ const openWorkflowTemplate = async (templateId: string) => {
interpolate: { name: template.name },
});
const workflow = await workfowStore.createNewWorkflow({
const workflow = await workflowsStore.createNewWorkflow({
name,
connections: template.workflow.connections,
nodes: template.workflow.nodes,

View File

@ -67,12 +67,11 @@
<div class="text-center mt-s">
<n8n-heading tag="h2" size="xlarge" class="mb-2xs">
{{
$locale.baseText(
currentUser.firstName
? 'workflows.empty.heading'
: 'workflows.empty.heading.userNotSetup',
{ interpolate: { name: currentUser.firstName } },
)
currentUser.firstName
? $locale.baseText('workflows.empty.heading', {
interpolate: { name: currentUser.firstName },
})
: $locale.baseText('workflows.empty.heading.userNotSetup')
}}
</n8n-heading>
<n8n-text size="large" color="text-base">
@ -178,13 +177,13 @@ import { useSourceControlStore } from '@/stores/sourceControl.store';
import { useTagsStore } from '@/stores/tags.store';
import { useProjectsStore } from '@/features/projects/projects.store';
import ProjectTabs from '@/features/projects/components/ProjectTabs.vue';
import { useTemplatesStore } from '@/stores/templates.store';
type IResourcesListLayoutInstance = InstanceType<typeof ResourcesListLayout>;
interface Filters {
search: string;
ownedBy: string;
sharedWith: string;
homeProject: string;
status: string | boolean;
tags: string[];
}
@ -210,9 +209,9 @@ const WorkflowsView = defineComponent({
filters: {
search: '',
homeProject: '',
status: StatusFilter.ALL as string | boolean,
tags: [] as string[],
},
status: StatusFilter.ALL,
tags: [],
} as Filters,
sourceControlStoreUnsubscribe: () => {},
};
},
@ -225,6 +224,7 @@ const WorkflowsView = defineComponent({
useSourceControlStore,
useTagsStore,
useProjectsStore,
useTemplatesStore,
),
readOnlyEnv(): boolean {
return this.sourceControlStore.preferences.branchReadOnly;
@ -258,10 +258,18 @@ const WorkflowsView = defineComponent({
return this.uiStore.suggestedTemplates;
},
userRole() {
const userRole: string | undefined =
this.usersStore.currentUserCloudInfo?.role ??
this.usersStore.currentUser?.personalizationAnswers?.role;
return userRole;
const role = this.usersStore.currentUserCloudInfo?.role;
if (role) {
return role;
}
const answers = this.usersStore.currentUser?.personalizationAnswers;
if (answers && 'role' in answers) {
return answers.role;
}
return undefined;
},
isSalesUser() {
if (!this.userRole) {