Skip to content

Commit e7596c1

Browse files
committed
new sample prep
1 parent 92d902e commit e7596c1

File tree

5 files changed

+106
-59
lines changed

5 files changed

+106
-59
lines changed

.github/workflows/deploy.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,13 @@ jobs:
4444
path: defang
4545
ref: ${{ github.event_name == 'repository_dispatch' && github.event.client_payload.version || 'main' }}
4646

47+
- name: Checkout DefangLabs/samples
48+
uses: actions/checkout@v3
49+
with:
50+
repository: DefangLabs/samples
51+
path: samples
52+
ref: main
53+
4754
- name: Set up Go
4855
uses: actions/setup-go@v2
4956
with:

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,6 @@ npm-debug.log*
1919
yarn-debug.log*
2020
yarn-error.log*
2121

22-
defang
22+
defang
23+
samples
24+
/static/samples.json

docs/samples.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,4 @@ Check out our sample projects here to get some inspiration and get a sense of ho
1212

1313
import Samples from "../src/components/Samples";
1414

15+
<Samples />

scripts/prep-samples.js

Lines changed: 37 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,57 @@
11
const fs = require('fs');
22
const path = require('path');
33

4-
const samplesDir = path.join(__dirname, '..', 'defang', 'samples');
4+
const samplesDir = path.join(__dirname, '..', 'samples', 'samples');
55

66
// categories are directories in the current directory (i.e. we're running in samples/ and we might have a samples/ruby/ directory)
77
const directories = fs.readdirSync(samplesDir).filter(file => fs.statSync(path.join(samplesDir, file)).isDirectory());
88

99
let jsonArray = [];
1010

