mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-26 17:30:07 +00:00
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:
@@ -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>
|
||||
|
||||
|
||||
76
browser_tests/assets/single_ksampler.json
Normal file
76
browser_tests/assets/single_ksampler.json
Normal 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
|
||||
}
|
||||
@@ -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
8
package-lock.json
generated
@@ -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": {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -63,7 +63,8 @@ watch(
|
||||
inputElement.setSelectionRange(start, end)
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
const vFocus = {
|
||||
mounted: (el: HTMLElement) => el.focus()
|
||||
|
||||
@@ -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'
|
||||
|
||||
92
src/components/graph/NodeTitleEditor.vue
Normal file
92
src/components/graph/NodeTitleEditor.vue
Normal 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>
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user