mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-18 02:21:13 +00:00
refactor(editor): Migrate ui.store to use composition API (no-changelog) (#9892)
This commit is contained in:
@@ -6,7 +6,6 @@ import type {
|
|||||||
TRIGGER_NODE_CREATOR_VIEW,
|
TRIGGER_NODE_CREATOR_VIEW,
|
||||||
REGULAR_NODE_CREATOR_VIEW,
|
REGULAR_NODE_CREATOR_VIEW,
|
||||||
AI_OTHERS_NODE_CREATOR_VIEW,
|
AI_OTHERS_NODE_CREATOR_VIEW,
|
||||||
VIEWS,
|
|
||||||
ROLE,
|
ROLE,
|
||||||
} from '@/constants';
|
} from '@/constants';
|
||||||
import type { IMenuItem, NodeCreatorTag } from 'n8n-design-system';
|
import type { IMenuItem, NodeCreatorTag } from 'n8n-design-system';
|
||||||
@@ -1264,41 +1263,6 @@ export interface NotificationOptions extends Partial<ElementNotificationOptions>
|
|||||||
message: string | ElementNotificationOptions['message'];
|
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 = {
|
export type IFakeDoor = {
|
||||||
id: FAKE_DOOR_FEATURES;
|
id: FAKE_DOOR_FEATURES;
|
||||||
featureName: BaseTextKey;
|
featureName: BaseTextKey;
|
||||||
|
|||||||
@@ -448,8 +448,8 @@ const showSharingContent = computed(() => activeTab.value === 'sharing' && !!cre
|
|||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
requiredCredentials.value =
|
requiredCredentials.value =
|
||||||
isCredentialModalState(uiStore.modals[CREDENTIAL_EDIT_MODAL_KEY]) &&
|
isCredentialModalState(uiStore.modalsById[CREDENTIAL_EDIT_MODAL_KEY]) &&
|
||||||
uiStore.modals[CREDENTIAL_EDIT_MODAL_KEY].showAuthSelector === true;
|
uiStore.modalsById[CREDENTIAL_EDIT_MODAL_KEY].showAuthSelector === true;
|
||||||
|
|
||||||
if (props.mode === 'new' && credentialTypeName.value) {
|
if (props.mode === 'new' && credentialTypeName.value) {
|
||||||
credentialName.value = await credentialsStore.getNewCredentialName({
|
credentialName.value = await credentialsStore.getNewCredentialName({
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ export default defineComponent({
|
|||||||
return this.rootStore.instanceId;
|
return this.rootStore.instanceId;
|
||||||
},
|
},
|
||||||
featureInfo(): IFakeDoor | undefined {
|
featureInfo(): IFakeDoor | undefined {
|
||||||
return this.uiStore.getFakeDoorById(this.featureId);
|
return this.uiStore.fakeDoorsById[this.featureId];
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|||||||
@@ -61,8 +61,8 @@ const ndvStore = useNDVStore();
|
|||||||
const modalBus = createEventBus();
|
const modalBus = createEventBus();
|
||||||
const formBus = createEventBus();
|
const formBus = createEventBus();
|
||||||
|
|
||||||
const initialServiceValue = uiStore.getModalData(GENERATE_CURL_MODAL_KEY)?.service as string;
|
const initialServiceValue = uiStore.modalsById[GENERATE_CURL_MODAL_KEY].data?.service as string;
|
||||||
const initialRequestValue = uiStore.getModalData(GENERATE_CURL_MODAL_KEY)?.request as string;
|
const initialRequestValue = uiStore.modalsById[GENERATE_CURL_MODAL_KEY].data?.request as string;
|
||||||
|
|
||||||
const formInputs: IFormInput[] = [
|
const formInputs: IFormInput[] = [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ const { importCurlCommand } = useImportCurlCommand({
|
|||||||
});
|
});
|
||||||
|
|
||||||
onMounted(() => {
|
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(() => {
|
setTimeout(() => {
|
||||||
inputRef.value?.focus();
|
inputRef.value?.focus();
|
||||||
|
|||||||
@@ -304,7 +304,7 @@ export default defineComponent({
|
|||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
workflowRunning(): boolean {
|
workflowRunning(): boolean {
|
||||||
return this.uiStore.isActionActive('workflowRunning');
|
return this.uiStore.isActionActive['workflowRunning'];
|
||||||
},
|
},
|
||||||
|
|
||||||
activeNode(): INodeUi | null {
|
activeNode(): INodeUi | null {
|
||||||
|
|||||||
@@ -111,7 +111,7 @@ const isNewWorkflow = computed(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const isWorkflowSaving = computed(() => {
|
const isWorkflowSaving = computed(() => {
|
||||||
return uiStore.isActionActive('workflowSaving');
|
return uiStore.isActionActive['workflowSaving'];
|
||||||
});
|
});
|
||||||
|
|
||||||
const onWorkflowPage = computed(() => {
|
const onWorkflowPage = computed(() => {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-dialog
|
<el-dialog
|
||||||
:model-value="uiStore.isModalOpen(name)"
|
:model-value="uiStore.modalsById[name].open"
|
||||||
:before-close="closeDialog"
|
:before-close="closeDialog"
|
||||||
:class="{
|
:class="{
|
||||||
'dialog-wrapper': true,
|
'dialog-wrapper': true,
|
||||||
@@ -169,7 +169,7 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
onWindowKeydown(event: KeyboardEvent) {
|
onWindowKeydown(event: KeyboardEvent) {
|
||||||
if (!this.uiStore.isModalActive(this.name)) {
|
if (!this.uiStore.isModalActiveById[this.name]) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -178,7 +178,7 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
handleEnter() {
|
handleEnter() {
|
||||||
if (this.uiStore.isModalActive(this.name)) {
|
if (this.uiStore.isModalActiveById[this.name]) {
|
||||||
this.$emit('enter');
|
this.$emit('enter');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<ElDrawer
|
<ElDrawer
|
||||||
:direction="direction"
|
:direction="direction"
|
||||||
:model-value="uiStore.isModalOpen(name)"
|
:model-value="uiStore.modalsById[name].open"
|
||||||
:size="width"
|
:size="width"
|
||||||
:before-close="close"
|
:before-close="close"
|
||||||
:modal="modal"
|
:modal="modal"
|
||||||
@@ -74,7 +74,7 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
onWindowKeydown(event: KeyboardEvent) {
|
onWindowKeydown(event: KeyboardEvent) {
|
||||||
if (!this.uiStore.isModalActive(this.name)) {
|
if (!this.uiStore.isModalActiveById[this.name]) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,7 +83,7 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
handleEnter() {
|
handleEnter() {
|
||||||
if (this.uiStore.isModalActive(this.name)) {
|
if (this.uiStore.isModalActiveById[this.name]) {
|
||||||
this.$emit('enter');
|
this.$emit('enter');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
<template>
|
<template>
|
||||||
<div v-if="uiStore.isModalOpen(name) || keepAlive">
|
<div v-if="uiStore.modalsById[name].open || keepAlive">
|
||||||
<slot
|
<slot
|
||||||
:modal-name="name"
|
:modal-name="name"
|
||||||
:active="uiStore.isModalActive(name)"
|
:active="uiStore.isModalActiveById[name]"
|
||||||
:open="uiStore.isModalOpen(name)"
|
:open="uiStore.modalsById[name].open"
|
||||||
:active-id="uiStore.getModalActiveId(name)"
|
:active-id="uiStore.modalsById[name].activeId"
|
||||||
:mode="uiStore.getModalMode(name)"
|
:mode="uiStore.modalsById[name].mode"
|
||||||
:data="uiStore.getModalData(name)"
|
:data="uiStore.modalsById[name].data"
|
||||||
></slot>
|
></slot>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -592,7 +592,7 @@ export default defineComponent({
|
|||||||
return undefined;
|
return undefined;
|
||||||
},
|
},
|
||||||
workflowRunning(): boolean {
|
workflowRunning(): boolean {
|
||||||
return this.uiStore.isActionActive('workflowRunning');
|
return this.uiStore.isActionActive['workflowRunning'];
|
||||||
},
|
},
|
||||||
nodeStyle() {
|
nodeStyle() {
|
||||||
const returnStyles: {
|
const returnStyles: {
|
||||||
|
|||||||
@@ -242,7 +242,7 @@ const activeNodeType = computed(() => {
|
|||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
|
|
||||||
const workflowRunning = computed(() => uiStore.isActionActive('workflowRunning'));
|
const workflowRunning = computed(() => uiStore.isActionActive['workflowRunning']);
|
||||||
|
|
||||||
const showTriggerWaitingWarning = computed(
|
const showTriggerWaitingWarning = computed(
|
||||||
() =>
|
() =>
|
||||||
@@ -432,7 +432,7 @@ const featureRequestUrl = computed(() => {
|
|||||||
|
|
||||||
const outputPanelEditMode = computed(() => ndvStore.outputPanelEditMode);
|
const outputPanelEditMode = computed(() => ndvStore.outputPanelEditMode);
|
||||||
|
|
||||||
const isWorkflowRunning = computed(() => uiStore.isActionActive('workflowRunning'));
|
const isWorkflowRunning = computed(() => uiStore.isActionActive['workflowRunning']);
|
||||||
|
|
||||||
const isExecutionWaitingForWebhook = computed(() => workflowsStore.executionWaitingForWebhook);
|
const isExecutionWaitingForWebhook = computed(() => workflowsStore.executionWaitingForWebhook);
|
||||||
|
|
||||||
|
|||||||
@@ -128,7 +128,7 @@ export default defineComponent({
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
workflowRunning(): boolean {
|
workflowRunning(): boolean {
|
||||||
return this.uiStore.isActionActive('workflowRunning');
|
return this.uiStore.isActionActive['workflowRunning'];
|
||||||
},
|
},
|
||||||
isTriggerNode(): boolean {
|
isTriggerNode(): boolean {
|
||||||
if (!this.node) {
|
if (!this.node) {
|
||||||
|
|||||||
@@ -230,7 +230,7 @@ export default defineComponent({
|
|||||||
return !!this.node && this.workflowsStore.isNodeExecuting(this.node.name);
|
return !!this.node && this.workflowsStore.isNodeExecuting(this.node.name);
|
||||||
},
|
},
|
||||||
workflowRunning(): boolean {
|
workflowRunning(): boolean {
|
||||||
return this.uiStore.isActionActive('workflowRunning');
|
return this.uiStore.isActionActive['workflowRunning'];
|
||||||
},
|
},
|
||||||
workflowExecution(): IExecutionResponse | null {
|
workflowExecution(): IExecutionResponse | null {
|
||||||
return this.workflowsStore.getWorkflowExecution;
|
return this.workflowsStore.getWorkflowExecution;
|
||||||
|
|||||||
@@ -8,7 +8,9 @@ import { useTelemetry } from '@/composables/useTelemetry';
|
|||||||
|
|
||||||
vi.mock('@/stores/ui.store', () => ({
|
vi.mock('@/stores/ui.store', () => ({
|
||||||
useUIStore: vi.fn().mockReturnValue({
|
useUIStore: vi.fn().mockReturnValue({
|
||||||
isModalOpen: vi.fn().mockReturnValue(() => true),
|
modalsById: vi.fn().mockReturnValue(() => {
|
||||||
|
open: true;
|
||||||
|
}),
|
||||||
closeModal: vi.fn(),
|
closeModal: vi.fn(),
|
||||||
}),
|
}),
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -46,7 +46,9 @@ export default defineComponent({
|
|||||||
computed: {
|
computed: {
|
||||||
...mapStores(useRootStore, useSettingsStore, useUIStore),
|
...mapStores(useRootStore, useSettingsStore, useUIStore),
|
||||||
settingsFakeDoorFeatures(): IFakeDoor[] {
|
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[] {
|
sidebarMenuItems(): IMenuItem[] {
|
||||||
const menuItems: IMenuItem[] = [
|
const menuItems: IMenuItem[] = [
|
||||||
|
|||||||
@@ -141,7 +141,7 @@ function getContext() {
|
|||||||
return 'workflows';
|
return 'workflows';
|
||||||
} else if (
|
} else if (
|
||||||
route.fullPath.startsWith('/credentials') ||
|
route.fullPath.startsWith('/credentials') ||
|
||||||
uiStore.modals[CREDENTIAL_EDIT_MODAL_KEY].open
|
uiStore.modalsById[CREDENTIAL_EDIT_MODAL_KEY].open
|
||||||
) {
|
) {
|
||||||
return 'credentials';
|
return 'credentials';
|
||||||
} else if (route.fullPath.startsWith('/workflow/')) {
|
} else if (route.fullPath.startsWith('/workflow/')) {
|
||||||
|
|||||||
@@ -288,7 +288,7 @@ export default defineComponent({
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
workflowRunning(): boolean {
|
workflowRunning(): boolean {
|
||||||
return this.uiStore.isActionActive('workflowRunning');
|
return this.uiStore.isActionActive['workflowRunning'];
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
|
|||||||
@@ -264,7 +264,7 @@ export default defineComponent({
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
workflowRunning(): boolean {
|
workflowRunning(): boolean {
|
||||||
return this.uiStore.isActionActive('workflowRunning');
|
return this.uiStore.isActionActive['workflowRunning'];
|
||||||
},
|
},
|
||||||
isActivelyPolling(): boolean {
|
isActivelyPolling(): boolean {
|
||||||
const triggeredNode = this.workflowsStore.executedNode;
|
const triggeredNode = this.workflowsStore.executedNode;
|
||||||
|
|||||||
@@ -219,7 +219,7 @@ export default defineComponent({
|
|||||||
computed: {
|
computed: {
|
||||||
...mapStores(useWorkflowsStore, useUIStore, useNodeTypesStore),
|
...mapStores(useWorkflowsStore, useUIStore, useNodeTypesStore),
|
||||||
isLoading(): boolean {
|
isLoading(): boolean {
|
||||||
return this.uiStore.isActionActive('workflowRunning');
|
return this.uiStore.isActionActive['workflowRunning'];
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
async mounted() {
|
async mounted() {
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ const renderComponent = createComponentRenderer(ChatEmbedModal, {
|
|||||||
pinia: createTestingPinia({
|
pinia: createTestingPinia({
|
||||||
initialState: {
|
initialState: {
|
||||||
[STORES.UI]: {
|
[STORES.UI]: {
|
||||||
modals: {
|
modalsById: {
|
||||||
[CHAT_EMBED_MODAL_KEY]: { open: true },
|
[CHAT_EMBED_MODAL_KEY]: { open: true },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ const renderComponent = createComponentRenderer(CommunityPackageInstallModal, {
|
|||||||
pinia: createTestingPinia({
|
pinia: createTestingPinia({
|
||||||
initialState: {
|
initialState: {
|
||||||
[STORES.UI]: {
|
[STORES.UI]: {
|
||||||
modals: {
|
modalsById: {
|
||||||
[COMMUNITY_PACKAGE_INSTALL_MODAL_KEY]: { open: true },
|
[COMMUNITY_PACKAGE_INSTALL_MODAL_KEY]: { open: true },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import { useUsageStore } from '@/stores/usage.store';
|
|||||||
const pinia = createTestingPinia({
|
const pinia = createTestingPinia({
|
||||||
initialState: {
|
initialState: {
|
||||||
[STORES.UI]: {
|
[STORES.UI]: {
|
||||||
modals: {
|
modalsById: {
|
||||||
[PERSONALIZATION_MODAL_KEY]: { open: true },
|
[PERSONALIZATION_MODAL_KEY]: { open: true },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ describe('WorkflowSettingsVue', () => {
|
|||||||
versionId: '123',
|
versionId: '123',
|
||||||
} as IWorkflowDb);
|
} as IWorkflowDb);
|
||||||
|
|
||||||
uiStore.modals[WORKFLOW_SETTINGS_MODAL_KEY] = {
|
uiStore.modalsById[WORKFLOW_SETTINGS_MODAL_KEY] = {
|
||||||
open: true,
|
open: true,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ defineEmits<{
|
|||||||
const uiStore = useUIStore();
|
const uiStore = useUIStore();
|
||||||
const locale = useI18n();
|
const locale = useI18n();
|
||||||
|
|
||||||
const workflowRunning = computed(() => uiStore.isActionActive('workflowRunning'));
|
const workflowRunning = computed(() => uiStore.isActionActive['workflowRunning']);
|
||||||
|
|
||||||
const runButtonText = computed(() => {
|
const runButtonText = computed(() => {
|
||||||
if (!workflowRunning.value) {
|
if (!workflowRunning.value) {
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ vi.mock('@/stores/ui.store', () => ({
|
|||||||
useUIStore: vi.fn(() => ({
|
useUIStore: vi.fn(() => ({
|
||||||
nodeViewOffsetPosition: [0, 0],
|
nodeViewOffsetPosition: [0, 0],
|
||||||
nodeViewMoveInProgress: false,
|
nodeViewMoveInProgress: false,
|
||||||
isActionActive: vi.fn(),
|
isActionActive: vi.fn().mockReturnValue(() => true),
|
||||||
})),
|
})),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -62,7 +62,7 @@ describe('useCanvasPanning()', () => {
|
|||||||
vi.mocked(useUIStore).mockReturnValueOnce({
|
vi.mocked(useUIStore).mockReturnValueOnce({
|
||||||
nodeViewOffsetPosition: [0, 0],
|
nodeViewOffsetPosition: [0, 0],
|
||||||
nodeViewMoveInProgress: true,
|
nodeViewMoveInProgress: true,
|
||||||
isActionActive: vi.fn(),
|
isActionActive: vi.fn().mockReturnValue(() => true),
|
||||||
} as unknown as ReturnType<typeof useUIStore>);
|
} as unknown as ReturnType<typeof useUIStore>);
|
||||||
|
|
||||||
const removeEventListenerSpy = vi.spyOn(element, 'removeEventListener');
|
const removeEventListenerSpy = vi.spyOn(element, 'removeEventListener');
|
||||||
|
|||||||
@@ -140,21 +140,15 @@ describe('useNodeBase', () => {
|
|||||||
it('should handle mouse left click correctly', () => {
|
it('should handle mouse left click correctly', () => {
|
||||||
const { mouseLeftClick } = nodeBase;
|
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', {
|
const event = new MouseEvent('click', {
|
||||||
bubbles: true,
|
bubbles: true,
|
||||||
cancelable: true,
|
cancelable: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
uiStore.addActiveAction('notDragActive');
|
||||||
|
|
||||||
mouseLeftClick(event);
|
mouseLeftClick(event);
|
||||||
|
|
||||||
expect(isActionActiveFn).toHaveBeenCalledWith('dragActive');
|
|
||||||
expect(emit).toHaveBeenCalledWith('deselectAllNodes');
|
expect(emit).toHaveBeenCalledWith('deselectAllNodes');
|
||||||
expect(emit).toHaveBeenCalledWith('nodeSelected', node.name);
|
expect(emit).toHaveBeenCalledWith('nodeSelected', node.name);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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', () => ({
|
vi.mock('@/composables/useTelemetry', () => ({
|
||||||
useTelemetry: vi.fn().mockReturnValue({ track: vi.fn() }),
|
useTelemetry: vi.fn().mockReturnValue({ track: vi.fn() }),
|
||||||
}));
|
}));
|
||||||
@@ -103,6 +95,10 @@ describe('useRunWorkflow({ router })', () => {
|
|||||||
workflowHelpers = useWorkflowHelpers({ router });
|
workflowHelpers = useWorkflowHelpers({ router });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
uiStore.activeActions = [];
|
||||||
|
});
|
||||||
|
|
||||||
describe('runWorkflowApi()', () => {
|
describe('runWorkflowApi()', () => {
|
||||||
it('should throw an error if push connection is not active', async () => {
|
it('should throw an error if push connection is not active', async () => {
|
||||||
const { runWorkflowApi } = useRunWorkflow({ router });
|
const { runWorkflowApi } = useRunWorkflow({ router });
|
||||||
@@ -157,7 +153,7 @@ describe('useRunWorkflow({ router })', () => {
|
|||||||
describe('runWorkflow()', () => {
|
describe('runWorkflow()', () => {
|
||||||
it('should return undefined if UI action "workflowRunning" is active', async () => {
|
it('should return undefined if UI action "workflowRunning" is active', async () => {
|
||||||
const { runWorkflow } = useRunWorkflow({ router });
|
const { runWorkflow } = useRunWorkflow({ router });
|
||||||
vi.mocked(uiStore).isActionActive.mockReturnValue(true);
|
uiStore.addActiveAction('workflowRunning');
|
||||||
const result = await runWorkflow({});
|
const result = await runWorkflow({});
|
||||||
expect(result).toBeUndefined();
|
expect(result).toBeUndefined();
|
||||||
});
|
});
|
||||||
@@ -166,7 +162,7 @@ describe('useRunWorkflow({ router })', () => {
|
|||||||
const mockExecutionResponse = { executionId: '123' };
|
const mockExecutionResponse = { executionId: '123' };
|
||||||
const { runWorkflow } = useRunWorkflow({ router });
|
const { runWorkflow } = useRunWorkflow({ router });
|
||||||
|
|
||||||
vi.mocked(uiStore).isActionActive.mockReturnValue(false);
|
vi.mocked(uiStore).activeActions = [''];
|
||||||
vi.mocked(workflowHelpers).getCurrentWorkflow.mockReturnValue({
|
vi.mocked(workflowHelpers).getCurrentWorkflow.mockReturnValue({
|
||||||
name: 'Test Workflow',
|
name: 'Test Workflow',
|
||||||
} as unknown as Workflow);
|
} as unknown as Workflow);
|
||||||
|
|||||||
@@ -167,7 +167,7 @@ export default function useCanvasMouseSelect() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (uiStore.isActionActive('dragActive')) {
|
if (uiStore.isActionActive['dragActive']) {
|
||||||
// If a node does currently get dragged we do not activate the selection
|
// If a node does currently get dragged we do not activate the selection
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ export function useCanvasPanning(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (uiStore.isActionActive('dragActive')) {
|
if (uiStore.isActionActive['dragActive']) {
|
||||||
// If a node does currently get dragged we do not activate the selection
|
// If a node does currently get dragged we do not activate the selection
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -95,7 +95,7 @@ export function useCanvasPanning(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (uiStore.isActionActive('dragActive')) {
|
if (uiStore.isActionActive['dragActive']) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -621,7 +621,7 @@ export function useNodeBase({
|
|||||||
}
|
}
|
||||||
|
|
||||||
function touchEnd(_e: MouseEvent) {
|
function touchEnd(_e: MouseEvent) {
|
||||||
if (deviceSupport.isTouchDevice && uiStore.isActionActive('dragActive')) {
|
if (deviceSupport.isTouchDevice && uiStore.isActionActive['dragActive']) {
|
||||||
uiStore.removeActiveAction('dragActive');
|
uiStore.removeActiveAction('dragActive');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -640,14 +640,14 @@ export function useNodeBase({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!deviceSupport.isTouchDevice) {
|
if (!deviceSupport.isTouchDevice) {
|
||||||
if (uiStore.isActionActive('dragActive')) {
|
if (uiStore.isActionActive['dragActive']) {
|
||||||
uiStore.removeActiveAction('dragActive');
|
uiStore.removeActiveAction('dragActive');
|
||||||
} else {
|
} else {
|
||||||
if (!deviceSupport.isCtrlKeyPressed(e)) {
|
if (!deviceSupport.isCtrlKeyPressed(e)) {
|
||||||
emit('deselectAllNodes');
|
emit('deselectAllNodes');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (uiStore.isNodeSelected(data.value?.name ?? '')) {
|
if (uiStore.isNodeSelected[data.value?.name ?? '']) {
|
||||||
emit('deselectNode', name);
|
emit('deselectNode', name);
|
||||||
} else {
|
} else {
|
||||||
emit('nodeSelected', name);
|
emit('nodeSelected', name);
|
||||||
|
|||||||
@@ -149,7 +149,7 @@ export function usePushConnection({ router }: { router: ReturnType<typeof useRou
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (receivedData.type === 'nodeExecuteAfter' || receivedData.type === 'nodeExecuteBefore') {
|
if (receivedData.type === 'nodeExecuteAfter' || receivedData.type === 'nodeExecuteBefore') {
|
||||||
if (!uiStore.isActionActive('workflowRunning')) {
|
if (!uiStore.isActionActive['workflowRunning']) {
|
||||||
// No workflow is running so ignore the messages
|
// No workflow is running so ignore the messages
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -169,7 +169,7 @@ export function usePushConnection({ router }: { router: ReturnType<typeof useRou
|
|||||||
let recoveredPushData: IPushDataExecutionFinished | undefined = undefined;
|
let recoveredPushData: IPushDataExecutionFinished | undefined = undefined;
|
||||||
if (receivedData.type === 'executionRecovered') {
|
if (receivedData.type === 'executionRecovered') {
|
||||||
const recoveredExecutionId = receivedData.data?.executionId;
|
const recoveredExecutionId = receivedData.data?.executionId;
|
||||||
const isWorkflowRunning = uiStore.isActionActive('workflowRunning');
|
const isWorkflowRunning = uiStore.isActionActive['workflowRunning'];
|
||||||
if (isWorkflowRunning && workflowsStore.activeExecutionId === recoveredExecutionId) {
|
if (isWorkflowRunning && workflowsStore.activeExecutionId === recoveredExecutionId) {
|
||||||
// pull execution data for the recovered execution from the server
|
// pull execution data for the recovered execution from the server
|
||||||
const executionData = await workflowsStore.fetchExecutionDataById(
|
const executionData = await workflowsStore.fetchExecutionDataById(
|
||||||
@@ -262,7 +262,7 @@ export function usePushConnection({ router }: { router: ReturnType<typeof useRou
|
|||||||
workflowsStore.finishActiveExecution(pushData);
|
workflowsStore.finishActiveExecution(pushData);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!uiStore.isActionActive('workflowRunning')) {
|
if (!uiStore.isActionActive['workflowRunning']) {
|
||||||
// No workflow is running so ignore the messages
|
// No workflow is running so ignore the messages
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ export function useRunWorkflow(useRunWorkflowOpts: { router: ReturnType<typeof u
|
|||||||
}): Promise<IExecutionPushResponse | undefined> {
|
}): Promise<IExecutionPushResponse | undefined> {
|
||||||
const workflow = workflowHelpers.getCurrentWorkflow();
|
const workflow = workflowHelpers.getCurrentWorkflow();
|
||||||
|
|
||||||
if (uiStore.isActionActive('workflowRunning')) {
|
if (uiStore.isActionActive['workflowRunning']) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -175,7 +175,7 @@ export function useToast() {
|
|||||||
function showNotificationForViews(views: VIEWS[]) {
|
function showNotificationForViews(views: VIEWS[]) {
|
||||||
const notifications: NotificationOptions[] = [];
|
const notifications: NotificationOptions[] = [];
|
||||||
views.forEach((view) => {
|
views.forEach((view) => {
|
||||||
notifications.push(...uiStore.getNotificationsForView(view));
|
notifications.push(...(uiStore.pendingNotificationsForViews[view] ?? []));
|
||||||
});
|
});
|
||||||
if (notifications.length) {
|
if (notifications.length) {
|
||||||
notifications.forEach(async (notification) => {
|
notifications.forEach(async (notification) => {
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ import { FAKE_DOOR_FEATURES } from '@/constants';
|
|||||||
import type { BaseTextKey } from '@/plugins/i18n';
|
import type { BaseTextKey } from '@/plugins/i18n';
|
||||||
|
|
||||||
export function compileFakeDoorFeatures(): IFakeDoor[] {
|
export function compileFakeDoorFeatures(): IFakeDoor[] {
|
||||||
const store = useUIStore();
|
const uiStore = useUIStore();
|
||||||
const fakeDoorFeatures: IFakeDoor[] = store.fakeDoorFeatures.map((feature) => ({ ...feature }));
|
const fakeDoorFeatures: IFakeDoor[] = uiStore.fakeDoorFeatures.map((feature) => ({ ...feature }));
|
||||||
|
|
||||||
const environmentsFeature = fakeDoorFeatures.find(
|
const environmentsFeature = fakeDoorFeatures.find(
|
||||||
(feature) => feature.id === FAKE_DOOR_FEATURES.ENVIRONMENTS,
|
(feature) => feature.id === FAKE_DOOR_FEATURES.ENVIRONMENTS,
|
||||||
@@ -28,7 +28,7 @@ export function compileFakeDoorFeatures(): IFakeDoor[] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const hooksAddFakeDoorFeatures = () => {
|
export const hooksAddFakeDoorFeatures = () => {
|
||||||
const store = useUIStore();
|
const uiStore = useUIStore();
|
||||||
|
|
||||||
store.fakeDoorFeatures = compileFakeDoorFeatures();
|
uiStore.fakeDoorFeatures = compileFakeDoorFeatures();
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -357,7 +357,7 @@ describe('Resolution-based completions', () => {
|
|||||||
|
|
||||||
vi.spyOn(workflowHelpers, 'resolveParameter').mockReturnValue($input);
|
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);
|
set(settingsStore.settings, ['enterprise', EnterpriseEditionFeature.ExternalSecrets], true);
|
||||||
externalSecretsStore.state.secrets = {
|
externalSecretsStore.state.secrets = {
|
||||||
[provider]: secrets,
|
[provider]: secrets,
|
||||||
@@ -380,7 +380,7 @@ describe('Resolution-based completions', () => {
|
|||||||
|
|
||||||
vi.spyOn(workflowHelpers, 'resolveParameter').mockReturnValue($input);
|
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);
|
set(settingsStore.settings, ['enterprise', EnterpriseEditionFeature.ExternalSecrets], true);
|
||||||
externalSecretsStore.state.secrets = {
|
externalSecretsStore.state.secrets = {
|
||||||
[provider]: secrets,
|
[provider]: secrets,
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ export function hasNoParams(toResolve: string) {
|
|||||||
// state-based utils
|
// 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 = () => {
|
export const isInHttpNodePagination = () => {
|
||||||
const ndvStore = useNDVStore();
|
const ndvStore = useNDVStore();
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { createPinia, setActivePinia } from 'pinia';
|
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 { useSettingsStore } from '@/stores/settings.store';
|
||||||
import { useUsersStore } from '@/stores/users.store';
|
import { useUsersStore } from '@/stores/users.store';
|
||||||
import { merge } from 'lodash-es';
|
import { merge } from 'lodash-es';
|
||||||
@@ -98,7 +98,7 @@ describe('UI store', () => {
|
|||||||
'https://n8n.io/pricing?utm_campaign=utm-test-campaign&source=test_source',
|
'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) => {
|
async (type, environment, role, expectation) => {
|
||||||
setUser(role as IRole);
|
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);
|
expect(updateLinkUrl).toBe(expectation);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -236,7 +236,7 @@ export const useCanvasStore = defineStore('canvas', () => {
|
|||||||
if (!nodeName) return;
|
if (!nodeName) return;
|
||||||
isDragging.value = true;
|
isDragging.value = true;
|
||||||
|
|
||||||
const isSelected = uiStore.isNodeSelected(nodeName);
|
const isSelected = uiStore.isNodeSelected[nodeName];
|
||||||
|
|
||||||
if (params.e && !isSelected) {
|
if (params.e && !isSelected) {
|
||||||
// Only the node which gets dragged directly gets an event, for all others it is
|
// 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;
|
if (!nodeName) return;
|
||||||
const nodeData = workflowStore.getNodeByName(nodeName);
|
const nodeData = workflowStore.getNodeByName(nodeName);
|
||||||
isDragging.value = false;
|
isDragging.value = false;
|
||||||
if (uiStore.isActionActive('dragActive') && nodeData) {
|
if (uiStore.isActionActive['dragActive'] && nodeData) {
|
||||||
const moveNodes = uiStore.getSelectedNodes.slice();
|
const moveNodes = uiStore.getSelectedNodes.slice();
|
||||||
const selectedNodeNames = moveNodes.map((node: INodeUi) => node.name);
|
const selectedNodeNames = moveNodes.map((node: INodeUi) => node.name);
|
||||||
if (!selectedNodeNames.includes(nodeData.name)) {
|
if (!selectedNodeNames.includes(nodeData.name)) {
|
||||||
@@ -300,7 +300,7 @@ export const useCanvasStore = defineStore('canvas', () => {
|
|||||||
if (moveNodes.length > 1) {
|
if (moveNodes.length > 1) {
|
||||||
historyStore.stopRecordingUndo();
|
historyStore.stopRecordingUndo();
|
||||||
}
|
}
|
||||||
if (uiStore.isActionActive('dragActive')) {
|
if (uiStore.isActionActive['dragActive']) {
|
||||||
uiStore.removeActiveAction('dragActive');
|
uiStore.removeActiveAction('dragActive');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,4 @@
|
|||||||
import {
|
import * as onboardingApi from '@/api/workflow-webhooks';
|
||||||
applyForOnboardingCall,
|
|
||||||
fetchNextOnboardingPrompt,
|
|
||||||
submitEmailOnSignup,
|
|
||||||
} from '@/api/workflow-webhooks';
|
|
||||||
import {
|
import {
|
||||||
ABOUT_MODAL_KEY,
|
ABOUT_MODAL_KEY,
|
||||||
CHAT_EMBED_MODAL_KEY,
|
CHAT_EMBED_MODAL_KEY,
|
||||||
@@ -44,24 +40,21 @@ import {
|
|||||||
} from '@/constants';
|
} from '@/constants';
|
||||||
import type {
|
import type {
|
||||||
CloudUpdateLinkSourceType,
|
CloudUpdateLinkSourceType,
|
||||||
CurlToJSONResponse,
|
|
||||||
IFakeDoorLocation,
|
IFakeDoorLocation,
|
||||||
INodeUi,
|
INodeUi,
|
||||||
IOnboardingCallPrompt,
|
|
||||||
UIState,
|
|
||||||
UTMCampaign,
|
UTMCampaign,
|
||||||
XYPosition,
|
XYPosition,
|
||||||
Modals,
|
Modals,
|
||||||
NewCredentialsModal,
|
NewCredentialsModal,
|
||||||
ThemeOption,
|
ThemeOption,
|
||||||
AppliedThemeOption,
|
|
||||||
NotificationOptions,
|
NotificationOptions,
|
||||||
ModalState,
|
ModalState,
|
||||||
ModalKey,
|
ModalKey,
|
||||||
|
IFakeDoor,
|
||||||
} from '@/Interface';
|
} from '@/Interface';
|
||||||
import { defineStore } from 'pinia';
|
import { defineStore } from 'pinia';
|
||||||
import { useRootStore } from '@/stores/root.store';
|
import { useRootStore } from '@/stores/root.store';
|
||||||
import { getCurlToJson } from '@/api/curlHelper';
|
import * as curlParserApi from '@/api/curlHelper';
|
||||||
import { useCloudPlanStore } from '@/stores/cloudPlan.store';
|
import { useCloudPlanStore } from '@/stores/cloudPlan.store';
|
||||||
import { useWorkflowsStore } from '@/stores/workflows.store';
|
import { useWorkflowsStore } from '@/stores/workflows.store';
|
||||||
import { useSettingsStore } from '@/stores/settings.store';
|
import { useSettingsStore } from '@/stores/settings.store';
|
||||||
@@ -77,6 +70,7 @@ import {
|
|||||||
isValidTheme,
|
isValidTheme,
|
||||||
updateTheme,
|
updateTheme,
|
||||||
} from './ui.utils';
|
} from './ui.utils';
|
||||||
|
import { computed, ref } from 'vue';
|
||||||
|
|
||||||
let savedTheme: ThemeOption = 'system';
|
let savedTheme: ThemeOption = 'system';
|
||||||
try {
|
try {
|
||||||
@@ -87,14 +81,21 @@ try {
|
|||||||
}
|
}
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
|
|
||||||
export type UiStore = ReturnType<typeof useUIStore>;
|
type UiStore = ReturnType<typeof useUIStore>;
|
||||||
|
|
||||||
export const useUIStore = defineStore(STORES.UI, {
|
type Draggable = {
|
||||||
state: (): UIState => ({
|
isDragging: boolean;
|
||||||
activeActions: [],
|
type: string;
|
||||||
activeCredentialType: null,
|
data: string;
|
||||||
theme: savedTheme,
|
canDrop: boolean;
|
||||||
modals: {
|
stickyPosition: null | XYPosition;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useUIStore = defineStore(STORES.UI, () => {
|
||||||
|
const activeActions = ref<string[]>([]);
|
||||||
|
const activeCredentialType = ref<string | null>(null);
|
||||||
|
const theme = ref<ThemeOption>(savedTheme);
|
||||||
|
const modalsById = ref<Record<string, ModalState>>({
|
||||||
...Object.fromEntries(
|
...Object.fromEntries(
|
||||||
[
|
[
|
||||||
ABOUT_MODAL_KEY,
|
ABOUT_MODAL_KEY,
|
||||||
@@ -157,13 +158,12 @@ export const useUIStore = defineStore(STORES.UI, {
|
|||||||
activeId: null,
|
activeId: null,
|
||||||
showAuthSelector: false,
|
showAuthSelector: false,
|
||||||
} as ModalState,
|
} as ModalState,
|
||||||
},
|
});
|
||||||
modalStack: [],
|
|
||||||
sidebarMenuCollapsed: true,
|
const modalStack = ref<string[]>([]);
|
||||||
isPageLoading: true,
|
const sidebarMenuCollapsed = ref<boolean>(true);
|
||||||
currentView: '',
|
const currentView = ref<string>('');
|
||||||
mainPanelPosition: 0.5,
|
const fakeDoorFeatures = ref<IFakeDoor[]>([
|
||||||
fakeDoorFeatures: [
|
|
||||||
{
|
{
|
||||||
id: FAKE_DOOR_FEATURES.SSO,
|
id: FAKE_DOOR_FEATURES.SSO,
|
||||||
featureName: 'fakeDoor.settings.sso.name',
|
featureName: 'fakeDoor.settings.sso.name',
|
||||||
@@ -173,44 +173,50 @@ export const useUIStore = defineStore(STORES.UI, {
|
|||||||
linkURL: 'https://n8n-community.typeform.com/to/l7QOrERN#f=sso',
|
linkURL: 'https://n8n-community.typeform.com/to/l7QOrERN#f=sso',
|
||||||
uiLocations: ['settings/users'],
|
uiLocations: ['settings/users'],
|
||||||
},
|
},
|
||||||
],
|
]);
|
||||||
draggable: {
|
|
||||||
|
const draggable = ref<Draggable>({
|
||||||
isDragging: false,
|
isDragging: false,
|
||||||
type: '',
|
type: '',
|
||||||
data: '',
|
data: '',
|
||||||
canDrop: false,
|
canDrop: false,
|
||||||
stickyPosition: null,
|
stickyPosition: null,
|
||||||
},
|
});
|
||||||
stateIsDirty: false,
|
|
||||||
lastSelectedNode: null,
|
const stateIsDirty = ref<boolean>(false);
|
||||||
lastSelectedNodeOutputIndex: null,
|
const lastSelectedNode = ref<string | null>(null);
|
||||||
lastSelectedNodeEndpointUuid: null,
|
const lastSelectedNodeOutputIndex = ref<number | null>(null);
|
||||||
nodeViewOffsetPosition: [0, 0],
|
const lastSelectedNodeEndpointUuid = ref<string | null>(null);
|
||||||
nodeViewMoveInProgress: false,
|
const nodeViewOffsetPosition = ref<[number, number]>([0, 0]);
|
||||||
selectedNodes: [],
|
const nodeViewMoveInProgress = ref<boolean>(false);
|
||||||
nodeViewInitialized: false,
|
const selectedNodes = ref<INodeUi[]>([]);
|
||||||
addFirstStepOnLoad: false,
|
const nodeViewInitialized = ref<boolean>(false);
|
||||||
bannersHeight: 0,
|
const addFirstStepOnLoad = ref<boolean>(false);
|
||||||
bannerStack: [],
|
const bannersHeight = ref<number>(0);
|
||||||
// Notifications that should show when a view is initialized
|
const bannerStack = ref<BannerName[]>([]);
|
||||||
// This enables us to set a queue of notifications form outside (another component)
|
const pendingNotificationsForViews = ref<{ [key in VIEWS]?: NotificationOptions[] }>({});
|
||||||
// and then show them when the view is initialized
|
const isCreateNodeActive = ref<boolean>(false);
|
||||||
pendingNotificationsForViews: {},
|
|
||||||
isCreateNodeActive: false,
|
const settingsStore = useSettingsStore();
|
||||||
}),
|
const workflowsStore = useWorkflowsStore();
|
||||||
getters: {
|
const rootStore = useRootStore();
|
||||||
appliedTheme(): AppliedThemeOption {
|
const telemetryStore = useTelemetryStore();
|
||||||
return this.theme === 'system' ? getPreferredTheme() : this.theme;
|
const cloudPlanStore = useCloudPlanStore();
|
||||||
},
|
const userStore = useUsersStore();
|
||||||
logo(): string {
|
|
||||||
const { releaseChannel } = useSettingsStore().settings;
|
const appliedTheme = computed(() => {
|
||||||
const suffix = this.appliedTheme === 'dark' ? '-dark.svg' : '.svg';
|
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/${
|
return `static/logo/${
|
||||||
releaseChannel === 'stable' ? 'expanded' : `channel/${releaseChannel}`
|
releaseChannel === 'stable' ? 'expanded' : `channel/${releaseChannel}`
|
||||||
}${suffix}`;
|
}${suffix}`;
|
||||||
},
|
});
|
||||||
contextBasedTranslationKeys() {
|
|
||||||
const settingsStore = useSettingsStore();
|
const contextBasedTranslationKeys = computed(() => {
|
||||||
const deploymentType = settingsStore.deploymentType;
|
const deploymentType = settingsStore.deploymentType;
|
||||||
|
|
||||||
let contextKey: '' | '.cloud' | '.desktop' = '';
|
let contextKey: '' | '.cloud' | '.desktop' = '';
|
||||||
@@ -268,291 +274,283 @@ export const useUIStore = defineStore(STORES.UI, {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
} as const;
|
} as const;
|
||||||
},
|
});
|
||||||
getLastSelectedNode(): INodeUi | null {
|
|
||||||
const workflowsStore = useWorkflowsStore();
|
const getLastSelectedNode = computed(() => {
|
||||||
if (this.lastSelectedNode) {
|
if (lastSelectedNode.value) {
|
||||||
return workflowsStore.getNodeByName(this.lastSelectedNode);
|
return workflowsStore.getNodeByName(lastSelectedNode.value);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
},
|
});
|
||||||
areExpressionsDisabled(): boolean {
|
|
||||||
return this.currentView === VIEWS.DEMO;
|
const isVersionsOpen = computed(() => {
|
||||||
},
|
return modalsById.value[VERSIONS_MODAL_KEY].open;
|
||||||
isVersionsOpen(): boolean {
|
});
|
||||||
return this.modals[VERSIONS_MODAL_KEY].open;
|
|
||||||
},
|
const isModalActiveById = computed(() =>
|
||||||
isModalOpen() {
|
Object.keys(modalsById.value).reduce((acc: { [key: string]: boolean }, name) => {
|
||||||
return (name: ModalKey) => this.modals[name].open;
|
acc[name] = name === modalStack.value[0];
|
||||||
},
|
return acc;
|
||||||
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,
|
|
||||||
);
|
);
|
||||||
},
|
|
||||||
isNodeView(): boolean {
|
const fakeDoorsByLocation = computed(() =>
|
||||||
return [
|
fakeDoorFeatures.value.reduce((acc: { [uiLocation: string]: IFakeDoor }, fakeDoor) => {
|
||||||
VIEWS.NEW_WORKFLOW.toString(),
|
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.WORKFLOW.toString(),
|
||||||
VIEWS.WORKFLOW_EXECUTIONS.toString(),
|
VIEWS.NEW_WORKFLOW.toString(),
|
||||||
].includes(this.currentView);
|
VIEWS.EXECUTION_DEBUG.toString(),
|
||||||
},
|
].includes(currentView.value);
|
||||||
isActionActive() {
|
});
|
||||||
return (action: string) => this.activeActions.includes(action);
|
|
||||||
},
|
const isActionActive = computed(() =>
|
||||||
getSelectedNodes(): INodeUi[] {
|
activeActions.value.reduce((acc: { [action: string]: boolean }, action) => {
|
||||||
|
acc[action] = true;
|
||||||
|
return acc;
|
||||||
|
}, {}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const getSelectedNodes = computed(() => {
|
||||||
const seen = new Set();
|
const seen = new Set();
|
||||||
return this.selectedNodes.filter((node: INodeUi) => {
|
return selectedNodes.value.filter((node) => {
|
||||||
// dedupe for instances when same node is selected in different ways
|
// dedupe for instances when same node is selected in different ways
|
||||||
if (!seen.has(node.id)) {
|
if (!seen.has(node)) {
|
||||||
seen.add(node.id);
|
seen.add(node);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
},
|
});
|
||||||
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 = '';
|
|
||||||
|
|
||||||
const searchParams = new URLSearchParams();
|
const isNodeSelected = computed(() =>
|
||||||
|
selectedNodes.value.reduce((acc: { [nodeName: string]: true }, node) => {
|
||||||
|
acc[node.name] = true;
|
||||||
|
return acc;
|
||||||
|
}, {}),
|
||||||
|
);
|
||||||
|
|
||||||
if (deploymentType === 'cloud' && hasPermission(['instanceOwner'])) {
|
const headerHeight = computed(() => {
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (utm_campaign) {
|
|
||||||
searchParams.set('utm_campaign', utm_campaign);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (source) {
|
|
||||||
searchParams.set('source', source);
|
|
||||||
}
|
|
||||||
return `${linkUrl}?${searchParams.toString()}`;
|
|
||||||
};
|
|
||||||
},
|
|
||||||
headerHeight() {
|
|
||||||
const style = getComputedStyle(document.body);
|
const style = getComputedStyle(document.body);
|
||||||
return Number(style.getPropertyValue('--header-height'));
|
return Number(style.getPropertyValue('--header-height'));
|
||||||
},
|
});
|
||||||
isAnyModalOpen(): boolean {
|
|
||||||
return this.modalStack.length > 0;
|
const isAnyModalOpen = computed(() => {
|
||||||
},
|
return modalStack.value.length > 0;
|
||||||
},
|
});
|
||||||
actions: {
|
|
||||||
setTheme(theme: ThemeOption): void {
|
// Methods
|
||||||
this.theme = theme;
|
|
||||||
updateTheme(theme);
|
const setTheme = (newTheme: ThemeOption): void => {
|
||||||
},
|
theme.value = newTheme;
|
||||||
setMode(name: keyof Modals, mode: string): void {
|
updateTheme(newTheme);
|
||||||
this.modals[name] = {
|
};
|
||||||
...this.modals[name],
|
|
||||||
|
const setMode = (name: keyof Modals, mode: string): void => {
|
||||||
|
modalsById.value[name] = {
|
||||||
|
...modalsById.value[name],
|
||||||
mode,
|
mode,
|
||||||
};
|
};
|
||||||
},
|
};
|
||||||
setActiveId(name: keyof Modals, activeId: string): void {
|
|
||||||
this.modals[name] = {
|
const setActiveId = (name: keyof Modals, activeId: string | null): void => {
|
||||||
...this.modals[name],
|
modalsById.value[name] = {
|
||||||
|
...modalsById.value[name],
|
||||||
activeId,
|
activeId,
|
||||||
};
|
};
|
||||||
},
|
};
|
||||||
setShowAuthSelector(name: keyof Modals, showAuthSelector: boolean) {
|
|
||||||
this.modals[name] = {
|
const setShowAuthSelector = (name: keyof Modals, showAuthSelector: boolean): void => {
|
||||||
...this.modals[name],
|
modalsById.value[name] = {
|
||||||
|
...modalsById.value[name],
|
||||||
showAuthSelector,
|
showAuthSelector,
|
||||||
} as NewCredentialsModal;
|
} as NewCredentialsModal;
|
||||||
},
|
};
|
||||||
setModalData(payload: { name: keyof Modals; data: Record<string, unknown> }) {
|
|
||||||
this.modals[payload.name] = {
|
const setModalData = (payload: { name: keyof Modals; data: Record<string, unknown> }) => {
|
||||||
...this.modals[payload.name],
|
modalsById.value[payload.name] = {
|
||||||
|
...modalsById.value[payload.name],
|
||||||
data: payload.data,
|
data: payload.data,
|
||||||
};
|
};
|
||||||
},
|
};
|
||||||
openModal(name: keyof Modals): void {
|
|
||||||
this.modals[name] = {
|
const openModal = (name: ModalKey) => {
|
||||||
...this.modals[name],
|
modalsById.value[name] = {
|
||||||
|
...modalsById.value[name],
|
||||||
open: true,
|
open: true,
|
||||||
};
|
};
|
||||||
this.modalStack = [name].concat(this.modalStack) as string[];
|
modalStack.value = [name].concat(modalStack.value) as string[];
|
||||||
},
|
};
|
||||||
openModalWithData(payload: { name: keyof Modals; data: Record<string, unknown> }): void {
|
|
||||||
this.setModalData(payload);
|
const openModalWithData = (payload: { name: ModalKey; data: Record<string, unknown> }) => {
|
||||||
this.openModal(payload.name);
|
setModalData(payload);
|
||||||
},
|
openModal(payload.name);
|
||||||
closeModal(name: keyof Modals): void {
|
};
|
||||||
this.modals[name] = {
|
|
||||||
...this.modals[name],
|
const closeModal = (name: ModalKey) => {
|
||||||
|
modalsById.value[name] = {
|
||||||
|
...modalsById.value[name],
|
||||||
open: false,
|
open: false,
|
||||||
};
|
};
|
||||||
this.modalStack = this.modalStack.filter((openModalName: string) => {
|
modalStack.value = modalStack.value.filter((openModalName) => name !== openModalName);
|
||||||
return name !== openModalName;
|
};
|
||||||
});
|
|
||||||
},
|
const draggableStartDragging = (type: string, data: string) => {
|
||||||
draggableStartDragging(type: string, data: string): void {
|
draggable.value = {
|
||||||
this.draggable = {
|
|
||||||
isDragging: true,
|
isDragging: true,
|
||||||
type,
|
type,
|
||||||
data,
|
data,
|
||||||
canDrop: false,
|
canDrop: false,
|
||||||
stickyPosition: null,
|
stickyPosition: null,
|
||||||
};
|
};
|
||||||
},
|
};
|
||||||
draggableStopDragging(): void {
|
|
||||||
this.draggable = {
|
const draggableStopDragging = () => {
|
||||||
|
draggable.value = {
|
||||||
isDragging: false,
|
isDragging: false,
|
||||||
type: '',
|
type: '',
|
||||||
data: '',
|
data: '',
|
||||||
canDrop: false,
|
canDrop: false,
|
||||||
stickyPosition: null,
|
stickyPosition: null,
|
||||||
};
|
};
|
||||||
},
|
};
|
||||||
setDraggableStickyPos(position: XYPosition): void {
|
|
||||||
this.draggable = {
|
const setDraggableStickyPos = (position: XYPosition) => {
|
||||||
...this.draggable,
|
draggable.value = {
|
||||||
|
...draggable.value,
|
||||||
stickyPosition: position,
|
stickyPosition: position,
|
||||||
};
|
};
|
||||||
},
|
};
|
||||||
setDraggableCanDrop(canDrop: boolean): void {
|
|
||||||
this.draggable = {
|
const setDraggableCanDrop = (canDrop: boolean) => {
|
||||||
...this.draggable,
|
draggable.value = {
|
||||||
|
...draggable.value,
|
||||||
canDrop,
|
canDrop,
|
||||||
};
|
};
|
||||||
},
|
};
|
||||||
openDeleteUserModal(id: string): void {
|
|
||||||
this.setActiveId(DELETE_USER_MODAL_KEY, id);
|
const openDeleteUserModal = (id: string) => {
|
||||||
this.openModal(DELETE_USER_MODAL_KEY);
|
setActiveId(DELETE_USER_MODAL_KEY, id);
|
||||||
},
|
openModal(DELETE_USER_MODAL_KEY);
|
||||||
openExistingCredential(id: string): void {
|
};
|
||||||
this.setActiveId(CREDENTIAL_EDIT_MODAL_KEY, id);
|
|
||||||
this.setMode(CREDENTIAL_EDIT_MODAL_KEY, 'edit');
|
const openExistingCredential = (id: string) => {
|
||||||
this.openModal(CREDENTIAL_EDIT_MODAL_KEY);
|
setActiveId(CREDENTIAL_EDIT_MODAL_KEY, id);
|
||||||
},
|
setMode(CREDENTIAL_EDIT_MODAL_KEY, 'edit');
|
||||||
openNewCredential(type: string, showAuthOptions = false): void {
|
openModal(CREDENTIAL_EDIT_MODAL_KEY);
|
||||||
this.setActiveId(CREDENTIAL_EDIT_MODAL_KEY, type);
|
};
|
||||||
this.setShowAuthSelector(CREDENTIAL_EDIT_MODAL_KEY, showAuthOptions);
|
|
||||||
this.setMode(CREDENTIAL_EDIT_MODAL_KEY, 'new');
|
const openNewCredential = (type: string, showAuthOptions = false) => {
|
||||||
this.openModal(CREDENTIAL_EDIT_MODAL_KEY);
|
setActiveId(CREDENTIAL_EDIT_MODAL_KEY, type);
|
||||||
},
|
setShowAuthSelector(CREDENTIAL_EDIT_MODAL_KEY, showAuthOptions);
|
||||||
async getNextOnboardingPrompt(): Promise<IOnboardingCallPrompt | null> {
|
setMode(CREDENTIAL_EDIT_MODAL_KEY, 'new');
|
||||||
const rootStore = useRootStore();
|
openModal(CREDENTIAL_EDIT_MODAL_KEY);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getNextOnboardingPrompt = async () => {
|
||||||
const instanceId = rootStore.instanceId;
|
const instanceId = rootStore.instanceId;
|
||||||
const { currentUser } = useUsersStore();
|
const { currentUser } = userStore;
|
||||||
if (currentUser) {
|
if (currentUser) {
|
||||||
return await fetchNextOnboardingPrompt(instanceId, currentUser);
|
return await onboardingApi.fetchNextOnboardingPrompt(instanceId, currentUser);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
},
|
};
|
||||||
async applyForOnboardingCall(email: string): Promise<string | null> {
|
|
||||||
const rootStore = useRootStore();
|
const applyForOnboardingCall = async (email: string) => {
|
||||||
const instanceId = rootStore.instanceId;
|
const instanceId = rootStore.instanceId;
|
||||||
const { currentUser } = useUsersStore();
|
const { currentUser } = userStore;
|
||||||
if (currentUser) {
|
if (currentUser) {
|
||||||
return await applyForOnboardingCall(instanceId, currentUser, email);
|
return await onboardingApi.applyForOnboardingCall(instanceId, currentUser, email);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
},
|
};
|
||||||
async submitContactEmail(email: string, agree: boolean): Promise<string | null> {
|
|
||||||
const rootStore = useRootStore();
|
const submitContactEmail = async (email: string, agree: boolean) => {
|
||||||
const instanceId = rootStore.instanceId;
|
const instanceId = rootStore.instanceId;
|
||||||
const { currentUser } = useUsersStore();
|
const { currentUser } = userStore;
|
||||||
if (currentUser) {
|
if (currentUser) {
|
||||||
return await submitEmailOnSignup(
|
return await onboardingApi.submitEmailOnSignup(
|
||||||
instanceId,
|
instanceId,
|
||||||
currentUser,
|
currentUser,
|
||||||
email ?? currentUser?.email,
|
email ?? currentUser.email,
|
||||||
agree,
|
agree,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
},
|
};
|
||||||
openCommunityPackageUninstallConfirmModal(packageName: string) {
|
|
||||||
this.setActiveId(COMMUNITY_PACKAGE_CONFIRM_MODAL_KEY, packageName);
|
const openCommunityPackageUninstallConfirmModal = (packageName: string) => {
|
||||||
this.setMode(COMMUNITY_PACKAGE_CONFIRM_MODAL_KEY, COMMUNITY_PACKAGE_MANAGE_ACTIONS.UNINSTALL);
|
setMode(COMMUNITY_PACKAGE_CONFIRM_MODAL_KEY, packageName);
|
||||||
this.openModal(COMMUNITY_PACKAGE_CONFIRM_MODAL_KEY);
|
setActiveId(COMMUNITY_PACKAGE_CONFIRM_MODAL_KEY, COMMUNITY_PACKAGE_MANAGE_ACTIONS.UNINSTALL);
|
||||||
},
|
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);
|
const openCommunityPackageUpdateConfirmModal = (packageName: string) => {
|
||||||
this.openModal(COMMUNITY_PACKAGE_CONFIRM_MODAL_KEY);
|
setMode(COMMUNITY_PACKAGE_CONFIRM_MODAL_KEY, packageName);
|
||||||
},
|
setActiveId(COMMUNITY_PACKAGE_CONFIRM_MODAL_KEY, COMMUNITY_PACKAGE_MANAGE_ACTIONS.UPDATE);
|
||||||
addActiveAction(action: string): void {
|
openModal(COMMUNITY_PACKAGE_CONFIRM_MODAL_KEY);
|
||||||
if (!this.activeActions.includes(action)) {
|
};
|
||||||
this.activeActions.push(action);
|
|
||||||
|
const addActiveAction = (action: string) => {
|
||||||
|
if (!activeActions.value.includes(action)) {
|
||||||
|
activeActions.value.push(action);
|
||||||
}
|
}
|
||||||
},
|
};
|
||||||
removeActiveAction(action: string): void {
|
|
||||||
const actionIndex = this.activeActions.indexOf(action);
|
const removeActiveAction = (action: string) => {
|
||||||
|
const actionIndex = activeActions.value.indexOf(action);
|
||||||
if (actionIndex !== -1) {
|
if (actionIndex !== -1) {
|
||||||
this.activeActions.splice(actionIndex, 1);
|
activeActions.value.splice(actionIndex, 1);
|
||||||
}
|
}
|
||||||
},
|
};
|
||||||
addSelectedNode(node: INodeUi): void {
|
|
||||||
const isAlreadySelected = this.selectedNodes.some((n) => n.name === node.name);
|
const addSelectedNode = (node: INodeUi) => {
|
||||||
|
const isAlreadySelected = selectedNodes.value.some((n) => n.name === node.name);
|
||||||
if (!isAlreadySelected) {
|
if (!isAlreadySelected) {
|
||||||
this.selectedNodes.push(node);
|
selectedNodes.value.push(node);
|
||||||
}
|
}
|
||||||
},
|
};
|
||||||
removeNodeFromSelection(node: INodeUi): void {
|
|
||||||
let index;
|
const removeNodeFromSelection = (node: INodeUi) => {
|
||||||
for (index in this.selectedNodes) {
|
for (const [index] of selectedNodes.value.entries()) {
|
||||||
if (this.selectedNodes[index].name === node.name) {
|
if (selectedNodes.value[index].name === node.name) {
|
||||||
this.selectedNodes.splice(parseInt(index, 10), 1);
|
selectedNodes.value.splice(Number(index), 1);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
};
|
||||||
resetSelectedNodes(): void {
|
|
||||||
this.selectedNodes = [];
|
const resetSelectedNodes = () => {
|
||||||
},
|
selectedNodes.value = [];
|
||||||
setCurlCommand(payload: { name: string; command: string }): void {
|
};
|
||||||
this.modals[payload.name] = {
|
|
||||||
...this.modals[payload.name],
|
const setCurlCommand = (payload: { name: string; command: string }) => {
|
||||||
|
modalsById.value[payload.name] = {
|
||||||
|
...modalsById.value[payload.name],
|
||||||
curlCommand: payload.command,
|
curlCommand: payload.command,
|
||||||
};
|
};
|
||||||
},
|
};
|
||||||
toggleSidebarMenuCollapse(): void {
|
|
||||||
this.sidebarMenuCollapsed = !this.sidebarMenuCollapsed;
|
const toggleSidebarMenuCollapse = () => {
|
||||||
},
|
sidebarMenuCollapsed.value = !sidebarMenuCollapsed.value;
|
||||||
async getCurlToJson(curlCommand: string): Promise<CurlToJSONResponse> {
|
};
|
||||||
const rootStore = useRootStore();
|
|
||||||
const parameters = await getCurlToJson(rootStore.restApiContext, curlCommand);
|
const getCurlToJson = async (curlCommand: string) => {
|
||||||
|
const parameters = await curlParserApi.getCurlToJson(rootStore.restApiContext, curlCommand);
|
||||||
|
|
||||||
// Normalize placeholder values
|
// Normalize placeholder values
|
||||||
if (parameters['parameters.url']) {
|
if (parameters['parameters.url']) {
|
||||||
@@ -562,17 +560,18 @@ export const useUIStore = defineStore(STORES.UI, {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return parameters;
|
return parameters;
|
||||||
},
|
};
|
||||||
async goToUpgrade(
|
|
||||||
|
const goToUpgrade = async (
|
||||||
source: CloudUpdateLinkSourceType,
|
source: CloudUpdateLinkSourceType,
|
||||||
utm_campaign: UTMCampaign,
|
utm_campaign: UTMCampaign,
|
||||||
mode: 'open' | 'redirect' = 'open',
|
mode: 'open' | 'redirect' = 'open',
|
||||||
): Promise<void> {
|
) => {
|
||||||
const { usageLeft, trialDaysLeft, userIsTrialing } = useCloudPlanStore();
|
const { usageLeft, trialDaysLeft, userIsTrialing } = cloudPlanStore;
|
||||||
const { executionsLeft, workflowsLeft } = usageLeft;
|
const { executionsLeft, workflowsLeft } = usageLeft;
|
||||||
const deploymentType = useSettingsStore().deploymentType;
|
const deploymentType = settingsStore.deploymentType;
|
||||||
|
|
||||||
useTelemetryStore().track('User clicked upgrade CTA', {
|
telemetryStore.track('User clicked upgrade CTA', {
|
||||||
source,
|
source,
|
||||||
isTrial: userIsTrialing,
|
isTrial: userIsTrialing,
|
||||||
deploymentType,
|
deploymentType,
|
||||||
@@ -581,51 +580,124 @@ export const useUIStore = defineStore(STORES.UI, {
|
|||||||
workflowsLeft,
|
workflowsLeft,
|
||||||
});
|
});
|
||||||
|
|
||||||
const upgradeLink = await this.upgradeLinkUrl(source, utm_campaign, deploymentType);
|
const upgradeLink = await generateUpgradeLinkUrl(source, utm_campaign, deploymentType);
|
||||||
|
|
||||||
if (mode === 'open') {
|
if (mode === 'open') {
|
||||||
window.open(upgradeLink, '_blank');
|
window.open(upgradeLink, '_blank');
|
||||||
} else {
|
} else {
|
||||||
location.href = upgradeLink;
|
location.href = upgradeLink;
|
||||||
}
|
}
|
||||||
},
|
};
|
||||||
async dismissBanner(
|
|
||||||
name: BannerName,
|
const removeBannerFromStack = (name: BannerName) => {
|
||||||
type: 'temporary' | 'permanent' = 'temporary',
|
bannerStack.value = bannerStack.value.filter((bannerName) => bannerName !== name);
|
||||||
): Promise<void> {
|
};
|
||||||
|
|
||||||
|
const dismissBanner = async (name: BannerName, type: 'temporary' | 'permanent' = 'temporary') => {
|
||||||
if (type === 'permanent') {
|
if (type === 'permanent') {
|
||||||
await dismissBannerPermanently(useRootStore().restApiContext, {
|
await dismissBannerPermanently(rootStore.restApiContext, {
|
||||||
bannerName: name,
|
bannerName: name,
|
||||||
dismissedBanners: useSettingsStore().permanentlyDismissedBanners,
|
dismissedBanners: settingsStore.permanentlyDismissedBanners,
|
||||||
});
|
});
|
||||||
this.removeBannerFromStack(name);
|
removeBannerFromStack(name);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.removeBannerFromStack(name);
|
removeBannerFromStack(name);
|
||||||
},
|
};
|
||||||
updateBannersHeight(newHeight: number): void {
|
|
||||||
this.bannersHeight = newHeight;
|
const updateBannersHeight = (newHeight: number) => {
|
||||||
},
|
bannersHeight.value = newHeight;
|
||||||
pushBannerToStack(name: BannerName) {
|
};
|
||||||
if (this.bannerStack.includes(name)) return;
|
|
||||||
this.bannerStack.push(name);
|
const pushBannerToStack = (name: BannerName) => {
|
||||||
},
|
if (bannerStack.value.includes(name)) return;
|
||||||
removeBannerFromStack(name: BannerName) {
|
bannerStack.value.push(name);
|
||||||
this.bannerStack = this.bannerStack.filter((bannerName) => bannerName !== name);
|
};
|
||||||
},
|
|
||||||
clearBannerStack() {
|
const clearBannerStack = () => {
|
||||||
this.bannerStack = [];
|
bannerStack.value = [];
|
||||||
},
|
};
|
||||||
getNotificationsForView(view: VIEWS): NotificationOptions[] {
|
|
||||||
return this.pendingNotificationsForViews[view] ?? [];
|
const setNotificationsForView = (view: VIEWS, notifications: NotificationOptions[]) => {
|
||||||
},
|
pendingNotificationsForViews.value[view] = notifications;
|
||||||
setNotificationsForView(view: VIEWS, notifications: NotificationOptions[]) {
|
};
|
||||||
this.pendingNotificationsForViews[view] = notifications;
|
|
||||||
},
|
const deleteNotificationsForView = (view: VIEWS) => {
|
||||||
deleteNotificationsForView(view: VIEWS) {
|
delete pendingNotificationsForViews.value[view];
|
||||||
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()}`;
|
||||||
|
};
|
||||||
|
|||||||
@@ -678,7 +678,7 @@ export default defineComponent({
|
|||||||
return this.workflowsStore.getWorkflowExecution;
|
return this.workflowsStore.getWorkflowExecution;
|
||||||
},
|
},
|
||||||
workflowRunning(): boolean {
|
workflowRunning(): boolean {
|
||||||
return this.uiStore.isActionActive('workflowRunning');
|
return this.uiStore.isActionActive['workflowRunning'];
|
||||||
},
|
},
|
||||||
currentWorkflow(): string {
|
currentWorkflow(): string {
|
||||||
return this.$route.params.name?.toString() || this.workflowsStore.workflowId;
|
return this.$route.params.name?.toString() || this.workflowsStore.workflowId;
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ export default defineComponent({
|
|||||||
computed: {
|
computed: {
|
||||||
...mapStores(useUIStore),
|
...mapStores(useUIStore),
|
||||||
featureInfo(): IFakeDoor | undefined {
|
featureInfo(): IFakeDoor | undefined {
|
||||||
return this.uiStore.getFakeDoorById(this.featureId);
|
return this.uiStore.fakeDoorsById[this.featureId];
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|||||||
Reference in New Issue
Block a user