11-
directories.forEach((category) => {
12-
// in each category, we have a series of subdirectories that are the actual samples (which contain full programs, including a README.md)
13-
// we're going to loop through those directories and create a JSON object for each one
14-
const samples = fs.readdirSync(path.join(samplesDir, category))
15-
.filter(file => fs.statSync(path.join(samplesDir, category, file)).isDirectory());
16-
samples.forEach((sample) => {
17-
const name = sample;
18-
19-
let readme;
20-
try {
21-
readme = fs.readFileSync(path.join(samplesDir, category, sample, 'README.md'), 'utf8');
22-
} catch (error) {
23-
readme = `# ${sample}`;
24-
}
25-
26-
jsonArray.push({
27-
name,
28-
category,
29-
readme,
30-
});
31-
console.log(`@@ Added ${category}/${sample}`);
11+
directories.forEach((sample) => {
12+
const directoryName = sample;
13+
let readme;
14+
try {
15+
readme = fs.readFileSync(path.join(samplesDir, sample, 'README.md'), 'utf8');
16+
} catch (error) {
17+
readme = `# ${sample}`;
18+
}
19+
20+
// The readme should contain lines that start with the following:
21+
// Title:
22+
// Short Description:
23+
// Tags:
24+
// Languages:
25+
//
26+
// We want to extract the title, short description, tags, and languages from the readme. Tags and languages are comma separated lists.
27+
const title = readme.match(/Title: (.*)/)[1];
28+
const shortDescription = readme.match(/Short Description: (.*)/)[1];
29+
const tags = readme.match(/Tags: (.*)/)[1].split(',').map(tag => tag.trim());
30+
const languages = readme.match(/Languages: (.*)/)[1].split(',').map(language => language.trim());
31+
32+
jsonArray.push({
33+
name: directoryName,
34+
category: languages?.[0],
35+
readme,
36+
directoryName,
37+
title,
38+
shortDescription,
39+
tags,
40+
languages,
3241
});
42+
console.log(`@@ Added ${sample}`);
3343
});
3444

3545
const stringified = JSON.stringify(jsonArray, null, 2);
3646

37-
fs.writeFileSync(path.join(__dirname, '..', 'samples.json'), stringified);
47+
// fs.writeFileSync(path.join(__dirname, '..', 'samples.json'), stringified);
3848

3949
// we're going to open up the ../docs/samples.md file and replce [] with the stringified JSON
4050

41-
const samplesMd = path.join(__dirname, '..', 'docs', 'samples.md');
42-
let samplesMdContents = fs.readFileSync(samplesMd, 'utf8');
43-
samplesMdContents += `<Samples samples={${stringified}} />`;
44-
fs.writeFileSync(samplesMd, samplesMdContents);
51+
// const samplesMd = path.join(__dirname, '..', 'docs', 'samples.md');
52+
// let samplesMdContents = fs.readFileSync(samplesMd, 'utf8');
53+
// samplesMdContents += `<Samples samples={${stringified}} />`;
54+
// fs.writeFileSync(samplesMd, samplesMdContents);
4555

4656
// save the json to the samples.json file in static
4757
fs.writeFileSync(path.join(__dirname, '..', 'static', 'samples.json'), stringified);

src/components/Samples/index.tsx

Lines changed: 58 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Box, Button, Chip, Dialog, DialogActions, DialogContent, DialogTitle, Grid, List, ListItemButton, ListItemText, Stack, TextField } from '@mui/material';
22
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';
44
import ReactMarkdown from 'react-markdown';
55
import { capitalCase } from 'change-case';
66
import CodeBlock from '@theme/CodeBlock';
@@ -11,6 +11,10 @@ interface Sample {
1111
name: string;
1212
category: string;
1313
readme: string;
14+
title?: string;
15+
shortDescription?: string;
16+
languages?: string[];
17+
tags?: string[];
1418
}
1519

1620
interface SamplesProps {
@@ -20,7 +24,9 @@ interface SamplesProps {
2024
const categoryColors = {
2125
python: '#FFFFE0',
2226
nodejs: '#90EE90',
23-
golang: '#ADD8E6',
27+
golang: '#b8e4f3',
28+
go: '#b8e4f3',
29+
sql: '#ebaef4',
2430
ruby: '#FF7F7F',
2531
other: 'lightgray',
2632
};
@@ -77,25 +83,36 @@ function getHighlightedTextWithContext(text: string, matches: FuseResult<Sample>
7783

7884

7985

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[]>([]);
8688
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);
8891

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'],
9194
includeMatches: true,
9295
isCaseSensitive: false,
93-
threshold: 0.25,
96+
threshold: 0.3,
9497
})).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+
95112
const deferredFilter = useDeferredValue(filter);
96-
const results = useMemo((): FuseResult<TitledSample>[] => {
113+
const results = useMemo((): FuseResult<Sample>[] => {
97114
if (!deferredFilter) {
98-
return titled.map((item, i) => ({
115+
return samples.map((item, i) => ({
99116
item,
100117
score: 0,
101118
refIndex: i,
@@ -105,13 +122,15 @@ export default function Samples({ samples }: SamplesProps) {
105122
return fuse.search({
106123
$and: deferredFilter.split(/\s+/).map((word) => ({
107124
$or: [
108-
{ displayName: word },
125+
{ title: word },
109126
{ category: word },
110-
{ readme: word },
127+
{ shortDescription: word },
128+
{ tags: word },
129+
{ languages: word },
111130
],
112131
})),
113132
});
114-
}, [deferredFilter]);
133+
}, [deferredFilter, samples]);
115134

116135
return (
117136
<>
@@ -125,7 +144,7 @@ export default function Samples({ samples }: SamplesProps) {
125144
<DialogTitle component="div" display="flex">
126145
<Box>
127146
<Box fontWeight="bold" component="span">
128-
{selectedSample.displayName}
147+
{selectedSample.title}
129148
</Box>
130149
<Chip
131150
label={selectedSample.category}
@@ -159,7 +178,7 @@ export default function Samples({ samples }: SamplesProps) {
159178
Clone and open the sample in your terminal
160179
</small>
161180
<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}"`}
163182
</CodeBlock>
164183
</Box>
165184
{/* </Stack> */}
@@ -175,6 +194,11 @@ export default function Samples({ samples }: SamplesProps) {
175194
onChange={(e) => setFilter(e.target.value)}
176195
variant='filled'
177196
/>
197+
{loading && (
198+
<p>
199+
Loading samples...
200+
</p>
201+
)}
178202
</Box>
179203
<List
180204
sx={{
@@ -189,23 +213,26 @@ export default function Samples({ samples }: SamplesProps) {
189213
matches
190214
} = result;
191215

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');
194218

195219
let category: ReactNode = sample.category;
196220
const categoryMatched = matches.find((match) => match.key === 'category');
197221

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');
200227

201-
if (displayNameMatched) {
202-
displayName = highlightMatches(sample.name, [displayNameMatched]);
228+
if (titleMatched) {
229+
title = highlightMatches(sample.title, [titleMatched]);
203230
}
204231
if (categoryMatched) {
205232
category = highlightMatches(sample.category, [categoryMatched]);
206233
}
207-
if (readmeMatched) {
208-
readme = getHighlightedTextWithContext(sample.readme, [readmeMatched]);
234+
if (shortDescriptionMatched) {
235+
shortDescription = getHighlightedTextWithContext(sample.shortDescription, [shortDescriptionMatched]);
209236
}
210237

211238
return (
@@ -218,14 +245,14 @@ export default function Samples({ samples }: SamplesProps) {
218245
onClick={() => setSelectedSample(sample)}
219246
>
220247
<ListItemText
221-
primary={displayName}
248+
primary={title}
222249
secondary={(
223250
<>
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 && (
226253
<>
227254
<br />
228-
{readme}
255+
{shortDescription}
229256
</>
230257
)}
231258
</>

0 commit comments

Comments
 (0)