mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-26 17:30:07 +00:00
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:
@@ -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]
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user