diff --git a/src/platform/assets/types/assetDropdownTypes.ts b/src/platform/assets/types/assetDropdownTypes.ts new file mode 100644 index 000000000..216b21fc8 --- /dev/null +++ b/src/platform/assets/types/assetDropdownTypes.ts @@ -0,0 +1,15 @@ +import type { OptionId } from './filterTypes' + +/** + * Lightweight display projection of AssetItem for dropdown/selection UIs. + * Used by FormDropdown and WidgetSelectDropdown. + */ +export interface AssetDropdownItem { + id: OptionId + /** Display name (user-defined filename or asset name) */ + name: string + /** Original filename from asset */ + label?: string + /** Preview image/video URL */ + previewUrl: string +} diff --git a/src/platform/assets/utils/assetDropdownUtils.ts b/src/platform/assets/utils/assetDropdownUtils.ts new file mode 100644 index 000000000..9b30a9daf --- /dev/null +++ b/src/platform/assets/utils/assetDropdownUtils.ts @@ -0,0 +1,14 @@ +import type { AssetItem } from '../schemas/assetSchema' +import type { AssetDropdownItem } from '../types/assetDropdownTypes' + +/** + * Transforms an AssetItem to AssetDropdownItem for dropdown display. + */ +export function toAssetDropdownItem(asset: AssetItem): AssetDropdownItem { + return { + id: asset.id, + name: (asset.user_metadata?.filename as string | undefined) ?? asset.name, + label: asset.name, + previewUrl: asset.preview_url ?? '' + } +} diff --git a/src/platform/assets/utils/assetSortUtils.ts b/src/platform/assets/utils/assetSortUtils.ts index 65c2006ad..a093d8854 100644 --- a/src/platform/assets/utils/assetSortUtils.ts +++ b/src/platform/assets/utils/assetSortUtils.ts @@ -7,7 +7,7 @@ import type { AssetSortOption } from '../types/filterTypes' /** * Minimal interface for sortable items - * Works with both AssetItem and DropdownItem + * Works with both AssetItem and AssetDropdownItem */ export interface SortableItem { name: string diff --git a/src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.test.ts b/src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.test.ts index 9c1271e7d..86b41e0e4 100644 --- a/src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.test.ts +++ b/src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.test.ts @@ -5,15 +5,15 @@ import PrimeVue from 'primevue/config' import type { ComponentPublicInstance } from 'vue' import { describe, expect, it, vi } from 'vitest' +import type { AssetDropdownItem } from '@/platform/assets/types/assetDropdownTypes' import type { ComboInputSpec } from '@/schemas/nodeDef/nodeDefSchemaV2' -import type { DropdownItem } from '@/renderer/extensions/vueNodes/widgets/components/form/dropdown/types' import type { SimplifiedWidget } from '@/types/simplifiedWidget' import WidgetSelectDropdown from '@/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue' interface WidgetSelectDropdownInstance extends ComponentPublicInstance { - inputItems: DropdownItem[] - outputItems: DropdownItem[] + inputItems: AssetDropdownItem[] + outputItems: AssetDropdownItem[] updateSelectedItems: (selectedSet: Set) => void } @@ -231,7 +231,7 @@ describe('WidgetSelectDropdown custom label mapping', () => { // The missing value should be accessible via dropdownItems when filter is 'all' (default) const dropdownItems = ( - wrapper.vm as unknown as { dropdownItems: DropdownItem[] } + wrapper.vm as unknown as { dropdownItems: AssetDropdownItem[] } ).dropdownItems expect( dropdownItems.some((item) => item.name === 'template_image.png') @@ -248,7 +248,7 @@ describe('WidgetSelectDropdown custom label mapping', () => { const vmWithFilter = wrapper.vm as unknown as { filterSelected: string - dropdownItems: DropdownItem[] + dropdownItems: AssetDropdownItem[] } vmWithFilter.filterSelected = 'inputs' @@ -269,8 +269,8 @@ describe('WidgetSelectDropdown custom label mapping', () => { const vmWithFilter = wrapper.vm as unknown as { filterSelected: string - dropdownItems: DropdownItem[] - outputItems: DropdownItem[] + dropdownItems: AssetDropdownItem[] + outputItems: AssetDropdownItem[] } vmWithFilter.filterSelected = 'outputs' @@ -290,7 +290,7 @@ describe('WidgetSelectDropdown custom label mapping', () => { const wrapper = mountComponent(widget, 'img_001.png') const dropdownItems = ( - wrapper.vm as unknown as { dropdownItems: DropdownItem[] } + wrapper.vm as unknown as { dropdownItems: AssetDropdownItem[] } ).dropdownItems expect(dropdownItems).toHaveLength(2) expect( @@ -305,7 +305,7 @@ describe('WidgetSelectDropdown custom label mapping', () => { const wrapper = mountComponent(widget, undefined) const dropdownItems = ( - wrapper.vm as unknown as { dropdownItems: DropdownItem[] } + wrapper.vm as unknown as { dropdownItems: AssetDropdownItem[] } ).dropdownItems expect(dropdownItems).toHaveLength(2) expect( diff --git a/src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue b/src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue index 2b0304ed7..4edb3c17b 100644 --- a/src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue +++ b/src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue @@ -6,13 +6,13 @@ import { useTransformCompatOverlayProps } from '@/composables/useTransformCompat import { t } from '@/i18n' import { useToastStore } from '@/platform/updates/common/toastStore' import FormDropdown from '@/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdown.vue' -import { AssetKindKey } from '@/renderer/extensions/vueNodes/widgets/components/form/dropdown/types' +import type { AssetDropdownItem } from '@/platform/assets/types/assetDropdownTypes' import type { - DropdownItem, FilterOption, - LayoutMode, - SelectedKey -} from '@/renderer/extensions/vueNodes/widgets/components/form/dropdown/types' + OptionId +} from '@/platform/assets/types/filterTypes' +import { AssetKindKey } from '@/renderer/extensions/vueNodes/widgets/components/form/dropdown/types'; +import type { LayoutMode } from '@/renderer/extensions/vueNodes/widgets/components/form/dropdown/types'; import WidgetLayoutField from '@/renderer/extensions/vueNodes/widgets/components/layout/WidgetLayoutField.vue' import { useAssetWidgetData } from '@/renderer/extensions/vueNodes/widgets/composables/useAssetWidgetData' import type { ResultItemType } from '@/schemas/apiSchema' @@ -81,7 +81,7 @@ const filterOptions = computed(() => { ] }) -const selectedSet = ref>(new Set()) +const selectedSet = ref>(new Set()) /** * Transforms a value using getOptionLabel if available. @@ -100,7 +100,7 @@ function getDisplayLabel(value: string): string { } } -const inputItems = computed(() => { +const inputItems = computed(() => { const values = props.widget.options?.values || [] if (!Array.isArray(values)) { @@ -109,13 +109,12 @@ const inputItems = computed(() => { return values.map((value: string, index: number) => ({ id: `input-${index}`, - mediaSrc: getMediaUrl(value, 'input'), + previewUrl: getMediaUrl(value, 'input'), name: value, - label: getDisplayLabel(value), - metadata: '' + label: getDisplayLabel(value) })) }) -const outputItems = computed(() => { +const outputItems = computed(() => { if (!['image', 'video'].includes(props.assetKind ?? '')) return [] const outputs = new Set() @@ -140,10 +139,9 @@ const outputItems = computed(() => { return Array.from(outputs).map((output) => ({ id: `output-${output}`, - mediaSrc: getMediaUrl(output.replace(' [output]', ''), 'output'), + previewUrl: getMediaUrl(output.replace(' [output]', ''), 'output'), name: output, - label: getDisplayLabel(output), - metadata: '' + label: getDisplayLabel(output) })) }) @@ -153,7 +151,7 @@ const outputItems = computed(() => { * where the saved value may not exist in the current server environment. * Works for both local mode (inputItems/outputItems) and cloud mode (assetData). */ -const missingValueItem = computed(() => { +const missingValueItem = computed(() => { const currentValue = modelValue.value if (!currentValue) return undefined @@ -166,10 +164,9 @@ const missingValueItem = computed(() => { return { id: `missing-${currentValue}`, - mediaSrc: '', + previewUrl: '', name: currentValue, - label: getDisplayLabel(currentValue), - metadata: '' + label: getDisplayLabel(currentValue) } } @@ -190,14 +187,13 @@ const missingValueItem = computed(() => { return { id: `missing-${currentValue}`, - mediaSrc: getMediaUrl(strippedValue, isOutput ? 'output' : 'input'), + previewUrl: getMediaUrl(strippedValue, isOutput ? 'output' : 'input'), name: currentValue, - label: getDisplayLabel(currentValue), - metadata: '' + label: getDisplayLabel(currentValue) } }) -const allItems = computed(() => { +const allItems = computed(() => { if (props.isAssetMode && assetData) { const items = assetData.dropdownItems.value if (missingValueItem.value) { @@ -212,7 +208,7 @@ const allItems = computed(() => { ] }) -const dropdownItems = computed(() => { +const dropdownItems = computed(() => { if (props.isAssetMode) { return allItems.value } @@ -290,8 +286,8 @@ watch( { immediate: true } ) -function updateSelectedItems(selectedItems: Set) { - let id: SelectedKey | undefined = undefined +function updateSelectedItems(selectedItems: Set) { + let id: OptionId | undefined = undefined if (selectedItems.size > 0) { id = selectedItems.values().next().value! } diff --git a/src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdown.vue b/src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdown.vue index 5041d8cf4..8ff3ea5af 100644 --- a/src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdown.vue +++ b/src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdown.vue @@ -5,20 +5,19 @@ import { computed, ref, useTemplateRef } from 'vue' import { t } from '@/i18n' import { useToastStore } from '@/platform/updates/common/toastStore' +import type { AssetDropdownItem } from '@/platform/assets/types/assetDropdownTypes' +import type { + FilterOption, + OptionId +} from '@/platform/assets/types/filterTypes' + import FormDropdownInput from './FormDropdownInput.vue' import FormDropdownMenu from './FormDropdownMenu.vue' import { defaultSearcher, getDefaultSortOptions } from './shared' -import type { - DropdownItem, - FilterOption, - LayoutMode, - OptionId, - SelectedKey, - SortOption -} from './types' +import type { LayoutMode, SortOption } from './types' interface Props { - items: DropdownItem[] + items: AssetDropdownItem[] placeholder?: string /** * If true, allows multiple selections. If a number is provided, @@ -32,15 +31,15 @@ interface Props { filterOptions?: FilterOption[] sortOptions?: SortOption[] isSelected?: ( - selected: Set, - item: DropdownItem, + selected: Set, + item: AssetDropdownItem, index: number ) => boolean searcher?: ( query: string, - items: DropdownItem[], + items: AssetDropdownItem[], onCleanup: (cleanupFn: () => void) => void - ) => Promise + ) => Promise } const props = withDefaults(defineProps(), { @@ -54,7 +53,7 @@ const props = withDefaults(defineProps(), { searcher: defaultSearcher }) -const selected = defineModel>('selected', { +const selected = defineModel>('selected', { default: new Set() }) const filterSelected = defineModel('filterSelected', { default: '' }) @@ -80,7 +79,7 @@ const maxSelectable = computed(() => { const itemsKey = computed(() => props.items.map((item) => item.id).join('|')) -const filteredItems = ref([]) +const filteredItems = ref([]) const defaultSorter = computed(() => { const sorter = props.sortOptions.find( @@ -99,7 +98,7 @@ const sortedItems = computed(() => { return selectedSorter.value({ items: filteredItems.value }) || [] }) -function internalIsSelected(item: DropdownItem, index: number): boolean { +function internalIsSelected(item: AssetDropdownItem, index: number): boolean { return props.isSelected?.(selected.value, item, index) ?? false } @@ -128,7 +127,7 @@ function handleFileChange(event: Event) { input.value = '' } -function handleSelection(item: DropdownItem, index: number) { +function handleSelection(item: AssetDropdownItem, index: number) { if (props.disabled) return const sel = selected.value if (internalIsSelected(item, index)) { diff --git a/src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdownInput.vue b/src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdownInput.vue index 24f89995d..2095ced2b 100644 --- a/src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdownInput.vue +++ b/src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdownInput.vue @@ -1,16 +1,17 @@