Double click node title to trigger edit (#655)

* Update litegraph

* Double click edit node title

* Update

* Auto select all

* Update litegraph

* Add playwright test

* Update readme

* Update test expectations [skip ci]

---------

Co-authored-by: github-actions <github-actions@github.com>
This commit is contained in:
Chenlei Hu
2024-08-27 11:38:39 -04:00
committed by GitHub
parent 6ab92f28db
commit 50b418113c
10 changed files with 218 additions and 6 deletions

View File

@@ -90,6 +90,13 @@ https://github.com/user-attachments/assets/4bbca3ee-318f-4cf0-be32-a5a5541066cf
### QoL changes
<details>
<summary>v1.2.38: **Litegraph** Double click node title to edit</summary>
https://github.com/user-attachments/assets/d61d5d0e-f200-4153-b293-3e3f6a212b30
</details>
<details>
<summary>v1.2.7: **Litegraph** drags multiple links with shift pressed</summary>

View File

@@ -0,0 +1,76 @@
{
"last_node_id": 9,
"last_link_id": 13,
"nodes": [
{
"id": 3,
"type": "KSampler",
"pos": [
0,
30
],
"size": {
"0": 315,
"1": 262
},
"flags": {},
"order": 0,
"mode": 0,
"inputs": [
{
"name": "model",
"type": "MODEL",
"link": null
},
{
"name": "positive",
"type": "CONDITIONING",
"link": null
},
{
"name": "negative",
"type": "CONDITIONING",
"link": null
},
{
"name": "latent_image",
"type": "LATENT",
"link": null
}
],
"outputs": [
{
"name": "LATENT",
"type": "LATENT",
"links": [],
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "KSampler"
},
"widgets_values": [
156680208700286,
"randomize",
20,
8,
"euler",
"normal",
1
]
}
],
"links": [],
"groups": [],
"config": {},
"extra": {
"ds": {
"scale": 1,
"offset": [
0,
0
]
}
},
"version": 0.4
}

View File

@@ -132,6 +132,32 @@ test.describe('Node Interaction', () => {
})
await expect(comfyPage.canvas).toHaveScreenshot('prompt-dialog-closed.png')
})
test('Can double click node title to edit', async ({ comfyPage }) => {
await comfyPage.loadWorkflow('single_ksampler')
await comfyPage.canvas.dblclick({
position: {
x: 50,
y: 10
}
})
await comfyPage.page.keyboard.type('Hello World')
await comfyPage.page.keyboard.press('Enter')
await expect(comfyPage.canvas).toHaveScreenshot('node-title-edited.png')
})
test('Double click node body does not trigger edit', async ({
comfyPage
}) => {
await comfyPage.loadWorkflow('single_ksampler')
await comfyPage.canvas.dblclick({
position: {
x: 50,
y: 50
}
})
expect(await comfyPage.page.locator('.node-title-editor').count()).toBe(0)
})
})
test.describe('Canvas Interaction', () => {

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

8
package-lock.json generated
View File

@@ -9,7 +9,7 @@
"version": "1.2.37",
"dependencies": {
"@atlaskit/pragmatic-drag-and-drop": "^1.2.1",
"@comfyorg/litegraph": "^0.7.49",
"@comfyorg/litegraph": "^0.7.52",
"@primevue/themes": "^4.0.0-rc.2",
"@vitejs/plugin-vue": "^5.0.5",
"@vueuse/core": "^11.0.0",
@@ -1881,9 +1881,9 @@
"dev": true
},
"node_modules/@comfyorg/litegraph": {
"version": "0.7.49",
"resolved": "https://registry.npmjs.org/@comfyorg/litegraph/-/litegraph-0.7.49.tgz",
"integrity": "sha512-IBST5qinpDR1OU7KezuK5hRKbQxP3KNWjrceA36feZffw7MvQexHX2ojNK0ByYN6weMLKGiFNxIfPWMZcr/NvA==",
"version": "0.7.52",
"resolved": "https://registry.npmjs.org/@comfyorg/litegraph/-/litegraph-0.7.52.tgz",
"integrity": "sha512-MtJn4VH3eaP4XYfLQVrc00vJGamOfl5a5xMRJpW8tfPO2iPaetshoB9dkDfNJ6fSolzMt24qvZttkPdh2aNR1Q==",
"license": "MIT"
},
"node_modules/@cspotcode/source-map-support": {

View File

@@ -56,7 +56,7 @@
},
"dependencies": {
"@atlaskit/pragmatic-drag-and-drop": "^1.2.1",
"@comfyorg/litegraph": "^0.7.49",
"@comfyorg/litegraph": "^0.7.52",
"@primevue/themes": "^4.0.0-rc.2",
"@vitejs/plugin-vue": "^5.0.5",
"@vueuse/core": "^11.0.0",

View File

@@ -63,7 +63,8 @@ watch(
inputElement.setSelectionRange(start, end)
})
}
}
},
{ immediate: true }
)
const vFocus = {
mounted: (el: HTMLElement) => el.focus()

View File

@@ -5,6 +5,7 @@
<SideToolbar />
</template>
</LiteGraphCanvasSplitterOverlay>
<NodeTitleEditor />
<canvas ref="canvasRef" id="graph-canvas" tabindex="1" />
</teleport>
<NodeSearchboxPopover v-if="nodeSearchEnabled" />
@@ -12,6 +13,7 @@
</template>
<script setup lang="ts">
import NodeTitleEditor from '@/components/graph/NodeTitleEditor.vue'
import SideToolbar from '@/components/sidebar/SideToolbar.vue'
import LiteGraphCanvasSplitterOverlay from '@/components/LiteGraphCanvasSplitterOverlay.vue'
import NodeSearchboxPopover from '@/components/searchbox/NodeSearchBoxPopover.vue'

View File

@@ -0,0 +1,92 @@
<template>
<div v-if="showInput" class="node-title-editor" :style="inputStyle">
<EditableText
:isEditing="showInput"
:modelValue="editedTitle"
@edit="onEdit"
/>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, CSSProperties } from 'vue'
import { app } from '@/scripts/app'
import { LGraphNode } from '@comfyorg/litegraph'
import { ComfyExtension } from '@/types/comfy'
import EditableText from '@/components/common/EditableText.vue'
import { LiteGraph } from '@comfyorg/litegraph'
const showInput = ref(false)
const editedTitle = ref('')
const inputStyle = ref<CSSProperties>({
position: 'fixed',
left: '0px',
top: '0px',
width: '200px',
height: '20px'
})
const currentNode = ref<LGraphNode | null>(null)
const onEdit = (newValue: string) => {
if (currentNode.value && newValue.trim() !== '') {
currentNode.value.title = newValue.trim()
app.graph.setDirtyCanvas(true, true)
}
showInput.value = false
}
const extension: ComfyExtension = {
name: 'Comfy.NodeTitleEditor',
nodeCreated(node: LGraphNode) {
// Store the original callback
const originalCallback = node.onNodeTitleDblClick
node.onNodeTitleDblClick = function (e: MouseEvent, ...args: any[]) {
currentNode.value = this
editedTitle.value = this.title
showInput.value = true
const [x1, y1, x2, y2] = this.getBounding()
const [nodeWidth, nodeHeight] = this.size
const canvasWidth = nodeWidth
const canvasHeight = LiteGraph.NODE_TITLE_HEIGHT
const [left, top] = app.canvasPosToClientPos([x1, y1])
inputStyle.value.left = `${left}px`
inputStyle.value.top = `${top}px`
const width = canvasWidth * app.canvas.ds.scale
const height = canvasHeight * app.canvas.ds.scale
inputStyle.value.width = `${width}px`
inputStyle.value.height = `${height}px`
// Call the original callback if it exists
if (typeof originalCallback === 'function') {
originalCallback.call(this, e, ...args)
}
}
}
}
onMounted(() => {
app.registerExtension(extension)
})
</script>
<style scoped>
.node-title-editor {
z-index: 9999;
padding: 0.25rem;
}
:deep(.editable-text) {
width: 100%;
height: 100%;
}
:deep(.editable-text input) {
width: 100%;
height: 100%;
}
</style>

View File

@@ -3006,6 +3006,14 @@ export class ComfyApp {
) as Vector2
}
canvasPosToClientPos(pos: Vector2): Vector2 {
const rect = this.canvasContainer.getBoundingClientRect()
const containerOffsets = [rect.left, rect.top]
return _.zip(pos, this.canvas.ds.offset, containerOffsets).map(
([p, o1, o2]) => (p + o1) * this.canvas.ds.scale + o2
) as Vector2
}
getCanvasCenter(): Vector2 {
const dpi = Math.max(window.devicePixelRatio ?? 1, 1)
const [x, y, w, h] = app.canvas.ds.visible_area