From f64ca621e1a924a4da96dd537315b602e33dd1d4 Mon Sep 17 00:00:00 2001 From: Ricardo Espinoza Date: Wed, 3 Jul 2024 15:11:40 -0400 Subject: [PATCH] refactor(editor): Migrate `ui.store` to use composition API (no-changelog) (#9892) --- packages/editor-ui/src/Interface.ts | 36 - .../CredentialEdit/CredentialEdit.vue | 4 +- .../src/components/FeatureComingSoon.vue | 2 +- .../src/components/GenerateCurlModal.vue | 4 +- .../src/components/ImportCurlModal.vue | 2 +- .../editor-ui/src/components/InputPanel.vue | 2 +- .../components/MainHeader/WorkflowDetails.vue | 2 +- packages/editor-ui/src/components/Modal.vue | 6 +- .../editor-ui/src/components/ModalDrawer.vue | 6 +- .../editor-ui/src/components/ModalRoot.vue | 12 +- packages/editor-ui/src/components/Node.vue | 2 +- .../src/components/NodeDetailsView.vue | 4 +- .../src/components/NodeExecuteButton.vue | 2 +- .../editor-ui/src/components/OutputPanel.vue | 2 +- .../ProjectMoveResourceConfirmModal.test.ts | 4 +- .../src/components/SettingsSidebar.vue | 4 +- .../components/SourceControlPushModal.ee.vue | 2 +- packages/editor-ui/src/components/Sticky.vue | 2 +- .../editor-ui/src/components/TriggerPanel.vue | 2 +- .../src/components/WorkflowLMChat.vue | 2 +- .../__tests__/ChatEmbedModal.test.ts | 2 +- .../CommunityPackageInstallModal.spec.ts | 2 +- .../__tests__/PersonalizationModal.spec.ts | 2 +- .../__tests__/WorkflowSettings.spec.ts | 2 +- .../buttons/CanvasExecuteWorkflowButton.vue | 2 +- .../__tests__/useCanvasPanning.test.ts | 4 +- .../composables/__tests__/useNodeBase.spec.ts | 10 +- .../__tests__/useRunWorkflow.spec.ts | 16 +- .../src/composables/useCanvasMouseSelect.ts | 2 +- .../src/composables/useCanvasPanning.ts | 4 +- .../editor-ui/src/composables/useNodeBase.ts | 6 +- .../src/composables/usePushConnection.ts | 6 +- .../src/composables/useRunWorkflow.ts | 2 +- .../editor-ui/src/composables/useToast.ts | 2 +- .../hooks/utils/hooksAddFakeDoorFeatures.ts | 8 +- .../completions/__tests__/completions.test.ts | 4 +- .../plugins/codemirror/completions/utils.ts | 2 +- .../editor-ui/src/stores/__tests__/ui.test.ts | 6 +- packages/editor-ui/src/stores/canvas.store.ts | 6 +- packages/editor-ui/src/stores/ui.store.ts | 1165 +++++++++-------- packages/editor-ui/src/views/NodeView.vue | 2 +- .../src/views/SettingsFakeDoorView.vue | 2 +- 42 files changed, 710 insertions(+), 649 deletions(-) diff --git a/packages/editor-ui/src/Interface.ts b/packages/editor-ui/src/Interface.ts index f888e5b4d6..0f986ab947 100644 --- a/packages/editor-ui/src/Interface.ts +++ b/packages/editor-ui/src/Interface.ts @@ -6,7 +6,6 @@ import type { TRIGGER_NODE_CREATOR_VIEW, REGULAR_NODE_CREATOR_VIEW, AI_OTHERS_NODE_CREATOR_VIEW, - VIEWS, ROLE, } from '@/constants'; import type { IMenuItem, NodeCreatorTag } from 'n8n-design-system'; @@ -1264,41 +1263,6 @@ export interface NotificationOptions extends Partial message: string | ElementNotificationOptions['message']; } -export interface UIState { - activeActions: string[]; - activeCredentialType: string | null; - sidebarMenuCollapsed: boolean; - modalStack: string[]; - modals: Modals; - isPageLoading: boolean; - currentView: string; - mainPanelPosition: number; - fakeDoorFeatures: IFakeDoor[]; - draggable: { - isDragging: boolean; - type: string; - data: string; - canDrop: boolean; - stickyPosition: null | XYPosition; - }; - stateIsDirty: boolean; - lastSelectedNode: string | null; - lastSelectedNodeOutputIndex: number | null; - lastSelectedNodeEndpointUuid: string | null; - nodeViewOffsetPosition: XYPosition; - nodeViewMoveInProgress: boolean; - selectedNodes: INodeUi[]; - nodeViewInitialized: boolean; - addFirstStepOnLoad: boolean; - bannersHeight: number; - bannerStack: BannerName[]; - theme: ThemeOption; - pendingNotificationsForViews: { - [key in VIEWS]?: NotificationOptions[]; - }; - isCreateNodeActive: boolean; -} - export type IFakeDoor = { id: FAKE_DOOR_FEATURES; featureName: BaseTextKey; diff --git a/packages/editor-ui/src/components/CredentialEdit/CredentialEdit.vue b/packages/editor-ui/src/components/CredentialEdit/CredentialEdit.vue index d6cef425a7..ef905d9d7c 100644 --- a/packages/editor-ui/src/components/CredentialEdit/CredentialEdit.vue +++ b/packages/editor-ui/src/components/CredentialEdit/CredentialEdit.vue @@ -448,8 +448,8 @@ const showSharingContent = computed(() => activeTab.value === 'sharing' && !!cre onMounted(async () => { requiredCredentials.value = - isCredentialModalState(uiStore.modals[CREDENTIAL_EDIT_MODAL_KEY]) && - uiStore.modals[CREDENTIAL_EDIT_MODAL_KEY].showAuthSelector === true; + isCredentialModalState(uiStore.modalsById[CREDENTIAL_EDIT_MODAL_KEY]) && + uiStore.modalsById[CREDENTIAL_EDIT_MODAL_KEY].showAuthSelector === true; if (props.mode === 'new' && credentialTypeName.value) { credentialName.value = await credentialsStore.getNewCredentialName({ diff --git a/packages/editor-ui/src/components/FeatureComingSoon.vue b/packages/editor-ui/src/components/FeatureComingSoon.vue index 9ed6dc2d80..3dcf1e58b1 100644 --- a/packages/editor-ui/src/components/FeatureComingSoon.vue +++ b/packages/editor-ui/src/components/FeatureComingSoon.vue @@ -56,7 +56,7 @@ export default defineComponent({ return this.rootStore.instanceId; }, featureInfo(): IFakeDoor | undefined { - return this.uiStore.getFakeDoorById(this.featureId); + return this.uiStore.fakeDoorsById[this.featureId]; }, }, methods: { diff --git a/packages/editor-ui/src/components/GenerateCurlModal.vue b/packages/editor-ui/src/components/GenerateCurlModal.vue index b90bd97bcd..ef590ea4ca 100644 --- a/packages/editor-ui/src/components/GenerateCurlModal.vue +++ b/packages/editor-ui/src/components/GenerateCurlModal.vue @@ -61,8 +61,8 @@ const ndvStore = useNDVStore(); const modalBus = createEventBus(); const formBus = createEventBus(); -const initialServiceValue = uiStore.getModalData(GENERATE_CURL_MODAL_KEY)?.service as string; -const initialRequestValue = uiStore.getModalData(GENERATE_CURL_MODAL_KEY)?.request as string; +const initialServiceValue = uiStore.modalsById[GENERATE_CURL_MODAL_KEY].data?.service as string; +const initialRequestValue = uiStore.modalsById[GENERATE_CURL_MODAL_KEY].data?.request as string; const formInputs: IFormInput[] = [ { diff --git a/packages/editor-ui/src/components/ImportCurlModal.vue b/packages/editor-ui/src/components/ImportCurlModal.vue index 12e6a32b3e..77939b5032 100644 --- a/packages/editor-ui/src/components/ImportCurlModal.vue +++ b/packages/editor-ui/src/components/ImportCurlModal.vue @@ -66,7 +66,7 @@ const { importCurlCommand } = useImportCurlCommand({ }); onMounted(() => { - curlCommand.value = (uiStore.getModalData(IMPORT_CURL_MODAL_KEY)?.curlCommand as string) ?? ''; + curlCommand.value = (uiStore.modalsById[IMPORT_CURL_MODAL_KEY].data?.curlCommand as string) ?? ''; setTimeout(() => { inputRef.value?.focus(); diff --git a/packages/editor-ui/src/components/InputPanel.vue b/packages/editor-ui/src/components/InputPanel.vue index e65af781ab..3b7b1432ac 100644 --- a/packages/editor-ui/src/components/InputPanel.vue +++ b/packages/editor-ui/src/components/InputPanel.vue @@ -304,7 +304,7 @@ export default defineComponent({ return false; }, workflowRunning(): boolean { - return this.uiStore.isActionActive('workflowRunning'); + return this.uiStore.isActionActive['workflowRunning']; }, activeNode(): INodeUi | null { diff --git a/packages/editor-ui/src/components/MainHeader/WorkflowDetails.vue b/packages/editor-ui/src/components/MainHeader/WorkflowDetails.vue index 1b172b700d..9cda6ebc9d 100644 --- a/packages/editor-ui/src/components/MainHeader/WorkflowDetails.vue +++ b/packages/editor-ui/src/components/MainHeader/WorkflowDetails.vue @@ -111,7 +111,7 @@ const isNewWorkflow = computed(() => { }); const isWorkflowSaving = computed(() => { - return uiStore.isActionActive('workflowSaving'); + return uiStore.isActionActive['workflowSaving']; }); const onWorkflowPage = computed(() => { diff --git a/packages/editor-ui/src/components/Modal.vue b/packages/editor-ui/src/components/Modal.vue index 5f15462402..7a26314ad0 100644 --- a/packages/editor-ui/src/components/Modal.vue +++ b/packages/editor-ui/src/components/Modal.vue @@ -1,6 +1,6 @@ diff --git a/packages/editor-ui/src/components/Node.vue b/packages/editor-ui/src/components/Node.vue index 530b6a47ba..40d6103ac2 100644 --- a/packages/editor-ui/src/components/Node.vue +++ b/packages/editor-ui/src/components/Node.vue @@ -592,7 +592,7 @@ export default defineComponent({ return undefined; }, workflowRunning(): boolean { - return this.uiStore.isActionActive('workflowRunning'); + return this.uiStore.isActionActive['workflowRunning']; }, nodeStyle() { const returnStyles: { diff --git a/packages/editor-ui/src/components/NodeDetailsView.vue b/packages/editor-ui/src/components/NodeDetailsView.vue index ab3d61379a..785a1ad5cd 100644 --- a/packages/editor-ui/src/components/NodeDetailsView.vue +++ b/packages/editor-ui/src/components/NodeDetailsView.vue @@ -242,7 +242,7 @@ const activeNodeType = computed(() => { return null; }); -const workflowRunning = computed(() => uiStore.isActionActive('workflowRunning')); +const workflowRunning = computed(() => uiStore.isActionActive['workflowRunning']); const showTriggerWaitingWarning = computed( () => @@ -432,7 +432,7 @@ const featureRequestUrl = computed(() => { const outputPanelEditMode = computed(() => ndvStore.outputPanelEditMode); -const isWorkflowRunning = computed(() => uiStore.isActionActive('workflowRunning')); +const isWorkflowRunning = computed(() => uiStore.isActionActive['workflowRunning']); const isExecutionWaitingForWebhook = computed(() => workflowsStore.executionWaitingForWebhook); diff --git a/packages/editor-ui/src/components/NodeExecuteButton.vue b/packages/editor-ui/src/components/NodeExecuteButton.vue index 234309fca4..1a70354735 100644 --- a/packages/editor-ui/src/components/NodeExecuteButton.vue +++ b/packages/editor-ui/src/components/NodeExecuteButton.vue @@ -128,7 +128,7 @@ export default defineComponent({ ); }, workflowRunning(): boolean { - return this.uiStore.isActionActive('workflowRunning'); + return this.uiStore.isActionActive['workflowRunning']; }, isTriggerNode(): boolean { if (!this.node) { diff --git a/packages/editor-ui/src/components/OutputPanel.vue b/packages/editor-ui/src/components/OutputPanel.vue index 083e892018..9fb343a36c 100644 --- a/packages/editor-ui/src/components/OutputPanel.vue +++ b/packages/editor-ui/src/components/OutputPanel.vue @@ -230,7 +230,7 @@ export default defineComponent({ return !!this.node && this.workflowsStore.isNodeExecuting(this.node.name); }, workflowRunning(): boolean { - return this.uiStore.isActionActive('workflowRunning'); + return this.uiStore.isActionActive['workflowRunning']; }, workflowExecution(): IExecutionResponse | null { return this.workflowsStore.getWorkflowExecution; diff --git a/packages/editor-ui/src/components/Projects/ProjectMoveResourceConfirmModal.test.ts b/packages/editor-ui/src/components/Projects/ProjectMoveResourceConfirmModal.test.ts index c3d33f14e2..a81e412685 100644 --- a/packages/editor-ui/src/components/Projects/ProjectMoveResourceConfirmModal.test.ts +++ b/packages/editor-ui/src/components/Projects/ProjectMoveResourceConfirmModal.test.ts @@ -8,7 +8,9 @@ import { useTelemetry } from '@/composables/useTelemetry'; vi.mock('@/stores/ui.store', () => ({ useUIStore: vi.fn().mockReturnValue({ - isModalOpen: vi.fn().mockReturnValue(() => true), + modalsById: vi.fn().mockReturnValue(() => { + open: true; + }), closeModal: vi.fn(), }), })); diff --git a/packages/editor-ui/src/components/SettingsSidebar.vue b/packages/editor-ui/src/components/SettingsSidebar.vue index 9b3d7ddcc5..127e8849ac 100644 --- a/packages/editor-ui/src/components/SettingsSidebar.vue +++ b/packages/editor-ui/src/components/SettingsSidebar.vue @@ -46,7 +46,9 @@ export default defineComponent({ computed: { ...mapStores(useRootStore, useSettingsStore, useUIStore), settingsFakeDoorFeatures(): IFakeDoor[] { - return this.uiStore.getFakeDoorByLocation('settings'); + return Object.keys(this.uiStore.fakeDoorsByLocation) + .filter((location: string) => location.includes('settings')) + .map((location) => this.uiStore.fakeDoorsByLocation[location]); }, sidebarMenuItems(): IMenuItem[] { const menuItems: IMenuItem[] = [ diff --git a/packages/editor-ui/src/components/SourceControlPushModal.ee.vue b/packages/editor-ui/src/components/SourceControlPushModal.ee.vue index 99165c4409..2f15af6813 100644 --- a/packages/editor-ui/src/components/SourceControlPushModal.ee.vue +++ b/packages/editor-ui/src/components/SourceControlPushModal.ee.vue @@ -141,7 +141,7 @@ function getContext() { return 'workflows'; } else if ( route.fullPath.startsWith('/credentials') || - uiStore.modals[CREDENTIAL_EDIT_MODAL_KEY].open + uiStore.modalsById[CREDENTIAL_EDIT_MODAL_KEY].open ) { return 'credentials'; } else if (route.fullPath.startsWith('/workflow/')) { diff --git a/packages/editor-ui/src/components/Sticky.vue b/packages/editor-ui/src/components/Sticky.vue index 75320791f5..38b3cc1620 100644 --- a/packages/editor-ui/src/components/Sticky.vue +++ b/packages/editor-ui/src/components/Sticky.vue @@ -288,7 +288,7 @@ export default defineComponent({ ); }, workflowRunning(): boolean { - return this.uiStore.isActionActive('workflowRunning'); + return this.uiStore.isActionActive['workflowRunning']; }, }, mounted() { diff --git a/packages/editor-ui/src/components/TriggerPanel.vue b/packages/editor-ui/src/components/TriggerPanel.vue index 6782572162..612edfbc9f 100644 --- a/packages/editor-ui/src/components/TriggerPanel.vue +++ b/packages/editor-ui/src/components/TriggerPanel.vue @@ -264,7 +264,7 @@ export default defineComponent({ ); }, workflowRunning(): boolean { - return this.uiStore.isActionActive('workflowRunning'); + return this.uiStore.isActionActive['workflowRunning']; }, isActivelyPolling(): boolean { const triggeredNode = this.workflowsStore.executedNode; diff --git a/packages/editor-ui/src/components/WorkflowLMChat.vue b/packages/editor-ui/src/components/WorkflowLMChat.vue index 86e84d4a37..7ec6ef1bd2 100644 --- a/packages/editor-ui/src/components/WorkflowLMChat.vue +++ b/packages/editor-ui/src/components/WorkflowLMChat.vue @@ -219,7 +219,7 @@ export default defineComponent({ computed: { ...mapStores(useWorkflowsStore, useUIStore, useNodeTypesStore), isLoading(): boolean { - return this.uiStore.isActionActive('workflowRunning'); + return this.uiStore.isActionActive['workflowRunning']; }, }, async mounted() { diff --git a/packages/editor-ui/src/components/__tests__/ChatEmbedModal.test.ts b/packages/editor-ui/src/components/__tests__/ChatEmbedModal.test.ts index c2b0466910..418c964ec6 100644 --- a/packages/editor-ui/src/components/__tests__/ChatEmbedModal.test.ts +++ b/packages/editor-ui/src/components/__tests__/ChatEmbedModal.test.ts @@ -12,7 +12,7 @@ const renderComponent = createComponentRenderer(ChatEmbedModal, { pinia: createTestingPinia({ initialState: { [STORES.UI]: { - modals: { + modalsById: { [CHAT_EMBED_MODAL_KEY]: { open: true }, }, }, diff --git a/packages/editor-ui/src/components/__tests__/CommunityPackageInstallModal.spec.ts b/packages/editor-ui/src/components/__tests__/CommunityPackageInstallModal.spec.ts index 4e21495287..b4ec0c872e 100644 --- a/packages/editor-ui/src/components/__tests__/CommunityPackageInstallModal.spec.ts +++ b/packages/editor-ui/src/components/__tests__/CommunityPackageInstallModal.spec.ts @@ -17,7 +17,7 @@ const renderComponent = createComponentRenderer(CommunityPackageInstallModal, { pinia: createTestingPinia({ initialState: { [STORES.UI]: { - modals: { + modalsById: { [COMMUNITY_PACKAGE_INSTALL_MODAL_KEY]: { open: true }, }, }, diff --git a/packages/editor-ui/src/components/__tests__/PersonalizationModal.spec.ts b/packages/editor-ui/src/components/__tests__/PersonalizationModal.spec.ts index 4716eac590..188d6acf88 100644 --- a/packages/editor-ui/src/components/__tests__/PersonalizationModal.spec.ts +++ b/packages/editor-ui/src/components/__tests__/PersonalizationModal.spec.ts @@ -11,7 +11,7 @@ import { useUsageStore } from '@/stores/usage.store'; const pinia = createTestingPinia({ initialState: { [STORES.UI]: { - modals: { + modalsById: { [PERSONALIZATION_MODAL_KEY]: { open: true }, }, }, diff --git a/packages/editor-ui/src/components/__tests__/WorkflowSettings.spec.ts b/packages/editor-ui/src/components/__tests__/WorkflowSettings.spec.ts index 1fbb800b81..427ba49ef5 100644 --- a/packages/editor-ui/src/components/__tests__/WorkflowSettings.spec.ts +++ b/packages/editor-ui/src/components/__tests__/WorkflowSettings.spec.ts @@ -53,7 +53,7 @@ describe('WorkflowSettingsVue', () => { versionId: '123', } as IWorkflowDb); - uiStore.modals[WORKFLOW_SETTINGS_MODAL_KEY] = { + uiStore.modalsById[WORKFLOW_SETTINGS_MODAL_KEY] = { open: true, }; }); diff --git a/packages/editor-ui/src/components/canvas/elements/buttons/CanvasExecuteWorkflowButton.vue b/packages/editor-ui/src/components/canvas/elements/buttons/CanvasExecuteWorkflowButton.vue index f7128b7d97..4fb24f8c98 100644 --- a/packages/editor-ui/src/components/canvas/elements/buttons/CanvasExecuteWorkflowButton.vue +++ b/packages/editor-ui/src/components/canvas/elements/buttons/CanvasExecuteWorkflowButton.vue @@ -11,7 +11,7 @@ defineEmits<{ const uiStore = useUIStore(); const locale = useI18n(); -const workflowRunning = computed(() => uiStore.isActionActive('workflowRunning')); +const workflowRunning = computed(() => uiStore.isActionActive['workflowRunning']); const runButtonText = computed(() => { if (!workflowRunning.value) { diff --git a/packages/editor-ui/src/composables/__tests__/useCanvasPanning.test.ts b/packages/editor-ui/src/composables/__tests__/useCanvasPanning.test.ts index be826a1b17..b820e05c17 100644 --- a/packages/editor-ui/src/composables/__tests__/useCanvasPanning.test.ts +++ b/packages/editor-ui/src/composables/__tests__/useCanvasPanning.test.ts @@ -8,7 +8,7 @@ vi.mock('@/stores/ui.store', () => ({ useUIStore: vi.fn(() => ({ nodeViewOffsetPosition: [0, 0], nodeViewMoveInProgress: false, - isActionActive: vi.fn(), + isActionActive: vi.fn().mockReturnValue(() => true), })), })); @@ -62,7 +62,7 @@ describe('useCanvasPanning()', () => { vi.mocked(useUIStore).mockReturnValueOnce({ nodeViewOffsetPosition: [0, 0], nodeViewMoveInProgress: true, - isActionActive: vi.fn(), + isActionActive: vi.fn().mockReturnValue(() => true), } as unknown as ReturnType); const removeEventListenerSpy = vi.spyOn(element, 'removeEventListener'); diff --git a/packages/editor-ui/src/composables/__tests__/useNodeBase.spec.ts b/packages/editor-ui/src/composables/__tests__/useNodeBase.spec.ts index 697213bd3e..566d2ad78c 100644 --- a/packages/editor-ui/src/composables/__tests__/useNodeBase.spec.ts +++ b/packages/editor-ui/src/composables/__tests__/useNodeBase.spec.ts @@ -140,21 +140,15 @@ describe('useNodeBase', () => { it('should handle mouse left click correctly', () => { const { mouseLeftClick } = nodeBase; - const isActionActiveFn = vi.fn().mockReturnValue(false); - - // @ts-expect-error Pinia has a known issue when mocking getters, will be solved when migrating the uiStore to composition api - vi.spyOn(uiStore, 'isActionActive', 'get').mockReturnValue(isActionActiveFn); - // @ts-expect-error Pinia has a known issue when mocking getters, will be solved when migrating the uiStore to composition api - vi.spyOn(uiStore, 'isNodeSelected', 'get').mockReturnValue(() => false); - const event = new MouseEvent('click', { bubbles: true, cancelable: true, }); + uiStore.addActiveAction('notDragActive'); + mouseLeftClick(event); - expect(isActionActiveFn).toHaveBeenCalledWith('dragActive'); expect(emit).toHaveBeenCalledWith('deselectAllNodes'); expect(emit).toHaveBeenCalledWith('nodeSelected', node.name); }); diff --git a/packages/editor-ui/src/composables/__tests__/useRunWorkflow.spec.ts b/packages/editor-ui/src/composables/__tests__/useRunWorkflow.spec.ts index 3e50fb3d78..a1583280a9 100644 --- a/packages/editor-ui/src/composables/__tests__/useRunWorkflow.spec.ts +++ b/packages/editor-ui/src/composables/__tests__/useRunWorkflow.spec.ts @@ -25,14 +25,6 @@ vi.mock('@/stores/workflows.store', () => ({ }), })); -vi.mock('@/stores/ui.store', () => ({ - useUIStore: vi.fn().mockReturnValue({ - isActionActive: vi.fn().mockReturnValue(false), - addActiveAction: vi.fn(), - removeActiveAction: vi.fn(), - }), -})); - vi.mock('@/composables/useTelemetry', () => ({ useTelemetry: vi.fn().mockReturnValue({ track: vi.fn() }), })); @@ -103,6 +95,10 @@ describe('useRunWorkflow({ router })', () => { workflowHelpers = useWorkflowHelpers({ router }); }); + beforeEach(() => { + uiStore.activeActions = []; + }); + describe('runWorkflowApi()', () => { it('should throw an error if push connection is not active', async () => { const { runWorkflowApi } = useRunWorkflow({ router }); @@ -157,7 +153,7 @@ describe('useRunWorkflow({ router })', () => { describe('runWorkflow()', () => { it('should return undefined if UI action "workflowRunning" is active', async () => { const { runWorkflow } = useRunWorkflow({ router }); - vi.mocked(uiStore).isActionActive.mockReturnValue(true); + uiStore.addActiveAction('workflowRunning'); const result = await runWorkflow({}); expect(result).toBeUndefined(); }); @@ -166,7 +162,7 @@ describe('useRunWorkflow({ router })', () => { const mockExecutionResponse = { executionId: '123' }; const { runWorkflow } = useRunWorkflow({ router }); - vi.mocked(uiStore).isActionActive.mockReturnValue(false); + vi.mocked(uiStore).activeActions = ['']; vi.mocked(workflowHelpers).getCurrentWorkflow.mockReturnValue({ name: 'Test Workflow', } as unknown as Workflow); diff --git a/packages/editor-ui/src/composables/useCanvasMouseSelect.ts b/packages/editor-ui/src/composables/useCanvasMouseSelect.ts index e8f7ac52de..6beaf33d60 100644 --- a/packages/editor-ui/src/composables/useCanvasMouseSelect.ts +++ b/packages/editor-ui/src/composables/useCanvasMouseSelect.ts @@ -167,7 +167,7 @@ export default function useCanvasMouseSelect() { return; } - if (uiStore.isActionActive('dragActive')) { + if (uiStore.isActionActive['dragActive']) { // If a node does currently get dragged we do not activate the selection return; } diff --git a/packages/editor-ui/src/composables/useCanvasPanning.ts b/packages/editor-ui/src/composables/useCanvasPanning.ts index 4bf97cc809..e260424cf5 100644 --- a/packages/editor-ui/src/composables/useCanvasPanning.ts +++ b/packages/editor-ui/src/composables/useCanvasPanning.ts @@ -49,7 +49,7 @@ export function useCanvasPanning( return; } - if (uiStore.isActionActive('dragActive')) { + if (uiStore.isActionActive['dragActive']) { // If a node does currently get dragged we do not activate the selection return; } @@ -95,7 +95,7 @@ export function useCanvasPanning( return; } - if (uiStore.isActionActive('dragActive')) { + if (uiStore.isActionActive['dragActive']) { return; } diff --git a/packages/editor-ui/src/composables/useNodeBase.ts b/packages/editor-ui/src/composables/useNodeBase.ts index 7755950267..c7c6c9cfa5 100644 --- a/packages/editor-ui/src/composables/useNodeBase.ts +++ b/packages/editor-ui/src/composables/useNodeBase.ts @@ -621,7 +621,7 @@ export function useNodeBase({ } function touchEnd(_e: MouseEvent) { - if (deviceSupport.isTouchDevice && uiStore.isActionActive('dragActive')) { + if (deviceSupport.isTouchDevice && uiStore.isActionActive['dragActive']) { uiStore.removeActiveAction('dragActive'); } } @@ -640,14 +640,14 @@ export function useNodeBase({ } if (!deviceSupport.isTouchDevice) { - if (uiStore.isActionActive('dragActive')) { + if (uiStore.isActionActive['dragActive']) { uiStore.removeActiveAction('dragActive'); } else { if (!deviceSupport.isCtrlKeyPressed(e)) { emit('deselectAllNodes'); } - if (uiStore.isNodeSelected(data.value?.name ?? '')) { + if (uiStore.isNodeSelected[data.value?.name ?? '']) { emit('deselectNode', name); } else { emit('nodeSelected', name); diff --git a/packages/editor-ui/src/composables/usePushConnection.ts b/packages/editor-ui/src/composables/usePushConnection.ts index 929f2d61bc..1844919725 100644 --- a/packages/editor-ui/src/composables/usePushConnection.ts +++ b/packages/editor-ui/src/composables/usePushConnection.ts @@ -149,7 +149,7 @@ export function usePushConnection({ router }: { router: ReturnType { const workflow = workflowHelpers.getCurrentWorkflow(); - if (uiStore.isActionActive('workflowRunning')) { + if (uiStore.isActionActive['workflowRunning']) { return; } diff --git a/packages/editor-ui/src/composables/useToast.ts b/packages/editor-ui/src/composables/useToast.ts index 07bc5f7dbc..1d8994d0bc 100644 --- a/packages/editor-ui/src/composables/useToast.ts +++ b/packages/editor-ui/src/composables/useToast.ts @@ -175,7 +175,7 @@ export function useToast() { function showNotificationForViews(views: VIEWS[]) { const notifications: NotificationOptions[] = []; views.forEach((view) => { - notifications.push(...uiStore.getNotificationsForView(view)); + notifications.push(...(uiStore.pendingNotificationsForViews[view] ?? [])); }); if (notifications.length) { notifications.forEach(async (notification) => { diff --git a/packages/editor-ui/src/hooks/utils/hooksAddFakeDoorFeatures.ts b/packages/editor-ui/src/hooks/utils/hooksAddFakeDoorFeatures.ts index 814f8679a3..23b360a907 100644 --- a/packages/editor-ui/src/hooks/utils/hooksAddFakeDoorFeatures.ts +++ b/packages/editor-ui/src/hooks/utils/hooksAddFakeDoorFeatures.ts @@ -4,8 +4,8 @@ import { FAKE_DOOR_FEATURES } from '@/constants'; import type { BaseTextKey } from '@/plugins/i18n'; export function compileFakeDoorFeatures(): IFakeDoor[] { - const store = useUIStore(); - const fakeDoorFeatures: IFakeDoor[] = store.fakeDoorFeatures.map((feature) => ({ ...feature })); + const uiStore = useUIStore(); + const fakeDoorFeatures: IFakeDoor[] = uiStore.fakeDoorFeatures.map((feature) => ({ ...feature })); const environmentsFeature = fakeDoorFeatures.find( (feature) => feature.id === FAKE_DOOR_FEATURES.ENVIRONMENTS, @@ -28,7 +28,7 @@ export function compileFakeDoorFeatures(): IFakeDoor[] { } export const hooksAddFakeDoorFeatures = () => { - const store = useUIStore(); + const uiStore = useUIStore(); - store.fakeDoorFeatures = compileFakeDoorFeatures(); + uiStore.fakeDoorFeatures = compileFakeDoorFeatures(); }; diff --git a/packages/editor-ui/src/plugins/codemirror/completions/__tests__/completions.test.ts b/packages/editor-ui/src/plugins/codemirror/completions/__tests__/completions.test.ts index 5fa1043493..b3c034f7e9 100644 --- a/packages/editor-ui/src/plugins/codemirror/completions/__tests__/completions.test.ts +++ b/packages/editor-ui/src/plugins/codemirror/completions/__tests__/completions.test.ts @@ -357,7 +357,7 @@ describe('Resolution-based completions', () => { vi.spyOn(workflowHelpers, 'resolveParameter').mockReturnValue($input); - uiStore.modals[CREDENTIAL_EDIT_MODAL_KEY].open = true; + uiStore.modalsById[CREDENTIAL_EDIT_MODAL_KEY].open = true; set(settingsStore.settings, ['enterprise', EnterpriseEditionFeature.ExternalSecrets], true); externalSecretsStore.state.secrets = { [provider]: secrets, @@ -380,7 +380,7 @@ describe('Resolution-based completions', () => { vi.spyOn(workflowHelpers, 'resolveParameter').mockReturnValue($input); - uiStore.modals[CREDENTIAL_EDIT_MODAL_KEY].open = true; + uiStore.modalsById[CREDENTIAL_EDIT_MODAL_KEY].open = true; set(settingsStore.settings, ['enterprise', EnterpriseEditionFeature.ExternalSecrets], true); externalSecretsStore.state.secrets = { [provider]: secrets, diff --git a/packages/editor-ui/src/plugins/codemirror/completions/utils.ts b/packages/editor-ui/src/plugins/codemirror/completions/utils.ts index 3a0f7021ed..d30f8609c1 100644 --- a/packages/editor-ui/src/plugins/codemirror/completions/utils.ts +++ b/packages/editor-ui/src/plugins/codemirror/completions/utils.ts @@ -108,7 +108,7 @@ export function hasNoParams(toResolve: string) { // state-based utils // ---------------------------------- -export const isCredentialsModalOpen = () => useUIStore().modals[CREDENTIAL_EDIT_MODAL_KEY].open; +export const isCredentialsModalOpen = () => useUIStore().modalsById[CREDENTIAL_EDIT_MODAL_KEY].open; export const isInHttpNodePagination = () => { const ndvStore = useNDVStore(); diff --git a/packages/editor-ui/src/stores/__tests__/ui.test.ts b/packages/editor-ui/src/stores/__tests__/ui.test.ts index 793826f184..5ca85a010b 100644 --- a/packages/editor-ui/src/stores/__tests__/ui.test.ts +++ b/packages/editor-ui/src/stores/__tests__/ui.test.ts @@ -1,5 +1,5 @@ import { createPinia, setActivePinia } from 'pinia'; -import { useUIStore } from '@/stores/ui.store'; +import { generateUpgradeLinkUrl, useUIStore } from '@/stores/ui.store'; import { useSettingsStore } from '@/stores/settings.store'; import { useUsersStore } from '@/stores/users.store'; import { merge } from 'lodash-es'; @@ -98,7 +98,7 @@ describe('UI store', () => { 'https://n8n.io/pricing?utm_campaign=utm-test-campaign&source=test_source', ], ])( - '"upgradeLinkUrl" should generate the correct URL for "%s" deployment and "%s" license environment and user role "%s"', + '"generateUpgradeLinkUrl" should generate the correct URL for "%s" deployment and "%s" license environment and user role "%s"', async (type, environment, role, expectation) => { setUser(role as IRole); @@ -115,7 +115,7 @@ describe('UI store', () => { }), ); - const updateLinkUrl = await uiStore.upgradeLinkUrl('test_source', 'utm-test-campaign', type); + const updateLinkUrl = await generateUpgradeLinkUrl('test_source', 'utm-test-campaign', type); expect(updateLinkUrl).toBe(expectation); }, diff --git a/packages/editor-ui/src/stores/canvas.store.ts b/packages/editor-ui/src/stores/canvas.store.ts index d022e3bce8..01341c3635 100644 --- a/packages/editor-ui/src/stores/canvas.store.ts +++ b/packages/editor-ui/src/stores/canvas.store.ts @@ -236,7 +236,7 @@ export const useCanvasStore = defineStore('canvas', () => { if (!nodeName) return; isDragging.value = true; - const isSelected = uiStore.isNodeSelected(nodeName); + const isSelected = uiStore.isNodeSelected[nodeName]; if (params.e && !isSelected) { // Only the node which gets dragged directly gets an event, for all others it is @@ -255,7 +255,7 @@ export const useCanvasStore = defineStore('canvas', () => { if (!nodeName) return; const nodeData = workflowStore.getNodeByName(nodeName); isDragging.value = false; - if (uiStore.isActionActive('dragActive') && nodeData) { + if (uiStore.isActionActive['dragActive'] && nodeData) { const moveNodes = uiStore.getSelectedNodes.slice(); const selectedNodeNames = moveNodes.map((node: INodeUi) => node.name); if (!selectedNodeNames.includes(nodeData.name)) { @@ -300,7 +300,7 @@ export const useCanvasStore = defineStore('canvas', () => { if (moveNodes.length > 1) { historyStore.stopRecordingUndo(); } - if (uiStore.isActionActive('dragActive')) { + if (uiStore.isActionActive['dragActive']) { uiStore.removeActiveAction('dragActive'); } } diff --git a/packages/editor-ui/src/stores/ui.store.ts b/packages/editor-ui/src/stores/ui.store.ts index c621dbef91..855478ce43 100644 --- a/packages/editor-ui/src/stores/ui.store.ts +++ b/packages/editor-ui/src/stores/ui.store.ts @@ -1,8 +1,4 @@ -import { - applyForOnboardingCall, - fetchNextOnboardingPrompt, - submitEmailOnSignup, -} from '@/api/workflow-webhooks'; +import * as onboardingApi from '@/api/workflow-webhooks'; import { ABOUT_MODAL_KEY, CHAT_EMBED_MODAL_KEY, @@ -44,24 +40,21 @@ import { } from '@/constants'; import type { CloudUpdateLinkSourceType, - CurlToJSONResponse, IFakeDoorLocation, INodeUi, - IOnboardingCallPrompt, - UIState, UTMCampaign, XYPosition, Modals, NewCredentialsModal, ThemeOption, - AppliedThemeOption, NotificationOptions, ModalState, ModalKey, + IFakeDoor, } from '@/Interface'; import { defineStore } from 'pinia'; import { useRootStore } from '@/stores/root.store'; -import { getCurlToJson } from '@/api/curlHelper'; +import * as curlParserApi from '@/api/curlHelper'; import { useCloudPlanStore } from '@/stores/cloudPlan.store'; import { useWorkflowsStore } from '@/stores/workflows.store'; import { useSettingsStore } from '@/stores/settings.store'; @@ -77,6 +70,7 @@ import { isValidTheme, updateTheme, } from './ui.utils'; +import { computed, ref } from 'vue'; let savedTheme: ThemeOption = 'system'; try { @@ -87,545 +81,623 @@ try { } } catch (e) {} -export type UiStore = ReturnType; +type UiStore = ReturnType; -export const useUIStore = defineStore(STORES.UI, { - state: (): UIState => ({ - activeActions: [], - activeCredentialType: null, - theme: savedTheme, - modals: { - ...Object.fromEntries( - [ - ABOUT_MODAL_KEY, - CHAT_EMBED_MODAL_KEY, - CHANGE_PASSWORD_MODAL_KEY, - CONTACT_PROMPT_MODAL_KEY, - CREDENTIAL_SELECT_MODAL_KEY, - DUPLICATE_MODAL_KEY, - ONBOARDING_CALL_SIGNUP_MODAL_KEY, - PERSONALIZATION_MODAL_KEY, - INVITE_USER_MODAL_KEY, - TAGS_MANAGER_MODAL_KEY, - NPS_SURVEY_MODAL_KEY, - VERSIONS_MODAL_KEY, - WORKFLOW_LM_CHAT_MODAL_KEY, - WORKFLOW_SETTINGS_MODAL_KEY, - WORKFLOW_SHARE_MODAL_KEY, - WORKFLOW_ACTIVE_MODAL_KEY, - COMMUNITY_PACKAGE_INSTALL_MODAL_KEY, - MFA_SETUP_MODAL_KEY, - SOURCE_CONTROL_PUSH_MODAL_KEY, - SOURCE_CONTROL_PULL_MODAL_KEY, - EXTERNAL_SECRETS_PROVIDER_MODAL_KEY, - DEBUG_PAYWALL_MODAL_KEY, - WORKFLOW_HISTORY_VERSION_RESTORE, - SETUP_CREDENTIALS_MODAL_KEY, - PROJECT_MOVE_RESOURCE_MODAL, - PROJECT_MOVE_RESOURCE_CONFIRM_MODAL, - ].map((modalKey) => [modalKey, { open: false }]), - ), - [DELETE_USER_MODAL_KEY]: { - open: false, - activeId: null, - }, - [COMMUNITY_PACKAGE_CONFIRM_MODAL_KEY]: { - open: false, - mode: '', - activeId: null, - }, - [IMPORT_CURL_MODAL_KEY]: { - open: false, - data: { - curlCommand: '', - }, - }, - [GENERATE_CURL_MODAL_KEY]: { - open: false, - data: { - service: '', - request: '', - }, - }, - [LOG_STREAM_MODAL_KEY]: { - open: false, - data: undefined, - }, - [CREDENTIAL_EDIT_MODAL_KEY]: { - open: false, - mode: '', - activeId: null, - showAuthSelector: false, - } as ModalState, +type Draggable = { + isDragging: boolean; + type: string; + data: string; + canDrop: boolean; + stickyPosition: null | XYPosition; +}; + +export const useUIStore = defineStore(STORES.UI, () => { + const activeActions = ref([]); + const activeCredentialType = ref(null); + const theme = ref(savedTheme); + const modalsById = ref>({ + ...Object.fromEntries( + [ + ABOUT_MODAL_KEY, + CHAT_EMBED_MODAL_KEY, + CHANGE_PASSWORD_MODAL_KEY, + CONTACT_PROMPT_MODAL_KEY, + CREDENTIAL_SELECT_MODAL_KEY, + DUPLICATE_MODAL_KEY, + ONBOARDING_CALL_SIGNUP_MODAL_KEY, + PERSONALIZATION_MODAL_KEY, + INVITE_USER_MODAL_KEY, + TAGS_MANAGER_MODAL_KEY, + NPS_SURVEY_MODAL_KEY, + VERSIONS_MODAL_KEY, + WORKFLOW_LM_CHAT_MODAL_KEY, + WORKFLOW_SETTINGS_MODAL_KEY, + WORKFLOW_SHARE_MODAL_KEY, + WORKFLOW_ACTIVE_MODAL_KEY, + COMMUNITY_PACKAGE_INSTALL_MODAL_KEY, + MFA_SETUP_MODAL_KEY, + SOURCE_CONTROL_PUSH_MODAL_KEY, + SOURCE_CONTROL_PULL_MODAL_KEY, + EXTERNAL_SECRETS_PROVIDER_MODAL_KEY, + DEBUG_PAYWALL_MODAL_KEY, + WORKFLOW_HISTORY_VERSION_RESTORE, + SETUP_CREDENTIALS_MODAL_KEY, + PROJECT_MOVE_RESOURCE_MODAL, + PROJECT_MOVE_RESOURCE_CONFIRM_MODAL, + ].map((modalKey) => [modalKey, { open: false }]), + ), + [DELETE_USER_MODAL_KEY]: { + open: false, + activeId: null, }, - modalStack: [], - sidebarMenuCollapsed: true, - isPageLoading: true, - currentView: '', - mainPanelPosition: 0.5, - fakeDoorFeatures: [ - { - id: FAKE_DOOR_FEATURES.SSO, - featureName: 'fakeDoor.settings.sso.name', - icon: 'key', - actionBoxTitle: 'fakeDoor.settings.sso.actionBox.title', - actionBoxDescription: 'fakeDoor.settings.sso.actionBox.description', - linkURL: 'https://n8n-community.typeform.com/to/l7QOrERN#f=sso', - uiLocations: ['settings/users'], + [COMMUNITY_PACKAGE_CONFIRM_MODAL_KEY]: { + open: false, + mode: '', + activeId: null, + }, + [IMPORT_CURL_MODAL_KEY]: { + open: false, + data: { + curlCommand: '', }, - ], - draggable: { + }, + [GENERATE_CURL_MODAL_KEY]: { + open: false, + data: { + service: '', + request: '', + }, + }, + [LOG_STREAM_MODAL_KEY]: { + open: false, + data: undefined, + }, + [CREDENTIAL_EDIT_MODAL_KEY]: { + open: false, + mode: '', + activeId: null, + showAuthSelector: false, + } as ModalState, + }); + + const modalStack = ref([]); + const sidebarMenuCollapsed = ref(true); + const currentView = ref(''); + const fakeDoorFeatures = ref([ + { + id: FAKE_DOOR_FEATURES.SSO, + featureName: 'fakeDoor.settings.sso.name', + icon: 'key', + actionBoxTitle: 'fakeDoor.settings.sso.actionBox.title', + actionBoxDescription: 'fakeDoor.settings.sso.actionBox.description', + linkURL: 'https://n8n-community.typeform.com/to/l7QOrERN#f=sso', + uiLocations: ['settings/users'], + }, + ]); + + const draggable = ref({ + isDragging: false, + type: '', + data: '', + canDrop: false, + stickyPosition: null, + }); + + const stateIsDirty = ref(false); + const lastSelectedNode = ref(null); + const lastSelectedNodeOutputIndex = ref(null); + const lastSelectedNodeEndpointUuid = ref(null); + const nodeViewOffsetPosition = ref<[number, number]>([0, 0]); + const nodeViewMoveInProgress = ref(false); + const selectedNodes = ref([]); + const nodeViewInitialized = ref(false); + const addFirstStepOnLoad = ref(false); + const bannersHeight = ref(0); + const bannerStack = ref([]); + const pendingNotificationsForViews = ref<{ [key in VIEWS]?: NotificationOptions[] }>({}); + const isCreateNodeActive = ref(false); + + const settingsStore = useSettingsStore(); + const workflowsStore = useWorkflowsStore(); + const rootStore = useRootStore(); + const telemetryStore = useTelemetryStore(); + const cloudPlanStore = useCloudPlanStore(); + const userStore = useUsersStore(); + + const appliedTheme = computed(() => { + return theme.value === 'system' ? getPreferredTheme() : theme.value; + }); + + const logo = computed(() => { + const { releaseChannel } = settingsStore.settings; + const suffix = appliedTheme.value === 'dark' ? '-dark.svg' : '.svg'; + return `static/logo/${ + releaseChannel === 'stable' ? 'expanded' : `channel/${releaseChannel}` + }${suffix}`; + }); + + const contextBasedTranslationKeys = computed(() => { + const deploymentType = settingsStore.deploymentType; + + let contextKey: '' | '.cloud' | '.desktop' = ''; + if (deploymentType === 'cloud') { + contextKey = '.cloud'; + } else if (deploymentType === 'desktop_mac' || deploymentType === 'desktop_win') { + contextKey = '.desktop'; + } + + return { + feature: { + unavailable: { + title: `contextual.feature.unavailable.title${contextKey}`, + }, + }, + credentials: { + sharing: { + unavailable: { + title: `contextual.credentials.sharing.unavailable.title${contextKey}`, + description: `contextual.credentials.sharing.unavailable.description${contextKey}`, + action: `contextual.credentials.sharing.unavailable.action${contextKey}`, + button: `contextual.credentials.sharing.unavailable.button${contextKey}`, + }, + }, + }, + workflows: { + sharing: { + title: 'contextual.workflows.sharing.title', + unavailable: { + title: `contextual.workflows.sharing.unavailable.title${contextKey}`, + description: { + modal: `contextual.workflows.sharing.unavailable.description.modal${contextKey}`, + tooltip: `contextual.workflows.sharing.unavailable.description.tooltip${contextKey}`, + }, + action: `contextual.workflows.sharing.unavailable.action${contextKey}`, + button: `contextual.workflows.sharing.unavailable.button${contextKey}`, + }, + }, + }, + variables: { + unavailable: { + title: `contextual.variables.unavailable.title${contextKey}`, + description: 'contextual.variables.unavailable.description', + action: `contextual.variables.unavailable.action${contextKey}`, + button: `contextual.variables.unavailable.button${contextKey}`, + }, + }, + users: { + settings: { + unavailable: { + title: `contextual.users.settings.unavailable.title${contextKey}`, + description: `contextual.users.settings.unavailable.description${contextKey}`, + button: `contextual.users.settings.unavailable.button${contextKey}`, + }, + }, + }, + } as const; + }); + + const getLastSelectedNode = computed(() => { + if (lastSelectedNode.value) { + return workflowsStore.getNodeByName(lastSelectedNode.value); + } + return null; + }); + + const isVersionsOpen = computed(() => { + return modalsById.value[VERSIONS_MODAL_KEY].open; + }); + + const isModalActiveById = computed(() => + Object.keys(modalsById.value).reduce((acc: { [key: string]: boolean }, name) => { + acc[name] = name === modalStack.value[0]; + return acc; + }, {}), + ); + + const fakeDoorsByLocation = computed(() => + fakeDoorFeatures.value.reduce((acc: { [uiLocation: string]: IFakeDoor }, fakeDoor) => { + fakeDoor.uiLocations.forEach((uiLocation: IFakeDoorLocation) => { + acc[uiLocation] = fakeDoor; + }); + return acc; + }, {}), + ); + + const fakeDoorsById = computed(() => + fakeDoorFeatures.value.reduce((acc: { [id: string]: IFakeDoor }, fakeDoor) => { + acc[fakeDoor.id.toString()] = fakeDoor; + return acc; + }, {}), + ); + + const isReadOnlyView = computed(() => { + return ![ + VIEWS.WORKFLOW.toString(), + VIEWS.NEW_WORKFLOW.toString(), + VIEWS.EXECUTION_DEBUG.toString(), + ].includes(currentView.value); + }); + + const isActionActive = computed(() => + activeActions.value.reduce((acc: { [action: string]: boolean }, action) => { + acc[action] = true; + return acc; + }, {}), + ); + + const getSelectedNodes = computed(() => { + const seen = new Set(); + return selectedNodes.value.filter((node) => { + // dedupe for instances when same node is selected in different ways + if (!seen.has(node)) { + seen.add(node); + return true; + } + return false; + }); + }); + + const isNodeSelected = computed(() => + selectedNodes.value.reduce((acc: { [nodeName: string]: true }, node) => { + acc[node.name] = true; + return acc; + }, {}), + ); + + const headerHeight = computed(() => { + const style = getComputedStyle(document.body); + return Number(style.getPropertyValue('--header-height')); + }); + + const isAnyModalOpen = computed(() => { + return modalStack.value.length > 0; + }); + + // Methods + + const setTheme = (newTheme: ThemeOption): void => { + theme.value = newTheme; + updateTheme(newTheme); + }; + + const setMode = (name: keyof Modals, mode: string): void => { + modalsById.value[name] = { + ...modalsById.value[name], + mode, + }; + }; + + const setActiveId = (name: keyof Modals, activeId: string | null): void => { + modalsById.value[name] = { + ...modalsById.value[name], + activeId, + }; + }; + + const setShowAuthSelector = (name: keyof Modals, showAuthSelector: boolean): void => { + modalsById.value[name] = { + ...modalsById.value[name], + showAuthSelector, + } as NewCredentialsModal; + }; + + const setModalData = (payload: { name: keyof Modals; data: Record }) => { + modalsById.value[payload.name] = { + ...modalsById.value[payload.name], + data: payload.data, + }; + }; + + const openModal = (name: ModalKey) => { + modalsById.value[name] = { + ...modalsById.value[name], + open: true, + }; + modalStack.value = [name].concat(modalStack.value) as string[]; + }; + + const openModalWithData = (payload: { name: ModalKey; data: Record }) => { + setModalData(payload); + openModal(payload.name); + }; + + const closeModal = (name: ModalKey) => { + modalsById.value[name] = { + ...modalsById.value[name], + open: false, + }; + modalStack.value = modalStack.value.filter((openModalName) => name !== openModalName); + }; + + const draggableStartDragging = (type: string, data: string) => { + draggable.value = { + isDragging: true, + type, + data, + canDrop: false, + stickyPosition: null, + }; + }; + + const draggableStopDragging = () => { + draggable.value = { isDragging: false, type: '', data: '', canDrop: false, stickyPosition: null, - }, - stateIsDirty: false, - lastSelectedNode: null, - lastSelectedNodeOutputIndex: null, - lastSelectedNodeEndpointUuid: null, - nodeViewOffsetPosition: [0, 0], - nodeViewMoveInProgress: false, - selectedNodes: [], - nodeViewInitialized: false, - addFirstStepOnLoad: false, - bannersHeight: 0, - bannerStack: [], - // Notifications that should show when a view is initialized - // This enables us to set a queue of notifications form outside (another component) - // and then show them when the view is initialized - pendingNotificationsForViews: {}, - isCreateNodeActive: false, - }), - getters: { - appliedTheme(): AppliedThemeOption { - return this.theme === 'system' ? getPreferredTheme() : this.theme; - }, - logo(): string { - const { releaseChannel } = useSettingsStore().settings; - const suffix = this.appliedTheme === 'dark' ? '-dark.svg' : '.svg'; - return `static/logo/${ - releaseChannel === 'stable' ? 'expanded' : `channel/${releaseChannel}` - }${suffix}`; - }, - contextBasedTranslationKeys() { - const settingsStore = useSettingsStore(); - const deploymentType = settingsStore.deploymentType; + }; + }; - let contextKey: '' | '.cloud' | '.desktop' = ''; - if (deploymentType === 'cloud') { - contextKey = '.cloud'; - } else if (deploymentType === 'desktop_mac' || deploymentType === 'desktop_win') { - contextKey = '.desktop'; - } + const setDraggableStickyPos = (position: XYPosition) => { + draggable.value = { + ...draggable.value, + stickyPosition: position, + }; + }; - return { - feature: { - unavailable: { - title: `contextual.feature.unavailable.title${contextKey}`, - }, - }, - credentials: { - sharing: { - unavailable: { - title: `contextual.credentials.sharing.unavailable.title${contextKey}`, - description: `contextual.credentials.sharing.unavailable.description${contextKey}`, - action: `contextual.credentials.sharing.unavailable.action${contextKey}`, - button: `contextual.credentials.sharing.unavailable.button${contextKey}`, - }, - }, - }, - workflows: { - sharing: { - title: 'contextual.workflows.sharing.title', - unavailable: { - title: `contextual.workflows.sharing.unavailable.title${contextKey}`, - description: { - modal: `contextual.workflows.sharing.unavailable.description.modal${contextKey}`, - tooltip: `contextual.workflows.sharing.unavailable.description.tooltip${contextKey}`, - }, - action: `contextual.workflows.sharing.unavailable.action${contextKey}`, - button: `contextual.workflows.sharing.unavailable.button${contextKey}`, - }, - }, - }, - variables: { - unavailable: { - title: `contextual.variables.unavailable.title${contextKey}`, - description: 'contextual.variables.unavailable.description', - action: `contextual.variables.unavailable.action${contextKey}`, - button: `contextual.variables.unavailable.button${contextKey}`, - }, - }, - users: { - settings: { - unavailable: { - title: `contextual.users.settings.unavailable.title${contextKey}`, - description: `contextual.users.settings.unavailable.description${contextKey}`, - button: `contextual.users.settings.unavailable.button${contextKey}`, - }, - }, - }, - } as const; - }, - getLastSelectedNode(): INodeUi | null { - const workflowsStore = useWorkflowsStore(); - if (this.lastSelectedNode) { - return workflowsStore.getNodeByName(this.lastSelectedNode); - } - return null; - }, - areExpressionsDisabled(): boolean { - return this.currentView === VIEWS.DEMO; - }, - isVersionsOpen(): boolean { - return this.modals[VERSIONS_MODAL_KEY].open; - }, - isModalOpen() { - return (name: ModalKey) => this.modals[name].open; - }, - isModalActive() { - return (name: ModalKey) => this.modalStack.length > 0 && name === this.modalStack[0]; - }, - getModalActiveId() { - return (name: ModalKey) => this.modals[name].activeId; - }, - getModalMode() { - return (name: ModalKey) => this.modals[name].mode; - }, - getModalData() { - return (name: ModalKey) => this.modals[name].data; - }, - getFakeDoorByLocation() { - return (location: IFakeDoorLocation) => - this.fakeDoorFeatures.filter((fakeDoor) => fakeDoor.uiLocations.includes(location)); - }, - getFakeDoorById() { - return (id: string) => - this.fakeDoorFeatures.find((fakeDoor) => fakeDoor.id.toString() === id); - }, - isReadOnlyView(): boolean { - return ![VIEWS.WORKFLOW, VIEWS.NEW_WORKFLOW, VIEWS.EXECUTION_DEBUG].includes( - this.currentView as VIEWS, + const setDraggableCanDrop = (canDrop: boolean) => { + draggable.value = { + ...draggable.value, + canDrop, + }; + }; + + const openDeleteUserModal = (id: string) => { + setActiveId(DELETE_USER_MODAL_KEY, id); + openModal(DELETE_USER_MODAL_KEY); + }; + + const openExistingCredential = (id: string) => { + setActiveId(CREDENTIAL_EDIT_MODAL_KEY, id); + setMode(CREDENTIAL_EDIT_MODAL_KEY, 'edit'); + openModal(CREDENTIAL_EDIT_MODAL_KEY); + }; + + const openNewCredential = (type: string, showAuthOptions = false) => { + setActiveId(CREDENTIAL_EDIT_MODAL_KEY, type); + setShowAuthSelector(CREDENTIAL_EDIT_MODAL_KEY, showAuthOptions); + setMode(CREDENTIAL_EDIT_MODAL_KEY, 'new'); + openModal(CREDENTIAL_EDIT_MODAL_KEY); + }; + + const getNextOnboardingPrompt = async () => { + const instanceId = rootStore.instanceId; + const { currentUser } = userStore; + if (currentUser) { + return await onboardingApi.fetchNextOnboardingPrompt(instanceId, currentUser); + } + return null; + }; + + const applyForOnboardingCall = async (email: string) => { + const instanceId = rootStore.instanceId; + const { currentUser } = userStore; + if (currentUser) { + return await onboardingApi.applyForOnboardingCall(instanceId, currentUser, email); + } + return null; + }; + + const submitContactEmail = async (email: string, agree: boolean) => { + const instanceId = rootStore.instanceId; + const { currentUser } = userStore; + if (currentUser) { + return await onboardingApi.submitEmailOnSignup( + instanceId, + currentUser, + email ?? currentUser.email, + agree, ); - }, - isNodeView(): boolean { - return [ - VIEWS.NEW_WORKFLOW.toString(), - VIEWS.WORKFLOW.toString(), - VIEWS.WORKFLOW_EXECUTIONS.toString(), - ].includes(this.currentView); - }, - isActionActive() { - return (action: string) => this.activeActions.includes(action); - }, - getSelectedNodes(): INodeUi[] { - const seen = new Set(); - return this.selectedNodes.filter((node: INodeUi) => { - // dedupe for instances when same node is selected in different ways - if (!seen.has(node.id)) { - seen.add(node.id); - return true; - } - return false; + } + return null; + }; + + const openCommunityPackageUninstallConfirmModal = (packageName: string) => { + setMode(COMMUNITY_PACKAGE_CONFIRM_MODAL_KEY, packageName); + setActiveId(COMMUNITY_PACKAGE_CONFIRM_MODAL_KEY, COMMUNITY_PACKAGE_MANAGE_ACTIONS.UNINSTALL); + openModal(COMMUNITY_PACKAGE_CONFIRM_MODAL_KEY); + }; + + const openCommunityPackageUpdateConfirmModal = (packageName: string) => { + setMode(COMMUNITY_PACKAGE_CONFIRM_MODAL_KEY, packageName); + setActiveId(COMMUNITY_PACKAGE_CONFIRM_MODAL_KEY, COMMUNITY_PACKAGE_MANAGE_ACTIONS.UPDATE); + openModal(COMMUNITY_PACKAGE_CONFIRM_MODAL_KEY); + }; + + const addActiveAction = (action: string) => { + if (!activeActions.value.includes(action)) { + activeActions.value.push(action); + } + }; + + const removeActiveAction = (action: string) => { + const actionIndex = activeActions.value.indexOf(action); + if (actionIndex !== -1) { + activeActions.value.splice(actionIndex, 1); + } + }; + + const addSelectedNode = (node: INodeUi) => { + const isAlreadySelected = selectedNodes.value.some((n) => n.name === node.name); + if (!isAlreadySelected) { + selectedNodes.value.push(node); + } + }; + + const removeNodeFromSelection = (node: INodeUi) => { + for (const [index] of selectedNodes.value.entries()) { + if (selectedNodes.value[index].name === node.name) { + selectedNodes.value.splice(Number(index), 1); + break; + } + } + }; + + const resetSelectedNodes = () => { + selectedNodes.value = []; + }; + + const setCurlCommand = (payload: { name: string; command: string }) => { + modalsById.value[payload.name] = { + ...modalsById.value[payload.name], + curlCommand: payload.command, + }; + }; + + const toggleSidebarMenuCollapse = () => { + sidebarMenuCollapsed.value = !sidebarMenuCollapsed.value; + }; + + const getCurlToJson = async (curlCommand: string) => { + const parameters = await curlParserApi.getCurlToJson(rootStore.restApiContext, curlCommand); + + // Normalize placeholder values + if (parameters['parameters.url']) { + parameters['parameters.url'] = parameters['parameters.url'] + .replaceAll('%7B', '{') + .replaceAll('%7D', '}'); + } + + return parameters; + }; + + const goToUpgrade = async ( + source: CloudUpdateLinkSourceType, + utm_campaign: UTMCampaign, + mode: 'open' | 'redirect' = 'open', + ) => { + const { usageLeft, trialDaysLeft, userIsTrialing } = cloudPlanStore; + const { executionsLeft, workflowsLeft } = usageLeft; + const deploymentType = settingsStore.deploymentType; + + telemetryStore.track('User clicked upgrade CTA', { + source, + isTrial: userIsTrialing, + deploymentType, + trialDaysLeft, + executionsLeft, + workflowsLeft, + }); + + const upgradeLink = await generateUpgradeLinkUrl(source, utm_campaign, deploymentType); + + if (mode === 'open') { + window.open(upgradeLink, '_blank'); + } else { + location.href = upgradeLink; + } + }; + + const removeBannerFromStack = (name: BannerName) => { + bannerStack.value = bannerStack.value.filter((bannerName) => bannerName !== name); + }; + + const dismissBanner = async (name: BannerName, type: 'temporary' | 'permanent' = 'temporary') => { + if (type === 'permanent') { + await dismissBannerPermanently(rootStore.restApiContext, { + bannerName: name, + dismissedBanners: settingsStore.permanentlyDismissedBanners, }); - }, - isNodeSelected() { - return (nodeName: string): boolean => { - let index; - for (index in this.selectedNodes) { - if (this.selectedNodes[index].name === nodeName) { - return true; - } - } - return false; - }; - }, - upgradeLinkUrl() { - return async (source: string, utm_campaign: string, deploymentType: string) => { - let linkUrl = ''; + removeBannerFromStack(name); + return; + } + removeBannerFromStack(name); + }; - const searchParams = new URLSearchParams(); + const updateBannersHeight = (newHeight: number) => { + bannersHeight.value = newHeight; + }; - if (deploymentType === 'cloud' && hasPermission(['instanceOwner'])) { - const adminPanelHost = new URL(window.location.href).host.split('.').slice(1).join('.'); - const { code } = await useCloudPlanStore().getAutoLoginCode(); - linkUrl = `https://${adminPanelHost}/login`; - searchParams.set('code', code); - searchParams.set('returnPath', '/account/change-plan'); - } else { - linkUrl = N8N_PRICING_PAGE_URL; - } + const pushBannerToStack = (name: BannerName) => { + if (bannerStack.value.includes(name)) return; + bannerStack.value.push(name); + }; - if (utm_campaign) { - searchParams.set('utm_campaign', utm_campaign); - } + const clearBannerStack = () => { + bannerStack.value = []; + }; - if (source) { - searchParams.set('source', source); - } - return `${linkUrl}?${searchParams.toString()}`; - }; - }, - headerHeight() { - const style = getComputedStyle(document.body); - return Number(style.getPropertyValue('--header-height')); - }, - isAnyModalOpen(): boolean { - return this.modalStack.length > 0; - }, - }, - actions: { - setTheme(theme: ThemeOption): void { - this.theme = theme; - updateTheme(theme); - }, - setMode(name: keyof Modals, mode: string): void { - this.modals[name] = { - ...this.modals[name], - mode, - }; - }, - setActiveId(name: keyof Modals, activeId: string): void { - this.modals[name] = { - ...this.modals[name], - activeId, - }; - }, - setShowAuthSelector(name: keyof Modals, showAuthSelector: boolean) { - this.modals[name] = { - ...this.modals[name], - showAuthSelector, - } as NewCredentialsModal; - }, - setModalData(payload: { name: keyof Modals; data: Record }) { - this.modals[payload.name] = { - ...this.modals[payload.name], - data: payload.data, - }; - }, - openModal(name: keyof Modals): void { - this.modals[name] = { - ...this.modals[name], - open: true, - }; - this.modalStack = [name].concat(this.modalStack) as string[]; - }, - openModalWithData(payload: { name: keyof Modals; data: Record }): void { - this.setModalData(payload); - this.openModal(payload.name); - }, - closeModal(name: keyof Modals): void { - this.modals[name] = { - ...this.modals[name], - open: false, - }; - this.modalStack = this.modalStack.filter((openModalName: string) => { - return name !== openModalName; - }); - }, - draggableStartDragging(type: string, data: string): void { - this.draggable = { - isDragging: true, - type, - data, - canDrop: false, - stickyPosition: null, - }; - }, - draggableStopDragging(): void { - this.draggable = { - isDragging: false, - type: '', - data: '', - canDrop: false, - stickyPosition: null, - }; - }, - setDraggableStickyPos(position: XYPosition): void { - this.draggable = { - ...this.draggable, - stickyPosition: position, - }; - }, - setDraggableCanDrop(canDrop: boolean): void { - this.draggable = { - ...this.draggable, - canDrop, - }; - }, - openDeleteUserModal(id: string): void { - this.setActiveId(DELETE_USER_MODAL_KEY, id); - this.openModal(DELETE_USER_MODAL_KEY); - }, - openExistingCredential(id: string): void { - this.setActiveId(CREDENTIAL_EDIT_MODAL_KEY, id); - this.setMode(CREDENTIAL_EDIT_MODAL_KEY, 'edit'); - this.openModal(CREDENTIAL_EDIT_MODAL_KEY); - }, - openNewCredential(type: string, showAuthOptions = false): void { - this.setActiveId(CREDENTIAL_EDIT_MODAL_KEY, type); - this.setShowAuthSelector(CREDENTIAL_EDIT_MODAL_KEY, showAuthOptions); - this.setMode(CREDENTIAL_EDIT_MODAL_KEY, 'new'); - this.openModal(CREDENTIAL_EDIT_MODAL_KEY); - }, - async getNextOnboardingPrompt(): Promise { - const rootStore = useRootStore(); - const instanceId = rootStore.instanceId; - const { currentUser } = useUsersStore(); - if (currentUser) { - return await fetchNextOnboardingPrompt(instanceId, currentUser); - } - return null; - }, - async applyForOnboardingCall(email: string): Promise { - const rootStore = useRootStore(); - const instanceId = rootStore.instanceId; - const { currentUser } = useUsersStore(); - if (currentUser) { - return await applyForOnboardingCall(instanceId, currentUser, email); - } - return null; - }, - async submitContactEmail(email: string, agree: boolean): Promise { - const rootStore = useRootStore(); - const instanceId = rootStore.instanceId; - const { currentUser } = useUsersStore(); - if (currentUser) { - return await submitEmailOnSignup( - instanceId, - currentUser, - email ?? currentUser?.email, - agree, - ); - } - return null; - }, - openCommunityPackageUninstallConfirmModal(packageName: string) { - this.setActiveId(COMMUNITY_PACKAGE_CONFIRM_MODAL_KEY, packageName); - this.setMode(COMMUNITY_PACKAGE_CONFIRM_MODAL_KEY, COMMUNITY_PACKAGE_MANAGE_ACTIONS.UNINSTALL); - this.openModal(COMMUNITY_PACKAGE_CONFIRM_MODAL_KEY); - }, - openCommunityPackageUpdateConfirmModal(packageName: string) { - this.setActiveId(COMMUNITY_PACKAGE_CONFIRM_MODAL_KEY, packageName); - this.setMode(COMMUNITY_PACKAGE_CONFIRM_MODAL_KEY, COMMUNITY_PACKAGE_MANAGE_ACTIONS.UPDATE); - this.openModal(COMMUNITY_PACKAGE_CONFIRM_MODAL_KEY); - }, - addActiveAction(action: string): void { - if (!this.activeActions.includes(action)) { - this.activeActions.push(action); - } - }, - removeActiveAction(action: string): void { - const actionIndex = this.activeActions.indexOf(action); - if (actionIndex !== -1) { - this.activeActions.splice(actionIndex, 1); - } - }, - addSelectedNode(node: INodeUi): void { - const isAlreadySelected = this.selectedNodes.some((n) => n.name === node.name); - if (!isAlreadySelected) { - this.selectedNodes.push(node); - } - }, - removeNodeFromSelection(node: INodeUi): void { - let index; - for (index in this.selectedNodes) { - if (this.selectedNodes[index].name === node.name) { - this.selectedNodes.splice(parseInt(index, 10), 1); - break; - } - } - }, - resetSelectedNodes(): void { - this.selectedNodes = []; - }, - setCurlCommand(payload: { name: string; command: string }): void { - this.modals[payload.name] = { - ...this.modals[payload.name], - curlCommand: payload.command, - }; - }, - toggleSidebarMenuCollapse(): void { - this.sidebarMenuCollapsed = !this.sidebarMenuCollapsed; - }, - async getCurlToJson(curlCommand: string): Promise { - const rootStore = useRootStore(); - const parameters = await getCurlToJson(rootStore.restApiContext, curlCommand); + const setNotificationsForView = (view: VIEWS, notifications: NotificationOptions[]) => { + pendingNotificationsForViews.value[view] = notifications; + }; - // Normalize placeholder values - if (parameters['parameters.url']) { - parameters['parameters.url'] = parameters['parameters.url'] - .replaceAll('%7B', '{') - .replaceAll('%7D', '}'); - } + const deleteNotificationsForView = (view: VIEWS) => { + delete pendingNotificationsForViews.value[view]; + }; - return parameters; - }, - async goToUpgrade( - source: CloudUpdateLinkSourceType, - utm_campaign: UTMCampaign, - mode: 'open' | 'redirect' = 'open', - ): Promise { - const { usageLeft, trialDaysLeft, userIsTrialing } = useCloudPlanStore(); - const { executionsLeft, workflowsLeft } = usageLeft; - const deploymentType = useSettingsStore().deploymentType; - - useTelemetryStore().track('User clicked upgrade CTA', { - source, - isTrial: userIsTrialing, - deploymentType, - trialDaysLeft, - executionsLeft, - workflowsLeft, - }); - - const upgradeLink = await this.upgradeLinkUrl(source, utm_campaign, deploymentType); - - if (mode === 'open') { - window.open(upgradeLink, '_blank'); - } else { - location.href = upgradeLink; - } - }, - async dismissBanner( - name: BannerName, - type: 'temporary' | 'permanent' = 'temporary', - ): Promise { - if (type === 'permanent') { - await dismissBannerPermanently(useRootStore().restApiContext, { - bannerName: name, - dismissedBanners: useSettingsStore().permanentlyDismissedBanners, - }); - this.removeBannerFromStack(name); - return; - } - this.removeBannerFromStack(name); - }, - updateBannersHeight(newHeight: number): void { - this.bannersHeight = newHeight; - }, - pushBannerToStack(name: BannerName) { - if (this.bannerStack.includes(name)) return; - this.bannerStack.push(name); - }, - removeBannerFromStack(name: BannerName) { - this.bannerStack = this.bannerStack.filter((bannerName) => bannerName !== name); - }, - clearBannerStack() { - this.bannerStack = []; - }, - getNotificationsForView(view: VIEWS): NotificationOptions[] { - return this.pendingNotificationsForViews[view] ?? []; - }, - setNotificationsForView(view: VIEWS, notifications: NotificationOptions[]) { - this.pendingNotificationsForViews[view] = notifications; - }, - deleteNotificationsForView(view: VIEWS) { - delete this.pendingNotificationsForViews[view]; - }, - }, + return { + appliedTheme, + logo, + contextBasedTranslationKeys, + getLastSelectedNode, + isVersionsOpen, + isModalActiveById, + fakeDoorsByLocation, + isReadOnlyView, + isActionActive, + activeActions, + getSelectedNodes, + isNodeSelected, + headerHeight, + stateIsDirty, + lastSelectedNodeOutputIndex, + activeCredentialType, + lastSelectedNode, + selectedNodes, + bannersHeight, + lastSelectedNodeEndpointUuid, + nodeViewOffsetPosition, + nodeViewMoveInProgress, + nodeViewInitialized, + addFirstStepOnLoad, + isCreateNodeActive, + sidebarMenuCollapsed, + fakeDoorFeatures, + bannerStack, + theme, + modalsById, + currentView, + isAnyModalOpen, + fakeDoorsById, + pendingNotificationsForViews, + setTheme, + setMode, + setActiveId, + setShowAuthSelector, + setModalData, + openModalWithData, + openModal, + closeModal, + draggableStartDragging, + draggableStopDragging, + setDraggableStickyPos, + setDraggableCanDrop, + openDeleteUserModal, + openExistingCredential, + openNewCredential, + getNextOnboardingPrompt, + applyForOnboardingCall, + submitContactEmail, + openCommunityPackageUninstallConfirmModal, + openCommunityPackageUpdateConfirmModal, + addActiveAction, + removeActiveAction, + addSelectedNode, + removeNodeFromSelection, + resetSelectedNodes, + setCurlCommand, + toggleSidebarMenuCollapse, + getCurlToJson, + goToUpgrade, + removeBannerFromStack, + dismissBanner, + updateBannersHeight, + pushBannerToStack, + clearBannerStack, + setNotificationsForView, + deleteNotificationsForView, + }; }); /** @@ -668,3 +740,34 @@ export const listenForModalChanges = (opts: { }); }); }; + +export const generateUpgradeLinkUrl = async ( + source: string, + utm_campaign: string, + deploymentType: string, +) => { + let linkUrl = ''; + + const searchParams = new URLSearchParams(); + + const cloudPlanStore = useCloudPlanStore(); + + if (deploymentType === 'cloud' && hasPermission(['instanceOwner'])) { + const adminPanelHost = new URL(window.location.href).host.split('.').slice(1).join('.'); + const { code } = await cloudPlanStore.getAutoLoginCode(); + linkUrl = `https://${adminPanelHost}/login`; + searchParams.set('code', code); + searchParams.set('returnPath', '/account/change-plan'); + } else { + linkUrl = N8N_PRICING_PAGE_URL; + } + + if (utm_campaign) { + searchParams.set('utm_campaign', utm_campaign); + } + + if (source) { + searchParams.set('source', source); + } + return `${linkUrl}?${searchParams.toString()}`; +}; diff --git a/packages/editor-ui/src/views/NodeView.vue b/packages/editor-ui/src/views/NodeView.vue index 43aab6d58c..8698bdd026 100644 --- a/packages/editor-ui/src/views/NodeView.vue +++ b/packages/editor-ui/src/views/NodeView.vue @@ -678,7 +678,7 @@ export default defineComponent({ return this.workflowsStore.getWorkflowExecution; }, workflowRunning(): boolean { - return this.uiStore.isActionActive('workflowRunning'); + return this.uiStore.isActionActive['workflowRunning']; }, currentWorkflow(): string { return this.$route.params.name?.toString() || this.workflowsStore.workflowId; diff --git a/packages/editor-ui/src/views/SettingsFakeDoorView.vue b/packages/editor-ui/src/views/SettingsFakeDoorView.vue index b1b089b01b..4398c117fc 100644 --- a/packages/editor-ui/src/views/SettingsFakeDoorView.vue +++ b/packages/editor-ui/src/views/SettingsFakeDoorView.vue @@ -23,7 +23,7 @@ export default defineComponent({ computed: { ...mapStores(useUIStore), featureInfo(): IFakeDoor | undefined { - return this.uiStore.getFakeDoorById(this.featureId); + return this.uiStore.fakeDoorsById[this.featureId]; }, }, methods: {