1- import { ExternalLink , Flex , Tab , TabList , TabPanel , TabPanels , Tabs } from '@invoke-ai/ui-library' ;
1+ import {
2+ ExternalLink ,
3+ Flex ,
4+ IconButton ,
5+ Input ,
6+ InputGroup ,
7+ InputRightElement ,
8+ Tab ,
9+ TabList ,
10+ TabPanel ,
11+ TabPanels ,
12+ Tabs ,
13+ } from '@invoke-ai/ui-library' ;
214import { IAINoContentFallback , IAINoContentFallbackWithSpinner } from 'common/components/IAIImageFallback' ;
315import ScrollableContent from 'common/components/OverlayScrollbars/ScrollableContent' ;
416import ImageMetadataGraphTabContent from 'features/gallery/components/ImageMetadataViewer/ImageMetadataGraphTabContent' ;
517import { ImageMetadataHandlers } from 'features/metadata/parsing' ;
6- import { memo } from 'react' ;
18+ import type { ChangeEvent } from 'react' ;
19+ import { memo , useCallback , useState } from 'react' ;
720import { useTranslation } from 'react-i18next' ;
21+ import { PiXBold } from 'react-icons/pi' ;
822import { useDebouncedMetadata } from 'services/api/hooks/useDebouncedMetadata' ;
923import type { ImageDTO } from 'services/api/types' ;
1024
@@ -16,6 +30,16 @@ type ImageMetadataViewerProps = {
1630 image : ImageDTO ;
1731} ;
1832
33+ const CODE_TAB_PADDING_INLINE = 18 ;
34+ const TAB_INDEX = {
35+ recall : 0 ,
36+ metadata : 1 ,
37+ imageDetails : 2 ,
38+ workflow : 3 ,
39+ graph : 4 ,
40+ } as const ;
41+ const TAB_COUNT = Object . keys ( TAB_INDEX ) . length ;
42+
1943const ImageMetadataViewer = ( { image } : ImageMetadataViewerProps ) => {
2044 // TODO: fix hotkeys
2145 // const dispatch = useAppDispatch();
@@ -25,11 +49,40 @@ const ImageMetadataViewer = ({ image }: ImageMetadataViewerProps) => {
2549 const { t } = useTranslation ( ) ;
2650
2751 const { metadata, isLoading } = useDebouncedMetadata ( image . image_name ) ;
52+ const [ activeTabIndex , setActiveTabIndex ] = useState ( 0 ) ;
53+ const [ searchTerms , setSearchTerms ] = useState < string [ ] > ( ( ) => Array ( TAB_COUNT ) . fill ( '' ) ) ;
54+ const isSearchableTab = activeTabIndex !== TAB_INDEX . recall ;
55+ const activeSearchTerm = searchTerms [ activeTabIndex ] ?? '' ;
56+
57+ const handleTabChange = useCallback ( ( index : number ) => {
58+ setActiveTabIndex ( index ) ;
59+ } , [ ] ) ;
60+
61+ const handleChangeSearch = useCallback (
62+ ( e : ChangeEvent < HTMLInputElement > ) => {
63+ const value = e . target . value ;
64+ setSearchTerms ( ( prev ) => {
65+ const next = [ ...prev ] ;
66+ next [ activeTabIndex ] = value ;
67+ return next ;
68+ } ) ;
69+ } ,
70+ [ activeTabIndex ]
71+ ) ;
72+
73+ const handleClearSearch = useCallback ( ( ) => {
74+ setSearchTerms ( ( prev ) => {
75+ const next = [ ...prev ] ;
76+ next [ activeTabIndex ] = '' ;
77+ return next ;
78+ } ) ;
79+ } , [ activeTabIndex ] ) ;
2880
2981 return (
3082 < Flex
3183 layerStyle = "first"
3284 padding = { 4 }
85+ paddingInline = { 16 }
3386 gap = { 1 }
3487 flexDirection = "column"
3588 width = "full"
@@ -41,14 +94,42 @@ const ImageMetadataViewer = ({ image }: ImageMetadataViewerProps) => {
4194 < ExternalLink href = { image . image_url } label = { image . image_name } />
4295 < UnrecallableMetadataDatum metadata = { metadata } handler = { ImageMetadataHandlers . CreatedBy } />
4396
44- < Tabs variant = "line" isLazy = { true } display = "flex" flexDir = "column" w = "full" h = "full" >
45- < TabList >
46- < Tab > { t ( 'metadata.recallParameters' ) } </ Tab >
47- < Tab > { t ( 'metadata.metadata' ) } </ Tab >
48- < Tab > { t ( 'metadata.imageDetails' ) } </ Tab >
49- < Tab > { t ( 'metadata.workflow' ) } </ Tab >
50- < Tab > { t ( 'nodes.graph' ) } </ Tab >
51- </ TabList >
97+ < Tabs
98+ variant = "line"
99+ isLazy = { true }
100+ display = "flex"
101+ flexDir = "column"
102+ w = "full"
103+ h = "full"
104+ index = { activeTabIndex }
105+ onChange = { handleTabChange }
106+ >
107+ < Flex alignItems = "flex-start" gap = { 2 } borderBottomWidth = "1px" borderColor = "base.600" >
108+ < TabList flex = "1" pb = { 2 } borderBottom = "none" >
109+ < Tab > { t ( 'metadata.recallParameters' ) } </ Tab >
110+ < Tab > { t ( 'metadata.metadata' ) } </ Tab >
111+ < Tab > { t ( 'metadata.imageDetails' ) } </ Tab >
112+ < Tab > { t ( 'metadata.workflow' ) } </ Tab >
113+ < Tab > { t ( 'nodes.graph' ) } </ Tab >
114+ </ TabList >
115+ { isSearchableTab && (
116+ < InputGroup size = "sm" w = { 48 } me = { 6 } >
117+ < Input placeholder = { t ( 'common.search' ) } value = { activeSearchTerm } onChange = { handleChangeSearch } />
118+ { activeSearchTerm && (
119+ < InputRightElement h = "full" pe = { 2 } >
120+ < IconButton
121+ aria-label = { t ( 'boards.clearSearch' ) }
122+ icon = { < PiXBold size = { 16 } /> }
123+ variant = "link"
124+ opacity = { 0.7 }
125+ onClick = { handleClearSearch }
126+ size = "sm"
127+ />
128+ </ InputRightElement >
129+ ) }
130+ </ InputGroup >
131+ ) }
132+ </ Flex >
52133
53134 < TabPanels >
54135 < TabPanel >
@@ -62,31 +143,53 @@ const ImageMetadataViewer = ({ image }: ImageMetadataViewerProps) => {
62143 </ TabPanel >
63144 < TabPanel >
64145 { metadata ? (
65- < DataViewer
66- fileName = { `${ image . image_name . replace ( '.png' , '' ) } _metadata` }
67- data = { metadata }
68- label = { t ( 'metadata.metadata' ) }
69- />
146+ < Flex w = "full" h = "full" paddingInline = { CODE_TAB_PADDING_INLINE } >
147+ < DataViewer
148+ fileName = { `${ image . image_name . replace ( '.png' , '' ) } _metadata` }
149+ data = { metadata }
150+ label = { t ( 'metadata.metadata' ) }
151+ withSearch
152+ searchTerm = { searchTerms [ TAB_INDEX . metadata ] }
153+ showSearchInput = { false }
154+ />
155+ </ Flex >
70156 ) : (
71157 < IAINoContentFallback label = { t ( 'metadata.noMetaData' ) } />
72158 ) }
73159 </ TabPanel >
74160 < TabPanel >
75161 { image ? (
76- < DataViewer
77- fileName = { `${ image . image_name . replace ( '.png' , '' ) } _details` }
78- data = { image }
79- label = { t ( 'metadata.imageDetails' ) }
80- />
162+ < Flex w = "full" h = "full" paddingInline = { CODE_TAB_PADDING_INLINE } >
163+ < DataViewer
164+ fileName = { `${ image . image_name . replace ( '.png' , '' ) } _details` }
165+ data = { image }
166+ label = { t ( 'metadata.imageDetails' ) }
167+ withSearch
168+ searchTerm = { searchTerms [ TAB_INDEX . imageDetails ] }
169+ showSearchInput = { false }
170+ />
171+ </ Flex >
81172 ) : (
82173 < IAINoContentFallback label = { t ( 'metadata.noImageDetails' ) } />
83174 ) }
84175 </ TabPanel >
85176 < TabPanel >
86- < ImageMetadataWorkflowTabContent image = { image } />
177+ < Flex w = "full" h = "full" paddingInline = { CODE_TAB_PADDING_INLINE } >
178+ < ImageMetadataWorkflowTabContent
179+ image = { image }
180+ searchTerm = { searchTerms [ TAB_INDEX . workflow ] }
181+ showSearchInput = { false }
182+ />
183+ </ Flex >
87184 </ TabPanel >
88185 < TabPanel >
89- < ImageMetadataGraphTabContent image = { image } />
186+ < Flex w = "full" h = "full" paddingInline = { CODE_TAB_PADDING_INLINE } >
187+ < ImageMetadataGraphTabContent
188+ image = { image }
189+ searchTerm = { searchTerms [ TAB_INDEX . graph ] }
190+ showSearchInput = { false }
191+ />
192+ </ Flex >
90193 </ TabPanel >
91194 </ TabPanels >
92195 </ Tabs >
0 commit comments