mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-23 00:04:06 +00:00
feat: add ownership filter to AssetFilterBar for modal without left nav
When AssetBrowserModal opens with a category filter (e.g., from a node widget), the left navigation panel is hidden. This adds an ownership dropdown to the filter bar that allows users to filter by 'All', 'My models', or 'Public models' - providing the same functionality as the 'Imported' nav item. Amp-Thread-ID: https://ampcode.com/threads/T-019c1066-b236-705f-bc33-25781a6f9e9e Co-authored-by: Amp <amp@ampcode.com>
This commit is contained in:
@@ -51,6 +51,7 @@
|
||||
<template #contentFilter>
|
||||
<AssetFilterBar
|
||||
:assets="categoryFilteredAssets"
|
||||
:show-ownership-filter="!shouldShowLeftPanel"
|
||||
@filter-change="updateFilters"
|
||||
@click.self="focusedAsset = null"
|
||||
/>
|
||||
|
||||
@@ -7,6 +7,16 @@
|
||||
class="flex gap-4 items-center"
|
||||
data-component-id="asset-filter-bar-left"
|
||||
>
|
||||
<SingleSelect
|
||||
v-if="showOwnershipFilter"
|
||||
v-model="ownership"
|
||||
:label="$t('assetBrowser.ownership')"
|
||||
:options="ownershipOptions"
|
||||
class="min-w-32"
|
||||
data-component-id="asset-filter-ownership"
|
||||
@update:model-value="handleFilterChange"
|
||||
/>
|
||||
|
||||
<MultiSelect
|
||||
v-if="availableFileFormats.length > 0"
|
||||
v-model="fileFormats"
|
||||
@@ -58,6 +68,7 @@ import type { AssetItem } from '@/platform/assets/schemas/assetSchema'
|
||||
const { t } = useI18n()
|
||||
|
||||
type SortOption = 'recent' | 'name-asc' | 'name-desc'
|
||||
export type OwnershipOption = 'all' | 'my-models' | 'public-models'
|
||||
|
||||
const sortOptions = computed(() => [
|
||||
{ name: t('assetBrowser.sortRecent'), value: 'recent' as const },
|
||||
@@ -65,19 +76,31 @@ 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
|
||||
}
|
||||
])
|
||||
|
||||
export interface FilterState {
|
||||
fileFormats: string[]
|
||||
baseModels: string[]
|
||||
sortBy: SortOption
|
||||
ownership: OwnershipOption
|
||||
}
|
||||
|
||||
const { assets = [] } = defineProps<{
|
||||
const { assets = [], showOwnershipFilter = false } = defineProps<{
|
||||
assets?: AssetItem[]
|
||||
showOwnershipFilter?: boolean
|
||||
}>()
|
||||
|
||||
const fileFormats = ref<SelectOption[]>([])
|
||||
const baseModels = ref<SelectOption[]>([])
|
||||
const sortBy = ref<SortOption>('recent')
|
||||
const ownership = ref<OwnershipOption>('all')
|
||||
|
||||
const { availableFileFormats, availableBaseModels } = useAssetFilterOptions(
|
||||
() => assets
|
||||
@@ -91,7 +114,8 @@ function handleFilterChange() {
|
||||
emit('filterChange', {
|
||||
fileFormats: fileFormats.value.map((option: SelectOption) => option.value),
|
||||
baseModels: baseModels.value.map((option: SelectOption) => option.value),
|
||||
sortBy: sortBy.value
|
||||
sortBy: sortBy.value,
|
||||
ownership: ownership.value
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -295,7 +295,8 @@ describe('useAssetBrowser', () => {
|
||||
updateFilters({
|
||||
sortBy: 'name-asc',
|
||||
fileFormats: ['safetensors'],
|
||||
baseModels: []
|
||||
baseModels: [],
|
||||
ownership: 'all'
|
||||
})
|
||||
await nextTick()
|
||||
|
||||
@@ -330,7 +331,8 @@ describe('useAssetBrowser', () => {
|
||||
updateFilters({
|
||||
sortBy: 'name-asc',
|
||||
fileFormats: [],
|
||||
baseModels: ['SDXL']
|
||||
baseModels: ['SDXL'],
|
||||
ownership: 'all'
|
||||
})
|
||||
await nextTick()
|
||||
|
||||
@@ -384,7 +386,8 @@ describe('useAssetBrowser', () => {
|
||||
updateFilters({
|
||||
sortBy: 'name-asc',
|
||||
fileFormats: [],
|
||||
baseModels: []
|
||||
baseModels: [],
|
||||
ownership: 'all'
|
||||
})
|
||||
await nextTick()
|
||||
|
||||
@@ -408,7 +411,8 @@ describe('useAssetBrowser', () => {
|
||||
updateFilters({
|
||||
sortBy: 'recent',
|
||||
fileFormats: [],
|
||||
baseModels: []
|
||||
baseModels: [],
|
||||
ownership: 'all'
|
||||
})
|
||||
await nextTick()
|
||||
|
||||
@@ -440,7 +444,8 @@ describe('useAssetBrowser', () => {
|
||||
updateFilters({
|
||||
sortBy: 'name-asc',
|
||||
fileFormats: [],
|
||||
baseModels: []
|
||||
baseModels: [],
|
||||
ownership: 'all'
|
||||
})
|
||||
await nextTick()
|
||||
|
||||
@@ -493,6 +498,96 @@ describe('useAssetBrowser', () => {
|
||||
|
||||
expect(filteredAssets.value).toHaveLength(3)
|
||||
})
|
||||
|
||||
it('filters by ownership via filter bar - my-models', async () => {
|
||||
const assets = [
|
||||
createApiAsset({ name: 'my-model.safetensors', is_immutable: false }),
|
||||
createApiAsset({
|
||||
name: 'public-model.safetensors',
|
||||
is_immutable: true
|
||||
}),
|
||||
createApiAsset({
|
||||
name: 'another-my-model.safetensors',
|
||||
is_immutable: false
|
||||
})
|
||||
]
|
||||
|
||||
const { updateFilters, filteredAssets } = useAssetBrowser(ref(assets))
|
||||
|
||||
updateFilters({
|
||||
sortBy: 'name-asc',
|
||||
fileFormats: [],
|
||||
baseModels: [],
|
||||
ownership: 'my-models'
|
||||
})
|
||||
await nextTick()
|
||||
|
||||
expect(filteredAssets.value).toHaveLength(2)
|
||||
expect(filteredAssets.value.every((asset) => !asset.is_immutable)).toBe(
|
||||
true
|
||||
)
|
||||
})
|
||||
|
||||
it('filters by ownership via filter bar - public-models', async () => {
|
||||
const assets = [
|
||||
createApiAsset({ name: 'my-model.safetensors', is_immutable: false }),
|
||||
createApiAsset({
|
||||
name: 'public-model.safetensors',
|
||||
is_immutable: true
|
||||
}),
|
||||
createApiAsset({
|
||||
name: 'another-public-model.safetensors',
|
||||
is_immutable: true
|
||||
})
|
||||
]
|
||||
|
||||
const { updateFilters, filteredAssets } = useAssetBrowser(ref(assets))
|
||||
|
||||
updateFilters({
|
||||
sortBy: 'name-asc',
|
||||
fileFormats: [],
|
||||
baseModels: [],
|
||||
ownership: 'public-models'
|
||||
})
|
||||
await nextTick()
|
||||
|
||||
expect(filteredAssets.value).toHaveLength(2)
|
||||
expect(filteredAssets.value.every((asset) => asset.is_immutable)).toBe(
|
||||
true
|
||||
)
|
||||
})
|
||||
|
||||
it('nav imported selection overrides filter bar ownership', async () => {
|
||||
const assets = [
|
||||
createApiAsset({ name: 'my-model.safetensors', is_immutable: false }),
|
||||
createApiAsset({
|
||||
name: 'public-model.safetensors',
|
||||
is_immutable: true
|
||||
})
|
||||
]
|
||||
|
||||
const { selectedNavItem, updateFilters, filteredAssets } =
|
||||
useAssetBrowser(ref(assets))
|
||||
|
||||
// Set filter bar to public-models
|
||||
updateFilters({
|
||||
sortBy: 'name-asc',
|
||||
fileFormats: [],
|
||||
baseModels: [],
|
||||
ownership: 'public-models'
|
||||
})
|
||||
await nextTick()
|
||||
|
||||
expect(filteredAssets.value).toHaveLength(1)
|
||||
expect(filteredAssets.value[0].is_immutable).toBe(true)
|
||||
|
||||
// Nav selection to 'imported' should override filter bar
|
||||
selectedNavItem.value = 'imported'
|
||||
await nextTick()
|
||||
|
||||
expect(filteredAssets.value).toHaveLength(1)
|
||||
expect(filteredAssets.value[0].is_immutable).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Dynamic Category Extraction', () => {
|
||||
|
||||
@@ -5,7 +5,10 @@ import type { UseFuseOptions } from '@vueuse/integrations/useFuse'
|
||||
import { storeToRefs } from 'pinia'
|
||||
|
||||
import { d, t } from '@/i18n'
|
||||
import type { FilterState } from '@/platform/assets/components/AssetFilterBar.vue'
|
||||
import type {
|
||||
FilterState,
|
||||
OwnershipOption
|
||||
} from '@/platform/assets/components/AssetFilterBar.vue'
|
||||
import type { AssetItem } from '@/platform/assets/schemas/assetSchema'
|
||||
import {
|
||||
getAssetBaseModels,
|
||||
@@ -15,8 +18,6 @@ import {
|
||||
import { useAssetDownloadStore } from '@/stores/assetDownloadStore'
|
||||
import type { NavGroupData, NavItemData } from '@/types/navTypes'
|
||||
|
||||
type OwnershipOption = 'all' | 'my-models' | 'public-models'
|
||||
|
||||
type NavId = 'all' | 'imported' | (string & {})
|
||||
|
||||
function filterByCategory(category: string) {
|
||||
@@ -96,12 +97,13 @@ export function useAssetBrowser(
|
||||
const filters = ref<FilterState>({
|
||||
sortBy: 'recent',
|
||||
fileFormats: [],
|
||||
baseModels: []
|
||||
baseModels: [],
|
||||
ownership: 'all'
|
||||
})
|
||||
|
||||
const selectedOwnership = computed<OwnershipOption>(() => {
|
||||
if (selectedNavItem.value === 'imported') return 'my-models'
|
||||
return 'all'
|
||||
return filters.value.ownership
|
||||
})
|
||||
|
||||
const selectedCategory = computed(() => {
|
||||
|
||||
Reference in New Issue
Block a user