Skip to content

Commit

Permalink
Node library custom bookmark folder (#631)
Browse files Browse the repository at this point in the history
* Add new folder button

* Add tree util test

* nit

* Support empty folder in node library

* Drag to bookmark folder

* Use bookmark icon for bookmark folder

* Highlight on dragover

* nit

* Auto-expand on item added

* Extract bookmark system as store

* Add context menu on bookmark folder

* Add editable text

* Fix reactivity

* Plumb editable text

* refactor

* Rename node

* Fix focus

* Prevent name collision

* nit

* Add new folder

* nested folder support

* Change drag behavior

* Add basic playwright tests

* nit

* Target tree-node-content instead of tree-node
  • Loading branch information
huchenlei authored Aug 26, 2024
1 parent f36c934 commit 090fda2
Show file tree
Hide file tree
Showing 10 changed files with 692 additions and 82 deletions.
24 changes: 18 additions & 6 deletions browser_tests/ComfyPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,14 @@ class NodeLibrarySidebarTab {
return this.page.locator('.node-lib-node-preview')
}

get tabContainer() {
return this.page.locator('.sidebar-content-container')
}

get newFolderButton() {
return this.tabContainer.locator('.new-folder-button')
}

async open() {
if (await this.selectedTabButton.isVisible()) {
return
Expand All @@ -74,16 +82,20 @@ class NodeLibrarySidebarTab {
await this.nodeLibraryTree.waitFor({ state: 'visible' })
}

folderSelector(folderName: string) {
return `.p-tree-node-content:has(> .node-lib-tree-node-label:has(.folder-label:has-text("${folderName}")))`
}

getFolder(folderName: string) {
return this.page.locator(
`.p-tree-node-content:has(> .node-lib-tree-node-label:has(.folder-label:has-text("${folderName}")))`
)
return this.page.locator(this.folderSelector(folderName))
}

nodeSelector(nodeName: string) {
return `.p-tree-node-content:has(> .node-lib-tree-node-label:has(.node-label:has-text("${nodeName}")))`
}

getNode(nodeName: string) {
return this.page.locator(
`.p-tree-node-content:has(> .node-lib-tree-node-label:has(.node-label:has-text("${nodeName}")))`
)
return this.page.locator(this.nodeSelector(nodeName))
}
}

Expand Down
113 changes: 104 additions & 9 deletions browser_tests/menu.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,15 @@ test.describe('Menu', () => {
})

test.describe('Node library sidebar', () => {
test('Node preview and drag to canvas', async ({ comfyPage }) => {
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.setSetting('Comfy.NodeLibrary.Bookmarks', [])
// Open the sidebar
const tab = comfyPage.menu.nodeLibraryTab
await tab.open()
})

test('Node preview and drag to canvas', async ({ comfyPage }) => {
const tab = comfyPage.menu.nodeLibraryTab
await tab.getFolder('sampling').click()

// Hover over a node to display the preview
Expand All @@ -86,11 +91,7 @@ test.describe('Menu', () => {
})

test('Bookmark node', async ({ comfyPage }) => {
await comfyPage.setSetting('Comfy.NodeLibrary.Bookmarks', [])

// Open the sidebar
const tab = comfyPage.menu.nodeLibraryTab
await tab.open()
await tab.getFolder('sampling').click()

// Bookmark the node
Expand All @@ -116,13 +117,107 @@ test.describe('Menu', () => {
test('Ignores unrecognized node', async ({ comfyPage }) => {
await comfyPage.setSetting('Comfy.NodeLibrary.Bookmarks', ['foo'])

// Open the sidebar
const tab = comfyPage.menu.nodeLibraryTab
await tab.open()

expect(await tab.getFolder('sampling').count()).toBe(1)
expect(await tab.getNode('foo').count()).toBe(0)
await comfyPage.setSetting('Comfy.NodeLibrary.Bookmarks', [])
})

test('Displays empty bookmarks folder', async ({ comfyPage }) => {
await comfyPage.setSetting('Comfy.NodeLibrary.Bookmarks', ['foo/'])
const tab = comfyPage.menu.nodeLibraryTab
expect(await tab.getFolder('foo').count()).toBe(1)
})

test('Can add new bookmark folder', async ({ comfyPage }) => {
const tab = comfyPage.menu.nodeLibraryTab
await tab.newFolderButton.click()
await comfyPage.page.keyboard.press('Enter')
expect(await tab.getFolder('New Folder').count()).toBe(1)
expect(await comfyPage.getSetting('Comfy.NodeLibrary.Bookmarks')).toEqual(
['New Folder/']
)
})

test('Can add nested bookmark folder', async ({ comfyPage }) => {
await comfyPage.setSetting('Comfy.NodeLibrary.Bookmarks', ['foo/'])
const tab = comfyPage.menu.nodeLibraryTab

await tab.getFolder('foo').click({ button: 'right' })
await comfyPage.page.getByLabel('New Folder').click()
await comfyPage.page.keyboard.press('Enter')

expect(await comfyPage.getSetting('Comfy.NodeLibrary.Bookmarks')).toEqual(
['foo/', 'foo/New Folder/']
)
})

test('Can delete bookmark folder', async ({ comfyPage }) => {
await comfyPage.setSetting('Comfy.NodeLibrary.Bookmarks', ['foo/'])
const tab = comfyPage.menu.nodeLibraryTab

await tab.getFolder('foo').click({ button: 'right' })
await comfyPage.page.getByLabel('Delete').click()

expect(await comfyPage.getSetting('Comfy.NodeLibrary.Bookmarks')).toEqual(
[]
)
})

test('Can rename bookmark folder', async ({ comfyPage }) => {
await comfyPage.setSetting('Comfy.NodeLibrary.Bookmarks', ['foo/'])
const tab = comfyPage.menu.nodeLibraryTab

await tab.getFolder('foo').click({ button: 'right' })
await comfyPage.page.getByLabel('Rename').click()
await comfyPage.page.keyboard.insertText('bar')
await comfyPage.page.keyboard.press('Enter')

expect(await comfyPage.getSetting('Comfy.NodeLibrary.Bookmarks')).toEqual(
['bar/']
)
})

test('Can add bookmark by dragging node to bookmark folder', async ({
comfyPage
}) => {
await comfyPage.setSetting('Comfy.NodeLibrary.Bookmarks', ['foo/'])
const tab = comfyPage.menu.nodeLibraryTab
await tab.getFolder('sampling').click()
await comfyPage.page.dragAndDrop(
tab.nodeSelector('KSampler (Advanced)'),
tab.folderSelector('foo')
)
expect(await comfyPage.getSetting('Comfy.NodeLibrary.Bookmarks')).toEqual(
['foo/', 'foo/KSampler (Advanced)']
)
})

test('Can add bookmark by clicking bookmark button', async ({
comfyPage
}) => {
const tab = comfyPage.menu.nodeLibraryTab
await tab.getFolder('sampling').click()
await tab
.getNode('KSampler (Advanced)')
.locator('.bookmark-button')
.click()
expect(await comfyPage.getSetting('Comfy.NodeLibrary.Bookmarks')).toEqual(
['KSampler (Advanced)']
)
})

test('Can unbookmark node', async ({ comfyPage }) => {
await comfyPage.setSetting('Comfy.NodeLibrary.Bookmarks', [
'KSampler (Advanced)'
])
const tab = comfyPage.menu.nodeLibraryTab
await tab
.getNode('KSampler (Advanced)')
.locator('.bookmark-button')
.click()
expect(await comfyPage.getSetting('Comfy.NodeLibrary.Bookmarks')).toEqual(
[]
)
})
})

Expand Down
81 changes: 81 additions & 0 deletions src/components/common/EditableText.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
<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"
@click.stop
:pt="{
root: {
onBlur: finishEditing
}
}"
v-focus
/>
</div>
</template>

<script setup lang="ts">
import InputText from 'primevue/inputtext'
import { nextTick, ref, watch } from 'vue'
interface EditableTextProps {
modelValue: string
isEditing?: boolean
}
const props = withDefaults(defineProps<EditableTextProps>(), {
isEditing: 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.includes('.')
? inputValue.value.split('.').slice(0, -1).join('.')
: inputValue.value
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;
}
.editable-text input {
width: 100%;
box-sizing: border-box;
}
</style>
Loading

0 comments on commit 090fda2

Please sign in to comment.