fix: assignment and quiz rendering issue in courses

This commit is contained in:
Jannat Patel
2025-03-24 13:42:24 +05:30
parent c0f4a09e22
commit 89a348b154
11 changed files with 91 additions and 102 deletions

View File

@@ -66,6 +66,7 @@ declare module 'vue' {
MobileLayout: typeof import('./src/components/MobileLayout.vue')['default']
MultiSelect: typeof import('./src/components/Controls/MultiSelect.vue')['default']
NoPermission: typeof import('./src/components/NoPermission.vue')['default']
NoSidebarLayout: typeof import('./src/components/NoSidebarLayout.vue')['default']
NotPermitted: typeof import('./src/components/NotPermitted.vue')['default']
OnboardingBanner: typeof import('./src/components/OnboardingBanner.vue')['default']
PageModal: typeof import('./src/components/Modals/PageModal.vue')['default']

View File

@@ -8,18 +8,34 @@
<script setup>
import { Toasts } from 'frappe-ui'
import { Dialogs } from '@/utils/dialogs'
import { computed, onMounted, onUnmounted } from 'vue'
import { computed, onMounted, onUnmounted, ref } from 'vue'
import { useScreenSize } from './utils/composables'
import DesktopLayout from './components/DesktopLayout.vue'
import MobileLayout from './components/MobileLayout.vue'
import NoSidebarLayout from './components/NoSidebarLayout.vue'
import { stopSession } from '@/telemetry'
import { init as initTelemetry } from '@/telemetry'
import { usersStore } from '@/stores/user'
import { useRouter } from 'vue-router'
const screenSize = useScreenSize()
let { userResource } = usersStore()
const router = useRouter()
const noSidebar = ref(false)
router.beforeEach((to, from, next) => {
if (to.query.fromLesson) {
noSidebar.value = true
} else {
noSidebar.value = false
}
next()
})
const Layout = computed(() => {
if (noSidebar.value) {
return NoSidebarLayout
}
if (screenSize.width < 640) {
return MobileLayout
} else {
@@ -28,11 +44,11 @@ const Layout = computed(() => {
})
onMounted(async () => {
if (!userResource.data) return
await initTelemetry()
if (userResource.data) await initTelemetry()
})
onUnmounted(() => {
noSidebar.value = false
stopSession()
})
</script>

View File

@@ -1,10 +1,13 @@
<template>
<div
v-if="assignment.data"
class="grid grid-cols-[60%,40%] h-full"
:class="{ 'border rounded-lg': !showTitle }"
class="grid grid-cols-2 h-full"
:class="{ 'border rounded-lg overflow-auto': !showTitle }"
>
<div class="border-r p-5 overflow-y-auto h-[calc(100vh-3.2rem)]">
<div
class="border-r p-5 overflow-y-auto h-[calc(100vh-3.2rem)]"
:class="{ 'h-full': !showTitle }"
>
<div v-if="showTitle" class="text-lg font-semibold mb-5 text-ink-gray-9">
<div v-if="submissionName === 'new'">
{{ __('Submission by') }} {{ user.data?.full_name }}
@@ -115,13 +118,7 @@
:readonly="!canModifyAssignment"
/>
</div>
<div v-if="true">
<div class="text-sm mb-4">
{{ __('Write your answer here') }}
</div>
<FormControl />
</div>
<!-- <div v-else>
<div v-else>
<div class="text-sm mb-4">
{{ __('Write your answer here') }}
</div>
@@ -132,7 +129,7 @@
:fixedMenu="true"
editorClass="prose-sm max-w-none border-b border-x bg-surface-gray-2 rounded-b-md py-1 px-2 min-h-[7rem]"
/>
</div> -->
</div>
<div
v-if="
@@ -144,9 +141,7 @@
<div class="text-sm text-ink-gray-5 font-medium mb-2">
{{ __('Comments by Evaluator') }}:
</div>
<div class="leading-5">
{{ submissionResource.doc.comments }}
</div>
<div class="leading-5" v-html="submissionResource.doc.comments"></div>
</div>
<!-- Grading -->
@@ -204,7 +199,6 @@ const answer = ref(null)
const comments = ref(null)
const router = useRouter()
const user = inject('$user')
const showTitle = router.currentRoute.value.name == 'AssignmentSubmission'
const isDirty = ref(false)
const props = defineProps({
@@ -216,6 +210,10 @@ const props = defineProps({
type: String,
default: 'new',
},
showTitle: {
type: Boolean,
default: true,
},
})
onMounted(() => {
@@ -359,6 +357,7 @@ const addNewSubmission = () => {
assignmentID: props.assignmentID,
submissionName: data.name,
},
query: { fromLesson: router.currentRoute.value.query.fromLesson },
})
} else {
markLessonProgress()

View File

@@ -1,46 +0,0 @@
<template>
<Assignment
v-if="user.data && submission.data"
:assignmentID="assignmentID"
:submissionName="submission.data?.name || 'new'"
/>
<div v-else class="border rounded-md text-center py-20">
<div>
{{ __('Please login to access the assignment.') }}
</div>
<Button @click="redirectToLogin()" class="mt-2">
<span>
{{ __('Login') }}
</span>
</Button>
</div>
</template>
<script setup>
import { inject } from 'vue'
import { Button, createResource } from 'frappe-ui'
import Assignment from '@/components/Assignment.vue'
const user = inject('$user')
const props = defineProps({
assignmentID: {
type: String,
required: true,
},
})
const submission = createResource({
url: 'frappe.client.get_value',
makeParams(values) {
return {
doctype: 'LMS Assignment Submission',
fieldname: 'name',
filters: {
assignment: props.assignmentID,
member: user.data?.name,
},
}
},
auto: true,
})
</script>

View File

@@ -0,0 +1,11 @@
<template>
<div class="relative flex h-full flex-col">
<div class="h-full flex-1">
<div class="flex h-screen text-base bg-surface-white">
<div class="w-full overflow-auto" id="scrollContainer">
<slot />
</div>
</div>
</div>
</div>
</template>

View File

@@ -1,7 +1,7 @@
<template>
<div v-if="quiz.data">
<div
class="bg-surface-blue-2 space-y-1 py-2 px-2 mb-4 rounded-md text-sm text-ink-blue-800"
class="bg-surface-blue-2 space-y-1 py-2 px-2 mb-4 rounded-md text-sm text-ink-blue-2"
>
<div class="leading-5">
{{
@@ -29,7 +29,7 @@
).format(quiz.data.passing_percentage)
}}
</div>
<div v-if="quiz.data.max_attempts" class="leading-relaxed">
<div v-if="quiz.data.max_attempts" class="leading-5">
{{
__('You can attempt this quiz {0}.').format(
quiz.data.max_attempts == 1
@@ -52,7 +52,7 @@
<div v-if="activeQuestion == 0">
<div class="border text-center p-20 rounded-md">
<div class="font-semibold text-lg">
<div class="font-semibold text-lg text-ink-gray-9">
{{ quiz.data.title }}
</div>
<Button
@@ -67,7 +67,7 @@
{{ __('Start') }}
</span>
</Button>
<div v-else>
<div v-else class="leading-5 text-ink-gray-7">
{{
__(
'You have already exceeded the maximum number of attempts allowed for this quiz.'
@@ -222,11 +222,14 @@
</div>
</div>
</div>
<div v-else class="border rounded-md p-20 text-center space-y-4">
<div class="text-lg font-semibold">
<div v-else class="border rounded-md p-20 text-center space-y-2">
<div class="text-lg font-semibold text-ink-gray-9">
{{ __('Quiz Summary') }}
</div>
<div v-if="quizSubmission.data.is_open_ended">
<div
v-if="quizSubmission.data.is_open_ended"
class="leading-5 text-ink-gray-7"
>
{{
__(
"Your submission has been successfully saved. The instructor will review and grade it shortly, and you'll be notified of your final result."
@@ -613,7 +616,6 @@ const getInstructions = (question) => {
}
const markLessonProgress = () => {
console.log(router)
if (router.currentRoute.value.name == 'Lesson') {
call('lms.lms.api.mark_lesson_progress', {
course: router.currentRoute.value.params.courseName,

View File

@@ -1,19 +1,25 @@
<template>
<header
v-if="!fromLesson"
class="flex justify-between sticky top-0 z-10 border-b bg-surface-white px-3 py-2.5 sm:px-5"
>
<Breadcrumbs :items="breadcrumbs" />
</header>
<div class="overflow-hidden h-[calc(100vh-3.2rem)]">
<Assignment :assignmentID="assignmentID" :submissionName="submissionName" />
<Assignment
:assignmentID="assignmentID"
:submissionName="submissionName"
:showTitle="!fromLesson"
/>
</div>
</template>
<script setup>
import { Breadcrumbs, createResource } from 'frappe-ui'
import { computed, inject, onMounted } from 'vue'
import { computed, inject, onMounted, onBeforeUnmount, ref } from 'vue'
import Assignment from '@/components/Assignment.vue'
const user = inject('$user')
const fromLesson = ref(false)
const props = defineProps({
assignmentID: {
@@ -42,6 +48,10 @@ onMounted(() => {
if (!user.data) {
window.location.href = '/login'
}
if (new URLSearchParams(window.location.search).get('fromLesson')) {
fromLesson.value = true
}
})
const breadcrumbs = computed(() => {

View File

@@ -587,11 +587,6 @@ updateDocumentTitle(pageMeta)
line-height: 1.7;
}
iframe {
border-top: 3px solid theme('colors.gray.700');
border-bottom: 3px solid theme('colors.gray.700');
}
.tc-table {
border-left: 1px solid #e8e8eb;
}

View File

@@ -1,27 +1,36 @@
<template>
<header
v-if="!fromLesson"
class="sticky top-0 z-10 flex items-center justify-between border-b bg-surface-white px-3 py-2.5 sm:px-5"
>
<Breadcrumbs :items="breadcrumbs" />
</header>
<div class="md:w-7/12 md:mx-auto mx-4 py-10">
<div
class="md:w-7/12 md:mx-auto mx-4 py-10"
:class="{ 'pt-4 md:w-full': fromLesson }"
>
<Quiz :quizName="quizID" />
</div>
</template>
<script setup>
import Quiz from '@/components/Quiz.vue'
import { createResource, Breadcrumbs } from 'frappe-ui'
import { computed, inject, onMounted } from 'vue'
import { computed, inject, onMounted, ref } from 'vue'
import { useRouter } from 'vue-router'
import { updateDocumentTitle } from '@/utils'
const user = inject('$user')
const router = useRouter()
const fromLesson = ref(false)
onMounted(() => {
if (!user.data) {
router.push({ name: 'Courses' })
}
if (new URLSearchParams(window.location.search).get('fromLesson')) {
fromLesson.value = true
}
})
const props = defineProps({

View File

@@ -1,11 +1,9 @@
import { Pencil } from 'lucide-vue-next'
import { createApp, h } from 'vue'
import AssessmentPlugin from '@/components/AssessmentPlugin.vue'
import AssignmentBlock from '@/components/AssignmentBlock.vue'
import translationPlugin from '../translation'
import { usersStore } from '@/stores/user'
import router from '../router'
import { FrappeUI, setConfig, frappeRequest, pageMetaPlugin } from 'frappe-ui'
import { call } from 'frappe-ui'
export class Assignment {
constructor({ data, api, readOnly }) {
@@ -44,17 +42,18 @@ export class Assignment {
renderAssignment(assignment) {
if (this.readOnly) {
const app = createApp(AssignmentBlock, {
assignmentID: assignment,
})
app.use(FrappeUI)
setConfig('resourceFetcher', frappeRequest)
app.use(translationPlugin)
app.use(router)
app.use(pageMetaPlugin)
const { userResource } = usersStore()
app.provide('$user', userResource)
app.mount(this.wrapper)
call('frappe.client.get_value', {
doctype: 'LMS Assignment Submission',
filters: {
assignment: assignment,
member: userResource.data?.name,
},
fieldname: ['name'],
}).then((data) => {
let submission = data.name || 'new'
this.wrapper.innerHTML = `<iframe src="/lms/assignment-submission/${assignment}/${submission}?fromLesson=1" class="w-full h-[500px]"></iframe>`
})
return
}
this.wrapper.innerHTML = `<div class='border rounded-md p-10 text-center bg-surface-menu-bar mb-2'>

View File

@@ -43,14 +43,7 @@ export class Quiz {
renderQuiz(quiz) {
if (this.readOnly) {
const app = createApp(QuizBlock, {
quiz: quiz,
})
app.use(translationPlugin)
app.use(router)
const { userResource } = usersStore()
app.provide('$user', userResource)
app.mount(this.wrapper)
this.wrapper.innerHTML = `<iframe src="/lms/quiz/${quiz}?fromLesson=1" class="w-full h-[500px]"></iframe>`
return
}
this.wrapper.innerHTML = `<div class='border rounded-md p-10 text-center bg-surface-menu-bar mb-2'>