diff --git a/app/[locale]/content/page.tsx b/app/[locale]/content/page.tsx index 2dde554..b5158dd 100644 --- a/app/[locale]/content/page.tsx +++ b/app/[locale]/content/page.tsx @@ -1,5 +1,6 @@ import { ContentContent } from "@/components/content-content"; import { YOUTUBE_VIDEO_LINKS } from "@/data/youtube-videos"; +import { X_JAVASCRIPT_ANATOMY_VIDEOS } from "@/data/x-videos"; import { getPdfNotes } from "@/lib/content-page"; export default async function ContentPage() { @@ -9,6 +10,7 @@ export default async function ContentPage() { ); } diff --git a/components/content-content.tsx b/components/content-content.tsx index 3de80be..539de31 100644 --- a/components/content-content.tsx +++ b/components/content-content.tsx @@ -1,107 +1,72 @@ "use client"; -import { useEffect, useMemo, useRef, useState } from "react"; +import { useMemo, useState } from "react"; +import Image from "next/image"; import { Icon } from "@iconify/react"; import { Button, ButtonIcon, ButtonLabel, Card, Typography } from "poyraz-ui/atoms"; import { Modal, ModalContent, ModalTitle } from "poyraz-ui/molecules"; -import { YoutubeLiteEmbed } from "@/components/youtube-lite-embed"; import { useTranslations } from "next-intl"; +import { YoutubeLiteEmbed } from "@/components/youtube-lite-embed"; +import type { XVideo } from "@/data/x-videos"; +import { X_JAVASCRIPT_ANATOMY_URL } from "@/data/x-videos"; +import type { PdfNote } from "@/lib/content-page"; type ContentContentProps = { youtubeLinks: readonly string[]; - pdfFiles: string[]; + pdfFiles: PdfNote[]; + xVideos: readonly XVideo[]; }; -function PdfFirstPagePreview({ src, title }: { src: string; title: string }) { - const t = useTranslations("Content"); - const canvasRef = useRef(null); - const [failed, setFailed] = useState(false); - - useEffect(() => { - let cancelled = false; - let loadingTask: { - promise: Promise; - destroy?: () => void; - } | null = null; - - const render = async () => { - try { - const pdfjs = await import("pdfjs-dist"); - const lib = pdfjs as unknown as { - version: string; - getDocument: (src: string) => { - promise: Promise; - destroy?: () => void; - }; - GlobalWorkerOptions: { workerSrc: string }; - }; - - lib.GlobalWorkerOptions.workerSrc = `https://unpkg.com/pdfjs-dist@${lib.version}/build/pdf.worker.min.mjs`; - loadingTask = lib.getDocument(src); - - const pdf = (await loadingTask.promise) as { - getPage: (page: number) => Promise<{ - getViewport: (opts: { scale: number }) => { - width: number; - height: number; - }; - render: (opts: { - canvasContext: CanvasRenderingContext2D; - viewport: { width: number; height: number }; - }) => { promise: Promise }; - }>; - }; - - const page = await pdf.getPage(1); - const viewport = page.getViewport({ scale: 1 }); - const canvas = canvasRef.current; - if (!canvas || cancelled) return; - - const context = canvas.getContext("2d"); - if (!context) return; - - const ratio = window.devicePixelRatio || 1; - canvas.width = Math.floor(viewport.width * ratio); - canvas.height = Math.floor(viewport.height * ratio); - canvas.style.width = "100%"; - canvas.style.height = "auto"; - - context.setTransform(ratio, 0, 0, ratio, 0, 0); - await page.render({ canvasContext: context, viewport }).promise; - } catch { - if (!cancelled) { - setFailed(true); - } - } - }; - - void render(); - - return () => { - cancelled = true; - if (loadingTask?.destroy) { - loadingTask.destroy(); - } - }; - }, [src]); - - if (failed) { - return ( -
- - {t("pdfPreviewError")} - -
- ); - } +type SectionHeadingProps = { + title: string; + titlePrefix?: string; + titleIcon: string; + titleIconClassName?: string; + href: string; + label: string; + handle: string; + icon: string; +}; +function SectionHeading({ + title, + titlePrefix, + titleIcon, + titleIconClassName, + href, + label, + handle, + icon, +}: SectionHeadingProps) { return ( -
- +
+ + {titlePrefix ? {titlePrefix} : null} + +
); } @@ -109,6 +74,7 @@ function PdfFirstPagePreview({ src, title }: { src: string; title: string }) { export function ContentContent({ youtubeLinks, pdfFiles, + xVideos, }: ContentContentProps) { const t = useTranslations("Content"); const [pdfModalOpen, setPdfModalOpen] = useState(false); @@ -117,7 +83,6 @@ export function ContentContent({ const activePdf = pdfFiles[activePdfIndex] ?? null; const canGoPrev = activePdfIndex > 0; const canGoNext = activePdfIndex < pdfFiles.length - 1; - const embeddedVideos = useMemo( () => youtubeLinks.slice(0, 3), [youtubeLinks], @@ -129,25 +94,19 @@ export function ContentContent({ }; return ( -
-
-
- - {t("youtubeTitle")} - - +
+
+
+
{embeddedVideos.map((link) => ( @@ -161,50 +120,86 @@ export function ContentContent({
-
-
- - {t("pdfTitle")} - - +
+
+
{pdfFiles.map((pdf, index) => ( ))}
+
+
+ +
+
+ {xVideos.map((video) => ( + + + + ))} +
+
+ - {activePdf ? activePdf.replace(/\.pdf$/i, "") : t("pdfModalDefaultTitle")} + {activePdf?.title ?? t("pdfModalDefaultTitle")} - +
{pdfFiles.length === 0 @@ -218,7 +213,7 @@ export function ContentContent({ className="rounded-sm" disabled={!canGoPrev} onClick={() => - setActivePdfIndex((prev) => Math.max(0, prev - 1)) + setActivePdfIndex((previous) => Math.max(0, previous - 1)) } > {t("prev")} @@ -229,8 +224,8 @@ export function ContentContent({ className="rounded-sm" disabled={!canGoNext} onClick={() => - setActivePdfIndex((prev) => - Math.min(pdfFiles.length - 1, prev + 1), + setActivePdfIndex((previous) => + Math.min(pdfFiles.length - 1, previous + 1), ) } > @@ -238,13 +233,14 @@ export function ContentContent({
- + {activePdf ? (