diff --git a/frontend/src/components/Modals/QuizInVideo.vue b/frontend/src/components/Modals/QuizInVideo.vue index 889a3c39..3d12ea15 100644 --- a/frontend/src/components/Modals/QuizInVideo.vue +++ b/frontend/src/components/Modals/QuizInVideo.vue @@ -3,23 +3,14 @@ v-model="show" :options="{ title: __('Add quiz to this video'), - size: 'xl', - actions: [ - { - label: __('Save'), - variant: 'solid', - onClick({ close }) { - addQuizToVideo(close) - }, - }, - ], + size: '2xl', }" > - + @@ -39,43 +30,63 @@ - - + + {{ __('Quizzes in this video') }} + + - - - - - - + + + + + + + - - - - - - {{ row[column.key] }} - - + + + + + + {{ row[column.key] }} + + + + + + + + + + + + + - - - + + + + + {{ __('No quizzes added yet.') }} + + @@ -91,10 +102,11 @@ import { ListRows, ListRow, ListRowItem, + ListSelectBanner, toast, } from 'frappe-ui' import { computed, reactive, ref, watch } from 'vue' -import { Plus } from 'lucide-vue-next' +import { Plus, Trash2 } from 'lucide-vue-next' import Link from '@/components/Controls/Link.vue' type Quiz = { @@ -124,11 +136,6 @@ const props = defineProps({ }, }) -const addQuizToVideo = (close: () => void) => { - props.saveQuizzes(allQuizzes.value) - close() -} - const addQuiz = () => { if (quiz.time > props.duration) { toast.error(__('Time in video exceeds the total duration of the video.')) @@ -139,10 +146,23 @@ const addQuiz = () => { quiz: quiz.quiz, }) + props.saveQuizzes(allQuizzes.value) + quiz.time = 0 quiz.quiz = '' } +const removeQuiz = (selections: string, unselectAll: () => void) => { + Array.from(selections).forEach((selection) => { + const index = allQuizzes.value.findIndex((q) => q.quiz === selection) + if (index !== -1) { + allQuizzes.value.splice(index, 1) + } + unselectAll() + }) + props.saveQuizzes(allQuizzes.value) +} + watch( () => props.quizzes, (newQuizzes) => { @@ -159,7 +179,7 @@ const columns = computed(() => { }, { key: 'time', - label: __('Time in Video (seconds)'), + label: __('Time in Video (minutes)'), align: 'center', }, ] diff --git a/frontend/src/components/Quiz.vue b/frontend/src/components/Quiz.vue index c1b0f04a..0b483348 100644 --- a/frontend/src/components/Quiz.vue +++ b/frontend/src/components/Quiz.vue @@ -1,8 +1,11 @@ + + {{ __('You will have to complete the quiz to continue the video') }} + {{ __('This quiz consists of {0} questions.').format(questions.length) @@ -247,18 +250,23 @@ ) }} - - - {{ __('Try Again') }} - - + + + + {{ __('Try Again') }} + + + + {{ __('Resume Video') }} + + + + {{ + __('This video has {0} {1}:').format( + quizzes.length, + quizzes.length == 1 ? 'quiz' : 'quizzes' + ) + }} + + + {{ index + 1 }}. {{ quiz.quiz }} + {{ __('at {0} minutes').format(quiz.time) }} + + + + {{ __('Add Quiz to Video') }} @@ -106,6 +132,9 @@ let currentTime = ref(0) let duration = ref(0) let muted = ref(false) const showQuizModal = ref(false) +const showQuiz = ref(false) +const currentQuiz = ref(null) +const nextQuiz = ref({}) const props = defineProps({ file: { @@ -130,15 +159,54 @@ const props = defineProps({ }) onMounted(() => { + updateCurrentTime() + updateNextQuiz() +}) + +const updateCurrentTime = () => { setTimeout(() => { videoRef.value.onloadedmetadata = () => { duration.value = videoRef.value.duration } videoRef.value.ontimeupdate = () => { - currentTime.value = videoRef.value.currentTime + currentTime.value = videoRef.value?.currentTime || currentTime.value + if (currentTime.value >= nextQuiz.value.time * 60) { + videoRef.value.pause() + playing.value = false + videoRef.value.onTimeupdate = null + currentQuiz.value = nextQuiz.value.quiz + showQuiz.value = true + updateNextQuiz() + } } }, 0) -}) +} + +const resumeVideo = () => { + showQuiz.value = false + currentQuiz.value = null + updateCurrentTime() + setTimeout(() => { + videoRef.value.currentTime = currentTime.value + videoRef.value.play() + playing.value = true + }, 0) +} + +const updateNextQuiz = () => { + if (!props.quizzes.length) return + props.quizzes.sort((a, b) => a.time - b.time) + + const nextQuizIndex = props.quizzes.findIndex( + (quiz) => quiz.time * 60 > currentTime.value + ) + + if (nextQuizIndex !== -1) { + nextQuiz.value = props.quizzes[nextQuizIndex] + } else { + nextQuiz.value = {} + } +} const fileURL = computed(() => { if (isYoutube) { @@ -156,6 +224,7 @@ const isYoutube = computed(() => { }) const playVideo = () => { + console.log(currentTime.value) videoRef.value.play() playing.value = true } @@ -166,6 +235,7 @@ const pauseVideo = () => { } const togglePlay = () => { + console.log(currentTime.value) if (playing.value) { pauseVideo() } else { @@ -184,6 +254,8 @@ const toggleMute = () => { const changeCurrentTime = () => { videoRef.value.currentTime = currentTime.value + console.log('Current Time:', currentTime.value) + updateNextQuiz() } const toggleFullscreen = () => { diff --git a/frontend/src/utils/upload.js b/frontend/src/utils/upload.js index d8f5163c..5fe3aca8 100644 --- a/frontend/src/utils/upload.js +++ b/frontend/src/utils/upload.js @@ -47,6 +47,7 @@ export class Upload { const app = createApp(VideoBlock, { file: file.file_url, readOnly: this.readOnly, + quizzes: file.quizzes || [], saveQuizzes: (quizzes) => { if (this.readOnly) return this.data.quizzes = quizzes diff --git a/lms/lms/doctype/course_lesson/course_lesson.py b/lms/lms/doctype/course_lesson/course_lesson.py index 2e178ce6..a24c1032 100644 --- a/lms/lms/doctype/course_lesson/course_lesson.py +++ b/lms/lms/doctype/course_lesson/course_lesson.py @@ -96,6 +96,11 @@ def get_quiz_progress(lesson): for block in content.get("blocks"): if block.get("type") == "quiz": quizzes.append(block.get("data").get("quiz")) + if block.get("type") == "upload": + quizzes_in_video = block.get("data").get("quizzes") + if quizzes_in_video and len(quizzes_in_video) > 0: + for row in quizzes_in_video: + quizzes.append(row.get("quiz")) elif lesson_details.body: macros = find_macros(lesson_details.body)