Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 29 additions & 4 deletions codewit/client/src/components/demos/Demos.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// codewit/client/src/components/demos/Demos.tsx
import { useState } from "react";
import { Link } from "react-router-dom";
import LoadingIcons from "../loading/LoadingIcon";
import { TrashIcon, PencilSquareIcon, VideoCameraIcon } from "@heroicons/react/24/solid";
Expand All @@ -7,13 +8,26 @@ interface VideoProps {
title: string;
amountExercises: number;
uid: number;
youtube_id: string;
youtube_thumbnail: string;
isDeleting?: boolean;
handleEdit: () => void;
handleDelete: () => void;
}

const Video = ({ title, uid, amountExercises, isDeleting, handleEdit, handleDelete }: VideoProps): JSX.Element => {

const Video = ({ title, uid, amountExercises, youtube_id, youtube_thumbnail, isDeleting, handleEdit, handleDelete }: VideoProps): JSX.Element => {
const fallback_src = `https://i.ytimg.com/vi/${youtube_id}/hqdefault.jpg`;
const [imgSrc, setImgSrc] = useState(youtube_thumbnail || fallback_src);
const [imgFailed, setImgFailed] = useState(false);

const handleImgError = () => {
if (imgSrc !== fallback_src) {
setImgSrc(fallback_src);
} else {
setImgFailed(true);
}
};

const handleMenuClick = (e: React.MouseEvent<HTMLButtonElement>, action: string) => {
e.stopPropagation();
if (action === 'edit') {
Expand All @@ -26,8 +40,19 @@ const Video = ({ title, uid, amountExercises, isDeleting, handleEdit, handleDele
return (
<div className="flex flex-col h-full bg-gray-800 overflow-hidden rounded-lg transition-transform duration-150 ease-in-out hover:scale-102 cursor-pointer w-full">
<Link to={`/read/${uid}`} className="flex flex-grow w-full">
<div className="flex-shrink-0 flex justify-center items-center bg-gray-900 w-full h-full ">
<VideoCameraIcon fill="#3da2b4" className="w-12 h-12 opacity-50" />
<div className="relative flex-shrink-0 w-full h-full min-h-[8rem] bg-gray-900">
{imgFailed ? (
<div className="w-full h-full flex justify-center items-center">
<VideoCameraIcon fill="#3da2b4" className="w-12 h-12 opacity-50" />
</div>
) : (
<img
src={imgSrc}
alt={title}
className="w-full h-full object-cover"
onError={handleImgError}
/>
)}
</div>
</Link>
<div className = "flex flex-row justify-center items-center">
Expand Down
93 changes: 63 additions & 30 deletions codewit/client/src/pages/Read.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
PlayIcon,
CheckCircleIcon,
LinkIcon,
VideoCameraIcon,
} from '@heroicons/react/24/solid';
import { Editor } from '@monaco-editor/react';
import { useForm } from "@tanstack/react-form";
Expand Down Expand Up @@ -217,6 +218,67 @@ function LeftPanel({info, module_id, course_id}: LeftPanelProps) {
</Resizable>;
}

interface RelatedDemoCardProps {
demo: RelatedDemo,
link_path: string,
}

function RelatedDemoCard({demo, link_path}: RelatedDemoCardProps) {
const fallback_src = `https://i.ytimg.com/vi/${demo.youtube_id}/hqdefault.jpg`;
const [imgSrc, setImgSrc] = useState(demo.youtube_thumbnail || fallback_src);
const [imgFailed, setImgFailed] = useState(false);

const handleImgError = () => {
if (imgSrc !== fallback_src) {
setImgSrc(fallback_src);
} else {
setImgFailed(true);
}
};

let status = null;

if (demo.completion !== 0 && demo.completion !== 1) {
status = `${(demo.completion * 100).toFixed(0)}%`;
}

return <Link
key={demo.uid}
to={link_path}
className="flex-shrink-0 w-48 rounded-md overflow-hidden hover:shadow-lg transition-all duration-200 group/link border border-gray-800 hover:border-accent-500/50"
>
<div className="relative w-full h-28 overflow-hidden">
{imgFailed ? (
<div className="w-full h-full bg-gray-900 flex justify-center items-center">
<VideoCameraIcon fill="#3da2b4" className="w-12 h-12 opacity-50" />
</div>
) : (
<img
src={imgSrc}
alt={demo.title}
className="w-full h-full object-cover"
onError={handleImgError}
/>
)}
<div className="absolute inset-0 bg-black bg-opacity-40 flex items-center justify-center group-hover/link:bg-opacity-20 transition-all">
{demo.completion === 1 ?
<CheckCircleIcon className="h-8 w-8 text-green-500"/>
:
<div className="flex flex-row items-center gap-2">
{status}
<PlayIcon className="h-8 w-8 text-white" />
</div>
}
</div>
</div>
<div className="p-2">
<h3 className="text-sm font-medium text-white group-hover/link:text-accent-400 transition-colors truncate">
{demo.title}
</h3>
</div>
</Link>;
}

interface RelatedDemosProps {
demos: RelatedDemo[] | null,
course_id?: string | null,
Expand Down Expand Up @@ -246,36 +308,7 @@ function RelatedDemos({demos, course_id, module_id}: RelatedDemosProps) {
link_path += `module_id=${module_id}`;
}

let status = null;

if (demo.completion !== 0 && demo.completion !== 1) {
status = `${(demo.completion * 100).toFixed(0)}%`;
}

return <Link
key={demo.uid}
to={link_path}
className="flex-shrink-0 w-48 rounded-md overflow-hidden hover:shadow-lg transition-all duration-200 group/link border border-gray-800 hover:border-accent-500/50"
>
<div className="relative w-full h-28 overflow-hidden">
<img src={demo.youtube_thumbnail} alt={demo.title} className="w-full h-full object-cover"/>
<div className="absolute inset-0 bg-black bg-opacity-40 flex items-center justify-center group-hover/link:bg-opacity-20 transition-all">
{demo.completion === 1 ?
<CheckCircleIcon className="h-8 w-8 text-green-500"/>
:
<div className="flex flex-row items-center gap-2">
{status}
<PlayIcon className="h-8 w-8 text-white" />
</div>
}
</div>
</div>
<div className="p-2">
<h3 className="text-sm font-medium text-white group-hover/link:text-accent-400 transition-colors truncate">
{demo.title}
</h3>
</div>
</Link>
return <RelatedDemoCard key={demo.uid} demo={demo} link_path={link_path} />;
})}
</div>
</div>
Expand Down
37 changes: 28 additions & 9 deletions codewit/client/src/pages/course/StudentView.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useState } from "react";
import { Link } from "react-router-dom";
import { PlayIcon, CheckCircleIcon } from "@heroicons/react/24/solid";
import { PlayIcon, CheckCircleIcon, VideoCameraIcon } from "@heroicons/react/24/solid";

import { StudentCourse, StudentModule, StudentDemo } from "@codewit/interfaces";
import { cn } from "../../utils/styles";
Expand Down Expand Up @@ -158,6 +158,18 @@ interface CourseModuleDemoProps {
}

function CourseModuleDemo({course_id, module_id, demo}: CourseModuleDemoProps) {
const fallback_src = `https://i.ytimg.com/vi/${demo.youtube_id}/hqdefault.jpg`;
const [imgSrc, setImgSrc] = useState(demo.youtube_thumbnail || fallback_src);
const [imgFailed, setImgFailed] = useState(false);

const handleImgError = () => {
if (imgSrc !== fallback_src) {
setImgSrc(fallback_src);
} else {
setImgFailed(true);
}
};

let status = null;

if (demo.completion !== 0 && demo.completion !== 1) {
Expand All @@ -166,22 +178,29 @@ function CourseModuleDemo({course_id, module_id, demo}: CourseModuleDemoProps) {

return <div className="relative overflow-hidden w-48">
<div className="relative h-32">
<img
src={demo.youtube_thumbnail}
alt={demo.title}
className="w-full h-full object-cover rounded-xl"
/>
<div className="absolute inset-0 bg-black bg-opacity-80 flex rounded-xl items-center justify-center group hover:bg-opacity-30">
{imgFailed ? (
<div className="w-full h-full bg-gray-900 rounded-xl flex justify-center items-center">
<VideoCameraIcon fill="#3da2b4" className="w-12 h-12 opacity-50" />
</div>
) : (
<img
src={imgSrc}
alt={demo.title}
className="w-full h-full object-cover rounded-xl"
onError={handleImgError}
/>
)}
<div className="absolute inset-0 bg-black bg-opacity-40 flex rounded-xl items-center justify-center group hover:bg-opacity-20">
<Link
to={`/read/${demo.uid}?course_id=${course_id}&module_id=${module_id}`}
className="text-2xl opacity-70 group-hover:opacity-100"
>
{demo.completion === 1 ?
<CheckCircleIcon className="h-8 w-8 text-green-500 opacity-40 group-hover:opacity-100"/>
<CheckCircleIcon className="h-8 w-8 text-green-500 opacity-70 group-hover:opacity-100"/>
:
<div className="flex flex-row items-center gap-2 ">
{status}
<PlayIcon className="h-8 w-8 text-white opacity-40 group-hover:opacity-100"/>
<PlayIcon className="h-8 w-8 text-white opacity-70 group-hover:opacity-100"/>
</div>
}
</Link>
Expand Down
Loading