mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-24 16:54:03 +00:00
feat: add base model filter to FormDropdown (#8501)
## Summary Adds a base model filter to the FormDropdown component, allowing users to filter dropdown options by base model type. ## Changes - **Base model filter**: New filter popover in FormDropdownMenuActions that lets users select one or more base models to filter by - **Clear Filters button**: Added to the base model filter popover for quick reset - **Button component refactor**: Replaced native `<button>` elements with the `Button` component for consistent styling - **New i18n key**: Added `assets.baseModel` translation ## Files Changed - `FormDropdownMenuActions.vue` - Main implementation of base model filter UI - `WidgetSelectDropdown.vue` - Passes base model options to the dropdown - `FormDropdown.vue`, `FormDropdownMenu.vue`, `FormDropdownMenuFilter.vue` - Prop threading - `filterTypes.ts`, `types.ts` - Type definitions for filter options ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-8501-feat-add-base-model-filter-to-FormDropdown-2f96d73d3650813c994debb06070c7dd) by [Unito](https://www.unito.io) --------- Co-authored-by: Amp <amp@ampcode.com>
This commit is contained in:
@@ -2506,6 +2506,7 @@
|
||||
"imported": "Imported",
|
||||
"assetCollection": "Asset collection",
|
||||
"assets": "Assets",
|
||||
"baseModel": "Base model",
|
||||
"baseModels": "Base models",
|
||||
"browseAssets": "Browse Assets",
|
||||
"checkpoints": "Checkpoints",
|
||||
|
||||
@@ -3,17 +3,12 @@
|
||||
* Shared across AssetBrowser, AssetFilterBar, and widget dropdowns
|
||||
*/
|
||||
|
||||
/**
|
||||
* Generic option identifier type
|
||||
*/
|
||||
export type OptionId = string
|
||||
|
||||
/**
|
||||
* Generic filter/select option used across components
|
||||
* Compatible with both SelectOption (name/value) and FilterOption (id/name) patterns
|
||||
*/
|
||||
export interface FilterOption {
|
||||
id: OptionId
|
||||
id: string
|
||||
name: string
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import { computed, provide, ref, toRef, watch } from 'vue'
|
||||
import { useTransformCompatOverlayProps } from '@/composables/useTransformCompatOverlayProps'
|
||||
import { t } from '@/i18n'
|
||||
import {
|
||||
getAssetBaseModels,
|
||||
getAssetDisplayName,
|
||||
getAssetFilename
|
||||
} from '@/platform/assets/utils/assetMetadataUtils'
|
||||
@@ -12,7 +13,6 @@ import { useToastStore } from '@/platform/updates/common/toastStore'
|
||||
import FormDropdown from '@/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdown.vue'
|
||||
import type {
|
||||
FilterOption,
|
||||
OptionId,
|
||||
OwnershipOption
|
||||
} from '@/platform/assets/types/filterTypes'
|
||||
import { AssetKindKey } from '@/renderer/extensions/vueNodes/widgets/components/form/dropdown/types'
|
||||
@@ -99,7 +99,22 @@ const ownershipOptions = computed(() => [
|
||||
}
|
||||
])
|
||||
|
||||
const selectedSet = ref<Set<OptionId>>(new Set())
|
||||
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 }))
|
||||
})
|
||||
|
||||
const selectedSet = ref<Set<string>>(new Set())
|
||||
|
||||
/**
|
||||
* Transforms a value using getOptionLabel if available.
|
||||
@@ -222,7 +237,8 @@ const assetItems = computed<FormDropdownItem[]>(() => {
|
||||
name: getAssetFilename(asset),
|
||||
label: getAssetDisplayName(asset),
|
||||
preview_url: asset.preview_url,
|
||||
is_immutable: asset.is_immutable
|
||||
is_immutable: asset.is_immutable,
|
||||
base_models: getAssetBaseModels(asset)
|
||||
}))
|
||||
})
|
||||
|
||||
@@ -235,12 +251,23 @@ const ownershipFilteredAssetItems = computed<FormDropdownItem[]>(() => {
|
||||
return assetItems.value.filter((item) => item.is_immutable === isPublic)
|
||||
})
|
||||
|
||||
/**
|
||||
* Filters asset items by base model selection.
|
||||
*/
|
||||
const baseModelFilteredAssetItems = computed<FormDropdownItem[]>(() => {
|
||||
if (baseModelSelected.value.size === 0)
|
||||
return ownershipFilteredAssetItems.value
|
||||
return ownershipFilteredAssetItems.value.filter((item) =>
|
||||
item.base_models?.some((model) => baseModelSelected.value.has(model))
|
||||
)
|
||||
})
|
||||
|
||||
const allItems = computed<FormDropdownItem[]>(() => {
|
||||
if (props.isAssetMode && assetData) {
|
||||
if (missingValueItem.value) {
|
||||
return [missingValueItem.value, ...ownershipFilteredAssetItems.value]
|
||||
return [missingValueItem.value, ...baseModelFilteredAssetItems.value]
|
||||
}
|
||||
return ownershipFilteredAssetItems.value
|
||||
return baseModelFilteredAssetItems.value
|
||||
}
|
||||
return [
|
||||
...(missingValueItem.value ? [missingValueItem.value] : []),
|
||||
@@ -327,8 +354,8 @@ watch(
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
function updateSelectedItems(selectedItems: Set<OptionId>) {
|
||||
let id: OptionId | undefined = undefined
|
||||
function updateSelectedItems(selectedItems: Set<string>) {
|
||||
let id: string | undefined = undefined
|
||||
if (selectedItems.size > 0) {
|
||||
id = selectedItems.values().next().value!
|
||||
}
|
||||
@@ -436,6 +463,7 @@ function getMediaUrl(
|
||||
v-model:filter-selected="filterSelected"
|
||||
v-model:layout-mode="layoutMode"
|
||||
v-model:ownership-selected="ownershipSelected"
|
||||
v-model:base-model-selected="baseModelSelected"
|
||||
:items="dropdownItems"
|
||||
:placeholder="mediaPlaceholder"
|
||||
:multiple="false"
|
||||
@@ -444,6 +472,8 @@ function getMediaUrl(
|
||||
:filter-options
|
||||
:show-ownership-filter
|
||||
:ownership-options
|
||||
:show-base-model-filter
|
||||
:base-model-options
|
||||
v-bind="combinedProps"
|
||||
class="w-full"
|
||||
@update:selected="updateSelectedItems"
|
||||
|
||||
@@ -7,7 +7,6 @@ import { useToastStore } from '@/platform/updates/common/toastStore'
|
||||
|
||||
import type {
|
||||
FilterOption,
|
||||
OptionId,
|
||||
OwnershipFilterOption,
|
||||
OwnershipOption
|
||||
} from '@/platform/assets/types/filterTypes'
|
||||
@@ -33,8 +32,10 @@ interface Props {
|
||||
sortOptions?: SortOption[]
|
||||
showOwnershipFilter?: boolean
|
||||
ownershipOptions?: OwnershipFilterOption[]
|
||||
showBaseModelFilter?: boolean
|
||||
baseModelOptions?: FilterOption[]
|
||||
isSelected?: (
|
||||
selected: Set<OptionId>,
|
||||
selected: Set<string>,
|
||||
item: FormDropdownItem,
|
||||
index: number
|
||||
) => boolean
|
||||
@@ -56,11 +57,11 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
searcher: defaultSearcher
|
||||
})
|
||||
|
||||
const selected = defineModel<Set<OptionId>>('selected', {
|
||||
const selected = defineModel<Set<string>>('selected', {
|
||||
default: new Set()
|
||||
})
|
||||
const filterSelected = defineModel<OptionId>('filterSelected', { default: '' })
|
||||
const sortSelected = defineModel<OptionId>('sortSelected', {
|
||||
const filterSelected = defineModel<string>('filterSelected', { default: '' })
|
||||
const sortSelected = defineModel<string>('sortSelected', {
|
||||
default: 'default'
|
||||
})
|
||||
const layoutMode = defineModel<LayoutMode>('layoutMode', {
|
||||
@@ -71,6 +72,9 @@ const searchQuery = defineModel<string>('searchQuery', { default: '' })
|
||||
const ownershipSelected = defineModel<OwnershipOption>('ownershipSelected', {
|
||||
default: 'all'
|
||||
})
|
||||
const baseModelSelected = defineModel<Set<string>>('baseModelSelected', {
|
||||
default: new Set()
|
||||
})
|
||||
|
||||
const toastStore = useToastStore()
|
||||
const popoverRef = ref<InstanceType<typeof Popover>>()
|
||||
@@ -210,10 +214,13 @@ async function customSearcher(
|
||||
v-model:sort-selected="sortSelected"
|
||||
v-model:search-query="searchQuery"
|
||||
v-model:ownership-selected="ownershipSelected"
|
||||
v-model:base-model-selected="baseModelSelected"
|
||||
:filter-options
|
||||
:sort-options
|
||||
:show-ownership-filter
|
||||
:ownership-options
|
||||
:show-base-model-filter
|
||||
:base-model-options
|
||||
:disabled
|
||||
:searcher="customSearcher"
|
||||
:items="sortedItems"
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
|
||||
import type { OptionId } from '@/platform/assets/types/filterTypes'
|
||||
import { cn } from '@/utils/tailwindUtil'
|
||||
|
||||
import { WidgetInputBaseClass } from '../../layout'
|
||||
@@ -11,7 +10,7 @@ interface Props {
|
||||
isOpen?: boolean
|
||||
placeholder?: string
|
||||
items: FormDropdownItem[]
|
||||
selected: Set<OptionId>
|
||||
selected: Set<string>
|
||||
maxSelectable: number
|
||||
uploadable: boolean
|
||||
disabled: boolean
|
||||
|
||||
@@ -5,7 +5,6 @@ import { cn } from '@/utils/tailwindUtil'
|
||||
|
||||
import type {
|
||||
FilterOption,
|
||||
OptionId,
|
||||
OwnershipFilterOption,
|
||||
OwnershipOption
|
||||
} from '@/platform/assets/types/filterTypes'
|
||||
@@ -27,6 +26,8 @@ interface Props {
|
||||
updateKey?: MaybeRefOrGetter<unknown>
|
||||
showOwnershipFilter?: boolean
|
||||
ownershipOptions?: OwnershipFilterOption[]
|
||||
showBaseModelFilter?: boolean
|
||||
baseModelOptions?: FilterOption[]
|
||||
}
|
||||
|
||||
defineProps<Props>()
|
||||
@@ -34,11 +35,12 @@ const emit = defineEmits<{
|
||||
(e: 'item-click', item: FormDropdownItem, index: number): void
|
||||
}>()
|
||||
|
||||
const filterSelected = defineModel<OptionId>('filterSelected')
|
||||
const filterSelected = defineModel<string>('filterSelected')
|
||||
const layoutMode = defineModel<LayoutMode>('layoutMode')
|
||||
const sortSelected = defineModel<OptionId>('sortSelected')
|
||||
const sortSelected = defineModel<string>('sortSelected')
|
||||
const searchQuery = defineModel<string>('searchQuery')
|
||||
const ownershipSelected = defineModel<OwnershipOption>('ownershipSelected')
|
||||
const baseModelSelected = defineModel<Set<string>>('baseModelSelected')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -55,11 +57,14 @@ const ownershipSelected = defineModel<OwnershipOption>('ownershipSelected')
|
||||
v-model:sort-selected="sortSelected"
|
||||
v-model:search-query="searchQuery"
|
||||
v-model:ownership-selected="ownershipSelected"
|
||||
v-model:base-model-selected="baseModelSelected"
|
||||
:sort-options
|
||||
:searcher
|
||||
:update-key
|
||||
:show-ownership-filter
|
||||
:ownership-options
|
||||
:show-base-model-filter
|
||||
:base-model-options
|
||||
/>
|
||||
<div class="relative flex h-full mt-2 overflow-y-scroll">
|
||||
<div
|
||||
|
||||
@@ -5,8 +5,9 @@ import Popover from 'primevue/popover'
|
||||
import { ref, useTemplateRef } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import Button from '@/components/ui/button/Button.vue'
|
||||
import type {
|
||||
OptionId,
|
||||
FilterOption,
|
||||
OwnershipFilterOption,
|
||||
OwnershipOption
|
||||
} from '@/platform/assets/types/filterTypes'
|
||||
@@ -26,21 +27,24 @@ defineProps<{
|
||||
updateKey?: MaybeRefOrGetter<unknown>
|
||||
showOwnershipFilter?: boolean
|
||||
ownershipOptions?: OwnershipFilterOption[]
|
||||
showBaseModelFilter?: boolean
|
||||
baseModelOptions?: FilterOption[]
|
||||
}>()
|
||||
|
||||
const layoutMode = defineModel<LayoutMode>('layoutMode')
|
||||
const searchQuery = defineModel<string>('searchQuery')
|
||||
const sortSelected = defineModel<OptionId>('sortSelected')
|
||||
const sortSelected = defineModel<string>('sortSelected')
|
||||
const ownershipSelected = defineModel<OwnershipOption>('ownershipSelected', {
|
||||
default: 'all'
|
||||
})
|
||||
const baseModelSelected = defineModel<Set<string>>('baseModelSelected', {
|
||||
default: new Set()
|
||||
})
|
||||
|
||||
const actionButtonStyle = cn(
|
||||
'h-8 bg-zinc-500/20 rounded-lg outline outline-1 outline-offset-[-1px] outline-node-component-border transition-all duration-150'
|
||||
)
|
||||
|
||||
const resetInputStyle = 'bg-transparent border-0 outline-0 ring-0 text-left'
|
||||
|
||||
const layoutSwitchItemStyle =
|
||||
'size-6 flex justify-center items-center rounded-sm cursor-pointer transition-all duration-150 hover:scale-108 hover:text-base-foreground active:scale-95'
|
||||
|
||||
@@ -51,7 +55,7 @@ const isSortPopoverOpen = ref(false)
|
||||
function toggleSortPopover(event: Event) {
|
||||
if (!sortPopoverRef.value || !sortTriggerRef.value) return
|
||||
isSortPopoverOpen.value = !isSortPopoverOpen.value
|
||||
sortPopoverRef.value.toggle(event, sortTriggerRef.value)
|
||||
sortPopoverRef.value.toggle(event, sortTriggerRef.value.$el)
|
||||
}
|
||||
function closeSortPopover() {
|
||||
isSortPopoverOpen.value = false
|
||||
@@ -70,7 +74,7 @@ const isOwnershipPopoverOpen = ref(false)
|
||||
function toggleOwnershipPopover(event: Event) {
|
||||
if (!ownershipPopoverRef.value || !ownershipTriggerRef.value) return
|
||||
isOwnershipPopoverOpen.value = !isOwnershipPopoverOpen.value
|
||||
ownershipPopoverRef.value.toggle(event, ownershipTriggerRef.value)
|
||||
ownershipPopoverRef.value.toggle(event, ownershipTriggerRef.value.$el)
|
||||
}
|
||||
function closeOwnershipPopover() {
|
||||
isOwnershipPopoverOpen.value = false
|
||||
@@ -81,6 +85,26 @@ function handleOwnershipSelected(item: OwnershipFilterOption) {
|
||||
ownershipSelected.value = item.id
|
||||
closeOwnershipPopover()
|
||||
}
|
||||
|
||||
const baseModelPopoverRef = useTemplateRef('baseModelPopoverRef')
|
||||
const baseModelTriggerRef = useTemplateRef('baseModelTriggerRef')
|
||||
const isBaseModelPopoverOpen = ref(false)
|
||||
|
||||
function toggleBaseModelPopover(event: Event) {
|
||||
if (!baseModelPopoverRef.value || !baseModelTriggerRef.value) return
|
||||
isBaseModelPopoverOpen.value = !isBaseModelPopoverOpen.value
|
||||
baseModelPopoverRef.value.toggle(event, baseModelTriggerRef.value.$el)
|
||||
}
|
||||
|
||||
function toggleBaseModelSelection(item: FilterOption) {
|
||||
const current = baseModelSelected.value
|
||||
if (current.has(item.id)) {
|
||||
current.delete(item.id)
|
||||
} else {
|
||||
current.add(item.id)
|
||||
}
|
||||
baseModelSelected.value = new Set(current)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -98,15 +122,14 @@ function handleOwnershipSelected(item: OwnershipFilterOption) {
|
||||
"
|
||||
/>
|
||||
|
||||
<button
|
||||
<Button
|
||||
ref="sortTriggerRef"
|
||||
variant="textonly"
|
||||
size="icon"
|
||||
:class="
|
||||
cn(
|
||||
resetInputStyle,
|
||||
actionButtonStyle,
|
||||
'relative w-8 flex justify-center items-center cursor-pointer',
|
||||
'hover:outline-component-node-widget-background-highlighted',
|
||||
'active:!scale-95'
|
||||
'relative w-8 hover:outline-component-node-widget-background-highlighted active:scale-95'
|
||||
)
|
||||
"
|
||||
@click="toggleSortPopover"
|
||||
@@ -116,7 +139,7 @@ function handleOwnershipSelected(item: OwnershipFilterOption) {
|
||||
class="absolute top-[-2px] left-[-2px] size-2 rounded-full bg-component-node-widget-background-highlighted"
|
||||
/>
|
||||
<i class="icon-[lucide--arrow-up-down] size-4" />
|
||||
</button>
|
||||
</Button>
|
||||
<Popover
|
||||
ref="sortPopoverRef"
|
||||
:dismissable="true"
|
||||
@@ -141,16 +164,12 @@ function handleOwnershipSelected(item: OwnershipFilterOption) {
|
||||
)
|
||||
"
|
||||
>
|
||||
<button
|
||||
<Button
|
||||
v-for="item of sortOptions"
|
||||
:key="item.name"
|
||||
:class="
|
||||
cn(
|
||||
resetInputStyle,
|
||||
'flex justify-between items-center h-6 cursor-pointer',
|
||||
'hover:!text-blue-500'
|
||||
)
|
||||
"
|
||||
variant="textonly"
|
||||
size="unset"
|
||||
:class="cn('flex justify-between items-center h-6 text-left')"
|
||||
@click="handleSortSelected(item)"
|
||||
>
|
||||
<span>{{ item.name }}</span>
|
||||
@@ -158,22 +177,21 @@ function handleOwnershipSelected(item: OwnershipFilterOption) {
|
||||
v-if="sortSelected === item.id"
|
||||
class="icon-[lucide--check] size-4"
|
||||
/>
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
</Popover>
|
||||
|
||||
<button
|
||||
<Button
|
||||
v-if="showOwnershipFilter && ownershipOptions?.length"
|
||||
ref="ownershipTriggerRef"
|
||||
:aria-label="t('assetBrowser.ownership')"
|
||||
:title="t('assetBrowser.ownership')"
|
||||
variant="textonly"
|
||||
size="icon"
|
||||
:class="
|
||||
cn(
|
||||
resetInputStyle,
|
||||
actionButtonStyle,
|
||||
'relative w-8 flex justify-center items-center cursor-pointer',
|
||||
'hover:outline-component-node-widget-background-highlighted',
|
||||
'active:!scale-95'
|
||||
'relative w-8 hover:outline-component-node-widget-background-highlighted active:scale-95'
|
||||
)
|
||||
"
|
||||
@click="toggleOwnershipPopover"
|
||||
@@ -183,7 +201,7 @@ function handleOwnershipSelected(item: OwnershipFilterOption) {
|
||||
class="absolute top-[-2px] left-[-2px] size-2 rounded-full bg-component-node-widget-background-highlighted"
|
||||
/>
|
||||
<i class="icon-[lucide--user] size-4" />
|
||||
</button>
|
||||
</Button>
|
||||
<Popover
|
||||
ref="ownershipPopoverRef"
|
||||
:dismissable="true"
|
||||
@@ -208,16 +226,12 @@ function handleOwnershipSelected(item: OwnershipFilterOption) {
|
||||
)
|
||||
"
|
||||
>
|
||||
<button
|
||||
<Button
|
||||
v-for="item of ownershipOptions"
|
||||
:key="item.id"
|
||||
:class="
|
||||
cn(
|
||||
resetInputStyle,
|
||||
'flex justify-between items-center h-6 cursor-pointer',
|
||||
'hover:!text-blue-500'
|
||||
)
|
||||
"
|
||||
variant="textonly"
|
||||
size="unset"
|
||||
:class="cn('flex justify-between items-center h-6 text-left')"
|
||||
@click="handleOwnershipSelected(item)"
|
||||
>
|
||||
<span>{{ item.name }}</span>
|
||||
@@ -225,7 +239,78 @@ function handleOwnershipSelected(item: OwnershipFilterOption) {
|
||||
v-if="ownershipSelected === item.id"
|
||||
class="icon-[lucide--check] size-4"
|
||||
/>
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
</Popover>
|
||||
|
||||
<Button
|
||||
v-if="showBaseModelFilter && baseModelOptions?.length"
|
||||
ref="baseModelTriggerRef"
|
||||
:aria-label="t('assetBrowser.baseModel')"
|
||||
:title="t('assetBrowser.baseModel')"
|
||||
variant="textonly"
|
||||
size="icon"
|
||||
:class="
|
||||
cn(
|
||||
actionButtonStyle,
|
||||
'relative w-8 hover:outline-component-node-widget-background-highlighted active:scale-95'
|
||||
)
|
||||
"
|
||||
@click="toggleBaseModelPopover"
|
||||
>
|
||||
<div
|
||||
v-if="baseModelSelected.size > 0"
|
||||
class="absolute top-[-2px] left-[-2px] size-2 rounded-full bg-component-node-widget-background-highlighted"
|
||||
/>
|
||||
<i class="icon-[comfy--ai-model] size-4" />
|
||||
</Button>
|
||||
<Popover
|
||||
ref="baseModelPopoverRef"
|
||||
:dismissable="true"
|
||||
:close-on-escape="true"
|
||||
unstyled
|
||||
:pt="{
|
||||
root: {
|
||||
class: 'absolute z-50'
|
||||
},
|
||||
content: {
|
||||
class: ['bg-transparent border-none p-0 pt-2 rounded-lg shadow-lg']
|
||||
}
|
||||
}"
|
||||
@hide="isBaseModelPopoverOpen = false"
|
||||
>
|
||||
<div
|
||||
:class="
|
||||
cn(
|
||||
'flex flex-col gap-2 p-2 min-w-32',
|
||||
'bg-component-node-background',
|
||||
'rounded-lg outline outline-offset-[-1px] outline-component-node-border'
|
||||
)
|
||||
"
|
||||
>
|
||||
<Button
|
||||
v-for="item of baseModelOptions"
|
||||
:key="item.id"
|
||||
variant="textonly"
|
||||
size="unset"
|
||||
:class="cn('flex justify-between items-center h-6 text-left')"
|
||||
@click="toggleBaseModelSelection(item)"
|
||||
>
|
||||
<span>{{ item.name }}</span>
|
||||
<i
|
||||
v-if="baseModelSelected.has(item.id)"
|
||||
class="icon-[lucide--check] size-4"
|
||||
/>
|
||||
</Button>
|
||||
<span class="h-0 w-full border-b border-border-default" />
|
||||
<Button
|
||||
variant="textonly"
|
||||
size="unset"
|
||||
:class="cn('flex justify-between items-center h-6 text-left')"
|
||||
@click="baseModelSelected = new Set()"
|
||||
>
|
||||
{{ t('g.clearFilters') }}
|
||||
</Button>
|
||||
</div>
|
||||
</Popover>
|
||||
|
||||
@@ -237,34 +322,32 @@ function handleOwnershipSelected(item: OwnershipFilterOption) {
|
||||
)
|
||||
"
|
||||
>
|
||||
<button
|
||||
<Button
|
||||
variant="textonly"
|
||||
size="unset"
|
||||
:class="
|
||||
cn(
|
||||
resetInputStyle,
|
||||
layoutSwitchItemStyle,
|
||||
layoutMode === 'list'
|
||||
? 'bg-neutral-500/50 text-base-foreground'
|
||||
: ''
|
||||
layoutMode === 'list' && 'bg-neutral-500/50 text-base-foreground'
|
||||
)
|
||||
"
|
||||
@click="layoutMode = 'list'"
|
||||
>
|
||||
<i class="icon-[lucide--list] size-4" />
|
||||
</button>
|
||||
<button
|
||||
</Button>
|
||||
<Button
|
||||
variant="textonly"
|
||||
size="unset"
|
||||
:class="
|
||||
cn(
|
||||
resetInputStyle,
|
||||
layoutSwitchItemStyle,
|
||||
layoutMode === 'grid'
|
||||
? 'bg-neutral-500/50 text-base-foreground'
|
||||
: ''
|
||||
layoutMode === 'grid' && 'bg-neutral-500/50 text-base-foreground'
|
||||
)
|
||||
"
|
||||
@click="layoutMode = 'grid'"
|
||||
>
|
||||
<i class="icon-[lucide--layout-grid] size-4" />
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -3,17 +3,14 @@ import { computed } from 'vue'
|
||||
|
||||
import Button from '@/components/ui/button/Button.vue'
|
||||
import { useModelUpload } from '@/platform/assets/composables/useModelUpload'
|
||||
import type {
|
||||
FilterOption,
|
||||
OptionId
|
||||
} from '@/platform/assets/types/filterTypes'
|
||||
import type { FilterOption } from '@/platform/assets/types/filterTypes'
|
||||
import { cn } from '@/utils/tailwindUtil'
|
||||
|
||||
const { filterOptions } = defineProps<{
|
||||
filterOptions: FilterOption[]
|
||||
}>()
|
||||
|
||||
const filterSelected = defineModel<OptionId>('filterSelected')
|
||||
const filterSelected = defineModel<string>('filterSelected')
|
||||
|
||||
const { isUploadButtonEnabled, showUploadDialog } = useModelUpload()
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import type { ComputedRef, InjectionKey } from 'vue'
|
||||
|
||||
import type { OptionId } from '@/platform/assets/types/filterTypes'
|
||||
import type { AssetKind } from '@/types/widgetTypes'
|
||||
|
||||
/**
|
||||
@@ -8,7 +7,7 @@ import type { AssetKind } from '@/types/widgetTypes'
|
||||
* Both AssetItem (from cloud API) and local file items satisfy this contract.
|
||||
*/
|
||||
export interface FormDropdownItem {
|
||||
id: OptionId
|
||||
id: string
|
||||
/** Display name shown in the dropdown */
|
||||
name: string
|
||||
/** Original/alternate label (e.g., original filename) */
|
||||
@@ -17,9 +16,11 @@ export interface FormDropdownItem {
|
||||
preview_url?: string
|
||||
/** Whether the item is immutable (public model) - used for ownership filtering */
|
||||
is_immutable?: boolean
|
||||
/** Base models this item is compatible with - used for base model filtering */
|
||||
base_models?: string[]
|
||||
}
|
||||
|
||||
export interface SortOption<TId extends OptionId = OptionId> {
|
||||
export interface SortOption<TId extends string = string> {
|
||||
id: TId
|
||||
name: string
|
||||
sorter: (ctx: { items: readonly FormDropdownItem[] }) => FormDropdownItem[]
|
||||
|
||||
Reference in New Issue
Block a user