mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-01 22:09:55 +00:00
Restore context menu for new searchbox (#724)
* Searchbox revamp * nit * nit * Add playwright test * Update litegraph * Rename setting * Update test expectations [skip ci] --------- Co-authored-by: github-actions <github-actions@github.com>
This commit is contained in:
@@ -25,10 +25,8 @@ test.describe('Node Interaction', () => {
|
||||
})
|
||||
|
||||
test('Can disconnect/connect edge', async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting('Comfy.LinkRelease.Action', 'no action')
|
||||
await comfyPage.disconnectEdge()
|
||||
// Close search menu popped up.
|
||||
await comfyPage.page.keyboard.press('Escape')
|
||||
await comfyPage.nextFrame()
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
'disconnected-edge-with-menu.png'
|
||||
)
|
||||
|
||||
@@ -3,22 +3,14 @@ import { comfyPageFixture as test } from './ComfyPage'
|
||||
|
||||
test.describe('Node search box', () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting(
|
||||
'Comfy.NodeSearchBoxImpl.LinkReleaseTrigger',
|
||||
'always'
|
||||
)
|
||||
await comfyPage.setSetting('Comfy.LinkRelease.Action', 'search box')
|
||||
await comfyPage.setSetting('Comfy.LinkRelease.ActionShift', 'search box')
|
||||
await comfyPage.setSetting('Comfy.NodeSearchBoxImpl', 'default')
|
||||
})
|
||||
;['always', 'hold shift', 'NOT hold shift'].forEach((triggerMode) => {
|
||||
test(`Can trigger on empty canvas double click (${triggerMode})`, async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.setSetting(
|
||||
'Comfy.NodeSearchBoxImpl.LinkReleaseTrigger',
|
||||
triggerMode
|
||||
)
|
||||
await comfyPage.doubleClickCanvas()
|
||||
await expect(comfyPage.searchBox.input).toHaveCount(1)
|
||||
})
|
||||
|
||||
test(`Can trigger on empty canvas double click`, async ({ comfyPage }) => {
|
||||
await comfyPage.doubleClickCanvas()
|
||||
await expect(comfyPage.searchBox.input).toHaveCount(1)
|
||||
})
|
||||
|
||||
test('Can trigger on link release', async ({ comfyPage }) => {
|
||||
@@ -78,3 +70,18 @@ test.describe('Node search box', () => {
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
test.describe('Release context menu', () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting('Comfy.LinkRelease.Action', 'context menu')
|
||||
await comfyPage.setSetting('Comfy.LinkRelease.ActionShift', 'search box')
|
||||
await comfyPage.setSetting('Comfy.NodeSearchBoxImpl', 'default')
|
||||
})
|
||||
|
||||
test('Can trigger on link release', async ({ comfyPage }) => {
|
||||
await comfyPage.disconnectEdge()
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
'link-release-context-menu.png'
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 100 KiB |
8
package-lock.json
generated
8
package-lock.json
generated
@@ -9,7 +9,7 @@
|
||||
"version": "1.2.44",
|
||||
"dependencies": {
|
||||
"@atlaskit/pragmatic-drag-and-drop": "^1.2.1",
|
||||
"@comfyorg/litegraph": "^0.7.59",
|
||||
"@comfyorg/litegraph": "^0.7.60",
|
||||
"@primevue/themes": "^4.0.0-rc.2",
|
||||
"@vitejs/plugin-vue": "^5.0.5",
|
||||
"@vueuse/core": "^11.0.0",
|
||||
@@ -1883,9 +1883,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@comfyorg/litegraph": {
|
||||
"version": "0.7.59",
|
||||
"resolved": "https://registry.npmjs.org/@comfyorg/litegraph/-/litegraph-0.7.59.tgz",
|
||||
"integrity": "sha512-Qy4uRa/16MxvsNubBXs+hOVVXKpmNHiQCg/VanFEv2koYrXi4mf/9zKWNNNukoDgL2aMoFpVkXS5O/TiWzFWbg==",
|
||||
"version": "0.7.60",
|
||||
"resolved": "https://registry.npmjs.org/@comfyorg/litegraph/-/litegraph-0.7.60.tgz",
|
||||
"integrity": "sha512-iWo4CAJDL6Ma1mDboRUp58nDSNriquhC8KgHC9T68jA0+Cjd8YBnN8Nqbvv6VI52RvMGpG5MUBcjYEZs2kys/Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@cspotcode/source-map-support": {
|
||||
|
||||
@@ -59,7 +59,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@atlaskit/pragmatic-drag-and-drop": "^1.2.1",
|
||||
"@comfyorg/litegraph": "^0.7.59",
|
||||
"@comfyorg/litegraph": "^0.7.60",
|
||||
"@primevue/themes": "^4.0.0-rc.2",
|
||||
"@vitejs/plugin-vue": "^5.0.5",
|
||||
"@vueuse/core": "^11.0.0",
|
||||
|
||||
@@ -10,6 +10,10 @@
|
||||
<div class="setting-label">
|
||||
<span>
|
||||
<Tag v-if="setting.experimental" :value="$t('experimental')" />
|
||||
<Tag
|
||||
v-if="setting.deprecated"
|
||||
:value="$t('deprecated')"
|
||||
severity="danger" />
|
||||
{{ setting.name }}
|
||||
<i
|
||||
v-if="setting.tooltip"
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<TitleEditor />
|
||||
<canvas ref="canvasRef" id="graph-canvas" tabindex="1" />
|
||||
</teleport>
|
||||
<NodeSearchboxPopover v-if="nodeSearchEnabled" />
|
||||
<NodeSearchboxPopover />
|
||||
<NodeTooltip />
|
||||
</template>
|
||||
|
||||
@@ -18,7 +18,7 @@ import SideToolbar from '@/components/sidebar/SideToolbar.vue'
|
||||
import LiteGraphCanvasSplitterOverlay from '@/components/LiteGraphCanvasSplitterOverlay.vue'
|
||||
import NodeSearchboxPopover from '@/components/searchbox/NodeSearchBoxPopover.vue'
|
||||
import NodeTooltip from '@/components/graph/NodeTooltip.vue'
|
||||
import { ref, computed, onUnmounted, watch, onMounted, watchEffect } from 'vue'
|
||||
import { ref, computed, onUnmounted, onMounted, watchEffect } from 'vue'
|
||||
import { app as comfyApp } from '@/scripts/app'
|
||||
import { useSettingStore } from '@/stores/settingStore'
|
||||
import { dropTargetForElements } from '@atlaskit/pragmatic-drag-and-drop/element/adapter'
|
||||
@@ -48,16 +48,6 @@ const canvasStore = useCanvasStore()
|
||||
const betaMenuEnabled = computed(
|
||||
() => settingStore.get('Comfy.UseNewMenu') !== 'Disabled'
|
||||
)
|
||||
const nodeSearchEnabled = computed<boolean>(
|
||||
() => settingStore.get('Comfy.NodeSearchBoxImpl') === 'default'
|
||||
)
|
||||
|
||||
watchEffect(() => {
|
||||
LiteGraph.release_link_on_empty_shows_menu = !nodeSearchEnabled.value
|
||||
if (canvasStore.canvas) {
|
||||
canvasStore.canvas.allow_searchbox = !nodeSearchEnabled.value
|
||||
}
|
||||
})
|
||||
|
||||
watchEffect(() => {
|
||||
const canvasInfoEnabled = settingStore.get('Comfy.Graph.CanvasInfo')
|
||||
|
||||
@@ -116,15 +116,15 @@ const showIdName = computed(() =>
|
||||
settingStore.get('Comfy.NodeSearchBoxImpl.ShowIdName')
|
||||
)
|
||||
|
||||
const props = defineProps({
|
||||
filters: {
|
||||
type: Array<FilterAndValue>
|
||||
},
|
||||
searchLimit: {
|
||||
type: Number,
|
||||
default: 64
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
filters: FilterAndValue[]
|
||||
searchLimit: number
|
||||
}>(),
|
||||
{
|
||||
searchLimit: 64
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
const nodeSearchFilterVisible = ref(false)
|
||||
const inputId = `comfy-vue-node-search-box-input-${Math.random()}`
|
||||
|
||||
@@ -31,23 +31,21 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { app } from '@/scripts/app'
|
||||
import { computed, onMounted, onUnmounted, reactive, ref } from 'vue'
|
||||
import { computed, onMounted, onUnmounted, ref, watchEffect } from 'vue'
|
||||
import NodeSearchBox from './NodeSearchBox.vue'
|
||||
import Dialog from 'primevue/dialog'
|
||||
import { LiteGraphCanvasEvent, ConnectingLink } from '@comfyorg/litegraph'
|
||||
import { ConnectingLink } from '@comfyorg/litegraph'
|
||||
import { FilterAndValue } from '@/services/nodeSearchService'
|
||||
import { ComfyNodeDefImpl, useNodeDefStore } from '@/stores/nodeDefStore'
|
||||
import { ConnectingLinkImpl } from '@/types/litegraphTypes'
|
||||
import { useSettingStore } from '@/stores/settingStore'
|
||||
import { LinkReleaseTriggerMode } from '@/types/searchBoxTypes'
|
||||
import { LinkReleaseTriggerAction } from '@/types/searchBoxTypes'
|
||||
import { useCanvasStore } from '@/stores/graphStore'
|
||||
import { LiteGraphCanvasEvent } from '@comfyorg/litegraph'
|
||||
import { LiteGraph } from '@comfyorg/litegraph'
|
||||
|
||||
const settingStore = useSettingStore()
|
||||
|
||||
interface LiteGraphPointerEvent extends Event {
|
||||
canvasX: number
|
||||
canvasY: number
|
||||
}
|
||||
|
||||
const visible = ref(false)
|
||||
const dismissable = ref(true)
|
||||
const triggerEvent = ref<LiteGraphCanvasEvent | null>(null)
|
||||
@@ -56,22 +54,19 @@ const getNewNodeLocation = (): [number, number] => {
|
||||
return [100, 100]
|
||||
}
|
||||
|
||||
const originalEvent = triggerEvent.value.detail
|
||||
.originalEvent as LiteGraphPointerEvent
|
||||
const originalEvent = triggerEvent.value.detail.originalEvent
|
||||
// @ts-expect-error LiteGraph types are not typed
|
||||
return [originalEvent.canvasX, originalEvent.canvasY]
|
||||
}
|
||||
const nodeFilters = reactive([])
|
||||
const nodeFilters = ref<FilterAndValue[]>([])
|
||||
const addFilter = (filter: FilterAndValue) => {
|
||||
nodeFilters.push(filter)
|
||||
nodeFilters.value.push(filter)
|
||||
}
|
||||
const removeFilter = (filter: FilterAndValue) => {
|
||||
const index = nodeFilters.findIndex((f) => f === filter)
|
||||
if (index !== -1) {
|
||||
nodeFilters.splice(index, 1)
|
||||
}
|
||||
nodeFilters.value = nodeFilters.value.filter((f) => f !== filter)
|
||||
}
|
||||
const clearFilters = () => {
|
||||
nodeFilters.splice(0, nodeFilters.length)
|
||||
nodeFilters.value = []
|
||||
}
|
||||
const closeDialog = () => {
|
||||
visible.value = false
|
||||
@@ -95,41 +90,36 @@ const addNode = (nodeDef: ComfyNodeDefImpl) => {
|
||||
}, 100)
|
||||
}
|
||||
|
||||
const linkReleaseTriggerMode = computed(() => {
|
||||
return settingStore.get('Comfy.NodeSearchBoxImpl.LinkReleaseTrigger')
|
||||
})
|
||||
|
||||
const canvasEventHandler = (e: LiteGraphCanvasEvent) => {
|
||||
if (!['empty-release', 'empty-double-click'].includes(e.detail.subType)) {
|
||||
return
|
||||
const newSearchBoxEnabled = computed(
|
||||
() => settingStore.get('Comfy.NodeSearchBoxImpl') === 'default'
|
||||
)
|
||||
const showSearchBox = (e: LiteGraphCanvasEvent) => {
|
||||
if (newSearchBoxEnabled.value) {
|
||||
showNewSearchBox(e)
|
||||
} else {
|
||||
canvasStore.canvas.showSearchBox(e.detail.originalEvent as MouseEvent)
|
||||
}
|
||||
}
|
||||
|
||||
const shiftPressed = (e.detail.originalEvent as KeyboardEvent).shiftKey
|
||||
|
||||
if (e.detail.subType === 'empty-release') {
|
||||
if (
|
||||
(linkReleaseTriggerMode.value === LinkReleaseTriggerMode.HOLD_SHIFT &&
|
||||
!shiftPressed) ||
|
||||
(linkReleaseTriggerMode.value === LinkReleaseTriggerMode.NOT_HOLD_SHIFT &&
|
||||
shiftPressed)
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
const context = e.detail.linkReleaseContext
|
||||
if (context.links.length === 0) {
|
||||
const nodeDefStore = useNodeDefStore()
|
||||
const showNewSearchBox = (e: LiteGraphCanvasEvent) => {
|
||||
if (e.detail.linkReleaseContext) {
|
||||
const links = e.detail.linkReleaseContext.links
|
||||
if (links.length === 0) {
|
||||
console.warn('Empty release with no links! This should never happen')
|
||||
return
|
||||
}
|
||||
const firstLink = ConnectingLinkImpl.createFromPlainObject(context.links[0])
|
||||
const filter = useNodeDefStore().nodeSearchService.getFilterById(
|
||||
const firstLink = ConnectingLinkImpl.createFromPlainObject(links[0])
|
||||
const filter = nodeDefStore.nodeSearchService.getFilterById(
|
||||
firstLink.releaseSlotType
|
||||
)
|
||||
const dataType = firstLink.type
|
||||
addFilter([filter, dataType])
|
||||
}
|
||||
triggerEvent.value = e
|
||||
|
||||
visible.value = true
|
||||
triggerEvent.value = e
|
||||
|
||||
// Prevent the dialog from being dismissed immediately
|
||||
dismissable.value = false
|
||||
setTimeout(() => {
|
||||
@@ -137,20 +127,80 @@ const canvasEventHandler = (e: LiteGraphCanvasEvent) => {
|
||||
}, 300)
|
||||
}
|
||||
|
||||
const handleEscapeKeyPress = (event) => {
|
||||
if (event.key === 'Escape') {
|
||||
closeDialog()
|
||||
const showContextMenu = (e: LiteGraphCanvasEvent) => {
|
||||
const links = e.detail.linkReleaseContext.links
|
||||
if (links.length === 0) {
|
||||
console.warn('Empty release with no links! This should never happen')
|
||||
return
|
||||
}
|
||||
|
||||
const firstLink = ConnectingLinkImpl.createFromPlainObject(links[0])
|
||||
const mouseEvent = e.detail.originalEvent as MouseEvent
|
||||
const commonOptions = {
|
||||
e: mouseEvent,
|
||||
allow_searchbox: true,
|
||||
showSearchBox: () => showSearchBox(e)
|
||||
}
|
||||
const connectionOptions = firstLink.output
|
||||
? { nodeFrom: firstLink.node, slotFrom: firstLink.output }
|
||||
: { nodeTo: firstLink.node, slotTo: firstLink.input }
|
||||
canvasStore.canvas.showConnectionMenu({
|
||||
...connectionOptions,
|
||||
...commonOptions
|
||||
})
|
||||
}
|
||||
|
||||
// Disable litegraph's default behavior of release link and search box.
|
||||
const canvasStore = useCanvasStore()
|
||||
watchEffect(() => {
|
||||
if (canvasStore.canvas) {
|
||||
LiteGraph.release_link_on_empty_shows_menu = false
|
||||
canvasStore.canvas.allow_searchbox = false
|
||||
}
|
||||
})
|
||||
|
||||
const canvasEventHandler = (e: LiteGraphCanvasEvent) => {
|
||||
if (e.detail.subType === 'empty-double-click') {
|
||||
showSearchBox(e)
|
||||
} else if (e.detail.subType === 'empty-release') {
|
||||
handleCanvasEmptyRelease(e)
|
||||
}
|
||||
}
|
||||
|
||||
const linkReleaseAction = computed(() => {
|
||||
return settingStore.get('Comfy.LinkRelease.Action')
|
||||
})
|
||||
|
||||
const linkReleaseActionShift = computed(() => {
|
||||
return settingStore.get('Comfy.LinkRelease.ActionShift')
|
||||
})
|
||||
|
||||
const handleCanvasEmptyRelease = (e: LiteGraphCanvasEvent) => {
|
||||
const originalEvent = e.detail.originalEvent as MouseEvent
|
||||
const shiftPressed = originalEvent.shiftKey
|
||||
|
||||
const action = shiftPressed
|
||||
? linkReleaseActionShift.value
|
||||
: linkReleaseAction.value
|
||||
switch (action) {
|
||||
case LinkReleaseTriggerAction.SEARCH_BOX:
|
||||
showSearchBox(e)
|
||||
break
|
||||
case LinkReleaseTriggerAction.CONTEXT_MENU:
|
||||
showContextMenu(e)
|
||||
break
|
||||
case LinkReleaseTriggerAction.NO_ACTION:
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
document.addEventListener('litegraph:canvas', canvasEventHandler)
|
||||
document.addEventListener('keydown', handleEscapeKeyPress)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
document.removeEventListener('litegraph:canvas', canvasEventHandler)
|
||||
document.removeEventListener('keydown', handleEscapeKeyPress)
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
@@ -10,7 +10,10 @@
|
||||
import { app } from '@/scripts/app'
|
||||
import { ComfySettingsDialog } from '@/scripts/ui/settings'
|
||||
import { Settings } from '@/types/apiTypes'
|
||||
import { LinkReleaseTriggerMode } from '@/types/searchBoxTypes'
|
||||
import {
|
||||
LinkReleaseTriggerAction,
|
||||
LinkReleaseTriggerMode
|
||||
} from '@/types/searchBoxTypes'
|
||||
import { SettingParams } from '@/types/settingTypes'
|
||||
import { buildTree } from '@/utils/treeUtil'
|
||||
import { defineStore } from 'pinia'
|
||||
@@ -83,10 +86,26 @@ export const useSettingStore = defineStore('setting', {
|
||||
id: 'Comfy.NodeSearchBoxImpl.LinkReleaseTrigger',
|
||||
category: ['Comfy', 'Node Search Box', 'LinkReleaseTrigger'],
|
||||
name: 'Trigger on link release',
|
||||
tooltip: 'Only applies to the default implementation',
|
||||
type: 'combo',
|
||||
type: 'hidden',
|
||||
options: Object.values(LinkReleaseTriggerMode),
|
||||
defaultValue: LinkReleaseTriggerMode.ALWAYS
|
||||
defaultValue: LinkReleaseTriggerMode.ALWAYS,
|
||||
deprecated: true
|
||||
})
|
||||
|
||||
app.ui.settings.addSetting({
|
||||
id: 'Comfy.LinkRelease.Action',
|
||||
name: 'Action on link release (No modifier)',
|
||||
type: 'combo',
|
||||
options: Object.values(LinkReleaseTriggerAction),
|
||||
defaultValue: LinkReleaseTriggerAction.CONTEXT_MENU
|
||||
})
|
||||
|
||||
app.ui.settings.addSetting({
|
||||
id: 'Comfy.LinkRelease.ActionShift',
|
||||
name: 'Action on link release (Shift)',
|
||||
type: 'combo',
|
||||
options: Object.values(LinkReleaseTriggerAction),
|
||||
defaultValue: LinkReleaseTriggerAction.SEARCH_BOX
|
||||
})
|
||||
|
||||
app.ui.settings.addSetting({
|
||||
@@ -219,7 +238,8 @@ export const useSettingStore = defineStore('setting', {
|
||||
id: 'Comfy.NodeLibrary.Bookmarks',
|
||||
name: 'Node library bookmarks with display name (deprecated)',
|
||||
type: 'hidden',
|
||||
defaultValue: []
|
||||
defaultValue: [],
|
||||
deprecated: true
|
||||
})
|
||||
|
||||
app.ui.settings.addSetting({
|
||||
|
||||
@@ -2,6 +2,7 @@ import { ZodType, z } from 'zod'
|
||||
import { zComfyWorkflow, zNodeId } from './comfyWorkflow'
|
||||
import { fromZodError } from 'zod-validation-error'
|
||||
import { colorPalettesSchema } from './colorPalette'
|
||||
import { LinkReleaseTriggerAction } from './searchBoxTypes'
|
||||
|
||||
const zNodeType = z.string()
|
||||
const zQueueIndex = z.number()
|
||||
@@ -419,6 +420,10 @@ const zBookmarkCustomization = z.object({
|
||||
})
|
||||
export type BookmarkCustomization = z.infer<typeof zBookmarkCustomization>
|
||||
|
||||
const zLinkReleaseTriggerAction = z.enum(
|
||||
Object.values(LinkReleaseTriggerAction) as [string, ...string[]]
|
||||
)
|
||||
|
||||
const zSettings = z.record(z.any()).and(
|
||||
z
|
||||
.object({
|
||||
@@ -454,6 +459,8 @@ const zSettings = z.record(z.any()).and(
|
||||
'hold shift',
|
||||
'NOT hold shift'
|
||||
]),
|
||||
'Comfy.LinkRelease.Action': zLinkReleaseTriggerAction,
|
||||
'Comfy.LinkRelease.ActionShift': zLinkReleaseTriggerAction,
|
||||
'Comfy.NodeSearchBoxImpl.NodePreview': z.boolean(),
|
||||
'Comfy.NodeSearchBoxImpl': z.enum(['default', 'simple']),
|
||||
'Comfy.NodeSearchBoxImpl.ShowCategory': z.boolean(),
|
||||
|
||||
@@ -3,3 +3,9 @@ export enum LinkReleaseTriggerMode {
|
||||
HOLD_SHIFT = 'hold shift',
|
||||
NOT_HOLD_SHIFT = 'NOT hold shift'
|
||||
}
|
||||
|
||||
export enum LinkReleaseTriggerAction {
|
||||
CONTEXT_MENU = 'context menu',
|
||||
SEARCH_BOX = 'search box',
|
||||
NO_ACTION = 'no action'
|
||||
}
|
||||
|
||||
@@ -44,4 +44,5 @@ export interface SettingParams {
|
||||
// Note: Like id, category value need to be unique.
|
||||
category?: string[]
|
||||
experimental?: boolean
|
||||
deprecated?: boolean
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user