-
Notifications
You must be signed in to change notification settings - Fork 2.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(nx-dev): add video course visualizer
- Loading branch information
Showing
37 changed files
with
1,101 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
--- | ||
title: 'From PNPM Workspaces to Distributed CI' | ||
description: '' | ||
slug: nx-19-8-update | ||
authors: [Juri Strumpflohner] | ||
cover_image: /blog/images/2024-09-20/thumbnail.png | ||
--- | ||
|
||
**Key takeaways:** | ||
|
||
- add Nx to an existing PNPM workspace | ||
- fine tune your repo with local caching, defining task piplines and establishing dependencies among projects | ||
- connect and configure your workspace with Nx Cloud | ||
- learn how to debug cache misses and control remote cache access with Nx Cloud | ||
- automatically split your Playwright e2e tests to optimize CI time from 20 minutes to 9 minutes |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
--- | ||
title: 'Overview' | ||
videoUrl: 'https://www.youtube.com/watch?v=dQw4w9WgXcQ' | ||
--- | ||
|
||
# Overview | ||
|
||
Here's some text related to the overview |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
--- | ||
title: 'Initialize the workspace' | ||
videoUrl: 'https://www.youtube.com/watch?v=dQw4w9WgXcQ' | ||
duration: '04:20' | ||
--- | ||
|
||
In this lesson we're going to look into how to initialize a new Nx workspace. | ||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. | ||
|
||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. | ||
|
||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. | ||
|
||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. | ||
|
||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. | ||
|
||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. | ||
|
||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. | ||
|
||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. | ||
|
||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. | ||
|
||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. | ||
|
||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
--- | ||
title: 'Configure caching' | ||
videoUrl: 'https://www.youtube.com/watch?v=dQw4w9WgXcQ' | ||
--- | ||
|
||
# Configure caching | ||
|
||
... |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
{ | ||
"name": "data-access-courses", | ||
"$schema": "../../node_modules/nx/schemas/project-schema.json", | ||
"sourceRoot": "nx-dev/data-access-courses/src", | ||
"projectType": "library", | ||
"targets": { | ||
"lint": { | ||
"executor": "@nx/linter:eslint", | ||
"outputs": ["{options.outputFile}"], | ||
"options": { | ||
"lintFilePatterns": ["nx-dev/data-access-courses/**/*.ts"] | ||
} | ||
} | ||
}, | ||
"tags": [] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from './lib/courses.api'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
import { readFile, readdir } from 'fs/promises'; | ||
import { join } from 'path'; | ||
import { extractFrontmatter } from '@nx/nx-dev/ui-markdoc'; | ||
import { readFileSync } from 'fs'; | ||
import type { BlogAuthor } from '@nx/nx-dev/data-access-documents/node-only'; | ||
|
||
export interface Course { | ||
id: string; | ||
title: string; | ||
description: string; | ||
content: string; | ||
authors: BlogAuthor[]; | ||
repository?: string; | ||
lessons: Lesson[]; | ||
filePath: string; | ||
totalDuration: string; | ||
} | ||
|
||
export interface Lesson { | ||
id: string; | ||
title: string; | ||
description: string; | ||
videoUrl: string; | ||
duration: string; | ||
filePath: string; | ||
} | ||
|
||
function calculateTotalDuration(lessons: Lesson[]): string { | ||
const totalMinutes = lessons.reduce((total, lesson) => { | ||
if (!lesson.duration) return total; | ||
const [minutes, seconds] = lesson.duration.split(':').map(Number); | ||
return total + minutes + seconds / 60; | ||
}, 0); | ||
|
||
const hours = Math.floor(totalMinutes / 60); | ||
const minutes = Math.round(totalMinutes % 60); | ||
|
||
if (hours > 0) { | ||
return `${hours}h ${minutes}m`; | ||
} | ||
return `${minutes}m`; | ||
} | ||
|
||
export class CoursesApi { | ||
constructor( | ||
private readonly options: { | ||
coursesRoot: string; | ||
} | ||
) { | ||
if (!options.coursesRoot) { | ||
throw new Error('courses root cannot be undefined'); | ||
} | ||
} | ||
|
||
async getAllCourses(): Promise<Course[]> { | ||
const courseFolders = await readdir(this.options.coursesRoot); | ||
const courses = await Promise.all( | ||
courseFolders.map((folder) => this.getCourse(folder)) | ||
); | ||
return courses; | ||
} | ||
|
||
// TODO: move to shared lib | ||
private readonly blogRoot = 'public/documentation/blog'; | ||
|
||
async getCourse(folderName: string): Promise<Course> { | ||
const authors = JSON.parse( | ||
readFileSync(join(this.blogRoot, 'authors.json'), 'utf8') | ||
); | ||
const coursePath = join(this.options.coursesRoot, folderName); | ||
const courseFilePath = join(coursePath, 'course.md'); | ||
|
||
const content = await readFile(courseFilePath, 'utf-8'); | ||
const frontmatter = extractFrontmatter(content); | ||
|
||
const lessonFolders = await readdir(coursePath); | ||
const lessons = await Promise.all( | ||
lessonFolders | ||
.filter((folder) => folder !== 'course.md') | ||
.map((folder) => this.getLessons(folderName, folder)) | ||
); | ||
const flattenedLessons = lessons.flat(); | ||
|
||
return { | ||
id: folderName, | ||
title: frontmatter.title, | ||
description: frontmatter.description, | ||
content, | ||
authors: authors.filter((author: { name: string }) => | ||
frontmatter.authors.includes(author.name) | ||
), | ||
repository: frontmatter.repository, | ||
lessons: flattenedLessons, | ||
filePath: courseFilePath, | ||
totalDuration: calculateTotalDuration(flattenedLessons), | ||
}; | ||
} | ||
|
||
private async getLessons( | ||
courseId: string, | ||
lessonFolder: string | ||
): Promise<Lesson[]> { | ||
const lessonPath = join(this.options.coursesRoot, courseId, lessonFolder); | ||
const lessonFiles = await readdir(lessonPath); | ||
|
||
const lessons = await Promise.all( | ||
lessonFiles.map(async (file) => { | ||
if (!file.endsWith('.md')) return null; | ||
const filePath = join(lessonPath, file); | ||
const content = await readFile(filePath, 'utf-8'); | ||
const frontmatter = extractFrontmatter(content); | ||
if (!frontmatter || !frontmatter.title) { | ||
throw new Error(`Lesson ${lessonFolder}/${file} has no title`); | ||
} | ||
return { | ||
id: `${lessonFolder}-${file.replace('.md', '')}`, | ||
title: frontmatter.title, | ||
description: content, | ||
videoUrl: frontmatter.videoUrl || null, | ||
duration: frontmatter.duration || null, | ||
filePath, | ||
}; | ||
}) | ||
); | ||
|
||
return lessons.filter((lesson): lesson is Lesson => lesson !== null); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
{ | ||
"compilerOptions": { | ||
"jsx": "react-jsx", | ||
"allowJs": false, | ||
"esModuleInterop": false, | ||
"allowSyntheticDefaultImports": true, | ||
"strict": true | ||
}, | ||
"files": [], | ||
"include": [], | ||
"references": [ | ||
{ | ||
"path": "./tsconfig.lib.json" | ||
} | ||
], | ||
"extends": "../../tsconfig.base.json" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
{ | ||
"extends": "./tsconfig.json", | ||
"compilerOptions": { | ||
"outDir": "../../dist/out-tsc", | ||
"types": ["node"] | ||
}, | ||
"exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], | ||
"include": ["src/**/*.ts"] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
import { coursesApi } from 'nx-dev/nx-dev/lib/courses.api'; | ||
import { DefaultLayout, YouTube } from '@nx/nx-dev/ui-common'; | ||
import { LessonPlayer } from '@nx/nx-dev/ui-courses'; | ||
import { Metadata } from 'next'; | ||
|
||
interface LessonPageProps { | ||
params: { courseId: string; lessonId: string }; | ||
} | ||
|
||
export async function generateMetadata({ | ||
params, | ||
}: LessonPageProps): Promise<Metadata> { | ||
const course = await coursesApi.getCourse(params.courseId); | ||
const lesson = course.lessons.find((l) => l.id === params.lessonId); | ||
|
||
if (!lesson) { | ||
return { | ||
title: 'Lesson Not Found', | ||
}; | ||
} | ||
|
||
return { | ||
title: `${lesson.title} | ${course.title} | Nx Courses`, | ||
description: lesson.description.substring(0, 160), | ||
}; | ||
} | ||
|
||
export async function generateStaticParams() { | ||
const courses = await coursesApi.getAllCourses(); | ||
return courses.flatMap((course) => | ||
course.lessons.map((lesson) => ({ | ||
courseId: course.id, | ||
lessonId: lesson.id, | ||
})) | ||
); | ||
} | ||
|
||
export default async function LessonPage({ params }: LessonPageProps) { | ||
const course = await coursesApi.getCourse(params.courseId); | ||
const lesson = course.lessons.find((l) => l.id === params.lessonId); | ||
|
||
if (!lesson) { | ||
return <div>Lesson not found</div>; | ||
} | ||
|
||
return ( | ||
<DefaultLayout hideHeader hideFooter> | ||
<LessonPlayer course={course} lesson={lesson} /> | ||
</DefaultLayout> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
import type { Metadata, ResolvingMetadata } from 'next'; | ||
import { coursesApi } from 'nx-dev/nx-dev/lib/courses.api'; | ||
import { CourseDetails } from '@nx/nx-dev/ui-courses'; | ||
import { DefaultLayout } from '@nx/nx-dev/ui-common'; | ||
|
||
interface CourseDetailProps { | ||
params: { courseId: string }; | ||
} | ||
|
||
export async function generateMetadata( | ||
{ params: { courseId } }: CourseDetailProps, | ||
parent: ResolvingMetadata | ||
): Promise<Metadata> { | ||
const course = await coursesApi.getCourse(courseId); | ||
const previousImages = (await parent).openGraph?.images ?? []; | ||
|
||
return { | ||
title: `${course.title} | Nx Courses`, | ||
description: course.description, | ||
openGraph: { | ||
url: `https://nx.dev/courses/${courseId}`, | ||
title: course.title, | ||
description: course.description, | ||
images: [ | ||
{ | ||
url: '/path/to/default/course/image.png', // Add a default course image | ||
width: 800, | ||
height: 421, | ||
alt: 'Nx Course: ' + course.title, | ||
type: 'image/png', | ||
}, | ||
...previousImages, | ||
], | ||
}, | ||
}; | ||
} | ||
|
||
export async function generateStaticParams() { | ||
const courses = await coursesApi.getAllCourses(); | ||
return courses.map((course) => { | ||
return { courseId: course.id }; | ||
}); | ||
} | ||
|
||
export default async function CourseDetail({ | ||
params: { courseId }, | ||
}: CourseDetailProps) { | ||
const course = await coursesApi.getCourse(courseId); | ||
return course ? ( | ||
<> | ||
{/* This empty div is necessary as app router does not automatically scroll on route changes */} | ||
<div></div> | ||
<DefaultLayout> | ||
<CourseDetails course={course} /> | ||
</DefaultLayout> | ||
</> | ||
) : null; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
import React from 'react'; | ||
import { DefaultLayout } from '@nx/nx-dev/ui-common'; | ||
import { CourseOverview, CourseHero } from '@nx/nx-dev/ui-video-courses'; | ||
import { coursesApi } from '../../lib/courses.api'; | ||
|
||
import type { Metadata } from 'next'; | ||
|
||
export const metadata: Metadata = { | ||
title: 'Nx Video Courses', | ||
description: | ||
'Explore our comprehensive video courses to master Nx and boost your development skills.', | ||
openGraph: { | ||
url: 'https://nx.dev/courses', | ||
title: 'Nx Video Courses', | ||
description: | ||
'Explore our comprehensive video courses to master Nx and boost your development skills.', | ||
images: [ | ||
{ | ||
url: 'https://nx.dev/socials/nx-courses-media.png', | ||
width: 800, | ||
height: 421, | ||
alt: 'Nx Video Courses: Master Nx through comprehensive tutorials', | ||
type: 'image/jpeg', | ||
}, | ||
], | ||
siteName: 'NxDev', | ||
type: 'website', | ||
}, | ||
}; | ||
|
||
export default async function CoursesPage(): Promise<JSX.Element> { | ||
const courses = await coursesApi.getAllCourses(); | ||
|
||
return ( | ||
<DefaultLayout> | ||
<CourseHero /> | ||
<div className="mt-8"> | ||
<CourseOverview courses={courses} /> | ||
</div> | ||
</DefaultLayout> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
import { CoursesApi } from '@nx/nx-dev/data-access-courses'; | ||
|
||
export const coursesApi = new CoursesApi({ | ||
coursesRoot: 'public/documentation/courses', | ||
}); |
Oops, something went wrong.