[Beta Menu] Shows unsaved state on browser tab title (#860)

* [Beta Menu] Shows unsaved state on browser tab title

* Proper state management

* Add playwright test

* Fix browser tests
This commit is contained in:
Chenlei Hu
2024-09-17 16:14:06 +09:00
committed by GitHub
parent e8daebdc0c
commit 4e41db2d6a
7 changed files with 106 additions and 11 deletions

View File

@@ -108,10 +108,29 @@ class NodeLibrarySidebarTab {
class ComfyMenu {
public readonly sideToolbar: Locator
public readonly themeToggleButton: Locator
public readonly saveButton: Locator
constructor(public readonly page: Page) {
this.sideToolbar = page.locator('.side-tool-bar-container')
this.themeToggleButton = page.locator('.comfy-vue-theme-toggle')
this.saveButton = page
.locator('button[title="Save the current workflow"]')
.nth(0)
}
async saveWorkflow(name: string) {
const acceptDialog = async (dialog) => {
await dialog.accept(name)
}
this.page.on('dialog', acceptDialog)
await this.saveButton.click()
// Wait a moment to ensure the dialog has been handled
await this.page.waitForTimeout(300)
// Remove the dialog listener
this.page.off('dialog', acceptDialog)
}
get nodeLibraryTab() {
@@ -178,6 +197,10 @@ export class ComfyPage {
async setup() {
await this.goto()
await this.page.evaluate(() => {
localStorage.clear()
sessionStorage.clear()
})
// Unify font for consistent screenshots.
await this.page.addStyleTag({
url: 'https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap'

View File

@@ -0,0 +1,55 @@
import { expect } from '@playwright/test'
import { comfyPageFixture as test } from './ComfyPage'
test.describe('Browser tab title', () => {
test.describe('Beta Menu', () => {
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.setSetting('Comfy.UseNewMenu', 'Top')
})
test.afterEach(async ({ comfyPage }) => {
await comfyPage.setSetting('Comfy.UseNewMenu', 'Disabled')
})
test('Can display workflow name', async ({ comfyPage }) => {
const workflowName = await comfyPage.page.evaluate(async () => {
return window['app'].workflowManager.activeWorkflow.name
})
// Note: unsaved workflow name is always prepended with "*".
expect(await comfyPage.page.title()).toBe(`*${workflowName}`)
})
test('Can display workflow name with unsaved changes', async ({
comfyPage
}) => {
const workflowName = await comfyPage.page.evaluate(async () => {
return window['app'].workflowManager.activeWorkflow.name
})
// Note: unsaved workflow name is always prepended with "*".
expect(await comfyPage.page.title()).toBe(`*${workflowName}`)
await comfyPage.menu.saveWorkflow('test')
expect(await comfyPage.page.title()).toBe('test')
const textBox = comfyPage.widgetTextBox
await textBox.fill('Hello World')
await comfyPage.clickEmptySpace()
expect(await comfyPage.page.title()).toBe(`*test`)
// Delete the saved workflow for cleanup.
await comfyPage.page.evaluate(async () => {
window['app'].workflowManager.activeWorkflow.delete()
})
})
})
test.describe('Legacy Menu', () => {
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.setSetting('Comfy.UseNewMenu', 'Disabled')
})
test('Can display default title', async ({ comfyPage }) => {
expect(await comfyPage.page.title()).toBe('ComfyUI')
})
})
})

View File

@@ -11,6 +11,7 @@ import { useWorkflowStore } from '@/stores/workflowStore'
import { useTitle } from '@vueuse/core'
import { computed } from 'vue'
const DEFAULT_TITLE = 'ComfyUI'
const executionStore = useExecutionStore()
const executionText = computed(() =>
executionStore.isIdle ? '' : `[${executionStore.executionProgress}%]`
@@ -22,12 +23,18 @@ const betaMenuEnabled = computed(
)
const workflowStore = useWorkflowStore()
const workflowNameText = computed(
() =>
(betaMenuEnabled.value ? workflowStore.activeWorkflow?.name : undefined) ??
'ComfyUI'
const isUnsavedText = computed(() =>
workflowStore.previousWorkflowUnsaved ? ' *' : ''
)
const workflowNameText = computed(() => {
const workflowName = workflowStore.activeWorkflow?.name
return workflowName ? isUnsavedText.value + workflowName : DEFAULT_TITLE
})
const title = computed(() => executionText.value + workflowNameText.value)
const title = computed(
() =>
executionText.value +
(betaMenuEnabled.value ? workflowNameText.value : DEFAULT_TITLE)
)
useTitle(title)
</script>

View File

@@ -9,6 +9,7 @@ import { ComfyAsyncDialog } from '../components/asyncDialog'
import { trimJsonExt } from '@/utils/formatUtil'
import type { ComfyApp } from '@/scripts/app'
import type { ComfyComponent } from '../components'
import { useWorkflowStore } from '@/stores/workflowStore'
export class ComfyWorkflowsMenu {
#first = true
@@ -68,7 +69,11 @@ export class ComfyWorkflowsMenu {
this.unsaved = prop(this, 'unsaved', classList.unsaved, (v) => {
classList.unsaved = v
this.button.classList = classList
setStorageValue('Comfy.PreviousWorkflowUnsaved', v)
setStorageValue('Comfy.PreviousWorkflowUnsaved', String(v))
if (this.app.vueAppReady) {
useWorkflowStore().previousWorkflowUnsaved = v
}
})
}

View File

@@ -153,7 +153,7 @@ export function prop<T>(
return defaultValue
}
export function getStorageValue(id) {
export function getStorageValue(id: string) {
const clientId = api.clientId ?? api.initialClientId
return (
(clientId && sessionStorage.getItem(`${id}:${clientId}`)) ??
@@ -161,7 +161,7 @@ export function getStorageValue(id) {
)
}
export function setStorageValue(id, value) {
export function setStorageValue(id: string, value: string) {
const clientId = api.clientId ?? api.initialClientId
if (clientId) {
sessionStorage.setItem(`${id}:${clientId}`, value)

View File

@@ -1,11 +1,16 @@
import { defineStore } from 'pinia'
import { ref } from 'vue'
import { ComfyWorkflow } from '@/scripts/workflows'
import { getStorageValue } from '@/scripts/utils'
export const useWorkflowStore = defineStore('workflow', () => {
const activeWorkflow = ref<ComfyWorkflow | null>(null)
const previousWorkflowUnsaved = ref<boolean>(
Boolean(getStorageValue('Comfy.PreviousWorkflowUnsaved'))
)
return {
activeWorkflow
activeWorkflow,
previousWorkflowUnsaved
}
})

View File

@@ -30,8 +30,8 @@ export function appendJsonExt(path: string) {
return path
}
export function trimJsonExt(path: string) {
return path.replace(/\.json$/, '')
export function trimJsonExt(path?: string) {
return path?.replace(/\.json$/, '')
}
export function highlightQuery(text: string, query: string) {