Skip to content

[New Feature] Selection Toolbox #2608

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Feb 18, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 29 additions & 3 deletions browser_tests/fixtures/ComfyPage.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { LGraphNode } from '@comfyorg/litegraph'
import type { APIRequestContext, Locator, Page } from '@playwright/test'
import { expect } from '@playwright/test'
import { test as base } from '@playwright/test'
Expand Down Expand Up @@ -646,6 +647,18 @@ export class ComfyPage {
await this.nextFrame()
}

async selectNodes(nodeTitles: string[]) {
await this.page.keyboard.down('Control')
for (const nodeTitle of nodeTitles) {
const nodes = await this.getNodeRefsByTitle(nodeTitle)
for (const node of nodes) {
await node.click('title')
}
}
await this.page.keyboard.up('Control')
await this.nextFrame()
}

async select2Nodes() {
// Select 2 CLIP nodes.
await this.page.keyboard.down('Control')
Expand Down Expand Up @@ -835,12 +848,24 @@ export class ComfyPage {
(
await this.page.evaluate((type) => {
return window['app'].graph.nodes
.filter((n) => n.type === type)
.map((n) => n.id)
.filter((n: LGraphNode) => n.type === type)
.map((n: LGraphNode) => n.id)
}, type)
).map((id: NodeId) => this.getNodeRefById(id))
)
}
async getNodeRefsByTitle(title: string): Promise<NodeReference[]> {
return Promise.all(
(
await this.page.evaluate((title) => {
return window['app'].graph.nodes
.filter((n: LGraphNode) => n.title === title)
.map((n: LGraphNode) => n.id)
}, title)
).map((id: NodeId) => this.getNodeRefById(id))
)
}

