Skip to content

Commit 9346c23

Browse files
authored
Merge pull request #638 from CodingTrain/showcase-page
Rough draft of showcase page
2 parents 47e7362 + b199c41 commit 9346c23

File tree

36 files changed

+1224
-186
lines changed

36 files changed

+1224
-186
lines changed

content-testing/content-structure.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,13 @@ const contentStructure = {
298298
isFolderSensitive: true,
299299
isRequired: true
300300
},
301+
showcase: {
302+
folders: {},
303+
files: {},
304+
isFileSensitive: false,
305+
isFolderSensitive: true,
306+
isRequired: true
307+
},
301308
404: {
302309
folders: {},
303310
files: {},

content-testing/file-formats.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ const video = {
88
videoNumber: { type: 'string' },
99
videoId: { isRequired: true, type: 'string' },
1010
nebulaSlug: { isRequired: false, type: 'string' },
11+
canonicalTrack: { type: 'string' },
1112
date: {
1213
isRequired: true,
1314
type: 'string'
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
const glob = require('glob');
2+
const fs = require('fs');
3+
4+
describe('challenge videos should never have a canonicalTrack property', () => {
5+
const challengeVideos = getChallengeVideos();
6+
7+
for (const [path, content] of challengeVideos) {
8+
test(`${path}`, () => {
9+
expect(content).not.toHaveProperty('canonicalTrack');
10+
});
11+
}
12+
});
13+
14+
describe('track videos `canonicalTrack`', () => {
15+
const tracks = getTracks();
16+
const trackVideos = getTrackVideos();
17+
18+
describe('track referenced does not exist', () => {
19+
for (const [path, _, content] of trackVideos) {
20+
if (content.canonicalTrack) {
21+
test(`${path}`, () => {
22+
expect(tracks.has(content.canonicalTrack)).toEqual(true);
23+
});
24+
}
25+
}
26+
});
27+
28+
describe('video is not used in the referenced track', () => {
29+
for (const [videoPath, videoSlug, content] of trackVideos) {
30+
if (content.canonicalTrack && tracks.has(content.canonicalTrack)) {
31+
const [trackPath, _, trackVideos] = tracks.get(content.canonicalTrack);
32+
33+
test(`track: ${trackPath} | video: ${videoPath}`, () => {
34+
expect(trackVideos).toContain(videoSlug);
35+
});
36+
}
37+
}
38+
});
39+
40+
describe('canonicalTrack requirement', () => {
41+
for (const [videoPath, videoSlug, content] of trackVideos) {
42+
const trackRefs = getTracksReferingToVideo(tracks, videoSlug);
43+
44+
if (trackRefs.length > 1) {
45+
test(`video is referenced by ${trackRefs.length} tracks but doesn't have a canonicalTrack property ${videoPath} | Tracks: ${trackRefs}`, () => {
46+
expect(content).toHaveProperty('canonicalTrack');
47+
});
48+
} else {
49+
test(`video is referenced by at most one track and should not have a canonicalTrack property ${videoPath}`, () => {
50+
expect(content).not.toHaveProperty('canonicalTrack');
51+
});
52+
}
53+
}
54+
});
55+
});
56+
57+
// ---
58+
59+
function getChallengeVideos() {
60+
return glob.sync('content/videos/challenges/**/index.json').map((file) => {
61+
const content = JSON.parse(fs.readFileSync(file));
62+
return [file, content];
63+
});
64+
}
65+
66+
function getTrackVideos() {
67+
return glob
68+
.sync('content/videos/**/index.json', {
69+
ignore: 'content/videos/challenges/**'
70+
})
71+
.map((file) => {
72+
const content = JSON.parse(fs.readFileSync(file));
73+
const slug = file.split('content/videos/')[1].split('/index.json')[0];
74+
return [file, slug, content];
75+
});
76+
}
77+
78+
function getTracks() {
79+
const refs = glob.sync('content/tracks/**/index.json').map((file) => {
80+
const content = JSON.parse(fs.readFileSync(file));
81+
82+
// pluck out video slugs from main and side tracks for convenience
83+
const videos = [];
84+
content.chapters?.forEach((chapter) => videos.push(...chapter.videos));
85+
content.videos?.forEach((video) => videos.push(video));
86+
87+
return [file, content, videos];
88+
});
89+
90+
const m = new Map();
91+
for (const ref of refs) {
92+
const parts = ref[0].split('/');
93+
const slug = parts[parts.length - 2];
94+
m.set(slug, ref);
95+
}
96+
return m;
97+
}
98+
99+
function getTracksReferingToVideo(tracks, videoSlug) {
100+
const res = [];
101+
tracks.forEach(([path, content, videoSlugs], trackSlug) => {
102+
if (videoSlugs.includes(videoSlug)) {
103+
res.push(path);
104+
}
105+
});
106+
return res;
107+
}

content/pages/homepage/index.json

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,9 +60,10 @@
6060
"challenges/112-3d-rendering-with-rotation-and-projection/showcase/contribution5.json",
6161
"challenges/149-tic-tac-toe/showcase/contribution1.json"
6262
],
63-
"cta": {
64-
"text": "Ready to be inspired?",
65-
"buttonText": "Enjoy this showcase highlight"
63+
"showcaseCta": {
64+
"text": "Want to see more?",
65+
"buttonText": "Browse the full showcase",
66+
"href": "/showcase"
6667
}
6768
},
6869
"events": {

content/pages/showcase/index.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"title": "Showcase",
3+
"description": "The Coding Train Passenger Showcase is collection of amazing projects created by viewers. Add yours!"
4+
}

content/videos/code/6-objects/2-classes/index.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"description": "This video introduces Object-Oriented Programming in JavaScript with ES6 classes and the p5.js library.",
44
"videoNumber": "6.2",
55
"videoId": "T-HGdc8L-7w",
6+
"canonicalTrack": "code-programming-with-p5-js",
67
"date": "2017-10-06",
78
"languages": ["p5.js", "JavaScript"],
89
"topics": ["basics", "OOP", "objects"],

content/videos/code/6-objects/3-constructor/index.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"description": "This video covers constructor arguments for ES6 classes and the p5.js library. This is one technique for creating multiple objects from the same class with variation.",
44
"videoNumber": "6.3",
55
"videoId": "rHiSsgFRgx4",
6+
"canonicalTrack": "code-programming-with-p5-js",
67
"date": "2017-10-09",
78
"languages": ["p5.js", "JavaScript"],
89
"topics": ["basics", "constructor", "classes", "objects"],

content/videos/more-p5/github-pages-hosting/index.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"description": "How to host your p5.js sketch with GitHub pages, updated for 2020!",
44
"videoNumber": "Video number",
55
"videoId": "ZneWjyn18e8",
6+
"canonicalTrack": "p5-tips-and-tricks",
67
"date": "2020-10-02",
78
"languages": ["JavaScript"],
89
"topics": ["GitHub"],

content/videos/noc/3-angles/4-polar-coordinates/index.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
"languages": ["JavaScript", "p5.js"],
55
"topics": ["For beginners"],
66
"videoId": "O5wjXoFrau4",
7+
"canonicalTrack": "the-nature-of-code-2",
78
"canContribute": true,
89
"timestamps": [],
910
"codeExamples": [

content/videos/workflow/5-node/index.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
"title": "Node",
33
"description": "In this video, I discuss how I use node.js and global packages (to generate p5 sketches, run a web server) as part of my workflow.",
44
"videoId": "FjWbUK2HdCo",
5+
"canonicalTrack": "2018-workflow",
56
"date": "2018-09-24",
67
"languages": ["node.js", "JavaScript"],
78
"topics": ["npm", "workflow"],

0 commit comments

Comments
 (0)