fix: branding, course, lesson and certificate creation

This commit is contained in:
Jannat Patel
2024-04-03 22:42:59 +05:30
parent efcdba3a29
commit a46306720b
14 changed files with 84 additions and 23 deletions

View File

@@ -18,7 +18,9 @@
</div> </div>
</div> </div>
<SidebarLink <SidebarLink
:label="isSidebarCollapsed ? 'Expand' : 'Collapse'" :link="{
label: isSidebarCollapsed ? 'Expand' : 'Collapse',
}"
:isCollapsed="isSidebarCollapsed" :isCollapsed="isSidebarCollapsed"
@click="isSidebarCollapsed = !isSidebarCollapsed" @click="isSidebarCollapsed = !isSidebarCollapsed"
class="m-2" class="m-2"

View File

@@ -97,10 +97,6 @@ const addFile = (data) => {
getCurrentEditor().blocks.insert('upload', data) getCurrentEditor().blocks.insert('upload', data)
} }
const getBlocksCount = () => {
return getCurrentEditor().blocks.getBlocksCount
}
const validateFile = (file) => { const validateFile = (file) => {
let extension = file.name.split('.').pop().toLowerCase() let extension = file.name.split('.').pop().toLowerCase()
if (!['jpg', 'jpeg', 'png', 'mp4', 'mov', 'mp3'].includes(extension)) { if (!['jpg', 'jpeg', 'png', 'mp4', 'mov', 'mp3'].includes(extension)) {

View File

@@ -11,7 +11,12 @@
: 'hover:bg-gray-200 px-2 w-52' : 'hover:bg-gray-200 px-2 w-52'
" "
> >
<LMSLogo class="w-8 h-8 rounded flex-shrink-0" /> <span
v-if="branding.data?.brand_html"
v-html="branding.data?.brand_html"
class="w-8 h-8 rounded flex-shrink-0"
></span>
<LMSLogo v-else class="w-8 h-8 rounded flex-shrink-0" />
<div <div
class="flex flex-1 flex-col text-left duration-300 ease-in-out" class="flex flex-1 flex-col text-left duration-300 ease-in-out"
:class=" :class="
@@ -21,7 +26,10 @@
" "
> >
<div class="text-base font-medium text-gray-900 leading-none"> <div class="text-base font-medium text-gray-900 leading-none">
Learning <span v-if="branding.data?.brand_name">
{{ branding.data?.brand_name }}
</span>
<span v-else> Learning </span>
</div> </div>
<div v-if="user" class="mt-1 text-sm text-gray-700 leading-none"> <div v-if="user" class="mt-1 text-sm text-gray-700 leading-none">
{{ convertToTitleCase(user.split('@')[0]) }} {{ convertToTitleCase(user.split('@')[0]) }}
@@ -49,6 +57,7 @@ import { Dropdown } from 'frappe-ui'
import { ChevronDown, LogIn, LogOut, User } from 'lucide-vue-next' import { ChevronDown, LogIn, LogOut, User } from 'lucide-vue-next'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { convertToTitleCase } from '../utils' import { convertToTitleCase } from '../utils'
import { onMounted } from 'vue'
const router = useRouter() const router = useRouter()
const props = defineProps({ const props = defineProps({
@@ -58,8 +67,9 @@ const props = defineProps({
}, },
}) })
const { logout, user } = sessionStore() const { logout, user, branding } = sessionStore()
let { isLoggedIn } = sessionStore() let { isLoggedIn } = sessionStore()
const userDropdownOptions = [ const userDropdownOptions = [
/* { /* {
icon: User, icon: User,

View File

@@ -187,14 +187,16 @@ import {
FormControl, FormControl,
FileUploader, FileUploader,
} from 'frappe-ui' } from 'frappe-ui'
import { inject, onMounted, computed, ref, reactive } from 'vue' import { inject, onMounted, computed, ref, reactive, watch } from 'vue'
import { convertToTitleCase, showToast, getFileSize } from '../utils' import { convertToTitleCase, showToast, getFileSize } from '../utils'
import Link from '@/components/Controls/Link.vue' import Link from '@/components/Controls/Link.vue'
import { FileText, X } from 'lucide-vue-next' import { FileText, X } from 'lucide-vue-next'
import { useRouter } from 'vue-router'
import CourseOutline from '@/components/CourseOutline.vue' import CourseOutline from '@/components/CourseOutline.vue'
const user = inject('$user') const user = inject('$user')
const newTag = ref('') const newTag = ref('')
const router = useRouter()
const props = defineProps({ const props = defineProps({
courseName: { courseName: {
@@ -318,8 +320,12 @@ const submitCourse = () => {
) )
} else { } else {
courseCreationResource.submit(course, { courseCreationResource.submit(course, {
onSuccess() { onSuccess(data) {
showToast('Success', 'Course created successfully', 'check') showToast('Success', 'Course created successfully', 'check')
router.push({
name: 'CreateCourse',
params: { courseName: data.name },
})
}, },
onError(err) { onError(err) {
showToast(err) showToast(err)
@@ -347,6 +353,15 @@ const validateMandatoryFields = () => {
} }
} }
watch(
() => props.courseName !== 'new',
(newVal) => {
if (newVal) {
courseResource.reload()
}
}
)
const validateFile = (file) => { const validateFile = (file) => {
let extension = file.name.split('.').pop().toLowerCase() let extension = file.name.split('.').pop().toLowerCase()
if (!['jpg', 'jpeg', 'png'].includes(extension)) { if (!['jpg', 'jpeg', 'png'].includes(extension)) {

View File

@@ -73,17 +73,19 @@ import {
Button, Button,
createDocumentResource, createDocumentResource,
} from 'frappe-ui' } from 'frappe-ui'
import { computed, reactive, onMounted, inject, ref } from 'vue' import { computed, reactive, onMounted, inject, ref, watch } from 'vue'
import EditorJS from '@editorjs/editorjs' import EditorJS from '@editorjs/editorjs'
import { createToast } from '../utils' import { createToast } from '../utils'
import LessonPlugins from '@/components/LessonPlugins.vue' import LessonPlugins from '@/components/LessonPlugins.vue'
import { getEditorTools } from '../utils' import { getEditorTools } from '../utils'
import { ChevronRight } from 'lucide-vue-next' import { ChevronRight } from 'lucide-vue-next'
import { useRouter } from 'vue-router'
const editor = ref(null) const editor = ref(null)
const instructorEditor = ref(null) const instructorEditor = ref(null)
const user = inject('$user') const user = inject('$user')
const openInstructorEditor = ref(false) const openInstructorEditor = ref(false)
const router = useRouter()
const props = defineProps({ const props = defineProps({
courseName: { courseName: {
@@ -319,6 +321,7 @@ const createNewLesson = () => {
{ {
onSuccess() { onSuccess() {
showToast('Success', 'Lesson created successfully', 'check') showToast('Success', 'Lesson created successfully', 'check')
lessonDetails.reload()
}, },
} }
) )

View File

@@ -114,7 +114,10 @@
</span> </span>
</div> </div>
<div <div
v-if="lesson.data.instructor_content && allowInstructorContent()" v-if="
lesson.data.instructor_content.blocks?.length &&
allowInstructorContent()
"
class="bg-gray-100 p-3 rounded-md mt-6" class="bg-gray-100 p-3 rounded-md mt-6"
> >
<div class="text-gray-600 font-medium"> <div class="text-gray-600 font-medium">
@@ -233,8 +236,7 @@ const lesson = createResource({
markProgress(data) markProgress(data)
if (data.content) editor = renderEditor('editor', data.content) if (data.content) editor = renderEditor('editor', data.content)
if (data.instructor_content?.blocks?.length)
if (data.instructor_content)
instructorEditor = renderEditor( instructorEditor = renderEditor(
'instructor-content', 'instructor-content',
data.instructor_content data.instructor_content
@@ -253,7 +255,8 @@ const renderEditor = (holder, content) => {
} }
const markProgress = (data) => { const markProgress = (data) => {
if (!data.progress) progress.submit() console.log(user.data)
if (user.data && !data.progress) progress.submit()
} }
const current_lesson = createResource({ const current_lesson = createResource({

View File

@@ -41,10 +41,20 @@ export const sessionStore = defineStore('lms-session', () => {
}, },
}) })
const branding = createResource({
url: 'lms.lms.api.get_branding',
auto: true,
cache: true,
onSuccess(data) {
document.querySelector("link[rel='icon']").href = data.favicon
},
})
return { return {
user, user,
isLoggedIn, isLoggedIn,
login, login,
logout, logout,
branding,
} }
}) })

View File

@@ -117,9 +117,6 @@ export function getEditorTools() {
paragraph: { paragraph: {
class: Paragraph, class: Paragraph,
inlineToolbar: true, inlineToolbar: true,
config: {
preserveBlank: true,
},
}, },
list: { list: {
class: NestedList, class: NestedList,

View File

@@ -15,11 +15,11 @@ export class Upload {
} }
renderUpload(file) { renderUpload(file) {
if (file.file_type == 'video') { if (this.isVideo(file.file_type)) {
return `<video controls width='100%' controls controlsList='nodownload' class="mb-4"> return `<video controls width='100%' controls controlsList='nodownload' class="mb-4">
<source src=${encodeURI(file.file_url)} type='video/mp4'> <source src=${encodeURI(file.file_url)} type='video/mp4'>
</video>` </video>`
} else if (file.file_type == 'audio') { } else if (this.isAudio(file.file_type)) {
return `<audio controls width='100%' controls controlsList='nodownload' class="mb-4"> return `<audio controls width='100%' controls controlsList='nodownload' class="mb-4">
<source src=${encodeURI(file.file_url)} type='audio/mp3'> <source src=${encodeURI(file.file_url)} type='audio/mp3'>
</audio>` </audio>`
@@ -40,4 +40,12 @@ export class Upload {
file_type: this.data.file_type, file_type: this.data.file_type,
} }
} }
isVideo(type) {
return ['mov', 'mp4', 'avi', 'mkv', 'webm'].includes(type.toLowerCase())
}
isAudio(type) {
return ['mp3', 'wav', 'ogg'].includes(type.toLowerCase())
}
} }

View File

@@ -278,3 +278,13 @@ def get_file_info(file_url):
"File", {"file_url": file_url}, ["file_name", "file_size", "file_url"], as_dict=1 "File", {"file_url": file_url}, ["file_name", "file_size", "file_url"], as_dict=1
) )
return file_info return file_info
@frappe.whitelist(allow_guest=True)
def get_branding():
"""Get branding details."""
return {
"brand_name": frappe.db.get_single_value("Website Settings", "app_name"),
"brand_html": frappe.db.get_single_value("Website Settings", "brand_html"),
"favicon": frappe.db.get_single_value("Website Settings", "favicon"),
}

View File

@@ -15,7 +15,11 @@ class LMSCertificate(Document):
def after_insert(self): def after_insert(self):
if not frappe.flags.in_test: if not frappe.flags.in_test:
self.send_mail() outgoing_email_account = frappe.get_cached_value(
"Email Account", {"default_outgoing": 1, "enable_outgoing": 1}, "name"
)
if outgoing_email_account:
self.send_mail()
def send_mail(self): def send_mail(self):
subject = _("Congratulations on getting certified!") subject = _("Congratulations on getting certified!")

View File

@@ -161,7 +161,6 @@ def get_lesson_details(chapter):
) )
lesson_details.number = f"{chapter.idx}.{row.idx}" lesson_details.number = f"{chapter.idx}.{row.idx}"
lesson_details.icon = get_lesson_icon(lesson_details.body) lesson_details.icon = get_lesson_icon(lesson_details.body)
lesson_details.is_complete = get_progress(lesson_details.course, lesson_details.name)
lessons.append(lesson_details) lessons.append(lesson_details)
return lessons return lessons

View File

@@ -10,7 +10,7 @@
{{ _("With this certification, you can now showcase your updated skills and share your achievement with your colleagues and on LinkedIn. To access your certificate, please click on the link provided below. Make sure you are logged in to the portal.") }} {{ _("With this certification, you can now showcase your updated skills and share your achievement with your colleagues and on LinkedIn. To access your certificate, please click on the link provided below. Make sure you are logged in to the portal.") }}
</p> </p>
<br> <br>
<a href="/api/method/frappe.utils.print_format.download_pdf?doctype=LMS+Certificate&name={{certificate_name}}&format={{encodeURIComponent(template)}}">{{ _("Certificate Link") }}</a> <a href="/api/method/frappe.utils.print_format.download_pdf?doctype=LMS+Certificate&name={{certificate_name}}&format={{template | urlencode }}">{{ _("Certificate Link") }}</a>
<br> <br>
<p> <p>
{{ _("Once again, congratulations on this significant accomplishment.")}} {{ _("Once again, congratulations on this significant accomplishment.")}}

4
yarn.lock Normal file
View File

@@ -0,0 +1,4 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1