fix: quiz and time validation before linking to video
This commit is contained in:
@@ -60,9 +60,15 @@
|
|||||||
<ListRows>
|
<ListRows>
|
||||||
<ListRow :row="row" v-for="row in allQuizzes">
|
<ListRow :row="row" v-for="row in allQuizzes">
|
||||||
<template #default="{ column, item }">
|
<template #default="{ column, item }">
|
||||||
<ListRowItem :item="row[column.key]" :align="column.align">
|
<ListRowItem
|
||||||
<div class="leading-5 text-sm">
|
:item="row[column.key as keyof Quiz]"
|
||||||
{{ row[column.key] }}
|
:align="column.align"
|
||||||
|
>
|
||||||
|
<div v-if="column.key == 'time'" class="leading-5 text-sm">
|
||||||
|
{{ formatTimestamp(row[column.key as keyof Quiz]) }}
|
||||||
|
</div>
|
||||||
|
<div v-else class="leading-5 text-sm">
|
||||||
|
{{ row[column.key as keyof Quiz] }}
|
||||||
</div>
|
</div>
|
||||||
</ListRowItem>
|
</ListRowItem>
|
||||||
</template>
|
</template>
|
||||||
@@ -107,17 +113,18 @@ import {
|
|||||||
} from 'frappe-ui'
|
} from 'frappe-ui'
|
||||||
import { computed, reactive, ref, watch } from 'vue'
|
import { computed, reactive, ref, watch } from 'vue'
|
||||||
import { Plus, Trash2 } from 'lucide-vue-next'
|
import { Plus, Trash2 } from 'lucide-vue-next'
|
||||||
|
import { formatTimestamp } from '@/utils'
|
||||||
import Link from '@/components/Controls/Link.vue'
|
import Link from '@/components/Controls/Link.vue'
|
||||||
|
|
||||||
type Quiz = {
|
type Quiz = {
|
||||||
time: number
|
time: string
|
||||||
quiz: string
|
quiz: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const show = defineModel()
|
const show = defineModel()
|
||||||
const allQuizzes = ref<Quiz[]>([])
|
const allQuizzes = ref<Quiz[]>([])
|
||||||
const quiz = reactive<Quiz>({
|
const quiz = reactive<Quiz>({
|
||||||
time: 0,
|
time: '',
|
||||||
quiz: '',
|
quiz: '',
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -137,10 +144,9 @@ const props = defineProps({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const addQuiz = () => {
|
const addQuiz = () => {
|
||||||
if (quiz.time > props.duration) {
|
quiz.time = `${getTimeInSeconds()}`
|
||||||
toast.error(__('Time in video exceeds the total duration of the video.'))
|
if (!isTimeValid() || !isFormComplete()) return
|
||||||
return
|
|
||||||
}
|
|
||||||
allQuizzes.value.push({
|
allQuizzes.value.push({
|
||||||
time: quiz.time,
|
time: quiz.time,
|
||||||
quiz: quiz.quiz,
|
quiz: quiz.quiz,
|
||||||
@@ -148,10 +154,42 @@ const addQuiz = () => {
|
|||||||
|
|
||||||
props.saveQuizzes(allQuizzes.value)
|
props.saveQuizzes(allQuizzes.value)
|
||||||
|
|
||||||
quiz.time = 0
|
quiz.time = ''
|
||||||
quiz.quiz = ''
|
quiz.quiz = ''
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getTimeInSeconds = () => {
|
||||||
|
if (quiz.time && !quiz.time.includes(':')) {
|
||||||
|
quiz.time = `${quiz.time}:00`
|
||||||
|
}
|
||||||
|
const timeParts = quiz.time.split(':')
|
||||||
|
const timeInSeconds = parseInt(timeParts[0]) * 60 + parseInt(timeParts[1])
|
||||||
|
|
||||||
|
return timeInSeconds
|
||||||
|
}
|
||||||
|
|
||||||
|
const isTimeValid = () => {
|
||||||
|
if (parseInt(quiz.time) > props.duration) {
|
||||||
|
toast.error(__('Time in video exceeds the total duration of the video.'))
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
const isFormComplete = () => {
|
||||||
|
if (!quiz.time) {
|
||||||
|
toast.error(__('Please enter a valid timestamp'))
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!quiz.quiz) {
|
||||||
|
toast.error(__('Please select a quiz'))
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
const removeQuiz = (selections: string, unselectAll: () => void) => {
|
const removeQuiz = (selections: string, unselectAll: () => void) => {
|
||||||
Array.from(selections).forEach((selection) => {
|
Array.from(selections).forEach((selection) => {
|
||||||
const index = allQuizzes.value.findIndex((q) => q.quiz === selection)
|
const index = allQuizzes.value.findIndex((q) => q.quiz === selection)
|
||||||
|
|||||||
@@ -118,10 +118,10 @@
|
|||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted, computed, watch } from 'vue'
|
import { ref, onMounted, computed } from 'vue'
|
||||||
import { Pause, Maximize, Volume2, VolumeX } from 'lucide-vue-next'
|
import { Pause, Maximize, Volume2, VolumeX } from 'lucide-vue-next'
|
||||||
import { Button } from 'frappe-ui'
|
import { Button } from 'frappe-ui'
|
||||||
import { formatSeconds } from '@/utils'
|
import { formatSeconds, formatTimestamp } from '@/utils'
|
||||||
import Play from '@/components/Icons/Play.vue'
|
import Play from '@/components/Icons/Play.vue'
|
||||||
import QuizInVideo from '@/components/Modals/QuizInVideo.vue'
|
import QuizInVideo from '@/components/Modals/QuizInVideo.vue'
|
||||||
|
|
||||||
@@ -259,13 +259,6 @@ const toggleFullscreen = () => {
|
|||||||
videoContainer.value.requestFullscreen()
|
videoContainer.value.requestFullscreen()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const formatTimestamp = (seconds) => {
|
|
||||||
const date = new Date(seconds * 1000)
|
|
||||||
const minutes = String(date.getUTCMinutes()).padStart(2, '0')
|
|
||||||
const secs = String(date.getUTCSeconds()).padStart(2, '0')
|
|
||||||
return `${minutes}:${secs}`
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
@@ -210,7 +210,7 @@ const addInstructorNotes = (data) => {
|
|||||||
const enableAutoSave = () => {
|
const enableAutoSave = () => {
|
||||||
autoSaveInterval = setInterval(() => {
|
autoSaveInterval = setInterval(() => {
|
||||||
saveLesson({ showSuccessMessage: false })
|
saveLesson({ showSuccessMessage: false })
|
||||||
}, 10000)
|
}, 5000)
|
||||||
}
|
}
|
||||||
|
|
||||||
const keyboardShortcut = (e) => {
|
const keyboardShortcut = (e) => {
|
||||||
|
|||||||
@@ -615,3 +615,10 @@ export const updateMetaInfo = (type, route, meta) => {
|
|||||||
console.error(error)
|
console.error(error)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const formatTimestamp = (seconds) => {
|
||||||
|
const date = new Date(seconds * 1000)
|
||||||
|
const minutes = String(date.getUTCMinutes()).padStart(2, '0')
|
||||||
|
const secs = String(date.getUTCSeconds()).padStart(2, '0')
|
||||||
|
return `${minutes}:${secs}`
|
||||||
|
}
|
||||||
|
|||||||
@@ -56,7 +56,6 @@ def save_progress(lesson, course):
|
|||||||
)
|
)
|
||||||
|
|
||||||
quiz_completed = get_quiz_progress(lesson)
|
quiz_completed = get_quiz_progress(lesson)
|
||||||
print("quiz_completed", quiz_completed)
|
|
||||||
assignment_completed = get_assignment_progress(lesson)
|
assignment_completed = get_assignment_progress(lesson)
|
||||||
|
|
||||||
if not already_completed and quiz_completed and assignment_completed:
|
if not already_completed and quiz_completed and assignment_completed:
|
||||||
@@ -115,10 +114,8 @@ def get_quiz_progress(lesson):
|
|||||||
macros = find_macros(lesson_details.body)
|
macros = find_macros(lesson_details.body)
|
||||||
quizzes = [value for name, value in macros if name == "Quiz"]
|
quizzes = [value for name, value in macros if name == "Quiz"]
|
||||||
|
|
||||||
print(quizzes)
|
|
||||||
for quiz in quizzes:
|
for quiz in quizzes:
|
||||||
passing_percentage = frappe.db.get_value("LMS Quiz", quiz, "passing_percentage")
|
passing_percentage = frappe.db.get_value("LMS Quiz", quiz, "passing_percentage")
|
||||||
print(quiz, passing_percentage)
|
|
||||||
if not frappe.db.exists(
|
if not frappe.db.exists(
|
||||||
"LMS Quiz Submission",
|
"LMS Quiz Submission",
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user