refactor: unify DropdownItem with AssetDropdownItem

- Create AssetDropdownItem in platform/assets/types

- Create toAssetDropdownItem() transform utility

- Remove deprecated DropdownItem, SelectedKey aliases

- Rename mediaSrc to previewUrl, remove unused metadata field

- Import types directly from source, remove re-exports

Amp-Thread-ID: https://ampcode.com/threads/T-019c10b4-cabd-779d-a787-1ebf5dc8a067
Co-authored-by: Amp <amp@ampcode.com>
This commit is contained in:
Alexander Brown
2026-01-30 13:15:22 -08:00
parent bbceecc94a
commit 22bb79adcc
16 changed files with 127 additions and 117 deletions

View File

@@ -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
}

View File

@@ -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 ?? ''
}
}

View File

@@ -7,7 +7,7 @@ import type { AssetSortOption } from '../types/filterTypes'
/** /**
* Minimal interface for sortable items * Minimal interface for sortable items
* Works with both AssetItem and DropdownItem * Works with both AssetItem and AssetDropdownItem
*/ */
export interface SortableItem { export interface SortableItem {
name: string name: string

View File

@@ -5,15 +5,15 @@ import PrimeVue from 'primevue/config'
import type { ComponentPublicInstance } from 'vue' import type { ComponentPublicInstance } from 'vue'
import { describe, expect, it, vi } from 'vitest' import { describe, expect, it, vi } from 'vitest'
import type { AssetDropdownItem } from '@/platform/assets/types/assetDropdownTypes'
import type { ComboInputSpec } from '@/schemas/nodeDef/nodeDefSchemaV2' 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 type { SimplifiedWidget } from '@/types/simplifiedWidget'
import WidgetSelectDropdown from '@/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue' import WidgetSelectDropdown from '@/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue'
interface WidgetSelectDropdownInstance extends ComponentPublicInstance { interface WidgetSelectDropdownInstance extends ComponentPublicInstance {
inputItems: DropdownItem[] inputItems: AssetDropdownItem[]
outputItems: DropdownItem[] outputItems: AssetDropdownItem[]
updateSelectedItems: (selectedSet: Set<string>) => void updateSelectedItems: (selectedSet: Set<string>) => void
} }
@@ -231,7 +231,7 @@ describe('WidgetSelectDropdown custom label mapping', () => {
// The missing value should be accessible via dropdownItems when filter is 'all' (default) // The missing value should be accessible via dropdownItems when filter is 'all' (default)
const dropdownItems = ( const dropdownItems = (
wrapper.vm as unknown as { dropdownItems: DropdownItem[] } wrapper.vm as unknown as { dropdownItems: AssetDropdownItem[] }
).dropdownItems ).dropdownItems
expect( expect(
dropdownItems.some((item) => item.name === 'template_image.png') dropdownItems.some((item) => item.name === 'template_image.png')
@@ -248,7 +248,7 @@ describe('WidgetSelectDropdown custom label mapping', () => {
const vmWithFilter = wrapper.vm as unknown as { const vmWithFilter = wrapper.vm as unknown as {
filterSelected: string filterSelected: string
dropdownItems: DropdownItem[] dropdownItems: AssetDropdownItem[]
} }
vmWithFilter.filterSelected = 'inputs' vmWithFilter.filterSelected = 'inputs'
@@ -269,8 +269,8 @@ describe('WidgetSelectDropdown custom label mapping', () => {
const vmWithFilter = wrapper.vm as unknown as { const vmWithFilter = wrapper.vm as unknown as {
filterSelected: string filterSelected: string
dropdownItems: DropdownItem[] dropdownItems: AssetDropdownItem[]
outputItems: DropdownItem[] outputItems: AssetDropdownItem[]
} }
vmWithFilter.filterSelected = 'outputs' vmWithFilter.filterSelected = 'outputs'
@@ -290,7 +290,7 @@ describe('WidgetSelectDropdown custom label mapping', () => {
const wrapper = mountComponent(widget, 'img_001.png') const wrapper = mountComponent(widget, 'img_001.png')
const dropdownItems = ( const dropdownItems = (
wrapper.vm as unknown as { dropdownItems: DropdownItem[] } wrapper.vm as unknown as { dropdownItems: AssetDropdownItem[] }
).dropdownItems ).dropdownItems
expect(dropdownItems).toHaveLength(2) expect(dropdownItems).toHaveLength(2)
expect( expect(
@@ -305,7 +305,7 @@ describe('WidgetSelectDropdown custom label mapping', () => {
const wrapper = mountComponent(widget, undefined) const wrapper = mountComponent(widget, undefined)
const dropdownItems = ( const dropdownItems = (
wrapper.vm as unknown as { dropdownItems: DropdownItem[] } wrapper.vm as unknown as { dropdownItems: AssetDropdownItem[] }
).dropdownItems ).dropdownItems
expect(dropdownItems).toHaveLength(2) expect(dropdownItems).toHaveLength(2)
expect( expect(

View File

@@ -6,13 +6,13 @@ import { useTransformCompatOverlayProps } from '@/composables/useTransformCompat
import { t } from '@/i18n' import { t } from '@/i18n'
import { useToastStore } from '@/platform/updates/common/toastStore' import { useToastStore } from '@/platform/updates/common/toastStore'
import FormDropdown from '@/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdown.vue' 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 { import type {
DropdownItem,
FilterOption, FilterOption,
LayoutMode, OptionId
SelectedKey } from '@/platform/assets/types/filterTypes'
} from '@/renderer/extensions/vueNodes/widgets/components/form/dropdown/types' 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 WidgetLayoutField from '@/renderer/extensions/vueNodes/widgets/components/layout/WidgetLayoutField.vue'
import { useAssetWidgetData } from '@/renderer/extensions/vueNodes/widgets/composables/useAssetWidgetData' import { useAssetWidgetData } from '@/renderer/extensions/vueNodes/widgets/composables/useAssetWidgetData'
import type { ResultItemType } from '@/schemas/apiSchema' import type { ResultItemType } from '@/schemas/apiSchema'
@@ -81,7 +81,7 @@ const filterOptions = computed<FilterOption[]>(() => {
] ]
}) })
const selectedSet = ref<Set<SelectedKey>>(new Set()) const selectedSet = ref<Set<OptionId>>(new Set())
/** /**
* Transforms a value using getOptionLabel if available. * Transforms a value using getOptionLabel if available.
@@ -100,7 +100,7 @@ function getDisplayLabel(value: string): string {
} }
} }
const inputItems = computed<DropdownItem[]>(() => { const inputItems = computed<AssetDropdownItem[]>(() => {
const values = props.widget.options?.values || [] const values = props.widget.options?.values || []
if (!Array.isArray(values)) { if (!Array.isArray(values)) {
@@ -109,13 +109,12 @@ const inputItems = computed<DropdownItem[]>(() => {
return values.map((value: string, index: number) => ({ return values.map((value: string, index: number) => ({
id: `input-${index}`, id: `input-${index}`,
mediaSrc: getMediaUrl(value, 'input'), previewUrl: getMediaUrl(value, 'input'),
name: value, name: value,
label: getDisplayLabel(value), label: getDisplayLabel(value)
metadata: ''
})) }))
}) })
const outputItems = computed<DropdownItem[]>(() => { const outputItems = computed<AssetDropdownItem[]>(() => {
if (!['image', 'video'].includes(props.assetKind ?? '')) return [] if (!['image', 'video'].includes(props.assetKind ?? '')) return []
const outputs = new Set<string>() const outputs = new Set<string>()
@@ -140,10 +139,9 @@ const outputItems = computed<DropdownItem[]>(() => {
return Array.from(outputs).map((output) => ({ return Array.from(outputs).map((output) => ({
id: `output-${output}`, id: `output-${output}`,
mediaSrc: getMediaUrl(output.replace(' [output]', ''), 'output'), previewUrl: getMediaUrl(output.replace(' [output]', ''), 'output'),
name: output, name: output,
label: getDisplayLabel(output), label: getDisplayLabel(output)
metadata: ''
})) }))
}) })
@@ -153,7 +151,7 @@ const outputItems = computed<DropdownItem[]>(() => {
* where the saved value may not exist in the current server environment. * where the saved value may not exist in the current server environment.
* Works for both local mode (inputItems/outputItems) and cloud mode (assetData). * Works for both local mode (inputItems/outputItems) and cloud mode (assetData).
*/ */
const missingValueItem = computed<DropdownItem | undefined>(() => { const missingValueItem = computed<AssetDropdownItem | undefined>(() => {
const currentValue = modelValue.value const currentValue = modelValue.value
if (!currentValue) return undefined if (!currentValue) return undefined
@@ -166,10 +164,9 @@ const missingValueItem = computed<DropdownItem | undefined>(() => {
return { return {
id: `missing-${currentValue}`, id: `missing-${currentValue}`,
mediaSrc: '', previewUrl: '',
name: currentValue, name: currentValue,
label: getDisplayLabel(currentValue), label: getDisplayLabel(currentValue)
metadata: ''
} }
} }
@@ -190,14 +187,13 @@ const missingValueItem = computed<DropdownItem | undefined>(() => {
return { return {
id: `missing-${currentValue}`, id: `missing-${currentValue}`,
mediaSrc: getMediaUrl(strippedValue, isOutput ? 'output' : 'input'), previewUrl: getMediaUrl(strippedValue, isOutput ? 'output' : 'input'),
name: currentValue, name: currentValue,
label: getDisplayLabel(currentValue), label: getDisplayLabel(currentValue)
metadata: ''
} }
}) })
const allItems = computed<DropdownItem[]>(() => { const allItems = computed<AssetDropdownItem[]>(() => {
if (props.isAssetMode && assetData) { if (props.isAssetMode && assetData) {
const items = assetData.dropdownItems.value const items = assetData.dropdownItems.value
if (missingValueItem.value) { if (missingValueItem.value) {
@@ -212,7 +208,7 @@ const allItems = computed<DropdownItem[]>(() => {
] ]
}) })
const dropdownItems = computed<DropdownItem[]>(() => { const dropdownItems = computed<AssetDropdownItem[]>(() => {
if (props.isAssetMode) { if (props.isAssetMode) {
return allItems.value return allItems.value
} }
@@ -290,8 +286,8 @@ watch(
{ immediate: true } { immediate: true }
) )
function updateSelectedItems(selectedItems: Set<SelectedKey>) { function updateSelectedItems(selectedItems: Set<OptionId>) {
let id: SelectedKey | undefined = undefined let id: OptionId | undefined = undefined
if (selectedItems.size > 0) { if (selectedItems.size > 0) {
id = selectedItems.values().next().value! id = selectedItems.values().next().value!
} }

View File

@@ -5,20 +5,19 @@ import { computed, ref, useTemplateRef } from 'vue'
import { t } from '@/i18n' import { t } from '@/i18n'
import { useToastStore } from '@/platform/updates/common/toastStore' 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 FormDropdownInput from './FormDropdownInput.vue'
import FormDropdownMenu from './FormDropdownMenu.vue' import FormDropdownMenu from './FormDropdownMenu.vue'
import { defaultSearcher, getDefaultSortOptions } from './shared' import { defaultSearcher, getDefaultSortOptions } from './shared'
import type { import type { LayoutMode, SortOption } from './types'
DropdownItem,
FilterOption,
LayoutMode,
OptionId,
SelectedKey,
SortOption
} from './types'
interface Props { interface Props {
items: DropdownItem[] items: AssetDropdownItem[]
placeholder?: string placeholder?: string
/** /**
* If true, allows multiple selections. If a number is provided, * If true, allows multiple selections. If a number is provided,
@@ -32,15 +31,15 @@ interface Props {
filterOptions?: FilterOption[] filterOptions?: FilterOption[]
sortOptions?: SortOption[] sortOptions?: SortOption[]
isSelected?: ( isSelected?: (
selected: Set<SelectedKey>, selected: Set<OptionId>,
item: DropdownItem, item: AssetDropdownItem,
index: number index: number
) => boolean ) => boolean
searcher?: ( searcher?: (
query: string, query: string,
items: DropdownItem[], items: AssetDropdownItem[],
onCleanup: (cleanupFn: () => void) => void onCleanup: (cleanupFn: () => void) => void
) => Promise<DropdownItem[]> ) => Promise<AssetDropdownItem[]>
} }
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
@@ -54,7 +53,7 @@ const props = withDefaults(defineProps<Props>(), {
searcher: defaultSearcher searcher: defaultSearcher
}) })
const selected = defineModel<Set<SelectedKey>>('selected', { const selected = defineModel<Set<OptionId>>('selected', {
default: new Set() default: new Set()
}) })
const filterSelected = defineModel<OptionId>('filterSelected', { default: '' }) const filterSelected = defineModel<OptionId>('filterSelected', { default: '' })
@@ -80,7 +79,7 @@ const maxSelectable = computed(() => {
const itemsKey = computed(() => props.items.map((item) => item.id).join('|')) const itemsKey = computed(() => props.items.map((item) => item.id).join('|'))
const filteredItems = ref<DropdownItem[]>([]) const filteredItems = ref<AssetDropdownItem[]>([])
const defaultSorter = computed<SortOption['sorter']>(() => { const defaultSorter = computed<SortOption['sorter']>(() => {
const sorter = props.sortOptions.find( const sorter = props.sortOptions.find(
@@ -99,7 +98,7 @@ const sortedItems = computed(() => {
return selectedSorter.value({ items: filteredItems.value }) || [] 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 return props.isSelected?.(selected.value, item, index) ?? false
} }
@@ -128,7 +127,7 @@ function handleFileChange(event: Event) {
input.value = '' input.value = ''
} }
function handleSelection(item: DropdownItem, index: number) { function handleSelection(item: AssetDropdownItem, index: number) {
if (props.disabled) return if (props.disabled) return
const sel = selected.value const sel = selected.value
if (internalIsSelected(item, index)) { if (internalIsSelected(item, index)) {

View File

@@ -1,16 +1,17 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed } from 'vue' import { computed } from 'vue'
import type { AssetDropdownItem } from '@/platform/assets/types/assetDropdownTypes'
import type { OptionId } from '@/platform/assets/types/filterTypes'
import { cn } from '@/utils/tailwindUtil' import { cn } from '@/utils/tailwindUtil'
import { WidgetInputBaseClass } from '../../layout' import { WidgetInputBaseClass } from '../../layout'
import type { DropdownItem, SelectedKey } from './types'
interface Props { interface Props {
isOpen?: boolean isOpen?: boolean
placeholder?: string placeholder?: string
items: DropdownItem[] items: AssetDropdownItem[]
selected: Set<SelectedKey> selected: Set<OptionId>
maxSelectable: number maxSelectable: number
uploadable: boolean uploadable: boolean
disabled: boolean disabled: boolean

View File

@@ -3,20 +3,20 @@ import type { MaybeRefOrGetter } from 'vue'
import { cn } from '@/utils/tailwindUtil' import { cn } from '@/utils/tailwindUtil'
import type { AssetDropdownItem } from '@/platform/assets/types/assetDropdownTypes'
import type {
FilterOption,
OptionId
} from '@/platform/assets/types/filterTypes'
import FormDropdownMenuActions from './FormDropdownMenuActions.vue' import FormDropdownMenuActions from './FormDropdownMenuActions.vue'
import FormDropdownMenuFilter from './FormDropdownMenuFilter.vue' import FormDropdownMenuFilter from './FormDropdownMenuFilter.vue'
import FormDropdownMenuItem from './FormDropdownMenuItem.vue' import FormDropdownMenuItem from './FormDropdownMenuItem.vue'
import type { import type { LayoutMode, SortOption } from './types'
DropdownItem,
FilterOption,
LayoutMode,
OptionId,
SortOption
} from './types'
interface Props { interface Props {
items: DropdownItem[] items: AssetDropdownItem[]
isSelected: (item: DropdownItem, index: number) => boolean isSelected: (item: AssetDropdownItem, index: number) => boolean
filterOptions: FilterOption[] filterOptions: FilterOption[]
sortOptions: SortOption[] sortOptions: SortOption[]
searcher?: ( searcher?: (
@@ -28,7 +28,7 @@ interface Props {
defineProps<Props>() defineProps<Props>()
const emit = defineEmits<{ const emit = defineEmits<{
(e: 'item-click', item: DropdownItem, index: number): void (e: 'item-click', item: AssetDropdownItem, index: number): void
}>() }>()
// Define models for two-way binding // Define models for two-way binding
@@ -90,10 +90,9 @@ const searchQuery = defineModel<string>('searchQuery')
:key="item.id" :key="item.id"
:index="index" :index="index"
:selected="isSelected(item, index)" :selected="isSelected(item, index)"
:media-src="item.mediaSrc" :preview-url="item.previewUrl"
:name="item.name" :name="item.name"
:label="item.label" :label="item.label"
:metadata="item.metadata"
:layout="layoutMode" :layout="layoutMode"
@click="emit('item-click', item, index)" @click="emit('item-click', item, index)"
/> />

View File

@@ -4,10 +4,11 @@ import type { MaybeRefOrGetter } from 'vue'
import Popover from 'primevue/popover' import Popover from 'primevue/popover'
import { ref, useTemplateRef } from 'vue' import { ref, useTemplateRef } from 'vue'
import type { OptionId } from '@/platform/assets/types/filterTypes'
import { cn } from '@/utils/tailwindUtil' import { cn } from '@/utils/tailwindUtil'
import FormSearchInput from '../FormSearchInput.vue' import FormSearchInput from '../FormSearchInput.vue'
import type { LayoutMode, OptionId, SortOption } from './types' import type { LayoutMode, SortOption } from './types'
defineProps<{ defineProps<{
searcher?: ( searcher?: (

View File

@@ -3,10 +3,12 @@ import { computed } from 'vue'
import Button from '@/components/ui/button/Button.vue' import Button from '@/components/ui/button/Button.vue'
import { useModelUpload } from '@/platform/assets/composables/useModelUpload' import { useModelUpload } from '@/platform/assets/composables/useModelUpload'
import type {
FilterOption,
OptionId
} from '@/platform/assets/types/filterTypes'
import { cn } from '@/utils/tailwindUtil' import { cn } from '@/utils/tailwindUtil'
import type { FilterOption, OptionId } from './types'
const { filterOptions } = defineProps<{ const { filterOptions } = defineProps<{
filterOptions: FilterOption[] filterOptions: FilterOption[]
}>() }>()

View File

@@ -10,10 +10,9 @@ import type { LayoutMode } from './types'
interface Props { interface Props {
index: number index: number
selected: boolean selected: boolean
mediaSrc: string previewUrl: string
name: string name: string
label?: string label?: string
metadata?: string
layout?: LayoutMode layout?: LayoutMode
} }
@@ -102,16 +101,16 @@ function handleVideoLoad(event: Event) {
/> />
</div> </div>
<video <video
v-if="mediaSrc && isVideo" v-if="previewUrl && isVideo"
:src="mediaSrc" :src="previewUrl"
class="size-full object-cover" class="size-full object-cover"
preload="metadata" preload="metadata"
muted muted
@loadeddata="handleVideoLoad" @loadeddata="handleVideoLoad"
/> />
<LazyImage <LazyImage
v-else-if="mediaSrc" v-else-if="previewUrl"
:src="mediaSrc" :src="previewUrl"
:alt="name" :alt="name"
image-class="size-full object-cover" image-class="size-full object-cover"
@load="handleImageLoad" @load="handleImageLoad"
@@ -146,9 +145,9 @@ function handleVideoLoad(event: Event) {
{{ label ?? name }} {{ label ?? name }}
</span> </span>
<!-- Meta Data --> <!-- Meta Data -->
<span class="text-secondary block text-xs">{{ <span v-if="actualDimensions" class="text-secondary block text-xs">
metadata || actualDimensions {{ actualDimensions }}
}}</span> </span>
</div> </div>
</div> </div>
</template> </template>

View File

@@ -1,20 +1,20 @@
import { describe, expect, it } from 'vitest' import { describe, expect, it } from 'vitest'
import { defaultSearcher, getDefaultSortOptions } from './shared' import type { AssetDropdownItem } from '@/platform/assets/types/assetDropdownTypes'
import type { DropdownItem } from './types'
function createItem(name: string, label?: string): DropdownItem { import { defaultSearcher, getDefaultSortOptions } from './shared'
function createItem(name: string, label?: string): AssetDropdownItem {
return { return {
id: name, id: name,
mediaSrc: '', previewUrl: '',
name, name,
label, label
metadata: ''
} }
} }
describe('defaultSearcher', () => { describe('defaultSearcher', () => {
const items: DropdownItem[] = [ const items: AssetDropdownItem[] = [
createItem('apple.png'), createItem('apple.png'),
createItem('banana.jpg'), createItem('banana.jpg'),
createItem('cherry.gif') createItem('cherry.gif')

View File

@@ -1,9 +1,13 @@
import type { AssetDropdownItem } from '@/platform/assets/types/assetDropdownTypes'
import type { AssetSortOption } from '@/platform/assets/types/filterTypes' import type { AssetSortOption } from '@/platform/assets/types/filterTypes'
import { sortAssets } from '@/platform/assets/utils/assetSortUtils' import { sortAssets } from '@/platform/assets/utils/assetSortUtils'
import type { DropdownItem, SortOption } from './types' import type { SortOption } from './types'
export async function defaultSearcher(query: string, items: DropdownItem[]) { export async function defaultSearcher(
query: string,
items: AssetDropdownItem[]
) {
if (query.trim() === '') return items if (query.trim() === '') return items
const words = query.trim().toLowerCase().split(' ') const words = query.trim().toLowerCase().split(' ')
return items.filter((item) => { return items.filter((item) => {

View File

@@ -1,27 +1,13 @@
import type { ComputedRef, InjectionKey } from 'vue' import type { ComputedRef, InjectionKey } from 'vue'
import type { import type { AssetDropdownItem } from '@/platform/assets/types/assetDropdownTypes'
FilterOption, import type { OptionId } from '@/platform/assets/types/filterTypes'
OptionId
} from '@/platform/assets/types/filterTypes'
import type { AssetKind } from '@/types/widgetTypes' import type { AssetKind } from '@/types/widgetTypes'
export type { FilterOption, OptionId }
export type SelectedKey = OptionId
export interface DropdownItem {
id: SelectedKey
mediaSrc: string // URL for image, video, or other media
name: string
label?: string
metadata: string
}
export interface SortOption<TId extends OptionId = OptionId> { export interface SortOption<TId extends OptionId = OptionId> {
id: TId id: TId
name: string name: string
sorter: (ctx: { items: readonly DropdownItem[] }) => DropdownItem[] sorter: (ctx: { items: readonly AssetDropdownItem[] }) => AssetDropdownItem[]
} }
export type LayoutMode = 'list' | 'grid' | 'list-small' export type LayoutMode = 'list' | 'grid' | 'list-small'

View File

@@ -104,7 +104,7 @@ describe('useAssetWidgetData (cloud mode, isCloud=true)', () => {
expect(item.id).toBe('asset-1') expect(item.id).toBe('asset-1')
expect(item.name).toBe('models/beautiful_model.safetensors') expect(item.name).toBe('models/beautiful_model.safetensors')
expect(item.label).toBe('Beautiful Model') expect(item.label).toBe('Beautiful Model')
expect(item.mediaSrc).toBe('/api/preview/asset-1') expect(item.previewUrl).toBe('/api/preview/asset-1')
}) })
it('handles API errors gracefully', async () => { it('handles API errors gracefully', async () => {

View File

@@ -1,9 +1,10 @@
import { computed, toValue, watch } from 'vue' import { computed, toValue, watch } from 'vue'
import type { MaybeRefOrGetter } from 'vue' import type { MaybeRefOrGetter } from 'vue'
import { isCloud } from '@/platform/distribution/types'
import type { AssetItem } from '@/platform/assets/schemas/assetSchema' import type { AssetItem } from '@/platform/assets/schemas/assetSchema'
import type { DropdownItem } from '@/renderer/extensions/vueNodes/widgets/components/form/dropdown/types' import type { AssetDropdownItem } from '@/platform/assets/types/assetDropdownTypes'
import { toAssetDropdownItem } from '@/platform/assets/utils/assetDropdownUtils'
import { isCloud } from '@/platform/distribution/types'
import { useAssetsStore } from '@/stores/assetsStore' import { useAssetsStore } from '@/stores/assetsStore'
import { useModelToNodeStore } from '@/stores/modelToNodeStore' import { useModelToNodeStore } from '@/stores/modelToNodeStore'
@@ -47,15 +48,8 @@ export function useAssetWidgetData(
return resolvedType ? (assetsStore.getError(resolvedType) ?? null) : null return resolvedType ? (assetsStore.getError(resolvedType) ?? null) : null
}) })
const dropdownItems = computed<DropdownItem[]>(() => { const dropdownItems = computed<AssetDropdownItem[]>(() => {
return (assets.value ?? []).map((asset) => ({ return (assets.value ?? []).map(toAssetDropdownItem)
id: asset.id,
name:
(asset.user_metadata?.filename as string | undefined) ?? asset.name,
label: asset.name,
mediaSrc: asset.preview_url ?? '',
metadata: ''
}))
}) })
watch( watch(