Skip to content

Commit 1feee48

Browse files
viva-jinyiclaude
andauthored
[feat] Add search functionality to Media Asset Panel (#6691)
## Summary Add search functionality to the Media Asset Panel, allowing users to search for assets by filename. ## Changes ### 1. Search Feature - Added SearchBox component to AssetsSidebarTab header - Implemented fuzzy search using Fuse.js - Works in both Imported and Generated tabs - Search also available in folder view ### 2. New Composable: `useMediaAssetFiltering` - Location: `src/platform/assets/composables/useMediaAssetFiltering.ts` - Encapsulates search logic in a reusable composable - Extensible structure for future filter and sort features - Debounced search (50ms) ### 3. UX Improvements - Search query automatically clears when switching tabs - Search query automatically clears when exiting folder view ## Testing - ✅ TypeScript type check passed - ✅ ESLint/Oxlint passed - ✅ Lint-staged pre-commit hooks passed ## Modified Files - `src/components/sidebar/tabs/AssetsSidebarTab.vue` - Added SearchBox - `src/platform/assets/composables/useMediaAssetFiltering.ts` - New file - `src/locales/en/main.json` - Added i18n key (`sideToolbar.searchAssets`) ## Future Plans - Add filter functionality (file type, date, etc.) - Add sort functionality - Switch to server-side search for OSS/Cloud (after Asset API and Job API release) ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-6691-feat-Add-search-functionality-to-Media-Asset-Panel-2ab6d73d3650817b8b95f3450179524f) by [Unito](https://www.unito.io) --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent bd6825a commit 1feee48

File tree

4 files changed

+62
-3
lines changed

4 files changed

+62
-3
lines changed

src/components/sidebar/tabs/AssetSidebarTemplate.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
>
1111
<slot name="top" />
1212
</div>
13-
<div v-if="slots.header" class="px-4">
13+
<div v-if="slots.header" class="px-4 pb-4">
1414
<slot name="header" />
1515
</div>
1616
</div>

src/components/sidebar/tabs/AssetsSidebarTab.vue

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,14 @@
3939
<Tab value="input">{{ $t('sideToolbar.labels.imported') }}</Tab>
4040
<Tab value="output">{{ $t('sideToolbar.labels.generated') }}</Tab>
4141
</TabList>
42+
<!-- Search Bar -->
43+
<div class="pt-2">
44+
<SearchBox
45+
v-model="searchQuery"
46+
:placeholder="$t('sideToolbar.searchAssets')"
47+
size="lg"
48+
/>
49+
</div>
4250
</template>
4351
<template #body>
4452
<!-- Loading state -->
@@ -66,7 +74,7 @@
6674
:grid-style="{
6775
display: 'grid',
6876
gridTemplateColumns: 'repeat(auto-fill, minmax(200px, 1fr))',
69-
padding: '0.5rem',
77+
padding: '0 0.5rem',
7078
gap: '0.5rem'
7179
}"
7280
@approach-end="handleApproachEnd"
@@ -157,6 +165,7 @@ import IconTextButton from '@/components/button/IconTextButton.vue'
157165
import TextButton from '@/components/button/TextButton.vue'
158166
import NoResultsPlaceholder from '@/components/common/NoResultsPlaceholder.vue'
159167
import VirtualGrid from '@/components/common/VirtualGrid.vue'
168+
import SearchBox from '@/components/input/SearchBox.vue'
160169
import ResultGallery from '@/components/sidebar/tabs/queue/ResultGallery.vue'
161170
import Tab from '@/components/tab/Tab.vue'
162171
import TabList from '@/components/tab/TabList.vue'
@@ -165,6 +174,7 @@ import MediaAssetCard from '@/platform/assets/components/MediaAssetCard.vue'
165174
import { useMediaAssets } from '@/platform/assets/composables/media/useMediaAssets'
166175
import { useAssetSelection } from '@/platform/assets/composables/useAssetSelection'
167176
import { useMediaAssetActions } from '@/platform/assets/composables/useMediaAssetActions'
177+
import { useMediaAssetFiltering } from '@/platform/assets/composables/useMediaAssetFiltering'
168178
import { getOutputAssetMetadata } from '@/platform/assets/schemas/assetMetadataSchema'
169179
import type { AssetItem } from '@/platform/assets/schemas/assetSchema'
170180
import { ResultItemImpl } from '@/stores/queueStore'
@@ -228,13 +238,21 @@ const currentGalleryAssetId = ref<string | null>(null)
228238
229239
const folderAssets = ref<AssetItem[]>([])
230240
231-
const displayAssets = computed(() => {
241+
// Base assets before search filtering
242+
const baseAssets = computed(() => {
232243
if (isInFolderView.value) {
233244
return folderAssets.value
234245
}
235246
return mediaAssets.value
236247
})
237248
249+
// Use media asset filtering composable
250+
const { searchQuery, filteredAssets } = useMediaAssetFiltering(baseAssets)
251+
252+
const displayAssets = computed(() => {
253+
return filteredAssets.value
254+
})
255+
238256
watch(displayAssets, (newAssets) => {
239257
if (currentGalleryAssetId.value && galleryActiveIndex.value !== -1) {
240258
const newIndex = newAssets.findIndex(
@@ -293,6 +311,8 @@ watch(
293311
activeTab,
294312
() => {
295313
clearSelection()
314+
// Clear search when switching tabs
315+
searchQuery.value = ''
296316
// Reset pagination state when tab changes
297317
void refreshAssets()
298318
},
@@ -350,6 +370,7 @@ const exitFolderView = () => {
350370
folderPromptId.value = null
351371
folderExecutionTime.value = undefined
352372
folderAssets.value = []
373+
searchQuery.value = ''
353374
clearSelection()
354375
}
355376

src/locales/en/main.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -620,6 +620,7 @@
620620
"assets": "Assets",
621621
"mediaAssets": "Media Assets",
622622
"backToAssets": "Back to all assets",
623+
"searchAssets": "Search assets...",
623624
"labels": {
624625
"queue": "Queue",
625626
"nodes": "Nodes",
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { refDebounced } from '@vueuse/core'
2+
import Fuse from 'fuse.js'
3+
import { computed, ref } from 'vue'
4+
import type { Ref } from 'vue'
5+
6+
import type { AssetItem } from '@/platform/assets/schemas/assetSchema'
7+
8+
/**
9+
* Media Asset Filtering composable
10+
* Manages search, filter, and sort for media assets
11+
*/
12+
export function useMediaAssetFiltering(assets: Ref<AssetItem[]>) {
13+
const searchQuery = ref('')
14+
const debouncedSearchQuery = refDebounced(searchQuery, 50)
15+
16+
const fuseOptions = {
17+
keys: ['name'],
18+
threshold: 0.4,
19+
includeScore: true
20+
}
21+
22+
const fuse = computed(() => new Fuse(assets.value, fuseOptions))
23+
24+
const filteredAssets = computed(() => {
25+
if (!debouncedSearchQuery.value.trim()) {
26+
return assets.value
27+
}
28+
29+
const results = fuse.value.search(debouncedSearchQuery.value)
30+
return results.map((result) => result.item)
31+
})
32+
33+
return {
34+
searchQuery,
35+
filteredAssets
36+
}
37+
}

0 commit comments

Comments
 (0)