fix: assignment and quiz rendering issue in courses
This commit is contained in:
1
frontend/components.d.ts
vendored
1
frontend/components.d.ts
vendored
@@ -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']
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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>
|
||||
11
frontend/src/components/NoSidebarLayout.vue
Normal file
11
frontend/src/components/NoSidebarLayout.vue
Normal 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>
|
||||
@@ -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,
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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'>
|
||||
|
||||
@@ -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'>
|
||||
|
||||
Reference in New Issue
Block a user