From 5c723e1bdd516389dde9f283788ac4b1933ff123 Mon Sep 17 00:00:00 2001 From: Daria Date: Thu, 10 Jul 2025 15:04:31 +0300 Subject: [PATCH] feat(editor): Store focused panel state in local storage (no-changelog) (#17163) --- .../composables/useCanvasOperations.test.ts | 4 - .../src/composables/useCanvasOperations.ts | 4 - .../useNodeSettingsParameters.test.ts | 9 +- .../composables/useNodeSettingsParameters.ts | 4 +- .../src/composables/useWorkflowSaving.ts | 4 + packages/frontend/editor-ui/src/constants.ts | 1 + .../editor-ui/src/stores/focusPanel.store.ts | 112 ++++++++++++++---- 7 files changed, 100 insertions(+), 38 deletions(-) diff --git a/packages/frontend/editor-ui/src/composables/useCanvasOperations.test.ts b/packages/frontend/editor-ui/src/composables/useCanvasOperations.test.ts index cb36b3da38..a971b7158d 100644 --- a/packages/frontend/editor-ui/src/composables/useCanvasOperations.test.ts +++ b/packages/frontend/editor-ui/src/composables/useCanvasOperations.test.ts @@ -29,7 +29,6 @@ import { mock } from 'vitest-mock-extended'; import { useNodeTypesStore } from '@/stores/nodeTypes.store'; import { useCredentialsStore } from '@/stores/credentials.store'; import { useExecutionsStore } from '@/stores/executions.store'; -import { useFocusPanelStore } from '@/stores/focusPanel.store'; import { useNodeCreatorStore } from '@/stores/nodeCreator.store'; import { useProjectsStore } from '@/stores/projects.store'; import { waitFor } from '@testing-library/vue'; @@ -2823,7 +2822,6 @@ describe('useCanvasOperations', () => { const workflowsStore = mockedStore(useWorkflowsStore); const uiStore = mockedStore(useUIStore); const executionsStore = mockedStore(useExecutionsStore); - const focusPanelStore = mockedStore(useFocusPanelStore); const nodeHelpers = { credentialsUpdated: { value: true } }; @@ -2834,7 +2832,6 @@ describe('useCanvasOperations', () => { workflowsStore.resetState = vi.fn(); workflowsStore.setActiveExecutionId = vi.fn(); uiStore.resetLastInteractedWith = vi.fn(); - focusPanelStore.reset = vi.fn(); executionsStore.activeExecution = null; workflowsStore.executionWaitingForWebhook = true; @@ -2872,7 +2869,6 @@ describe('useCanvasOperations', () => { expect(workflowsStore.resetState).toHaveBeenCalled(); expect(workflowsStore.currentWorkflowExecutions).toEqual([]); expect(workflowsStore.setActiveExecutionId).toHaveBeenCalledWith(undefined); - expect(focusPanelStore.reset).toHaveBeenCalled(); expect(uiStore.resetLastInteractedWith).toHaveBeenCalled(); expect(uiStore.stateIsDirty).toBe(false); expect(executionsStore.activeExecution).toBeNull(); diff --git a/packages/frontend/editor-ui/src/composables/useCanvasOperations.ts b/packages/frontend/editor-ui/src/composables/useCanvasOperations.ts index cf8a7d8fb6..3883406d12 100644 --- a/packages/frontend/editor-ui/src/composables/useCanvasOperations.ts +++ b/packages/frontend/editor-ui/src/composables/useCanvasOperations.ts @@ -53,7 +53,6 @@ import { useSettingsStore } from '@/stores/settings.store'; import { useTagsStore } from '@/stores/tags.store'; import { useUIStore } from '@/stores/ui.store'; import { useWorkflowsStore } from '@/stores/workflows.store'; -import { useFocusPanelStore } from '@/stores/focusPanel.store'; import type { CanvasConnection, CanvasConnectionCreateData, @@ -153,7 +152,6 @@ export function useCanvasOperations() { const settingsStore = useSettingsStore(); const tagsStore = useTagsStore(); const nodeCreatorStore = useNodeCreatorStore(); - const focusPanelStore = useFocusPanelStore(); const executionsStore = useExecutionsStore(); const projectsStore = useProjectsStore(); const logsStore = useLogsStore(); @@ -1610,8 +1608,6 @@ export function useCanvasOperations() { workflowsStore.currentWorkflowExecutions = []; workflowsStore.setActiveExecutionId(undefined); - focusPanelStore.reset(); - // Reset actions uiStore.resetLastInteractedWith(); uiStore.stateIsDirty = false; diff --git a/packages/frontend/editor-ui/src/composables/useNodeSettingsParameters.test.ts b/packages/frontend/editor-ui/src/composables/useNodeSettingsParameters.test.ts index 3f43d8117b..4d134714ef 100644 --- a/packages/frontend/editor-ui/src/composables/useNodeSettingsParameters.test.ts +++ b/packages/frontend/editor-ui/src/composables/useNodeSettingsParameters.test.ts @@ -71,11 +71,11 @@ describe('useNodeSettingsParameters', () => { ndvStore.activeNodeName = 'Node1'; ndvStore.setActiveNodeName = vi.fn(); ndvStore.resetNDVPushRef = vi.fn(); - focusPanelStore.setFocusedNodeParameter = vi.fn(); + focusPanelStore.openWithFocusedNodeParameter = vi.fn(); focusPanelStore.focusPanelActive = false; }); - it('sets focused node parameter and activates panel', () => { + it('sets focused node parameter', () => { const { handleFocus } = useNodeSettingsParameters(); const node: INodeUi = { id: '1', @@ -95,12 +95,11 @@ describe('useNodeSettingsParameters', () => { handleFocus(node, path, parameter); - expect(focusPanelStore.setFocusedNodeParameter).toHaveBeenCalledWith({ + expect(focusPanelStore.openWithFocusedNodeParameter).toHaveBeenCalledWith({ nodeId: node.id, parameterPath: path, parameter, }); - expect(focusPanelStore.focusPanelActive).toBe(true); expect(ndvStore.setActiveNodeName).toHaveBeenCalledWith(null); expect(ndvStore.resetNDVPushRef).toHaveBeenCalled(); @@ -118,7 +117,7 @@ describe('useNodeSettingsParameters', () => { handleFocus(undefined, 'parameters.foo', parameter); - expect(focusPanelStore.setFocusedNodeParameter).not.toHaveBeenCalled(); + expect(focusPanelStore.openWithFocusedNodeParameter).not.toHaveBeenCalled(); }); }); }); diff --git a/packages/frontend/editor-ui/src/composables/useNodeSettingsParameters.ts b/packages/frontend/editor-ui/src/composables/useNodeSettingsParameters.ts index de0e6bde42..e733668387 100644 --- a/packages/frontend/editor-ui/src/composables/useNodeSettingsParameters.ts +++ b/packages/frontend/editor-ui/src/composables/useNodeSettingsParameters.ts @@ -233,7 +233,7 @@ export function useNodeSettingsParameters() { const ndvStore = useNDVStore(); const focusPanelStore = useFocusPanelStore(); - focusPanelStore.setFocusedNodeParameter({ + focusPanelStore.openWithFocusedNodeParameter({ nodeId: node.id, parameterPath: path, parameter, @@ -243,8 +243,6 @@ export function useNodeSettingsParameters() { ndvStore.setActiveNodeName(null); ndvStore.resetNDVPushRef(); } - - focusPanelStore.focusPanelActive = true; } function shouldDisplayNodeParameter( diff --git a/packages/frontend/editor-ui/src/composables/useWorkflowSaving.ts b/packages/frontend/editor-ui/src/composables/useWorkflowSaving.ts index d5d9de7d33..6baa5a6198 100644 --- a/packages/frontend/editor-ui/src/composables/useWorkflowSaving.ts +++ b/packages/frontend/editor-ui/src/composables/useWorkflowSaving.ts @@ -26,6 +26,7 @@ import { useTelemetry } from './useTelemetry'; import { useNodeHelpers } from './useNodeHelpers'; import { tryToParseNumber } from '@/utils/typesUtils'; import { useTemplatesStore } from '@/stores/templates.store'; +import { useFocusPanelStore } from '@/stores/focusPanel.store'; export function useWorkflowSaving({ router }: { router: ReturnType }) { const uiStore = useUIStore(); @@ -33,6 +34,7 @@ export function useWorkflowSaving({ router }: { router: ReturnType; + +const DEFAULT_FOCUS_PANEL_DATA: FocusPanelData = { isActive: false, parameters: [] }; + export const useFocusPanelStore = defineStore(STORES.FOCUS_PANEL, () => { const workflowsStore = useWorkflowsStore(); + const focusPanelStorage = useStorage(LOCAL_STORAGE_FOCUS_PANEL); - const focusPanelActive = ref(false); - const _focusedNodeParameters = ref([]); + const focusPanelData = computed((): FocusPanelDataByWid => { + const defaultValue: FocusPanelDataByWid = { + [workflowsStore.workflowId]: DEFAULT_FOCUS_PANEL_DATA, + }; + + return focusPanelStorage.value + ? jsonParse(focusPanelStorage.value, { fallbackValue: defaultValue }) + : defaultValue; + }); + + const currentFocusPanelData = computed( + (): FocusPanelData => + focusPanelData.value[workflowsStore.workflowId] ?? DEFAULT_FOCUS_PANEL_DATA, + ); + + const focusPanelActive = computed(() => currentFocusPanelData.value.isActive); + const _focusedNodeParameters = computed(() => currentFocusPanelData.value.parameters); // An unenriched parameter indicates a missing nodeId const focusedNodeParameters = computed>( @@ -38,40 +70,76 @@ export const useFocusPanelStore = defineStore(STORES.FOCUS_PANEL, () => { }), ); - const setFocusedNodeParameter = (nodeParameter: FocusedNodeParameter) => { - _focusedNodeParameters.value = [ - nodeParameter, - // Uncomment when tabs are implemented - // ...focusedNodeParameters.value.filter((p) => p.parameterPath !== nodeParameter.parameterPath), - ]; + const _setOptions = ({ + parameters, + isActive, + wid = workflowsStore.workflowId, + removeEmpty = false, + }: { + isActive?: boolean; + parameters?: FocusedNodeParameter[]; + wid?: string; + removeEmpty?: boolean; + }) => { + const focusPanelDataCurrent = focusPanelData.value; + + if (removeEmpty && PLACEHOLDER_EMPTY_WORKFLOW_ID in focusPanelDataCurrent) { + delete focusPanelDataCurrent[PLACEHOLDER_EMPTY_WORKFLOW_ID]; + } + + focusPanelStorage.value = JSON.stringify({ + ...focusPanelData.value, + [wid]: { + isActive: isActive ?? focusPanelActive.value, + parameters: parameters ?? _focusedNodeParameters.value, + }, + }); + }; + + // When a new workflow is saved, we should update the focus panel data with the new workflow ID + const onNewWorkflowSave = (wid: string) => { + if (!currentFocusPanelData.value || !(PLACEHOLDER_EMPTY_WORKFLOW_ID in focusPanelData.value)) { + return; + } + + const latestWorkflowData = focusPanelData.value[PLACEHOLDER_EMPTY_WORKFLOW_ID]; + _setOptions({ + wid, + parameters: latestWorkflowData.parameters, + isActive: latestWorkflowData.isActive, + removeEmpty: true, + }); + }; + + const openWithFocusedNodeParameter = (nodeParameter: FocusedNodeParameter) => { + const parameters = [nodeParameter]; + // TODO: uncomment when tabs are implemented + // ...focusedNodeParameters.value.filter((p) => p.parameterPath !== nodeParameter.parameterPath), + + _setOptions({ parameters, isActive: true }); }; const closeFocusPanel = () => { - focusPanelActive.value = false; + _setOptions({ isActive: false }); }; const toggleFocusPanel = () => { - focusPanelActive.value = !focusPanelActive.value; + _setOptions({ isActive: !focusPanelActive.value }); }; - const reset = () => { - focusPanelActive.value = false; - _focusedNodeParameters.value = []; - }; - - function isRichParameter( + const isRichParameter = ( p: RichFocusedNodeParameter | FocusedNodeParameter, - ): p is RichFocusedNodeParameter { + ): p is RichFocusedNodeParameter => { return 'value' in p && 'node' in p; - } + }; return { focusPanelActive, focusedNodeParameters, - setFocusedNodeParameter, + openWithFocusedNodeParameter, isRichParameter, closeFocusPanel, toggleFocusPanel, - reset, + onNewWorkflowSave, }; });