Skip to content

Commit

Permalink
Workflow sidebar tab (Step1) (#265)
Browse files Browse the repository at this point in the history
* Fix canvas not init issue (#283)

* Fix copy paste of widget value (#284)

* Fix copy paste of widget value

* Fix ui tests

* Allow undefined group font size

* Update test expectations [skip ci]

* nit

---------

Co-authored-by: github-actions <github-actions@github.com>

* 1.2.9 (#285)

* WIP

* Add refresh button

* Add context menu

* nit

* Add selection mode

* Editable text

* Fix relative path

* implement node delete

* Dynamic menu items

* Fix refresh

* Better dynamic handling of menu items

* Disable rename / delete for root

* Add workflow download

* Auto select file name

* Create workflow

* Generate non-dup name

* Fix folder name

* Rename workflwoStore to userFileStore

* load workflow when leaf node selected

* Extract common report error logic

* Basic workflows tab test

* Auto expand

* Add test on add/remove workflow

---------

Co-authored-by: github-actions <github-actions@github.com>
  • Loading branch information
huchenlei and github-actions committed Aug 4, 2024
1 parent cf9d95a commit 0b6ca50
Show file tree
Hide file tree
Showing 10 changed files with 579 additions and 23 deletions.
95 changes: 82 additions & 13 deletions browser_tests/ComfyPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,42 +37,107 @@ class ComfyNodeSearchBox {
}
}

class NodeLibrarySideBarTab {
public readonly tabId: string = 'node-library'
constructor(public readonly page: Page) {}

get tabButton() {
class SideBarTab {
constructor(
public readonly page: Page,
public readonly tabId: string,
public readonly tabName: string
) {}

public get tabButton() {
return this.page.locator(`.${this.tabId}-tab-button`)
}

get selectedTabButton() {
public get selectedTabButton() {
return this.page.locator(
`.${this.tabId}-tab-button.side-bar-button-selected`
)
}

get nodeLibraryTree() {
return this.page.locator('.node-lib-tree')
public get tabHeader() {
return this.page.locator(`.comfy-vue-side-bar-header`)
}

get nodePreview() {
return this.page.locator('.node-lib-node-preview')
public get tabBody() {
return this.page.locator(`.comfy-vue-side-bar-body`)
}

async open() {
if (await this.selectedTabButton.isVisible()) {
public async isOpen() {
return await this.selectedTabButton.isVisible()
}

public async open() {
if (await this.isOpen()) {
return
}

await this.tabButton.click()
await this.nodeLibraryTree.waitFor({ state: 'visible' })
await this.tabBody.waitFor({ state: 'visible' })
}

public async close() {
if (!(await this.isOpen())) {
return
}

await this.tabButton.click()
await this.tabBody.waitFor({ state: 'hidden' })
}
}

class NodeLibrarySideBarTab extends SideBarTab {
constructor(public readonly page: Page) {
super(page, 'node-library', 'Node Library')
}

get nodeLibraryTree() {
return this.page.locator('.node-lib-tree')
}

get nodePreview() {
return this.page.locator('.node-lib-node-preview')
}

async toggleFirstFolder() {
await this.page.locator('.p-tree-node-toggle-button').nth(0).click()
}
}

class WorkflowsSideBarTab extends SideBarTab {
public readonly testWorkflowName = 'test_workflow.json'

constructor(public readonly page: Page) {
super(page, 'workflows', 'Workflows')
}

get workflowTreeRoot() {
return this.page.locator('.p-tree-node[aria-label="workflows"]')
}

get testWorkflowItem() {
return this.page.locator(
`.p-tree-node[aria-label="${this.testWorkflowName}"]`
)
}

async addWorkflow(workflowName: string = this.testWorkflowName) {
await this.workflowTreeRoot.click({ button: 'right' })
await this.page.getByLabel('New Workflow').locator('a').click()
const textbox = this.workflowTreeRoot.getByRole('textbox')
await textbox.fill(workflowName)
await textbox.press('Enter')
await this.page.waitForTimeout(100)
}

async removeWorkflow(workflowName: string = this.testWorkflowName) {
await this.workflowTreeRoot
.getByText(workflowName)
.click({ button: 'right' })
await this.page.getByLabel('Delete').locator('a').click()
await this.page.waitForTimeout(100)
}
}

class ComfyMenu {
public readonly sideToolBar: Locator
public readonly themeToggleButton: Locator
Expand All @@ -86,6 +151,10 @@ class ComfyMenu {
return new NodeLibrarySideBarTab(this.page)
}

get workflowsTab() {
return new WorkflowsSideBarTab(this.page)
}

async toggleTheme() {
await this.themeToggleButton.click()
await this.page.evaluate(() => {
Expand Down
20 changes: 20 additions & 0 deletions browser_tests/menu.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,4 +92,24 @@ test.describe('Menu', () => {
// Verify the node is added to the canvas
expect(await comfyPage.getGraphNodesCount()).toBe(count + 1)
})

test.describe('Workflows sidebar tab', () => {
test('Can open and close workflows tab', async ({ comfyPage }) => {
const tab = comfyPage.menu.workflowsTab
await tab.open()
expect(await tab.isOpen()).toBe(true)

await tab.close()
expect(await tab.isOpen()).toBe(false)
})

test('Can add / remove workflow', async ({ comfyPage }) => {
const tab = comfyPage.menu.workflowsTab
await tab.open()
await tab.addWorkflow()
expect(await tab.testWorkflowItem.isVisible()).toBe(true)
await tab.removeWorkflow()
expect(await tab.testWorkflowItem.isVisible()).toBe(false)
})
})
})
11 changes: 11 additions & 0 deletions src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,17 @@
<ProgressSpinner v-if="isLoading" class="spinner"></ProgressSpinner>
<BlockUI full-screen :blocked="isLoading" />
<GraphCanvas />
<Toast />
</template>

<script setup lang="ts">
import { computed, markRaw, onMounted, watch } from 'vue'
import Toast from 'primevue/toast'
import BlockUI from 'primevue/blockui'
import ProgressSpinner from 'primevue/progressspinner'
import GraphCanvas from '@/components/graph/GraphCanvas.vue'
import QueueSideBarTab from '@/components/sidebar/tabs/QueueSideBarTab.vue'
import WorkflowsSideBarTab from '@/components/sidebar/tabs/WorkflowsSideBarTab.vue'
import { app } from './scripts/app'
import { useSettingStore } from './stores/settingStore'
import { useI18n } from 'vue-i18n'
Expand Down Expand Up @@ -54,6 +57,14 @@ const init = () => {
component: markRaw(NodeLibrarySideBarTab),
type: 'vue'
})
app.extensionManager.registerSidebarTab({
id: 'workflows',
icon: 'pi pi-copy',
title: t('sideToolBar.workflows'),
tooltip: t('sideToolBar.workflows'),
component: markRaw(WorkflowsSideBarTab),
type: 'vue'
})
}
onMounted(() => {
Expand Down
90 changes: 90 additions & 0 deletions src/components/sidebar/tabs/EditableText.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
<template>
<div class="editable-text">
<span v-if="!props.isEditing">
{{ modelValue }}
</span>
<InputText
v-else
type="text"
size="small"
fluid
v-model:modelValue="inputValue"
ref="inputRef"
@keyup.enter="finishEditing"
:pt="{
root: {
onBlur: finishEditing
}
}"
v-focus
/>
</div>
</template>

<script setup lang="ts">
import InputText from 'primevue/inputtext'
import { nextTick, ref, watch } from 'vue'
const props = defineProps({
modelValue: {
type: String,
required: true
},
isEditing: {
type: Boolean,
default: false
}
})
const emit = defineEmits(['update:modelValue', 'edit'])
const inputValue = ref<string>(props.modelValue)
const isEditingFinished = ref<boolean>(false)
const inputRef = ref(null)
const finishEditing = () => {
if (isEditingFinished.value) {
return
}
isEditingFinished.value = true
emit('edit', inputValue.value)
}
watch(
() => props.isEditing,
(newVal) => {
if (newVal) {
inputValue.value = props.modelValue
isEditingFinished.value = false
nextTick(() => {
if (!inputRef.value) return
const fileName = inputValue.value.split('.').slice(0, -1).join('.')
const start = 0
const end = fileName.length
const inputElement = inputRef.value.$el
inputElement.setSelectionRange(start, end)
})
}
}
)
const vFocus = {
mounted: (el: HTMLElement) => el.focus()
}
</script>

<style scoped>
.editable-text {
display: inline-block;
min-width: 50px;
padding: 2px;
cursor: pointer;
}
.editable-text input {
width: 100%;
box-sizing: border-box;
}
</style>
2 changes: 0 additions & 2 deletions src/components/sidebar/tabs/QueueSideBarTab.vue
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@
}"
>
<template #header>
<Toast />
<ConfirmPopup />
<Button
icon="pi pi-trash"
Expand Down Expand Up @@ -72,7 +71,6 @@ import Column from 'primevue/column'
import Tag from 'primevue/tag'
import Button from 'primevue/button'
import ConfirmPopup from 'primevue/confirmpopup'
import Toast from 'primevue/toast'
import Message from 'primevue/message'
import { useConfirm } from 'primevue/useconfirm'
import { useToast } from 'primevue/usetoast'
Expand Down
Loading

0 comments on commit 0b6ca50

Please sign in to comment.