[Extension] Selection toolbox API (#2672)

This commit is contained in:
Chenlei Hu
2025-02-21 19:25:30 -05:00
committed by GitHub
parent 3fa512957c
commit 9b88909caa
4 changed files with 106 additions and 1 deletions

View File

@@ -468,6 +468,35 @@ We will support custom icons later.
![image](https://github.com/user-attachments/assets/7bff028a-bf91-4cab-bf97-55c243b3f5e0)
</details>
<details>
<summary>v1.10.9: Selection Toolbox API</summary>
Extensions can register commands that appear in the selection toolbox when specific items are selected on the canvas.
```js
app.registerExtension({
name: 'TestExtension1',
commands: [
{
id: 'test.selection.command',
label: 'Test Command',
icon: 'pi pi-star',
function: () => {
// Command logic here
}
}
],
// Return an array of command IDs to show in the selection toolbox
// when an item is selected
getSelectionToolboxCommands: (selectedItem) => ['test.selection.command']
})
```
The selection toolbox will display the command button when items are selected:
![Image](https://github.com/user-attachments/assets/28d91267-c0a9-4bd5-a7c4-36e8ec44c9bd)
</details>
## Development
### Tech Stack

View File

@@ -298,4 +298,45 @@ test.describe('Topbar commands', () => {
expect(await comfyPage.page.evaluate(() => window['value'])).toBeNull()
})
})
test.describe('Selection Toolbox', () => {
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.setSetting('Comfy.Canvas.SelectionToolbox', true)
})
test('Should allow adding commands to selection toolbox', async ({
comfyPage
}) => {
// Register an extension with a selection toolbox command
await comfyPage.page.evaluate(() => {
window['app'].registerExtension({
name: 'TestExtension1',
commands: [
{
id: 'test.selection.command',
label: 'Test Command',
icon: 'pi pi-star',
function: () => {
window['selectionCommandExecuted'] = true
}
}
],
getSelectionToolboxCommands: () => ['test.selection.command']
})
})
await comfyPage.selectNodes(['CLIP Text Encode (Prompt)'])
// Click the command button in the selection toolbox
const toolboxButton = comfyPage.page.locator(
'.selection-toolbox button:has(.pi-star)'
)
await toolboxButton.click()
// Verify the command was executed
expect(
await comfyPage.page.evaluate(() => window['selectionCommandExecuted'])
).toBe(true)
})
})
})

View File

@@ -40,6 +40,14 @@
icon="pi pi-refresh"
@click="refreshSelected"
/>
<Button
v-for="command in extensionToolboxCommands"
:key="command.id"
severity="secondary"
text
:icon="typeof command.icon === 'function' ? command.icon() : command.icon"
@click="() => commandStore.execute(command.id)"
/>
</Panel>
</template>
@@ -50,12 +58,14 @@ import { computed } from 'vue'
import ColorPickerButton from '@/components/graph/selectionToolbox/ColorPickerButton.vue'
import { useRefreshableSelection } from '@/composables/useRefreshableSelection'
import { useCommandStore } from '@/stores/commandStore'
import { useExtensionService } from '@/services/extensionService'
import { ComfyCommand, useCommandStore } from '@/stores/commandStore'
import { useCanvasStore } from '@/stores/graphStore'
import { isLGraphGroup, isLGraphNode } from '@/utils/litegraphUtil'
const commandStore = useCommandStore()
const canvasStore = useCanvasStore()
const extensionService = useExtensionService()
const { isRefreshable, refreshSelected } = useRefreshableSelection()
const nodeSelected = computed(() =>
canvasStore.selectedItems.some(isLGraphNode)
@@ -63,6 +73,22 @@ const nodeSelected = computed(() =>
const groupSelected = computed(() =>
canvasStore.selectedItems.some(isLGraphGroup)
)
const extensionToolboxCommands = computed<ComfyCommand[]>(() => {
const commandIds = new Set<string>(
canvasStore.selectedItems
.map(
(item) =>
extensionService
.invokeExtensions('getSelectionToolboxCommands', item)
.flat() as string[]
)
.flat()
)
return Array.from(commandIds)
.map((commandId) => commandStore.getCommand(commandId))
.filter((command) => command !== undefined)
})
</script>
<style scoped>

View File

@@ -1,4 +1,5 @@
import type { LGraphNode } from '@comfyorg/litegraph'
import { Positionable } from '@comfyorg/litegraph/dist/interfaces'
import type { ComfyApp } from '@/scripts/app'
import type { ComfyWidgetConstructor } from '@/scripts/widgets'
@@ -97,6 +98,14 @@ export interface ComfyExtension {
* @returns An array of {[widget name]: widget data}
*/
getCustomWidgets?(app: ComfyApp): Promise<Widgets> | Widgets
/**
* Allows the extension to add additional commands to the selection toolbox
* @param selectedItem The selected item on the canvas
* @returns An array of command ids to add to the selection toolbox
*/
getSelectionToolboxCommands?(selectedItem: Positionable): string[]
/**
* Allows the extension to add additional handling to the node before it is registered with **LGraph**
* @param nodeType The node class (not an instance)