async getFirstNodeRef(): Promise<NodeReference | null> {
const id = await this.page.evaluate(() => {
return window['app'].graph.nodes[0]?.id
Expand Down Expand Up @@ -896,9 +921,10 @@ export const comfyPageFixture = base.extend<{ comfyPage: ComfyPage }>({
try {
await comfyPage.setupSettings({
'Comfy.UseNewMenu': 'Disabled',
// Hide canvas menu/info by default.
// Hide canvas menu/info/selection toolbox by default.
'Comfy.Graph.CanvasInfo': false,
'Comfy.Graph.CanvasMenu': false,
'Comfy.Canvas.SelectionToolbox': false,
// Hide all badges by default.
'Comfy.NodeBadge.NodeIdBadgeMode': NodeBadgeMode.None,
'Comfy.NodeBadge.NodeSourceBadgeMode': NodeBadgeMode.None,
Expand Down
60 changes: 60 additions & 0 deletions browser_tests/selectionToolbox.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { expect } from '@playwright/test'

import { comfyPageFixture } from './fixtures/ComfyPage'

const test = comfyPageFixture

test.describe('Selection Toolbox', () => {
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.setSetting('Comfy.Canvas.SelectionToolbox', true)
})

test('shows/hides selection toolbox based on setting', async ({
comfyPage
}) => {
// By default, selection toolbox should be enabled
expect(
await comfyPage.page.locator('.selection-overlay-container').isVisible()
).toBe(false)

// Select multiple nodes
await comfyPage.selectNodes(['KSampler', 'CLIP Text Encode (Prompt)'])

// Selection toolbox should be visible with multiple nodes selected
await expect(
comfyPage.page.locator('.selection-overlay-container')
).toBeVisible()
await expect(
comfyPage.page.locator('.selection-overlay-container.show-border')
).toBeVisible()
})

test('shows border only with multiple selections', async ({ comfyPage }) => {
// Select single node
await comfyPage.selectNodes(['KSampler'])

// Selection overlay should be visible but without border
await expect(
comfyPage.page.locator('.selection-overlay-container')
).toBeVisible()
await expect(
comfyPage.page.locator('.selection-overlay-container.show-border')
).not.toBeVisible()

// Select multiple nodes
await comfyPage.selectNodes(['KSampler', 'CLIP Text Encode (Prompt)'])

// Selection overlay should show border with multiple selections
await expect(
comfyPage.page.locator('.selection-overlay-container.show-border')
).toBeVisible()

// Deselect to single node
await comfyPage.selectNodes(['CLIP Text Encode (Prompt)'])

// Border should be hidden again
await expect(
comfyPage.page.locator('.selection-overlay-container.show-border')
).not.toBeVisible()
})
})
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@
"dependencies": {
"@atlaskit/pragmatic-drag-and-drop": "^1.3.1",
"@comfyorg/comfyui-electron-types": "^0.4.16",
"@comfyorg/litegraph": "^0.8.83",
"@comfyorg/litegraph": "^0.8.84",
"@primevue/forms": "^4.2.5",
"@primevue/themes": "^4.2.5",
"@sentry/vue": "^8.48.0",
Expand Down
15 changes: 12 additions & 3 deletions src/components/graph/GraphCanvas.vue
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,8 @@
class="w-full h-full touch-none"
/>
<NodeSearchboxPopover />
<SelectionOverlay>
<!-- Placeholder for selection overlay testing. -->
<!-- <div class="w-full h-full bg-red-500"></div> -->
<SelectionOverlay v-if="selectionToolboxEnabled">
<SelectionToolbox />
</SelectionOverlay>
<NodeTooltip v-if="tooltipEnabled" />
<NodeBadge />
Expand All @@ -45,10 +44,12 @@ import GraphCanvasMenu from '@/components/graph/GraphCanvasMenu.vue'
import NodeBadge from '@/components/graph/NodeBadge.vue'
import NodeTooltip from '@/components/graph/NodeTooltip.vue'
import SelectionOverlay from '@/components/graph/SelectionOverlay.vue'
import SelectionToolbox from '@/components/graph/SelectionToolbox.vue'
import TitleEditor from '@/components/graph/TitleEditor.vue'
import NodeSearchboxPopover from '@/components/searchbox/NodeSearchBoxPopover.vue'
import SideToolbar from '@/components/sidebar/SideToolbar.vue'
import SecondRowWorkflowTabs from '@/components/topbar/SecondRowWorkflowTabs.vue'
import { useChainCallback } from '@/composables/functional/useChainCallback'
import { useCanvasDrop } from '@/composables/useCanvasDrop'
import { useContextMenuTranslation } from '@/composables/useContextMenuTranslation'
import { useCopy } from '@/composables/useCopy'
Expand Down Expand Up @@ -87,6 +88,9 @@ const canvasMenuEnabled = computed(() =>
settingStore.get('Comfy.Graph.CanvasMenu')
)
const tooltipEnabled = computed(() => settingStore.get('Comfy.EnableTooltips'))
const selectionToolboxEnabled = computed(() =>
settingStore.get('Comfy.Canvas.SelectionToolbox')
)

watchEffect(() => {
nodeDefStore.showDeprecated = settingStore.get('Comfy.Node.ShowDeprecated')
Expand Down Expand Up @@ -192,6 +196,11 @@ onMounted(async () => {

comfyAppReady.value = true

comfyApp.canvas.onSelectionChange = useChainCallback(
comfyApp.canvas.onSelectionChange,
() => canvasStore.updateSelectedItems()
)

// Load color palette
colorPaletteStore.customPalettes = settingStore.get(
'Comfy.CustomColorPalettes'
Expand Down
10 changes: 10 additions & 0 deletions src/components/graph/SelectionOverlay.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
<template>
<div
class="selection-overlay-container pointer-events-none"
:class="{
'show-border': showBorder
}"
:style="style"
v-show="visible"
>
Expand All @@ -22,9 +25,12 @@ const canvasStore = useCanvasStore()
const { style, updatePosition } = useAbsolutePosition()

const visible = ref(false)
const showBorder = ref(false)

const positionSelectionOverlay = (canvas: LGraphCanvas) => {
const selectedItems = canvas.selectedItems
showBorder.value = selectedItems.size > 1

if (!selectedItems.size) {
visible.value = false
return
Expand Down Expand Up @@ -82,4 +88,8 @@ watch(
.selection-overlay-container > * {
pointer-events: auto;
}

.show-border {
@apply border-dashed rounded-md border-2 border-[var(--border-color)];
}
</style>
37 changes: 37 additions & 0 deletions src/components/graph/SelectionToolbox.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<template>
<Panel
class="selection-toolbox absolute left-1/2"
:pt="{
header: 'hidden',
content: 'p-0 flex flex-row'
}"
>
<Button
severity="secondary"
text
icon="pi pi-thumbtack"
@click="() => commandStore.execute('Comfy.Canvas.ToggleSelected.Pin')"
/>
<Button
severity="danger"
text
icon="pi pi-trash"
@click="() => commandStore.execute('Comfy.Canvas.DeleteSelectedItems')"
/>
</Panel>
</template>

<script setup lang="ts">
import Button from 'primevue/button'
import Panel from 'primevue/panel'

import { useCommandStore } from '@/stores/commandStore'

const commandStore = useCommandStore()
</script>

<style scoped>
.selection-toolbox {
transform: translateX(-50%) translateY(-120%);
}
</style>
1 change: 1 addition & 0 deletions src/composables/useCoreCommands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,7 @@ export function useCoreCommands(): ComfyCommand[] {
item.pin(!item.pinned)
}
}
app.canvas.setDirty(true, true)
}
},
{
Expand Down
8 changes: 8 additions & 0 deletions src/constants/coreSettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -747,5 +747,13 @@ export const CORE_SETTINGS: SettingParams[] = [
},
defaultValue: 0.6,
versionAdded: '1.9.1'
},
{
id: 'Comfy.Canvas.SelectionToolbox',
category: ['LiteGraph', 'Canvas', 'SelectionToolbox'],
name: 'Show selection toolbox',
type: 'boolean',
defaultValue: true,
versionAdded: '1.10.5'
}
]
3 changes: 3 additions & 0 deletions src/locales/en/commands.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@
"Comfy_BrowseTemplates": {
"label": "Browse Templates"
},
"Comfy_Canvas_DeleteSelectedItems": {
"label": "Delete Selected Items"
},
"Comfy_Canvas_FitView": {
"label": "Fit view to selected nodes"
},
Expand Down
1 change: 1 addition & 0 deletions src/locales/en/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,7 @@
"Reinstall": "Reinstall",
"Restart": "Restart",
"Browse Templates": "Browse Templates",
"Delete Selected Items": "Delete Selected Items",
"Fit view to selected nodes": "Fit view to selected nodes",
"Reset View": "Reset View",
"Canvas Toggle Link Visibility": "Canvas Toggle Link Visibility",
Expand Down
3 changes: 3 additions & 0 deletions src/locales/en/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@
"custom": "custom"
}
},
"Comfy_Canvas_SelectionToolbox": {
"name": "Show selection toolbox"
},
"Comfy_ConfirmClear": {
"name": "Require confirmation when clearing workflow"
},
Expand Down
3 changes: 3 additions & 0 deletions src/locales/fr/commands.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@
"Comfy_BrowseTemplates": {
"label": "Parcourir les modèles"
},
"Comfy_Canvas_DeleteSelectedItems": {
"label": "Supprimer les éléments sélectionnés"
},
"Comfy_Canvas_FitView": {
"label": "Ajuster la vue aux nœuds sélectionnés"
},
Expand Down
1 change: 1 addition & 0 deletions src/locales/fr/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,7 @@
"ComfyUI Forum": "Forum ComfyUI",
"ComfyUI Issues": "Problèmes de ComfyUI",
"Convert selected nodes to group node": "Convertir les nœuds sélectionnés en nœud de groupe",
"Delete Selected Items": "Supprimer les éléments sélectionnés",
"Desktop User Guide": "Guide de l'utilisateur de bureau",
"Duplicate Current Workflow": "Dupliquer le flux de travail actuel",
"Edit": "Éditer",
Expand Down
3 changes: 3 additions & 0 deletions src/locales/fr/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@
},
"tooltip": "Choisissez l'option personnalisée pour masquer la barre de titre du système"
},
"Comfy_Canvas_SelectionToolbox": {
"name": "Afficher la boîte à outils de sélection"
},
"Comfy_ConfirmClear": {
"name": "Demander une confirmation lors de l'effacement du flux de travail"
},
Expand Down
3 changes: 3 additions & 0 deletions src/locales/ja/commands.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@
"Comfy_BrowseTemplates": {
"label": "テンプレートを参照"
},
"Comfy_Canvas_DeleteSelectedItems": {
"label": "選択したアイテムを削除"
},
"Comfy_Canvas_FitView": {
"label": "選択したノードにビューを合わせる"
},
Expand Down
1 change: 1 addition & 0 deletions src/locales/ja/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,7 @@
"ComfyUI Forum": "ComfyUI フォーラム",
"ComfyUI Issues": "ComfyUIの問題",
"Convert selected nodes to group node": "選択したノードをグループノードに変換",
"Delete Selected Items": "選択したアイテムを削除",
"Desktop User Guide": "デスクトップユーザーガイド",
"Duplicate Current Workflow": "現在のワークフローを複製",
"Edit": "編集",
Expand Down
3 changes: 3 additions & 0 deletions src/locales/ja/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@
},
"tooltip": "システムタイトルバーを非表示にするにはカスタムオプションを選択してください"
},
"Comfy_Canvas_SelectionToolbox": {
"name": "選択ツールボックスを表示"
},
"Comfy_ConfirmClear": {
"name": "ワークフローをクリアする際に確認を要求する"
},
Expand Down
3 changes: 3 additions & 0 deletions src/locales/ko/commands.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@
"Comfy_BrowseTemplates": {
"label": "템플릿 탐색"
},
"Comfy_Canvas_DeleteSelectedItems": {
"label": "선택한 항목 삭제"
},
"Comfy_Canvas_FitView": {
"label": "선택한 노드에 뷰 맞추기"
},
Expand Down
1 change: 1 addition & 0 deletions src/locales/ko/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,7 @@
"ComfyUI Forum": "ComfyUI 포럼",
"ComfyUI Issues": "ComfyUI 이슈 페이지",
"Convert selected nodes to group node": "선택한 노드를 그룹 노드로 변환",
"Delete Selected Items": "선택한 항목 삭제",
"Desktop User Guide": "데스크톱 사용자 가이드",
"Duplicate Current Workflow": "현재 워크플로 복제",
"Edit": "편집",
Expand Down
Loading