refactor: unify filter option types to name/value pattern

- Add ownershipOptions to useAssetFilterOptions composable

- Change FilterOption and OwnershipFilterOption to use name/value (matching SelectOption)

- Remove duplicated ownership options from AssetFilterBar and WidgetSelectDropdown

- Use useI18n instead of direct t import in WidgetSelectDropdown

- Add vue-i18n mock to composable test

Amp-Thread-ID: https://ampcode.com/threads/T-019c1aec-2be2-7051-a21b-ae61e832e537
Co-authored-by: Amp <amp@ampcode.com>
This commit is contained in:
Alexander Brown
2026-02-01 13:02:55 -08:00
parent 091e67590b
commit 1aacd2795d
7 changed files with 48 additions and 51 deletions

View File

@@ -78,15 +78,6 @@ const sortOptions = computed(() => [
{ name: t('assetBrowser.sortZA'), value: 'name-desc' as const }
])
const ownershipOptions = computed(() => [
{ name: t('assetBrowser.ownershipAll'), value: 'all' as const },
{ name: t('assetBrowser.ownershipMyModels'), value: 'my-models' as const },
{
name: t('assetBrowser.ownershipPublicModels'),
value: 'public-models' as const
}
])
const { assets = [], showOwnershipFilter = false } = defineProps<{
assets?: AssetItem[]
showOwnershipFilter?: boolean
@@ -97,9 +88,8 @@ const baseModels = ref<SelectOption[]>([])
const sortBy = ref<AssetSortOption>('recent')
const ownership = ref<OwnershipOption>('all')
const { availableFileFormats, availableBaseModels } = useAssetFilterOptions(
() => assets
)
const { availableFileFormats, availableBaseModels, ownershipOptions } =
useAssetFilterOptions(() => assets)
const emit = defineEmits<{
filterChange: [filters: AssetFilterState]

View File

@@ -1,6 +1,7 @@
import { describe, expect, it } from 'vitest'
import { describe, expect, it, vi } from 'vitest'
import { useAssetFilterOptions } from '@/platform/assets/composables/useAssetFilterOptions'
import {
createAssetWithSpecificBaseModel,
createAssetWithSpecificExtension,
@@ -9,6 +10,12 @@ import {
createAssetWithoutUserMetadata
} from '@/platform/assets/fixtures/ui-mock-assets'
vi.mock('vue-i18n', () => ({
useI18n: () => ({
t: (key: string) => key
})
}))
describe('useAssetFilterOptions', () => {
describe('File Format Extraction', () => {
it('extracts file formats from asset names', () => {

View File

@@ -1,16 +1,25 @@
import { uniqWith } from 'es-toolkit'
import { computed, toValue } from 'vue'
import type { MaybeRefOrGetter } from 'vue'
import { useI18n } from 'vue-i18n'
import type { SelectOption } from '@/components/input/types'
import type { AssetItem } from '@/platform/assets/schemas/assetSchema'
import type { OwnershipFilterOption } from '@/platform/assets/types/filterTypes'
import { getAssetBaseModels } from '@/platform/assets/utils/assetMetadataUtils'
/**
* Composable that extracts available filter options from asset data
* Provides reactive computed properties for file formats and base models
* Provides reactive computed properties for file formats, base models, and ownership
*/
export function useAssetFilterOptions(assets: MaybeRefOrGetter<AssetItem[]>) {
const { t } = useI18n()
const ownershipOptions = computed<OwnershipFilterOption[]>(() => [
{ name: t('assetBrowser.ownershipAll'), value: 'all' },
{ name: t('assetBrowser.ownershipMyModels'), value: 'my-models' },
{ name: t('assetBrowser.ownershipPublicModels'), value: 'public-models' }
])
/**
* Extract unique file formats from asset names
* Returns sorted SelectOption array with extensions
@@ -50,6 +59,7 @@ export function useAssetFilterOptions(assets: MaybeRefOrGetter<AssetItem[]>) {
return {
availableFileFormats,
availableBaseModels
availableBaseModels,
ownershipOptions
}
}

View File

@@ -5,11 +5,11 @@
/**
* Generic filter/select option used across components
* Compatible with both SelectOption (name/value) and FilterOption (id/name) patterns
* Compatible with SelectOption (name/value) pattern
*/
export interface FilterOption {
id: string
name: string
value: string
}
/**
@@ -24,8 +24,8 @@ export type OwnershipOption = 'all' | 'my-models' | 'public-models'
* Ownership filter option for dropdowns/selects
*/
export interface OwnershipFilterOption {
id: OwnershipOption
name: string
value: OwnershipOption
}
/**

View File

@@ -1,9 +1,10 @@
<script setup lang="ts">
import { capitalize } from 'es-toolkit'
import { computed, provide, ref, toRef, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { useTransformCompatOverlayProps } from '@/composables/useTransformCompatOverlayProps'
import { t } from '@/i18n'
import { useAssetFilterOptions } from '@/platform/assets/composables/useAssetFilterOptions'
import {
filterItemByBaseModels,
filterItemByOwnership
@@ -60,6 +61,7 @@ const modelValue = defineModel<string | undefined>({
}
})
const { t } = useI18n()
const toastStore = useToastStore()
const queueStore = useQueueStore()
@@ -83,39 +85,27 @@ const filterSelected = ref('all')
const filterOptions = computed<FilterOption[]>(() => {
if (props.isAssetMode) {
const categoryName = assetData?.category.value ?? 'All'
return [{ id: 'all', name: capitalize(categoryName) }]
return [{ name: capitalize(categoryName), value: 'all' }]
}
return [
{ id: 'all', name: 'All' },
{ id: 'inputs', name: 'Inputs' },
{ id: 'outputs', name: 'Outputs' }
{ name: 'All', value: 'all' },
{ name: 'Inputs', value: 'inputs' },
{ name: 'Outputs', value: 'outputs' }
]
})
const ownershipSelected = ref<OwnershipOption>('all')
const showOwnershipFilter = computed(() => props.isAssetMode)
const ownershipOptions = computed(() => [
{ id: 'all' as const, name: t('assetBrowser.ownershipAll') },
{ id: 'my-models' as const, name: t('assetBrowser.ownershipMyModels') },
{
id: 'public-models' as const,
name: t('assetBrowser.ownershipPublicModels')
}
])
const { ownershipOptions, availableBaseModels } = useAssetFilterOptions(
() => assetData?.assets.value ?? []
)
const baseModelSelected = ref<Set<string>>(new Set())
const showBaseModelFilter = computed(() => props.isAssetMode)
const baseModelOptions = computed<FilterOption[]>(() => {
if (!props.isAssetMode || !assetData) return []
const models = new Set<string>()
for (const asset of assetData.assets.value) {
for (const model of getAssetBaseModels(asset)) {
models.add(model)
}
}
return Array.from(models)
.sort()
.map((model) => ({ id: model, name: model }))
return availableBaseModels.value
})
const selectedSet = ref<Set<string>>(new Set())

View File

@@ -82,7 +82,7 @@ function closeOwnershipPopover() {
}
function handleOwnershipSelected(item: OwnershipFilterOption) {
ownershipSelected.value = item.id
ownershipSelected.value = item.value
closeOwnershipPopover()
}
@@ -98,10 +98,10 @@ function toggleBaseModelPopover(event: Event) {
function toggleBaseModelSelection(item: FilterOption) {
const current = baseModelSelected.value
if (current.has(item.id)) {
current.delete(item.id)
if (current.has(item.value)) {
current.delete(item.value)
} else {
current.add(item.id)
current.add(item.value)
}
baseModelSelected.value = new Set(current)
}
@@ -228,7 +228,7 @@ function toggleBaseModelSelection(item: FilterOption) {
>
<Button
v-for="item of ownershipOptions"
:key="item.id"
:key="item.value"
variant="textonly"
size="unset"
:class="cn('flex justify-between items-center h-6 text-left')"
@@ -236,7 +236,7 @@ function toggleBaseModelSelection(item: FilterOption) {
>
<span>{{ item.name }}</span>
<i
v-if="ownershipSelected === item.id"
v-if="ownershipSelected === item.value"
class="icon-[lucide--check] size-4"
/>
</Button>
@@ -290,7 +290,7 @@ function toggleBaseModelSelection(item: FilterOption) {
>
<Button
v-for="item of baseModelOptions"
:key="item.id"
:key="item.value"
variant="textonly"
size="unset"
:class="cn('flex justify-between items-center h-6 text-left')"
@@ -298,7 +298,7 @@ function toggleBaseModelSelection(item: FilterOption) {
>
<span>{{ item.name }}</span>
<i
v-if="baseModelSelected.has(item.id)"
v-if="baseModelSelected.has(item.value)"
class="icon-[lucide--check] size-4"
/>
</Button>

View File

@@ -21,7 +21,7 @@ const singleFilterOption = computed(() => filterOptions.length === 1)
<div class="text-secondary mb-4 flex gap-1 px-4 justify-start">
<button
v-for="option in filterOptions"
:key="option.id"
:key="option.value"
type="button"
:disabled="singleFilterOption"
:class="
@@ -29,12 +29,12 @@ const singleFilterOption = computed(() => filterOptions.length === 1)
'px-4 py-2 rounded-md inline-flex justify-center items-center select-none appearance-none border-0 text-base-foreground',
!singleFilterOption &&
'transition-all duration-150 hover:text-base-foreground hover:bg-interface-menu-component-surface-hovered cursor-pointer active:scale-95',
!singleFilterOption && filterSelected === option.id
!singleFilterOption && filterSelected === option.value
? '!bg-interface-menu-component-surface-selected text-base-foreground'
: 'bg-transparent'
)
"
@click="filterSelected = option.id"
@click="filterSelected = option.value"
>
{{ option.name }}
</button>