mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-19 22:09:37 +00:00
Compare commits
10 Commits
core/1.34
...
command-bo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5da195c925 | ||
|
|
d3398944d5 | ||
|
|
b70b2c89b2 | ||
|
|
c0303a6553 | ||
|
|
1e6803fd65 | ||
|
|
38a77abecb | ||
|
|
fcffa51a24 | ||
|
|
d0ef1bb81a | ||
|
|
34b7fe14c4 | ||
|
|
a4d7b4dd55 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -41,6 +41,7 @@ tests-ui/workflows/examples
|
|||||||
/blob-report/
|
/blob-report/
|
||||||
/playwright/.cache/
|
/playwright/.cache/
|
||||||
browser_tests/**/*-win32.png
|
browser_tests/**/*-win32.png
|
||||||
|
browser_tests/**/*-darwin.png
|
||||||
|
|
||||||
.env
|
.env
|
||||||
|
|
||||||
|
|||||||
161
browser_tests/tests/commandSearchBox.spec.ts
Normal file
161
browser_tests/tests/commandSearchBox.spec.ts
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
import {
|
||||||
|
comfyExpect as expect,
|
||||||
|
comfyPageFixture as test
|
||||||
|
} from '../fixtures/ComfyPage'
|
||||||
|
|
||||||
|
test.describe('Command search box', () => {
|
||||||
|
test.beforeEach(async ({ comfyPage }) => {
|
||||||
|
await comfyPage.setSetting('Comfy.NodeSearchBoxImpl', 'default')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('Can trigger command mode with ">" prefix', async ({ comfyPage }) => {
|
||||||
|
await comfyPage.doubleClickCanvas()
|
||||||
|
await expect(comfyPage.searchBox.input).toHaveCount(1)
|
||||||
|
|
||||||
|
// Type ">" to enter command mode
|
||||||
|
await comfyPage.searchBox.input.fill('>')
|
||||||
|
|
||||||
|
// Verify filter button is hidden in command mode
|
||||||
|
const filterButton = comfyPage.page.locator('.filter-button')
|
||||||
|
await expect(filterButton).not.toBeVisible()
|
||||||
|
|
||||||
|
// Verify placeholder text changes
|
||||||
|
await expect(comfyPage.searchBox.input).toHaveAttribute(
|
||||||
|
'placeholder',
|
||||||
|
expect.stringContaining('Search Commands')
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('Shows command list when entering command mode', async ({
|
||||||
|
comfyPage
|
||||||
|
}) => {
|
||||||
|
await comfyPage.doubleClickCanvas()
|
||||||
|
await comfyPage.searchBox.input.fill('>')
|
||||||
|
|
||||||
|
// Wait for dropdown to appear
|
||||||
|
await comfyPage.searchBox.dropdown.waitFor({ state: 'visible' })
|
||||||
|
|
||||||
|
// Check that commands are shown
|
||||||
|
const firstItem = comfyPage.searchBox.dropdown.locator('li').first()
|
||||||
|
await expect(firstItem).toBeVisible()
|
||||||
|
|
||||||
|
// Verify it shows a command item with icon
|
||||||
|
const commandIcon = firstItem.locator('.item-icon')
|
||||||
|
await expect(commandIcon).toBeVisible()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('Can search and filter commands', async ({ comfyPage }) => {
|
||||||
|
await comfyPage.doubleClickCanvas()
|
||||||
|
await comfyPage.searchBox.input.fill('>save')
|
||||||
|
|
||||||
|
await comfyPage.searchBox.dropdown.waitFor({ state: 'visible' })
|
||||||
|
await comfyPage.page.waitForTimeout(500) // Wait for search to complete
|
||||||
|
|
||||||
|
// Get all visible command items
|
||||||
|
const items = comfyPage.searchBox.dropdown.locator('li')
|
||||||
|
const count = await items.count()
|
||||||
|
|
||||||
|
// Should have filtered results
|
||||||
|
expect(count).toBeGreaterThan(0)
|
||||||
|
expect(count).toBeLessThan(10) // Should be filtered, not showing all
|
||||||
|
|
||||||
|
// Verify first result contains "save"
|
||||||
|
const firstLabel = await items.first().locator('.item-label').textContent()
|
||||||
|
expect(firstLabel?.toLowerCase()).toContain('save')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('Shows keybindings for commands that have them', async ({
|
||||||
|
comfyPage
|
||||||
|
}) => {
|
||||||
|
await comfyPage.doubleClickCanvas()
|
||||||
|
await comfyPage.searchBox.input.fill('>undo')
|
||||||
|
|
||||||
|
await comfyPage.searchBox.dropdown.waitFor({ state: 'visible' })
|
||||||
|
await comfyPage.page.waitForTimeout(500)
|
||||||
|
|
||||||
|
// Find the undo command
|
||||||
|
const undoItem = comfyPage.searchBox.dropdown
|
||||||
|
.locator('li')
|
||||||
|
.filter({ hasText: 'Undo' })
|
||||||
|
.first()
|
||||||
|
|
||||||
|
// Check if keybinding is shown (if configured)
|
||||||
|
const keybinding = undoItem.locator('.item-keybinding')
|
||||||
|
const keybindingCount = await keybinding.count()
|
||||||
|
|
||||||
|
// Keybinding might or might not be present depending on configuration
|
||||||
|
if (keybindingCount > 0) {
|
||||||
|
await expect(keybinding).toBeVisible()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
test('Executes command on selection', async ({ comfyPage }) => {
|
||||||
|
await comfyPage.doubleClickCanvas()
|
||||||
|
await comfyPage.searchBox.input.fill('>new blank')
|
||||||
|
|
||||||
|
await comfyPage.searchBox.dropdown.waitFor({ state: 'visible' })
|
||||||
|
await comfyPage.page.waitForTimeout(500)
|
||||||
|
|
||||||
|
// Count nodes before
|
||||||
|
const nodesBefore = await comfyPage.page
|
||||||
|
.locator('.litegraph.litenode')
|
||||||
|
.count()
|
||||||
|
|
||||||
|
// Select the new blank workflow command
|
||||||
|
const newBlankItem = comfyPage.searchBox.dropdown
|
||||||
|
.locator('li')
|
||||||
|
.filter({ hasText: 'New Blank Workflow' })
|
||||||
|
.first()
|
||||||
|
await newBlankItem.click()
|
||||||
|
|
||||||
|
// Search box should close
|
||||||
|
await expect(comfyPage.searchBox.input).not.toBeVisible()
|
||||||
|
|
||||||
|
// Verify workflow was cleared (no nodes)
|
||||||
|
const nodesAfter = await comfyPage.page
|
||||||
|
.locator('.litegraph.litenode')
|
||||||
|
.count()
|
||||||
|
expect(nodesAfter).toBe(0)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('Returns to node search when removing ">"', async ({ comfyPage }) => {
|
||||||
|
await comfyPage.doubleClickCanvas()
|
||||||
|
|
||||||
|
// Enter command mode
|
||||||
|
await comfyPage.searchBox.input.fill('>')
|
||||||
|
await expect(comfyPage.page.locator('.filter-button')).not.toBeVisible()
|
||||||
|
|
||||||
|
// Return to node search by filling with empty string to trigger search
|
||||||
|
await comfyPage.searchBox.input.fill('')
|
||||||
|
|
||||||
|
// Small wait for UI update
|
||||||
|
await comfyPage.page.waitForTimeout(200)
|
||||||
|
|
||||||
|
// Filter button should be visible again
|
||||||
|
await expect(comfyPage.page.locator('.filter-button')).toBeVisible()
|
||||||
|
|
||||||
|
// Placeholder should change back
|
||||||
|
await expect(comfyPage.searchBox.input).toHaveAttribute(
|
||||||
|
'placeholder',
|
||||||
|
expect.stringContaining('Search Nodes')
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('Command search is case insensitive', async ({ comfyPage }) => {
|
||||||
|
await comfyPage.doubleClickCanvas()
|
||||||
|
|
||||||
|
// Search with lowercase
|
||||||
|
await comfyPage.searchBox.input.fill('>SAVE')
|
||||||
|
await comfyPage.searchBox.dropdown.waitFor({ state: 'visible' })
|
||||||
|
await comfyPage.page.waitForTimeout(500)
|
||||||
|
|
||||||
|
// Should find save commands
|
||||||
|
const items = comfyPage.searchBox.dropdown.locator('li')
|
||||||
|
const count = await items.count()
|
||||||
|
expect(count).toBeGreaterThan(0)
|
||||||
|
|
||||||
|
// Verify it found save-related commands
|
||||||
|
const firstLabel = await items.first().locator('.item-label').textContent()
|
||||||
|
expect(firstLabel?.toLowerCase()).toContain('save')
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -20,7 +20,7 @@
|
|||||||
>
|
>
|
||||||
<div class="shortcut-info flex-grow pr-4">
|
<div class="shortcut-info flex-grow pr-4">
|
||||||
<div class="shortcut-name text-sm font-medium">
|
<div class="shortcut-name text-sm font-medium">
|
||||||
{{ command.label || command.id }}
|
{{ command.getTranslatedLabel() }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -139,7 +139,6 @@ import Message from 'primevue/message'
|
|||||||
import Tag from 'primevue/tag'
|
import Tag from 'primevue/tag'
|
||||||
import { useToast } from 'primevue/usetoast'
|
import { useToast } from 'primevue/usetoast'
|
||||||
import { computed, ref, watchEffect } from 'vue'
|
import { computed, ref, watchEffect } from 'vue'
|
||||||
import { useI18n } from 'vue-i18n'
|
|
||||||
|
|
||||||
import SearchBox from '@/components/common/SearchBox.vue'
|
import SearchBox from '@/components/common/SearchBox.vue'
|
||||||
import { useKeybindingService } from '@/services/keybindingService'
|
import { useKeybindingService } from '@/services/keybindingService'
|
||||||
@@ -149,7 +148,6 @@ import {
|
|||||||
KeybindingImpl,
|
KeybindingImpl,
|
||||||
useKeybindingStore
|
useKeybindingStore
|
||||||
} from '@/stores/keybindingStore'
|
} from '@/stores/keybindingStore'
|
||||||
import { normalizeI18nKey } from '@/utils/formatUtil'
|
|
||||||
|
|
||||||
import PanelTemplate from './PanelTemplate.vue'
|
import PanelTemplate from './PanelTemplate.vue'
|
||||||
import KeyComboDisplay from './keybinding/KeyComboDisplay.vue'
|
import KeyComboDisplay from './keybinding/KeyComboDisplay.vue'
|
||||||
@@ -161,7 +159,6 @@ const filters = ref({
|
|||||||
const keybindingStore = useKeybindingStore()
|
const keybindingStore = useKeybindingStore()
|
||||||
const keybindingService = useKeybindingService()
|
const keybindingService = useKeybindingService()
|
||||||
const commandStore = useCommandStore()
|
const commandStore = useCommandStore()
|
||||||
const { t } = useI18n()
|
|
||||||
|
|
||||||
interface ICommandData {
|
interface ICommandData {
|
||||||
id: string
|
id: string
|
||||||
@@ -173,10 +170,7 @@ interface ICommandData {
|
|||||||
const commandsData = computed<ICommandData[]>(() => {
|
const commandsData = computed<ICommandData[]>(() => {
|
||||||
return Object.values(commandStore.commands).map((command) => ({
|
return Object.values(commandStore.commands).map((command) => ({
|
||||||
id: command.id,
|
id: command.id,
|
||||||
label: t(
|
label: command.getTranslatedLabel(),
|
||||||
`commands.${normalizeI18nKey(command.id)}.label`,
|
|
||||||
command.label ?? ''
|
|
||||||
),
|
|
||||||
keybinding: keybindingStore.getKeybindingByCommandId(command.id),
|
keybinding: keybindingStore.getKeybindingByCommandId(command.id),
|
||||||
source: command.source
|
source: command.source
|
||||||
}))
|
}))
|
||||||
|
|||||||
50
src/components/searchbox/CommandSearchItem.vue
Normal file
50
src/components/searchbox/CommandSearchItem.vue
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
<template>
|
||||||
|
<div class="flex items-center gap-3 px-3 py-2 w-full">
|
||||||
|
<span
|
||||||
|
class="flex-shrink-0 w-5 text-center text-muted item-icon"
|
||||||
|
:class="command.icon ?? 'pi pi-chevron-right'"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<span
|
||||||
|
class="flex-grow overflow-hidden text-ellipsis whitespace-nowrap item-label"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
v-html="highlightQuery(command.getTranslatedLabel(), currentQuery)"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span
|
||||||
|
v-if="command.keybinding"
|
||||||
|
class="flex-shrink-0 text-xs px-1.5 py-0.5 border rounded font-mono keybinding-badge"
|
||||||
|
>
|
||||||
|
{{ command.keybinding.combo.toString() }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { ComfyCommandImpl } from '@/stores/commandStore'
|
||||||
|
import { highlightQuery } from '@/utils/formatUtil'
|
||||||
|
|
||||||
|
const { command, currentQuery } = defineProps<{
|
||||||
|
command: ComfyCommandImpl
|
||||||
|
currentQuery: string
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
:deep(.highlight) {
|
||||||
|
background-color: var(--p-primary-color);
|
||||||
|
color: var(--p-primary-contrast-color);
|
||||||
|
font-weight: bold;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
padding: 0 0.125rem;
|
||||||
|
margin: -0.125rem 0.125rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.keybinding-badge {
|
||||||
|
border-color: var(--p-content-border-color);
|
||||||
|
background-color: var(--p-content-hover-background);
|
||||||
|
color: var(--p-text-muted-color);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
class="comfy-vue-node-search-container flex justify-center items-center w-full min-w-96"
|
class="comfy-vue-node-search-container flex justify-center items-center w-full min-w-96"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
v-if="enableNodePreview"
|
v-if="enableNodePreview && !isCommandMode"
|
||||||
class="comfy-vue-node-preview-container absolute left-[-350px] top-[50px]"
|
class="comfy-vue-node-preview-container absolute left-[-350px] top-[50px]"
|
||||||
>
|
>
|
||||||
<NodePreview
|
<NodePreview
|
||||||
@@ -14,6 +14,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
|
v-if="!isCommandMode"
|
||||||
icon="pi pi-filter"
|
icon="pi pi-filter"
|
||||||
severity="secondary"
|
severity="secondary"
|
||||||
class="filter-button z-10"
|
class="filter-button z-10"
|
||||||
@@ -49,13 +50,24 @@
|
|||||||
auto-option-focus
|
auto-option-focus
|
||||||
force-selection
|
force-selection
|
||||||
multiple
|
multiple
|
||||||
:option-label="'display_name'"
|
:option-label="getOptionLabel"
|
||||||
@complete="search($event.query)"
|
@complete="search($event.query)"
|
||||||
@option-select="emit('addNode', $event.value)"
|
@option-select="onOptionSelect($event.value)"
|
||||||
@focused-option-changed="setHoverSuggestion($event)"
|
@focused-option-changed="setHoverSuggestion($event)"
|
||||||
|
@input="handleInput"
|
||||||
>
|
>
|
||||||
<template #option="{ option }">
|
<template #option="{ option }">
|
||||||
<NodeSearchItem :node-def="option" :current-query="currentQuery" />
|
<!-- Command search item, Remove the '>' prefix from the query -->
|
||||||
|
<CommandSearchItem
|
||||||
|
v-if="isCommandMode"
|
||||||
|
:command="option"
|
||||||
|
:current-query="currentQuery.substring(1)"
|
||||||
|
/>
|
||||||
|
<NodeSearchItem
|
||||||
|
v-else
|
||||||
|
:node-def="option"
|
||||||
|
:current-query="currentQuery"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
<!-- FilterAndValue -->
|
<!-- FilterAndValue -->
|
||||||
<template #chip="{ value }">
|
<template #chip="{ value }">
|
||||||
@@ -80,13 +92,16 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import Button from 'primevue/button'
|
import Button from 'primevue/button'
|
||||||
import Dialog from 'primevue/dialog'
|
import Dialog from 'primevue/dialog'
|
||||||
import { computed, nextTick, onMounted, ref } from 'vue'
|
import { computed, nextTick, onMounted, ref, watch } from 'vue'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
import NodePreview from '@/components/node/NodePreview.vue'
|
import NodePreview from '@/components/node/NodePreview.vue'
|
||||||
import AutoCompletePlus from '@/components/primevueOverride/AutoCompletePlus.vue'
|
import AutoCompletePlus from '@/components/primevueOverride/AutoCompletePlus.vue'
|
||||||
|
import CommandSearchItem from '@/components/searchbox/CommandSearchItem.vue'
|
||||||
import NodeSearchFilter from '@/components/searchbox/NodeSearchFilter.vue'
|
import NodeSearchFilter from '@/components/searchbox/NodeSearchFilter.vue'
|
||||||
import NodeSearchItem from '@/components/searchbox/NodeSearchItem.vue'
|
import NodeSearchItem from '@/components/searchbox/NodeSearchItem.vue'
|
||||||
|
import { CommandSearchService } from '@/services/commandSearchService'
|
||||||
|
import { type ComfyCommandImpl, useCommandStore } from '@/stores/commandStore'
|
||||||
import {
|
import {
|
||||||
ComfyNodeDefImpl,
|
ComfyNodeDefImpl,
|
||||||
useNodeDefStore,
|
useNodeDefStore,
|
||||||
@@ -99,6 +114,7 @@ import SearchFilterChip from '../common/SearchFilterChip.vue'
|
|||||||
|
|
||||||
const settingStore = useSettingStore()
|
const settingStore = useSettingStore()
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
const commandStore = useCommandStore()
|
||||||
|
|
||||||
const enableNodePreview = computed(() =>
|
const enableNodePreview = computed(() =>
|
||||||
settingStore.get('Comfy.NodeSearchBoxImpl.NodePreview')
|
settingStore.get('Comfy.NodeSearchBoxImpl.NodePreview')
|
||||||
@@ -111,18 +127,50 @@ const { filters, searchLimit = 64 } = defineProps<{
|
|||||||
|
|
||||||
const nodeSearchFilterVisible = ref(false)
|
const nodeSearchFilterVisible = ref(false)
|
||||||
const inputId = `comfy-vue-node-search-box-input-${Math.random()}`
|
const inputId = `comfy-vue-node-search-box-input-${Math.random()}`
|
||||||
const suggestions = ref<ComfyNodeDefImpl[]>([])
|
const suggestions = ref<ComfyNodeDefImpl[] | ComfyCommandImpl[]>([])
|
||||||
const hoveredSuggestion = ref<ComfyNodeDefImpl | null>(null)
|
const hoveredSuggestion = ref<ComfyNodeDefImpl | null>(null)
|
||||||
const currentQuery = ref('')
|
const currentQuery = ref('')
|
||||||
|
const isCommandMode = ref(false)
|
||||||
|
|
||||||
|
// Initialize command search service
|
||||||
|
const commandSearchService = ref<CommandSearchService | null>(null)
|
||||||
|
|
||||||
const placeholder = computed(() => {
|
const placeholder = computed(() => {
|
||||||
|
if (isCommandMode.value) {
|
||||||
|
return t('g.searchCommands', 'Search commands') + '...'
|
||||||
|
}
|
||||||
return filters.length === 0 ? t('g.searchNodes') + '...' : ''
|
return filters.length === 0 ? t('g.searchNodes') + '...' : ''
|
||||||
})
|
})
|
||||||
|
|
||||||
const nodeDefStore = useNodeDefStore()
|
const nodeDefStore = useNodeDefStore()
|
||||||
const nodeFrequencyStore = useNodeFrequencyStore()
|
const nodeFrequencyStore = useNodeFrequencyStore()
|
||||||
|
|
||||||
|
// Initialize command search service with commands
|
||||||
|
watch(
|
||||||
|
() => commandStore.commands,
|
||||||
|
(commands) => {
|
||||||
|
commandSearchService.value = new CommandSearchService(commands)
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
)
|
||||||
|
|
||||||
const search = (query: string) => {
|
const search = (query: string) => {
|
||||||
const queryIsEmpty = query === '' && filters.length === 0
|
|
||||||
currentQuery.value = query
|
currentQuery.value = query
|
||||||
|
|
||||||
|
// Check if we're in command mode (query starts with ">")
|
||||||
|
if (query.startsWith('>')) {
|
||||||
|
isCommandMode.value = true
|
||||||
|
if (commandSearchService.value) {
|
||||||
|
suggestions.value = commandSearchService.value.searchCommands(query, {
|
||||||
|
limit: searchLimit
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normal node search mode
|
||||||
|
isCommandMode.value = false
|
||||||
|
const queryIsEmpty = query === '' && filters.length === 0
|
||||||
suggestions.value = queryIsEmpty
|
suggestions.value = queryIsEmpty
|
||||||
? nodeFrequencyStore.topNodeDefs
|
? nodeFrequencyStore.topNodeDefs
|
||||||
: [
|
: [
|
||||||
@@ -132,7 +180,18 @@ const search = (query: string) => {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
const emit = defineEmits(['addFilter', 'removeFilter', 'addNode'])
|
const emit = defineEmits<{
|
||||||
|
(
|
||||||
|
e: 'addFilter',
|
||||||
|
filterAndValue: FuseFilterWithValue<ComfyNodeDefImpl, string>
|
||||||
|
): void
|
||||||
|
(
|
||||||
|
e: 'removeFilter',
|
||||||
|
filterAndValue: FuseFilterWithValue<ComfyNodeDefImpl, string>
|
||||||
|
): void
|
||||||
|
(e: 'addNode', nodeDef: ComfyNodeDefImpl): void
|
||||||
|
(e: 'executeCommand', command: ComfyCommandImpl): void
|
||||||
|
}>()
|
||||||
|
|
||||||
let inputElement: HTMLInputElement | null = null
|
let inputElement: HTMLInputElement | null = null
|
||||||
const reFocusInput = async () => {
|
const reFocusInput = async () => {
|
||||||
@@ -160,11 +219,47 @@ const onRemoveFilter = async (
|
|||||||
await reFocusInput()
|
await reFocusInput()
|
||||||
}
|
}
|
||||||
const setHoverSuggestion = (index: number) => {
|
const setHoverSuggestion = (index: number) => {
|
||||||
if (index === -1) {
|
if (index === -1 || isCommandMode.value) {
|
||||||
hoveredSuggestion.value = null
|
hoveredSuggestion.value = null
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const value = suggestions.value[index]
|
const value = suggestions.value[index] as ComfyNodeDefImpl
|
||||||
hoveredSuggestion.value = value
|
hoveredSuggestion.value = value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const onOptionSelect = (option: ComfyNodeDefImpl | ComfyCommandImpl) => {
|
||||||
|
if (isCommandMode.value) {
|
||||||
|
emit('executeCommand', option as ComfyCommandImpl)
|
||||||
|
} else {
|
||||||
|
emit('addNode', option as ComfyNodeDefImpl)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getOptionLabel = (
|
||||||
|
option: ComfyNodeDefImpl | ComfyCommandImpl
|
||||||
|
): string => {
|
||||||
|
if ('display_name' in option) {
|
||||||
|
return option.display_name
|
||||||
|
}
|
||||||
|
return option.label || option.id
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles direct input changes on the AutoCompletePlus component.
|
||||||
|
* This ensures search mode switching works properly when users clear the input
|
||||||
|
* or modify it directly, as the @complete event may not always trigger.
|
||||||
|
*
|
||||||
|
* @param event - The input event from the AutoCompletePlus component
|
||||||
|
* @note Known issue on empty input complete state:
|
||||||
|
* https://github.com/Comfy-Org/ComfyUI_frontend/issues/4887
|
||||||
|
*/
|
||||||
|
const handleInput = (event: Event) => {
|
||||||
|
const target = event.target as HTMLInputElement
|
||||||
|
const inputValue = target.value
|
||||||
|
|
||||||
|
// Trigger search to handle mode switching between node and command search
|
||||||
|
if (inputValue === '') {
|
||||||
|
search('')
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -26,6 +26,7 @@
|
|||||||
@add-filter="addFilter"
|
@add-filter="addFilter"
|
||||||
@remove-filter="removeFilter"
|
@remove-filter="removeFilter"
|
||||||
@add-node="addNode"
|
@add-node="addNode"
|
||||||
|
@execute-command="executeCommand"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
@@ -46,6 +47,7 @@ import {
|
|||||||
} from '@/lib/litegraph/src/litegraph'
|
} from '@/lib/litegraph/src/litegraph'
|
||||||
import type { CanvasPointerEvent } from '@/lib/litegraph/src/types/events'
|
import type { CanvasPointerEvent } from '@/lib/litegraph/src/types/events'
|
||||||
import { useLitegraphService } from '@/services/litegraphService'
|
import { useLitegraphService } from '@/services/litegraphService'
|
||||||
|
import { type ComfyCommandImpl, useCommandStore } from '@/stores/commandStore'
|
||||||
import { useCanvasStore } from '@/stores/graphStore'
|
import { useCanvasStore } from '@/stores/graphStore'
|
||||||
import { ComfyNodeDefImpl, useNodeDefStore } from '@/stores/nodeDefStore'
|
import { ComfyNodeDefImpl, useNodeDefStore } from '@/stores/nodeDefStore'
|
||||||
import { useSettingStore } from '@/stores/settingStore'
|
import { useSettingStore } from '@/stores/settingStore'
|
||||||
@@ -62,6 +64,7 @@ let disconnectOnReset = false
|
|||||||
|
|
||||||
const settingStore = useSettingStore()
|
const settingStore = useSettingStore()
|
||||||
const litegraphService = useLitegraphService()
|
const litegraphService = useLitegraphService()
|
||||||
|
const commandStore = useCommandStore()
|
||||||
|
|
||||||
const { visible } = storeToRefs(useSearchBoxStore())
|
const { visible } = storeToRefs(useSearchBoxStore())
|
||||||
const dismissable = ref(true)
|
const dismissable = ref(true)
|
||||||
@@ -109,6 +112,14 @@ const addNode = (nodeDef: ComfyNodeDefImpl) => {
|
|||||||
window.requestAnimationFrame(closeDialog)
|
window.requestAnimationFrame(closeDialog)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const executeCommand = async (command: ComfyCommandImpl) => {
|
||||||
|
// Close the dialog immediately
|
||||||
|
closeDialog()
|
||||||
|
|
||||||
|
// Execute the command
|
||||||
|
await commandStore.execute(command.id)
|
||||||
|
}
|
||||||
|
|
||||||
const newSearchBoxEnabled = computed(
|
const newSearchBoxEnabled = computed(
|
||||||
() => settingStore.get('Comfy.NodeSearchBoxImpl') === 'default'
|
() => settingStore.get('Comfy.NodeSearchBoxImpl') === 'default'
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -73,6 +73,7 @@
|
|||||||
"searchWorkflows": "Search Workflows",
|
"searchWorkflows": "Search Workflows",
|
||||||
"searchSettings": "Search Settings",
|
"searchSettings": "Search Settings",
|
||||||
"searchNodes": "Search Nodes",
|
"searchNodes": "Search Nodes",
|
||||||
|
"searchCommands": "Search Commands",
|
||||||
"searchModels": "Search Models",
|
"searchModels": "Search Models",
|
||||||
"searchKeybindings": "Search Keybindings",
|
"searchKeybindings": "Search Keybindings",
|
||||||
"searchExtensions": "Search Extensions",
|
"searchExtensions": "Search Extensions",
|
||||||
|
|||||||
@@ -370,6 +370,7 @@
|
|||||||
"resultsCount": "Encontrados {count} resultados",
|
"resultsCount": "Encontrados {count} resultados",
|
||||||
"save": "Guardar",
|
"save": "Guardar",
|
||||||
"saving": "Guardando",
|
"saving": "Guardando",
|
||||||
|
"searchCommands": "Buscar comandos",
|
||||||
"searchExtensions": "Buscar extensiones",
|
"searchExtensions": "Buscar extensiones",
|
||||||
"searchFailedMessage": "No pudimos encontrar ninguna configuración que coincida con tu búsqueda. Intenta ajustar tus términos de búsqueda.",
|
"searchFailedMessage": "No pudimos encontrar ninguna configuración que coincida con tu búsqueda. Intenta ajustar tus términos de búsqueda.",
|
||||||
"searchKeybindings": "Buscar combinaciones de teclas",
|
"searchKeybindings": "Buscar combinaciones de teclas",
|
||||||
|
|||||||
@@ -370,6 +370,7 @@
|
|||||||
"resultsCount": "{count} Résultats Trouvés",
|
"resultsCount": "{count} Résultats Trouvés",
|
||||||
"save": "Enregistrer",
|
"save": "Enregistrer",
|
||||||
"saving": "Enregistrement",
|
"saving": "Enregistrement",
|
||||||
|
"searchCommands": "Rechercher des commandes",
|
||||||
"searchExtensions": "Rechercher des extensions",
|
"searchExtensions": "Rechercher des extensions",
|
||||||
"searchFailedMessage": "Nous n'avons trouvé aucun paramètre correspondant à votre recherche. Essayez d'ajuster vos termes de recherche.",
|
"searchFailedMessage": "Nous n'avons trouvé aucun paramètre correspondant à votre recherche. Essayez d'ajuster vos termes de recherche.",
|
||||||
"searchKeybindings": "Rechercher des raccourcis clavier",
|
"searchKeybindings": "Rechercher des raccourcis clavier",
|
||||||
|
|||||||
@@ -370,6 +370,7 @@
|
|||||||
"resultsCount": "{count}件の結果が見つかりました",
|
"resultsCount": "{count}件の結果が見つかりました",
|
||||||
"save": "保存",
|
"save": "保存",
|
||||||
"saving": "保存中",
|
"saving": "保存中",
|
||||||
|
"searchCommands": "コマンドを検索",
|
||||||
"searchExtensions": "拡張機能を検索",
|
"searchExtensions": "拡張機能を検索",
|
||||||
"searchFailedMessage": "検索に一致する設定が見つかりませんでした。検索キーワードを調整してみてください。",
|
"searchFailedMessage": "検索に一致する設定が見つかりませんでした。検索キーワードを調整してみてください。",
|
||||||
"searchKeybindings": "キーバインディングを検索",
|
"searchKeybindings": "キーバインディングを検索",
|
||||||
|
|||||||
@@ -370,6 +370,7 @@
|
|||||||
"resultsCount": "{count} 개의 결과를 찾았습니다",
|
"resultsCount": "{count} 개의 결과를 찾았습니다",
|
||||||
"save": "저장",
|
"save": "저장",
|
||||||
"saving": "저장 중",
|
"saving": "저장 중",
|
||||||
|
"searchCommands": "명령어 검색",
|
||||||
"searchExtensions": "확장 검색",
|
"searchExtensions": "확장 검색",
|
||||||
"searchFailedMessage": "검색어와 일치하는 설정을 찾을 수 없습니다. 검색어를 조정해 보세요.",
|
"searchFailedMessage": "검색어와 일치하는 설정을 찾을 수 없습니다. 검색어를 조정해 보세요.",
|
||||||
"searchKeybindings": "키 바인딩 검색",
|
"searchKeybindings": "키 바인딩 검색",
|
||||||
|
|||||||
@@ -370,6 +370,7 @@
|
|||||||
"resultsCount": "Найдено {count} результатов",
|
"resultsCount": "Найдено {count} результатов",
|
||||||
"save": "Сохранить",
|
"save": "Сохранить",
|
||||||
"saving": "Сохранение",
|
"saving": "Сохранение",
|
||||||
|
"searchCommands": "Поиск команд",
|
||||||
"searchExtensions": "Поиск расширений",
|
"searchExtensions": "Поиск расширений",
|
||||||
"searchFailedMessage": "Мы не смогли найти настройки, соответствующие вашему запросу. Попробуйте изменить поисковые термины.",
|
"searchFailedMessage": "Мы не смогли найти настройки, соответствующие вашему запросу. Попробуйте изменить поисковые термины.",
|
||||||
"searchKeybindings": "Поиск сочетаний клавиш",
|
"searchKeybindings": "Поиск сочетаний клавиш",
|
||||||
|
|||||||
@@ -370,6 +370,7 @@
|
|||||||
"resultsCount": "找到 {count} 筆結果",
|
"resultsCount": "找到 {count} 筆結果",
|
||||||
"save": "儲存",
|
"save": "儲存",
|
||||||
"saving": "儲存中",
|
"saving": "儲存中",
|
||||||
|
"searchCommands": "搜尋指令",
|
||||||
"searchExtensions": "搜尋擴充套件",
|
"searchExtensions": "搜尋擴充套件",
|
||||||
"searchFailedMessage": "找不到符合您搜尋的設定。請嘗試調整搜尋條件。",
|
"searchFailedMessage": "找不到符合您搜尋的設定。請嘗試調整搜尋條件。",
|
||||||
"searchKeybindings": "搜尋快捷鍵",
|
"searchKeybindings": "搜尋快捷鍵",
|
||||||
|
|||||||
@@ -370,6 +370,7 @@
|
|||||||
"resultsCount": "找到 {count} 个结果",
|
"resultsCount": "找到 {count} 个结果",
|
||||||
"save": "保存",
|
"save": "保存",
|
||||||
"saving": "正在保存",
|
"saving": "正在保存",
|
||||||
|
"searchCommands": "搜尋指令",
|
||||||
"searchExtensions": "搜索扩展",
|
"searchExtensions": "搜索扩展",
|
||||||
"searchFailedMessage": "我们找不到任何与您的搜索匹配的设置。请尝试调整您的搜索词。",
|
"searchFailedMessage": "我们找不到任何与您的搜索匹配的设置。请尝试调整您的搜索词。",
|
||||||
"searchKeybindings": "搜索快捷键",
|
"searchKeybindings": "搜索快捷键",
|
||||||
|
|||||||
74
src/services/commandSearchService.ts
Normal file
74
src/services/commandSearchService.ts
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
import Fuse from 'fuse.js'
|
||||||
|
|
||||||
|
import type { ComfyCommandImpl } from '@/stores/commandStore'
|
||||||
|
|
||||||
|
export interface CommandSearchOptions {
|
||||||
|
limit?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CommandSearchService {
|
||||||
|
private fuse: Fuse<ComfyCommandImpl>
|
||||||
|
private commands: ComfyCommandImpl[]
|
||||||
|
|
||||||
|
constructor(commands: ComfyCommandImpl[]) {
|
||||||
|
this.commands = commands
|
||||||
|
this.fuse = new Fuse(commands, {
|
||||||
|
keys: [
|
||||||
|
{
|
||||||
|
name: 'translatedLabel',
|
||||||
|
weight: 2,
|
||||||
|
getFn: (command: ComfyCommandImpl) => command.getTranslatedLabel()
|
||||||
|
},
|
||||||
|
{ name: 'id', weight: 1 }
|
||||||
|
],
|
||||||
|
includeScore: true,
|
||||||
|
threshold: 0.4,
|
||||||
|
shouldSort: true,
|
||||||
|
minMatchCharLength: 1
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
public updateCommands(commands: ComfyCommandImpl[]) {
|
||||||
|
this.commands = commands
|
||||||
|
const options = {
|
||||||
|
keys: [
|
||||||
|
{
|
||||||
|
name: 'translatedLabel',
|
||||||
|
weight: 2,
|
||||||
|
getFn: (command: ComfyCommandImpl) => command.getTranslatedLabel()
|
||||||
|
},
|
||||||
|
{ name: 'id', weight: 1 }
|
||||||
|
],
|
||||||
|
includeScore: true,
|
||||||
|
threshold: 0.4,
|
||||||
|
shouldSort: true,
|
||||||
|
minMatchCharLength: 1
|
||||||
|
}
|
||||||
|
this.fuse = new Fuse(commands, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
public searchCommands(
|
||||||
|
query: string,
|
||||||
|
options?: CommandSearchOptions
|
||||||
|
): ComfyCommandImpl[] {
|
||||||
|
// Remove the leading ">" if present
|
||||||
|
const searchQuery = query.startsWith('>') ? query.slice(1).trim() : query
|
||||||
|
|
||||||
|
// If empty query, return all commands sorted alphabetically by translated label
|
||||||
|
if (!searchQuery) {
|
||||||
|
const sortedCommands = [...this.commands].sort((a, b) => {
|
||||||
|
const labelA = a.getTranslatedLabel()
|
||||||
|
const labelB = b.getTranslatedLabel()
|
||||||
|
return labelA.localeCompare(labelB)
|
||||||
|
})
|
||||||
|
return options?.limit
|
||||||
|
? sortedCommands.slice(0, options.limit)
|
||||||
|
: sortedCommands
|
||||||
|
}
|
||||||
|
|
||||||
|
const results = this.fuse.search(searchQuery)
|
||||||
|
const commands = results.map((result) => result.item)
|
||||||
|
|
||||||
|
return options?.limit ? commands.slice(0, options.limit) : commands
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,7 +2,9 @@ import { defineStore } from 'pinia'
|
|||||||
import { computed, ref } from 'vue'
|
import { computed, ref } from 'vue'
|
||||||
|
|
||||||
import { useErrorHandling } from '@/composables/useErrorHandling'
|
import { useErrorHandling } from '@/composables/useErrorHandling'
|
||||||
|
import { t } from '@/i18n'
|
||||||
import type { ComfyExtension } from '@/types/comfy'
|
import type { ComfyExtension } from '@/types/comfy'
|
||||||
|
import { normalizeI18nKey } from '@/utils/formatUtil'
|
||||||
|
|
||||||
import { type KeybindingImpl, useKeybindingStore } from './keybindingStore'
|
import { type KeybindingImpl, useKeybindingStore } from './keybindingStore'
|
||||||
|
|
||||||
@@ -66,6 +68,19 @@ export class ComfyCommandImpl implements ComfyCommand {
|
|||||||
get keybinding(): KeybindingImpl | null {
|
get keybinding(): KeybindingImpl | null {
|
||||||
return useKeybindingStore().getKeybindingByCommandId(this.id)
|
return useKeybindingStore().getKeybindingByCommandId(this.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getTranslatedLabel(): string {
|
||||||
|
// Use the same pattern as KeybindingPanel to get translated labels
|
||||||
|
return t(`commands.${normalizeI18nKey(this.id)}.label`, this.label ?? '')
|
||||||
|
}
|
||||||
|
|
||||||
|
getTranslatedMenubarLabel(): string {
|
||||||
|
// Use the same pattern but for menubar labels
|
||||||
|
return t(
|
||||||
|
`commands.${normalizeI18nKey(this.id)}.menubarLabel`,
|
||||||
|
this.menubarLabel ?? this.getTranslatedLabel()
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useCommandStore = defineStore('command', () => {
|
export const useCommandStore = defineStore('command', () => {
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ export const useMenuItemStore = defineStore('menuItem', () => {
|
|||||||
(command) =>
|
(command) =>
|
||||||
({
|
({
|
||||||
command: () => commandStore.execute(command.id),
|
command: () => commandStore.execute(command.id),
|
||||||
label: command.menubarLabel,
|
label: command.getTranslatedMenubarLabel(),
|
||||||
icon: command.icon,
|
icon: command.icon,
|
||||||
tooltip: command.tooltip,
|
tooltip: command.tooltip,
|
||||||
comfyCommand: command
|
comfyCommand: command
|
||||||
|
|||||||
131
tests-ui/tests/commandSearchService.test.ts
Normal file
131
tests-ui/tests/commandSearchService.test.ts
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
import { describe, expect, it } from 'vitest'
|
||||||
|
|
||||||
|
import { CommandSearchService } from '@/services/commandSearchService'
|
||||||
|
import { ComfyCommandImpl } from '@/stores/commandStore'
|
||||||
|
|
||||||
|
describe('CommandSearchService', () => {
|
||||||
|
// Mock commands
|
||||||
|
const mockCommands: ComfyCommandImpl[] = [
|
||||||
|
new ComfyCommandImpl({
|
||||||
|
id: 'Comfy.NewBlankWorkflow',
|
||||||
|
label: 'New Blank Workflow',
|
||||||
|
icon: 'pi pi-plus',
|
||||||
|
function: () => {}
|
||||||
|
}),
|
||||||
|
new ComfyCommandImpl({
|
||||||
|
id: 'Comfy.SaveWorkflow',
|
||||||
|
label: 'Save Workflow',
|
||||||
|
icon: 'pi pi-save',
|
||||||
|
function: () => {}
|
||||||
|
}),
|
||||||
|
new ComfyCommandImpl({
|
||||||
|
id: 'Comfy.OpenWorkflow',
|
||||||
|
label: 'Open Workflow',
|
||||||
|
icon: 'pi pi-folder-open',
|
||||||
|
function: () => {}
|
||||||
|
}),
|
||||||
|
new ComfyCommandImpl({
|
||||||
|
id: 'Comfy.ClearWorkflow',
|
||||||
|
label: 'Clear Workflow',
|
||||||
|
icon: 'pi pi-trash',
|
||||||
|
function: () => {}
|
||||||
|
}),
|
||||||
|
new ComfyCommandImpl({
|
||||||
|
id: 'Comfy.Undo',
|
||||||
|
label: 'Undo',
|
||||||
|
icon: 'pi pi-undo',
|
||||||
|
function: () => {}
|
||||||
|
})
|
||||||
|
]
|
||||||
|
|
||||||
|
describe('searchCommands', () => {
|
||||||
|
it('should return all commands sorted alphabetically when query is empty', () => {
|
||||||
|
const service = new CommandSearchService(mockCommands)
|
||||||
|
const results = service.searchCommands('')
|
||||||
|
|
||||||
|
expect(results).toHaveLength(mockCommands.length)
|
||||||
|
expect(results[0].label).toBe('Clear Workflow')
|
||||||
|
expect(results[1].label).toBe('New Blank Workflow')
|
||||||
|
expect(results[2].label).toBe('Open Workflow')
|
||||||
|
expect(results[3].label).toBe('Save Workflow')
|
||||||
|
expect(results[4].label).toBe('Undo')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should handle query with leading ">"', () => {
|
||||||
|
const service = new CommandSearchService(mockCommands)
|
||||||
|
const results = service.searchCommands('>workflow')
|
||||||
|
|
||||||
|
expect(results.length).toBeGreaterThan(0)
|
||||||
|
expect(
|
||||||
|
results.every(
|
||||||
|
(cmd) =>
|
||||||
|
cmd.label?.toLowerCase().includes('workflow') ||
|
||||||
|
cmd.id.toLowerCase().includes('workflow')
|
||||||
|
)
|
||||||
|
).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should search by label', () => {
|
||||||
|
const service = new CommandSearchService(mockCommands)
|
||||||
|
const results = service.searchCommands('save')
|
||||||
|
|
||||||
|
expect(results).toHaveLength(1)
|
||||||
|
expect(results[0].label).toBe('Save Workflow')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should search by id', () => {
|
||||||
|
const service = new CommandSearchService(mockCommands)
|
||||||
|
const results = service.searchCommands('ClearWorkflow')
|
||||||
|
|
||||||
|
expect(results.length).toBeGreaterThan(0)
|
||||||
|
expect(results[0].id).toBe('Comfy.ClearWorkflow')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should respect search limit', () => {
|
||||||
|
const service = new CommandSearchService(mockCommands)
|
||||||
|
const results = service.searchCommands('', { limit: 2 })
|
||||||
|
|
||||||
|
expect(results).toHaveLength(2)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should handle partial matches', () => {
|
||||||
|
const service = new CommandSearchService(mockCommands)
|
||||||
|
const results = service.searchCommands('work')
|
||||||
|
|
||||||
|
expect(results.length).toBeGreaterThan(1)
|
||||||
|
expect(
|
||||||
|
results.every(
|
||||||
|
(cmd) =>
|
||||||
|
cmd.label?.toLowerCase().includes('work') ||
|
||||||
|
cmd.id.toLowerCase().includes('work')
|
||||||
|
)
|
||||||
|
).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should return empty array for no matches', () => {
|
||||||
|
const service = new CommandSearchService(mockCommands)
|
||||||
|
const results = service.searchCommands('xyz123')
|
||||||
|
|
||||||
|
expect(results).toHaveLength(0)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('updateCommands', () => {
|
||||||
|
it('should update the commands list', () => {
|
||||||
|
const service = new CommandSearchService(mockCommands)
|
||||||
|
const newCommands = [
|
||||||
|
new ComfyCommandImpl({
|
||||||
|
id: 'Test.Command',
|
||||||
|
label: 'Test Command',
|
||||||
|
function: () => {}
|
||||||
|
})
|
||||||
|
]
|
||||||
|
|
||||||
|
service.updateCommands(newCommands)
|
||||||
|
const results = service.searchCommands('')
|
||||||
|
|
||||||
|
expect(results).toHaveLength(1)
|
||||||
|
expect(results[0].id).toBe('Test.Command')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -41,7 +41,9 @@ const mockCommands: ComfyCommandImpl[] = [
|
|||||||
icon: 'pi pi-test',
|
icon: 'pi pi-test',
|
||||||
tooltip: 'Test tooltip',
|
tooltip: 'Test tooltip',
|
||||||
menubarLabel: 'Other Command',
|
menubarLabel: 'Other Command',
|
||||||
keybinding: null
|
keybinding: null,
|
||||||
|
getTranslatedLabel: () => 'Other Command',
|
||||||
|
getTranslatedMenubarLabel: () => 'Other Command'
|
||||||
} as ComfyCommandImpl
|
} as ComfyCommandImpl
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,8 @@ describe('ShortcutsList', () => {
|
|||||||
combo: {
|
combo: {
|
||||||
getKeySequences: () => ['Control', 'n']
|
getKeySequences: () => ['Control', 'n']
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
getTranslatedLabel: () => 'New Workflow'
|
||||||
} as ComfyCommandImpl,
|
} as ComfyCommandImpl,
|
||||||
{
|
{
|
||||||
id: 'Node.Add',
|
id: 'Node.Add',
|
||||||
@@ -42,7 +43,8 @@ describe('ShortcutsList', () => {
|
|||||||
combo: {
|
combo: {
|
||||||
getKeySequences: () => ['Shift', 'a']
|
getKeySequences: () => ['Shift', 'a']
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
getTranslatedLabel: () => 'Add Node'
|
||||||
} as ComfyCommandImpl,
|
} as ComfyCommandImpl,
|
||||||
{
|
{
|
||||||
id: 'Queue.Clear',
|
id: 'Queue.Clear',
|
||||||
@@ -52,7 +54,8 @@ describe('ShortcutsList', () => {
|
|||||||
combo: {
|
combo: {
|
||||||
getKeySequences: () => ['Control', 'Shift', 'c']
|
getKeySequences: () => ['Control', 'Shift', 'c']
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
getTranslatedLabel: () => 'Clear Queue'
|
||||||
} as ComfyCommandImpl
|
} as ComfyCommandImpl
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -104,7 +107,8 @@ describe('ShortcutsList', () => {
|
|||||||
id: 'No.Keybinding',
|
id: 'No.Keybinding',
|
||||||
label: 'No Keybinding',
|
label: 'No Keybinding',
|
||||||
category: 'essentials',
|
category: 'essentials',
|
||||||
keybinding: null
|
keybinding: null,
|
||||||
|
getTranslatedLabel: () => 'No Keybinding'
|
||||||
} as ComfyCommandImpl
|
} as ComfyCommandImpl
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -130,7 +134,8 @@ describe('ShortcutsList', () => {
|
|||||||
combo: {
|
combo: {
|
||||||
getKeySequences: () => ['Meta', 'ArrowUp', 'Enter', 'Escape', ' ']
|
getKeySequences: () => ['Meta', 'ArrowUp', 'Enter', 'Escape', ' ']
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
getTranslatedLabel: () => 'Special Keys'
|
||||||
} as ComfyCommandImpl
|
} as ComfyCommandImpl
|
||||||
|
|
||||||
const wrapper = mount(ShortcutsList, {
|
const wrapper = mount(ShortcutsList, {
|
||||||
|
|||||||
Reference in New Issue
Block a user