diff --git a/src/composables/useBrowserTabTitle.ts b/src/composables/useBrowserTabTitle.ts index 82b08d71a..3245da5dd 100644 --- a/src/composables/useBrowserTabTitle.ts +++ b/src/composables/useBrowserTabTitle.ts @@ -5,6 +5,7 @@ import { t } from '@/i18n' import { useSettingStore } from '@/platform/settings/settingStore' import { useWorkflowStore } from '@/platform/workflow/management/stores/workflowStore' import { useExecutionStore } from '@/stores/executionStore' +import { useWorkspaceStore } from '@/stores/workspaceStore' const DEFAULT_TITLE = 'ComfyUI' const TITLE_SUFFIX = ' - ComfyUI' @@ -13,6 +14,7 @@ export const useBrowserTabTitle = () => { const executionStore = useExecutionStore() const settingStore = useSettingStore() const workflowStore = useWorkflowStore() + const workspaceStore = useWorkspaceStore() const executionText = computed(() => executionStore.isIdle @@ -24,11 +26,27 @@ export const useBrowserTabTitle = () => { () => settingStore.get('Comfy.UseNewMenu') !== 'Disabled' ) + const isAutoSaveEnabled = computed( + () => settingStore.get('Comfy.Workflow.AutoSave') === 'after delay' + ) + + const isActiveWorkflowModified = computed( + () => !!workflowStore.activeWorkflow?.isModified + ) + const isActiveWorkflowPersisted = computed( + () => !!workflowStore.activeWorkflow?.isPersisted + ) + + const shouldShowUnsavedIndicator = computed(() => { + if (workspaceStore.shiftDown) return false + if (isAutoSaveEnabled.value) return false + if (!isActiveWorkflowPersisted.value) return true + if (isActiveWorkflowModified.value) return true + return false + }) + const isUnsavedText = computed(() => - workflowStore.activeWorkflow?.isModified || - !workflowStore.activeWorkflow?.isPersisted - ? ' *' - : '' + shouldShowUnsavedIndicator.value ? ' *' : '' ) const workflowNameText = computed(() => { const workflowName = workflowStore.activeWorkflow?.filename diff --git a/tests-ui/tests/composables/BrowserTabTitle.test.ts b/tests-ui/tests/composables/BrowserTabTitle.test.ts index 7b55ffc25..3c0cc623f 100644 --- a/tests-ui/tests/composables/BrowserTabTitle.test.ts +++ b/tests-ui/tests/composables/BrowserTabTitle.test.ts @@ -1,5 +1,6 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' -import { nextTick, reactive } from 'vue' +import { effectScope, nextTick, reactive } from 'vue' +import type { EffectScope } from 'vue' import { useBrowserTabTitle } from '@/composables/useBrowserTabTitle' @@ -38,6 +39,14 @@ vi.mock('@/platform/workflow/management/stores/workflowStore', () => ({ useWorkflowStore: () => workflowStore })) +// Mock the workspace store +const workspaceStore = reactive({ + shiftDown: false +}) +vi.mock('@/stores/workspaceStore', () => ({ + useWorkspaceStore: () => workspaceStore +})) + describe('useBrowserTabTitle', () => { beforeEach(() => { // reset execution store @@ -51,14 +60,17 @@ describe('useBrowserTabTitle', () => { // reset setting and workflow stores ;(settingStore.get as any).mockReturnValue('Enabled') workflowStore.activeWorkflow = null + workspaceStore.shiftDown = false // reset document title document.title = '' }) it('sets default title when idle and no workflow', () => { - useBrowserTabTitle() + const scope: EffectScope = effectScope() + scope.run(() => useBrowserTabTitle()) expect(document.title).toBe('ComfyUI') + scope.stop() }) it('sets workflow name as title when workflow exists and menu enabled', async () => { @@ -68,9 +80,11 @@ describe('useBrowserTabTitle', () => { isModified: false, isPersisted: true } - useBrowserTabTitle() + const scope: EffectScope = effectScope() + scope.run(() => useBrowserTabTitle()) await nextTick() expect(document.title).toBe('myFlow - ComfyUI') + scope.stop() }) it('adds asterisk for unsaved workflow', async () => { @@ -80,9 +94,44 @@ describe('useBrowserTabTitle', () => { isModified: true, isPersisted: true } - useBrowserTabTitle() + const scope: EffectScope = effectScope() + scope.run(() => useBrowserTabTitle()) await nextTick() expect(document.title).toBe('*myFlow - ComfyUI') + scope.stop() + }) + + it('hides asterisk when autosave is enabled', async () => { + ;(settingStore.get as any).mockImplementation((key: string) => { + if (key === 'Comfy.Workflow.AutoSave') return 'after delay' + if (key === 'Comfy.UseNewMenu') return 'Enabled' + return 'Enabled' + }) + workflowStore.activeWorkflow = { + filename: 'myFlow', + isModified: true, + isPersisted: true + } + useBrowserTabTitle() + await nextTick() + expect(document.title).toBe('myFlow - ComfyUI') + }) + + it('hides asterisk while Shift key is held', async () => { + ;(settingStore.get as any).mockImplementation((key: string) => { + if (key === 'Comfy.Workflow.AutoSave') return 'off' + if (key === 'Comfy.UseNewMenu') return 'Enabled' + return 'Enabled' + }) + workspaceStore.shiftDown = true + workflowStore.activeWorkflow = { + filename: 'myFlow', + isModified: true, + isPersisted: true + } + useBrowserTabTitle() + await nextTick() + expect(document.title).toBe('myFlow - ComfyUI') }) // Fails when run together with other tests. Suspect to be caused by leaked @@ -94,17 +143,21 @@ describe('useBrowserTabTitle', () => { isModified: false, isPersisted: true } - useBrowserTabTitle() + const scope: EffectScope = effectScope() + scope.run(() => useBrowserTabTitle()) await nextTick() expect(document.title).toBe('ComfyUI') + scope.stop() }) it('shows execution progress when not idle without workflow', async () => { executionStore.isIdle = false executionStore.executionProgress = 0.3 - useBrowserTabTitle() + const scope: EffectScope = effectScope() + scope.run(() => useBrowserTabTitle()) await nextTick() expect(document.title).toBe('[30%]ComfyUI') + scope.stop() }) it('shows node execution title when executing a node using nodeProgressStates', async () => { @@ -122,9 +175,11 @@ describe('useBrowserTabTitle', () => { } } } - useBrowserTabTitle() + const scope: EffectScope = effectScope() + scope.run(() => useBrowserTabTitle()) await nextTick() expect(document.title).toBe('[40%][50%] Foo') + scope.stop() }) it('shows multiple nodes running when multiple nodes are executing', async () => { @@ -140,8 +195,10 @@ describe('useBrowserTabTitle', () => { }, '2': { state: 'running', value: 8, max: 10, node: '2', prompt_id: 'test' } } - useBrowserTabTitle() + const scope: EffectScope = effectScope() + scope.run(() => useBrowserTabTitle()) await nextTick() expect(document.title).toBe('[40%][2 nodes running]') + scope.stop() }) })