1
1
import { Box , Button , Chip , Dialog , DialogActions , DialogContent , DialogTitle , Grid , List , ListItemButton , ListItemText , Stack , TextField } from '@mui/material' ;
2
2
import Fuse , { FuseResult } from 'fuse.js' ;
3
- import { Fragment , ReactNode , useDeferredValue , useMemo , useRef , useState } from 'react' ;
3
+ import { Fragment , ReactNode , useDeferredValue , useEffect , useMemo , useRef , useState } from 'react' ;
4
4
import ReactMarkdown from 'react-markdown' ;
5
5
import { capitalCase } from 'change-case' ;
6
6
import CodeBlock from '@theme/CodeBlock' ;
@@ -11,6 +11,10 @@ interface Sample {
11
11
name : string ;
12
12
category : string ;
13
13
readme : string ;
14
+ title ?: string ;
15
+ shortDescription ?: string ;
16
+ languages ?: string [ ] ;
17
+ tags ?: string [ ] ;
14
18
}
15
19
16
20
interface SamplesProps {
@@ -20,7 +24,9 @@ interface SamplesProps {
20
24
const categoryColors = {
21
25
python : '#FFFFE0' ,
22
26
nodejs : '#90EE90' ,
23
- golang : '#ADD8E6' ,
27
+ golang : '#b8e4f3' ,
28
+ go : '#b8e4f3' ,
29
+ sql : '#ebaef4' ,
24
30
ruby : '#FF7F7F' ,
25
31
other : 'lightgray' ,
26
32
} ;
@@ -77,25 +83,36 @@ function getHighlightedTextWithContext(text: string, matches: FuseResult<Sample>
77
83
78
84
79
85
80
- export default function Samples ( { samples } : SamplesProps ) {
81
- const titled = samples . map ( ( sample ) => ( {
82
- ...sample ,
83
- displayName : capitalCase ( sample . name ) ,
84
- } ) ) ;
85
- type TitledSample = typeof titled [ 0 ] ;
86
+ export default function Samples ( ) {
87
+ const [ samples , setSamples ] = useState < Sample [ ] > ( [ ] ) ;
86
88
const [ filter , setFilter ] = useState ( "" ) ;
87
- const [ selectedSample , setSelectedSample ] = useState < TitledSample | null > ( null ) ;
89
+ const [ selectedSample , setSelectedSample ] = useState < Sample | null > ( null ) ;
90
+ const [ loading , setLoading ] = useState ( true ) ;
88
91
89
- const fuse = useRef ( new Fuse ( titled , {
90
- keys : [ 'displayName ' , 'category' , 'readme ' ] ,
92
+ const fuse = useRef ( new Fuse ( samples , {
93
+ keys : [ 'title ' , 'category' , 'shortDescription' , 'tags' , 'languages '] ,
91
94
includeMatches : true ,
92
95
isCaseSensitive : false ,
93
- threshold : 0.25 ,
96
+ threshold : 0.3 ,
94
97
} ) ) . current ;
98
+
99
+ useEffect ( ( ) => {
100
+ const fetchSamples = async ( ) => {
101
+ const response = await fetch ( '/samples.json' ) ;
102
+ const samples = await response . json ( ) ;
103
+
104
+ fuse . setCollection ( samples ) ;
105
+
106
+ setSamples ( samples ) ;
107
+ setLoading ( false ) ;
108
+ } ;
109
+ fetchSamples ( ) ;
110
+ } , [ ] ) ;
111
+
95
112
const deferredFilter = useDeferredValue ( filter ) ;
96
- const results = useMemo ( ( ) : FuseResult < TitledSample > [ ] => {
113
+ const results = useMemo ( ( ) : FuseResult < Sample > [ ] => {
97
114
if ( ! deferredFilter ) {
98
- return titled . map ( ( item , i ) => ( {
115
+ return samples . map ( ( item , i ) => ( {
99
116
item,
100
117
score : 0 ,
101
118
refIndex : i ,
@@ -105,13 +122,15 @@ export default function Samples({ samples }: SamplesProps) {
105
122
return fuse . search ( {
106
123
$and : deferredFilter . split ( / \s + / ) . map ( ( word ) => ( {
107
124
$or : [
108
- { displayName : word } ,
125
+ { title : word } ,
109
126
{ category : word } ,
110
- { readme : word } ,
127
+ { shortDescription : word } ,
128
+ { tags : word } ,
129
+ { languages : word } ,
111
130
] ,
112
131
} ) ) ,
113
132
} ) ;
114
- } , [ deferredFilter ] ) ;
133
+ } , [ deferredFilter , samples ] ) ;
115
134
116
135
return (
117
136
< >
@@ -125,7 +144,7 @@ export default function Samples({ samples }: SamplesProps) {
125
144
< DialogTitle component = "div" display = "flex" >
126
145
< Box >
127
146
< Box fontWeight = "bold" component = "span" >
128
- { selectedSample . displayName }
147
+ { selectedSample . title }
129
148
</ Box >
130
149
< Chip
131
150
label = { selectedSample . category }
@@ -159,7 +178,7 @@ export default function Samples({ samples }: SamplesProps) {
159
178
Clone and open the sample in your terminal
160
179
</ small >
161
180
< CodeBlock language = "bash" >
162
- { `git clone https://github.com/DefangLabs/defang dtmp && cp -r defang/samples/ ${ selectedSample . category } /${ selectedSample . name } "./${ selectedSample . name } " && rm -r ./dtmp && cd "${ selectedSample . name } "` }
181
+ { `git clone https://github.com/DefangLabs/samples dtmp && cp -r defang/samples/${ selectedSample . name } "./${ selectedSample . name } " && rm -r ./dtmp && cd "${ selectedSample . name } "` }
163
182
</ CodeBlock >
164
183
</ Box >
165
184
{ /* </Stack> */ }
@@ -175,6 +194,11 @@ export default function Samples({ samples }: SamplesProps) {
175
194
onChange = { ( e ) => setFilter ( e . target . value ) }
176
195
variant = 'filled'
177
196
/>
197
+ { loading && (
198
+ < p >
199
+ Loading samples...
200
+ </ p >
201
+ ) }
178
202
</ Box >
179
203
< List
180
204
sx = { {
@@ -189,23 +213,26 @@ export default function Samples({ samples }: SamplesProps) {
189
213
matches
190
214
} = result ;
191
215
192
- let displayName : ReactNode = sample . displayName ;
193
- const displayNameMatched = matches . find ( ( match ) => match . key === 'displayName ' ) ;
216
+ let title : ReactNode = sample . title ;
217
+ const titleMatched = matches . find ( ( match ) => match . key === 'title ' ) ;
194
218
195
219
let category : ReactNode = sample . category ;
196
220
const categoryMatched = matches . find ( ( match ) => match . key === 'category' ) ;
197
221
198
- let readme : ReactNode = "" ;
199
- const readmeMatched = matches . find ( ( match ) => match . key === 'readme' ) ;
222
+ let shortDescription : ReactNode = sample . shortDescription . slice ( 0 , 80 ) ;
223
+ if ( sample . shortDescription . length > 80 ) {
224
+ shortDescription += '...' ;
225
+ }
226
+ const shortDescriptionMatched = matches . find ( ( match ) => match . key === 'shortDescription' ) ;
200
227
201
- if ( displayNameMatched ) {
202
- displayName = highlightMatches ( sample . name , [ displayNameMatched ] ) ;
228
+ if ( titleMatched ) {
229
+ title = highlightMatches ( sample . title , [ titleMatched ] ) ;
203
230
}
204
231
if ( categoryMatched ) {
205
232
category = highlightMatches ( sample . category , [ categoryMatched ] ) ;
206
233
}
207
- if ( readmeMatched ) {
208
- readme = getHighlightedTextWithContext ( sample . readme , [ readmeMatched ] ) ;
234
+ if ( shortDescriptionMatched ) {
235
+ shortDescription = getHighlightedTextWithContext ( sample . shortDescription , [ shortDescriptionMatched ] ) ;
209
236
}
210
237
211
238
return (
@@ -218,14 +245,14 @@ export default function Samples({ samples }: SamplesProps) {
218
245
onClick = { ( ) => setSelectedSample ( sample ) }
219
246
>
220
247
< ListItemText
221
- primary = { displayName }
248
+ primary = { title }
222
249
secondary = { (
223
250
< >
224
- < Chip component = { "span" } label = { category } size = 'small' sx = { { backgroundColor : categoryColors [ sample . category ] || categoryColors [ 'other' ] } } />
225
- { readmeMatched && (
251
+ { category && < Chip component = { "span" } label = { category } size = 'small' sx = { { backgroundColor : categoryColors [ sample . category ] || categoryColors [ 'other' ] } } /> }
252
+ { true && (
226
253
< >
227
254
< br />
228
- { readme }
255
+ { shortDescription }
229
256
</ >
230
257
) }
231
258
</ >
0 commit comments