mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-01-26 19:09:52 +00:00
feat: inputs/outputs filter to widget dropdown (#5894)
related https://github.com/Comfy-Org/ComfyUI_frontend/issues/5827 ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-5894-feat-inputs-outputs-filter-to-widget-dropdown-2806d73d365081498d92d0576b7da6a8) by [Unito](https://www.unito.io) --------- Co-authored-by: bymyself <cbyrne@comfy.org>
This commit is contained in:
@@ -7,6 +7,7 @@ import { t } from '@/i18n'
|
||||
import { useToastStore } from '@/platform/updates/common/toastStore'
|
||||
import type { ResultItemType } from '@/schemas/apiSchema'
|
||||
import { api } from '@/scripts/api'
|
||||
import { useQueueStore } from '@/stores/queueStore'
|
||||
import type { SimplifiedWidget } from '@/types/simplifiedWidget'
|
||||
import type { AssetKind } from '@/types/widgetTypes'
|
||||
import {
|
||||
@@ -42,6 +43,7 @@ const { localValue, onChange } = useWidgetValue({
|
||||
})
|
||||
|
||||
const toastStore = useToastStore()
|
||||
const queueStore = useQueueStore()
|
||||
|
||||
const transformCompatProps = useTransformCompatOverlayProps()
|
||||
|
||||
@@ -50,8 +52,15 @@ const combinedProps = computed(() => ({
|
||||
...transformCompatProps.value
|
||||
}))
|
||||
|
||||
const filterSelected = ref('all')
|
||||
const filterOptions = ref<FilterOption[]>([
|
||||
{ id: 'all', name: 'All' },
|
||||
{ id: 'inputs', name: 'Inputs' },
|
||||
{ id: 'outputs', name: 'Outputs' }
|
||||
])
|
||||
|
||||
const selectedSet = ref<Set<SelectedKey>>(new Set())
|
||||
const dropdownItems = computed<DropdownItem[]>(() => {
|
||||
const inputItems = computed<DropdownItem[]>(() => {
|
||||
const values = props.widget.options?.values || []
|
||||
|
||||
if (!Array.isArray(values)) {
|
||||
@@ -59,12 +68,57 @@ const dropdownItems = computed<DropdownItem[]>(() => {
|
||||
}
|
||||
|
||||
return values.map((value: string, index: number) => ({
|
||||
id: index,
|
||||
imageSrc: getMediaUrl(value),
|
||||
id: `input-${index}`,
|
||||
imageSrc: getMediaUrl(value, 'input'),
|
||||
name: value,
|
||||
metadata: ''
|
||||
}))
|
||||
})
|
||||
const outputItems = computed<DropdownItem[]>(() => {
|
||||
if (!['image', 'video'].includes(props.assetKind ?? '')) return []
|
||||
|
||||
const outputs = new Set<string>()
|
||||
|
||||
// Extract output images/videos from queue history
|
||||
queueStore.historyTasks.forEach((task) => {
|
||||
task.flatOutputs.forEach((output) => {
|
||||
const isTargetType =
|
||||
(props.assetKind === 'image' && output.mediaType === 'images') ||
|
||||
(props.assetKind === 'video' && output.mediaType === 'video')
|
||||
|
||||
if (output.type === 'output' && isTargetType) {
|
||||
const path = output.subfolder
|
||||
? `${output.subfolder}/${output.filename}`
|
||||
: output.filename
|
||||
// Add [output] annotation so the preview component knows the type
|
||||
const annotatedPath = `${path} [output]`
|
||||
outputs.add(annotatedPath)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
return Array.from(outputs).map((output, index) => ({
|
||||
id: `output-${index}`,
|
||||
imageSrc: getMediaUrl(output.replace(' [output]', ''), 'output'),
|
||||
name: output,
|
||||
metadata: ''
|
||||
}))
|
||||
})
|
||||
|
||||
const allItems = computed<DropdownItem[]>(() => {
|
||||
return [...inputItems.value, ...outputItems.value]
|
||||
})
|
||||
const dropdownItems = computed<DropdownItem[]>(() => {
|
||||
switch (filterSelected.value) {
|
||||
case 'inputs':
|
||||
return inputItems.value
|
||||
case 'outputs':
|
||||
return outputItems.value
|
||||
case 'all':
|
||||
default:
|
||||
return allItems.value
|
||||
}
|
||||
})
|
||||
|
||||
const mediaPlaceholder = computed(() => {
|
||||
const options = props.widget.options
|
||||
@@ -197,19 +251,13 @@ async function handleFilesUpdate(files: File[]) {
|
||||
}
|
||||
}
|
||||
|
||||
function getMediaUrl(filename: string): string {
|
||||
if (props.assetKind !== 'image') return ''
|
||||
// TODO: This needs to be adapted based on actual ComfyUI API structure
|
||||
return `/api/view?filename=${encodeURIComponent(filename)}&type=input`
|
||||
function getMediaUrl(
|
||||
filename: string,
|
||||
type: 'input' | 'output' = 'input'
|
||||
): string {
|
||||
if (!['image', 'video'].includes(props.assetKind ?? '')) return ''
|
||||
return `/api/view?filename=${encodeURIComponent(filename)}&type=${type}`
|
||||
}
|
||||
|
||||
// TODO handle filter logic
|
||||
const filterSelected = ref('all')
|
||||
const filterOptions = ref<FilterOption[]>([
|
||||
{ id: 'all', name: 'All' },
|
||||
{ id: 'image', name: 'Inputs' },
|
||||
{ id: 'video', name: 'Outputs' }
|
||||
])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -89,7 +89,7 @@ watch(searchQuery, (value) => {
|
||||
})
|
||||
|
||||
watch(
|
||||
debouncedSearchQuery,
|
||||
[debouncedSearchQuery, () => props.items],
|
||||
(_, __, onCleanup) => {
|
||||
let isCleanup = false
|
||||
let cleanupFn: undefined | (() => void)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import WidgetButton from '@/renderer/extensions/vueNodes/widgets/components/WidgetButton.vue'
|
||||
import WidgetColorPicker from '@/renderer/extensions/vueNodes/widgets/components/WidgetColorPicker.vue'
|
||||
@@ -15,6 +15,12 @@ import {
|
||||
shouldRenderAsVue
|
||||
} from '@/renderer/extensions/vueNodes/widgets/registry/widgetRegistry'
|
||||
|
||||
vi.mock('@/stores/queueStore', () => ({
|
||||
useQueueStore: vi.fn(() => ({
|
||||
historyTasks: []
|
||||
}))
|
||||
}))
|
||||
|
||||
describe('widgetRegistry', () => {
|
||||
describe('getComponent', () => {
|
||||
// Test number type mappings
|
||||
|
||||
Reference in New Issue
Block a user