Skip to content

Commit 57523a0

Browse files
authored
Design: Model management (#7190)
## Summary Assorted updates to the components involved in uploading personal models. ## Changes - Standardize Import buttons - Let the images fill the space on the card - Order by recent by default - Nicer display on the model select popover ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-7190-Design-Model-management-2c06d73d365081e7b9fed7a83b730c0f) by [Unito](https://www.unito.io)
1 parent ec1a7f1 commit 57523a0

File tree

6 files changed

+43
-52
lines changed

6 files changed

+43
-52
lines changed

src/locales/en/main.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2091,7 +2091,7 @@
20912091
"connectionError": "Please check your connection and try again",
20922092
"failedToCreateNode": "Failed to create node. Please try again or check console for details.",
20932093
"noModelsInFolder": "No {type} available in this folder",
2094-
"uploadModel": "Import model",
2094+
"uploadModel": "Import",
20952095
"uploadModelFromCivitai": "Import a model from Civitai",
20962096
"uploadModelFailedToRetrieveMetadata": "Failed to retrieve metadata. Please check the link and try again.",
20972097
"onlyCivitaiUrlsSupported": "Only Civitai URLs are supported",

src/platform/assets/components/AssetBrowserModal.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
:on-click="showUploadDialog"
4040
>
4141
<template #icon>
42-
<i class="icon-[lucide--package-plus]" />
42+
<i class="icon-[lucide--folder-input]" />
4343
</template>
4444
</IconTextButton>
4545
</div>

src/platform/assets/components/AssetCard.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
v-else
2727
:src="asset.preview_url"
2828
:alt="displayName"
29-
class="size-full object-contain cursor-pointer"
29+
class="size-full object-cover cursor-pointer"
3030
role="button"
3131
@click.self="interactive && $emit('select', asset)"
3232
/>

src/platform/assets/components/AssetFilterBar.vue

Lines changed: 23 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,18 @@
11
<template>
2-
<div :class="containerClasses" data-component-id="asset-filter-bar">
3-
<div :class="leftSideClasses" data-component-id="asset-filter-bar-left">
2+
<div
3+
class="flex gap-4 items-center justify-between px-6 pt-2 pb-6"
4+
data-component-id="asset-filter-bar"
5+
>
6+
<div
7+
class="flex gap-4 items-center"
8+
data-component-id="asset-filter-bar-left"
9+
>
410
<MultiSelect
511
v-if="availableFileFormats.length > 0"
612
v-model="fileFormats"
713
:label="$t('assetBrowser.fileFormats')"
814
:options="availableFileFormats"
9-
:class="selectClasses"
15+
class="min-w-32"
1016
data-component-id="asset-filter-file-formats"
1117
@update:model-value="handleFilterChange"
1218
/>
@@ -16,18 +22,18 @@
1622
v-model="baseModels"
1723
:label="$t('assetBrowser.baseModels')"
1824
:options="availableBaseModels"
19-
:class="selectClasses"
25+
class="min-w-32"
2026
data-component-id="asset-filter-base-models"
2127
@update:model-value="handleFilterChange"
2228
/>
2329
</div>
2430

25-
<div :class="rightSideClasses" data-component-id="asset-filter-bar-right">
31+
<div class="flex items-center" data-component-id="asset-filter-bar-right">
2632
<SingleSelect
2733
v-model="sortBy"
2834
:label="$t('assetBrowser.sortBy')"
2935
:options="sortOptions"
30-
:class="selectClasses"
36+
class="min-w-32"
3137
data-component-id="asset-filter-sort"
3238
@update:model-value="handleFilterChange"
3339
>
@@ -48,43 +54,38 @@ import type { SelectOption } from '@/components/input/types'
4854
import { t } from '@/i18n'
4955
import { useAssetFilterOptions } from '@/platform/assets/composables/useAssetFilterOptions'
5056
import type { AssetItem } from '@/platform/assets/schemas/assetSchema'
51-
import { cn } from '@/utils/tailwindUtil'
5257
5358
export interface FilterState {
5459
fileFormats: string[]
5560
baseModels: string[]
5661
sortBy: string
5762
}
5863
64+
const SORT_OPTIONS = [
65+
{ name: t('assetBrowser.sortRecent'), value: 'recent' },
66+
{ name: t('assetBrowser.sortAZ'), value: 'name-asc' },
67+
{ name: t('assetBrowser.sortZA'), value: 'name-desc' }
68+
] as const
69+
70+
type SortOption = (typeof SORT_OPTIONS)[number]['value']
71+
72+
const sortOptions = [...SORT_OPTIONS]
73+
5974
const { assets = [] } = defineProps<{
6075
assets?: AssetItem[]
6176
}>()
6277
6378
const fileFormats = ref<SelectOption[]>([])
6479
const baseModels = ref<SelectOption[]>([])
65-
const sortBy = ref('name-asc')
80+
const sortBy = ref<SortOption>('recent')
6681
6782
const { availableFileFormats, availableBaseModels } =
6883
useAssetFilterOptions(assets)
6984
70-
const sortOptions = [
71-
{ name: t('assetBrowser.sortAZ'), value: 'name-asc' },
72-
{ name: t('assetBrowser.sortZA'), value: 'name-desc' },
73-
{ name: t('assetBrowser.sortRecent'), value: 'recent' }
74-
]
75-
7685
const emit = defineEmits<{
7786
filterChange: [filters: FilterState]
7887
}>()
7988
80-
const containerClasses = cn(
81-
'flex gap-4 items-center justify-between',
82-
'px-6 pt-2 pb-6'
83-
)
84-
const leftSideClasses = cn('flex gap-4 items-center')
85-
const rightSideClasses = cn('flex items-center')
86-
const selectClasses = cn('min-w-32')
87-
8889
function handleFilterChange() {
8990
emit('filterChange', {
9091
fileFormats: fileFormats.value.map((option: SelectOption) => option.value),

src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdownMenuFilter.vue

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,49 @@
11
<script setup lang="ts">
2+
import { computed } from 'vue'
3+
24
import IconTextButton from '@/components/button/IconTextButton.vue'
35
import { useModelUpload } from '@/platform/assets/composables/useModelUpload'
46
import { cn } from '@/utils/tailwindUtil'
57
68
import type { FilterOption, OptionId } from './types'
79
8-
defineProps<{
10+
const { filterOptions } = defineProps<{
911
filterOptions: FilterOption[]
1012
}>()
1113
1214
const filterSelected = defineModel<OptionId>('filterSelected')
1315
1416
const { isUploadButtonEnabled, showUploadDialog } = useModelUpload()
17+
18+
// TODO: Add real check to differentiate between the Model dialogs and Load Image
19+
const singleFilterOption = computed(() => filterOptions.length === 1)
1520
</script>
1621

1722
<template>
18-
<div class="text-secondary mb-4 flex gap-1 px-4 justify-between">
19-
<div
23+
<div class="text-secondary mb-4 flex gap-1 px-4 justify-start">
24+
<button
2025
v-for="option in filterOptions"
2126
:key="option.id"
27+
type="button"
28+
:disabled="singleFilterOption"
2229
:class="
2330
cn(
24-
'px-4 py-2 rounded-md inline-flex justify-center items-center cursor-pointer select-none',
25-
'transition-all duration-150',
26-
'hover:text-base-foreground hover:bg-interface-menu-component-surface-hovered',
27-
'active:scale-95',
28-
filterSelected === option.id
31+
'px-4 py-2 rounded-md inline-flex justify-center items-center select-none appearance-none border-0 text-base-foreground',
32+
!singleFilterOption &&
33+
'transition-all duration-150 hover:text-base-foreground hover:bg-interface-menu-component-surface-hovered cursor-pointer active:scale-95',
34+
!singleFilterOption && filterSelected === option.id
2935
? '!bg-interface-menu-component-surface-selected text-base-foreground'
3036
: 'bg-transparent'
3137
)
3238
"
3339
@click="filterSelected = option.id"
3440
>
3541
{{ option.name }}
36-
</div>
42+
</button>
3743
<IconTextButton
38-
v-if="isUploadButtonEnabled"
44+
v-if="isUploadButtonEnabled && singleFilterOption"
3945
:label="$t('g.import')"
46+
class="ml-auto"
4047
type="secondary"
4148
@click="showUploadDialog"
4249
>

tests-ui/platform/assets/components/AssetFilterBar.test.ts

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -75,23 +75,6 @@ function mountAssetFilterBar(props = {}) {
7575

7676
describe('AssetFilterBar', () => {
7777
describe('Filter State Management', () => {
78-
it('maintains correct initial state', () => {
79-
// Provide assets with options so filters are visible
80-
const assets = [
81-
createAssetWithSpecificExtension('safetensors'),
82-
createAssetWithSpecificBaseModel('sd15')
83-
]
84-
const wrapper = mountAssetFilterBar({ assets })
85-
86-
// Test initial state through component props
87-
const multiSelects = wrapper.findAllComponents({ name: 'MultiSelect' })
88-
const singleSelect = wrapper.findComponent({ name: 'SingleSelect' })
89-
90-
expect(multiSelects[0].props('modelValue')).toEqual([])
91-
expect(multiSelects[1].props('modelValue')).toEqual([])
92-
expect(singleSelect.props('modelValue')).toBe('name-asc')
93-
})
94-
9578
it('handles multiple simultaneous filter changes correctly', async () => {
9679
// Provide assets with options so filters are visible
9780
const assets = [

0 commit comments

Comments
 (0)