fix: branding, course, lesson and certificate creation
This commit is contained in:
@@ -18,7 +18,9 @@
|
||||
</div>
|
||||
</div>
|
||||
<SidebarLink
|
||||
:label="isSidebarCollapsed ? 'Expand' : 'Collapse'"
|
||||
:link="{
|
||||
label: isSidebarCollapsed ? 'Expand' : 'Collapse',
|
||||
}"
|
||||
:isCollapsed="isSidebarCollapsed"
|
||||
@click="isSidebarCollapsed = !isSidebarCollapsed"
|
||||
class="m-2"
|
||||
|
||||
@@ -97,10 +97,6 @@ const addFile = (data) => {
|
||||
getCurrentEditor().blocks.insert('upload', data)
|
||||
}
|
||||
|
||||
const getBlocksCount = () => {
|
||||
return getCurrentEditor().blocks.getBlocksCount
|
||||
}
|
||||
|
||||
const validateFile = (file) => {
|
||||
let extension = file.name.split('.').pop().toLowerCase()
|
||||
if (!['jpg', 'jpeg', 'png', 'mp4', 'mov', 'mp3'].includes(extension)) {
|
||||
|
||||
@@ -11,7 +11,12 @@
|
||||
: '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
|
||||
class="flex flex-1 flex-col text-left duration-300 ease-in-out"
|
||||
:class="
|
||||
@@ -21,7 +26,10 @@
|
||||
"
|
||||
>
|
||||
<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 v-if="user" class="mt-1 text-sm text-gray-700 leading-none">
|
||||
{{ convertToTitleCase(user.split('@')[0]) }}
|
||||
@@ -49,6 +57,7 @@ import { Dropdown } from 'frappe-ui'
|
||||
import { ChevronDown, LogIn, LogOut, User } from 'lucide-vue-next'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { convertToTitleCase } from '../utils'
|
||||
import { onMounted } from 'vue'
|
||||
|
||||
const router = useRouter()
|
||||
const props = defineProps({
|
||||
@@ -58,8 +67,9 @@ const props = defineProps({
|
||||
},
|
||||
})
|
||||
|
||||
const { logout, user } = sessionStore()
|
||||
const { logout, user, branding } = sessionStore()
|
||||
let { isLoggedIn } = sessionStore()
|
||||
|
||||
const userDropdownOptions = [
|
||||
/* {
|
||||
icon: User,
|
||||
|
||||
@@ -187,14 +187,16 @@ import {
|
||||
FormControl,
|
||||
FileUploader,
|
||||
} 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 Link from '@/components/Controls/Link.vue'
|
||||
import { FileText, X } from 'lucide-vue-next'
|
||||
import { useRouter } from 'vue-router'
|
||||
import CourseOutline from '@/components/CourseOutline.vue'
|
||||
|
||||
const user = inject('$user')
|
||||
const newTag = ref('')
|
||||
const router = useRouter()
|
||||
|
||||
const props = defineProps({
|
||||
courseName: {
|
||||
@@ -318,8 +320,12 @@ const submitCourse = () => {
|
||||
)
|
||||
} else {
|
||||
courseCreationResource.submit(course, {
|
||||
onSuccess() {
|
||||
onSuccess(data) {
|
||||
showToast('Success', 'Course created successfully', 'check')
|
||||
router.push({
|
||||
name: 'CreateCourse',
|
||||
params: { courseName: data.name },
|
||||
})
|
||||
},
|
||||
onError(err) {
|
||||
showToast(err)
|
||||
@@ -347,6 +353,15 @@ const validateMandatoryFields = () => {
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.courseName !== 'new',
|
||||
(newVal) => {
|
||||
if (newVal) {
|
||||
courseResource.reload()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
const validateFile = (file) => {
|
||||
let extension = file.name.split('.').pop().toLowerCase()
|
||||
if (!['jpg', 'jpeg', 'png'].includes(extension)) {
|
||||
|
||||
@@ -73,17 +73,19 @@ import {
|
||||
Button,
|
||||
createDocumentResource,
|
||||
} 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 { createToast } from '../utils'
|
||||
import LessonPlugins from '@/components/LessonPlugins.vue'
|
||||
import { getEditorTools } from '../utils'
|
||||
import { ChevronRight } from 'lucide-vue-next'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
const editor = ref(null)
|
||||
const instructorEditor = ref(null)
|
||||
const user = inject('$user')
|
||||
const openInstructorEditor = ref(false)
|
||||
const router = useRouter()
|
||||
|
||||
const props = defineProps({
|
||||
courseName: {
|
||||
@@ -319,6 +321,7 @@ const createNewLesson = () => {
|
||||
{
|
||||
onSuccess() {
|
||||
showToast('Success', 'Lesson created successfully', 'check')
|
||||
lessonDetails.reload()
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
@@ -114,7 +114,10 @@
|
||||
</span>
|
||||
</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"
|
||||
>
|
||||
<div class="text-gray-600 font-medium">
|
||||
@@ -233,8 +236,7 @@ const lesson = createResource({
|
||||
markProgress(data)
|
||||
|
||||
if (data.content) editor = renderEditor('editor', data.content)
|
||||
|
||||
if (data.instructor_content)
|
||||
if (data.instructor_content?.blocks?.length)
|
||||
instructorEditor = renderEditor(
|
||||
'instructor-content',
|
||||
data.instructor_content
|
||||
@@ -253,7 +255,8 @@ const renderEditor = (holder, content) => {
|
||||
}
|
||||
|
||||
const markProgress = (data) => {
|
||||
if (!data.progress) progress.submit()
|
||||
console.log(user.data)
|
||||
if (user.data && !data.progress) progress.submit()
|
||||
}
|
||||
|
||||
const current_lesson = createResource({
|
||||
|
||||
@@ -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 {
|
||||
user,
|
||||
isLoggedIn,
|
||||
login,
|
||||
logout,
|
||||
branding,
|
||||
}
|
||||
})
|
||||
|
||||
@@ -117,9 +117,6 @@ export function getEditorTools() {
|
||||
paragraph: {
|
||||
class: Paragraph,
|
||||
inlineToolbar: true,
|
||||
config: {
|
||||
preserveBlank: true,
|
||||
},
|
||||
},
|
||||
list: {
|
||||
class: NestedList,
|
||||
|
||||
@@ -15,11 +15,11 @@ export class Upload {
|
||||
}
|
||||
|
||||
renderUpload(file) {
|
||||
if (file.file_type == 'video') {
|
||||
if (this.isVideo(file.file_type)) {
|
||||
return `<video controls width='100%' controls controlsList='nodownload' class="mb-4">
|
||||
<source src=${encodeURI(file.file_url)} type='video/mp4'>
|
||||
</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">
|
||||
<source src=${encodeURI(file.file_url)} type='audio/mp3'>
|
||||
</audio>`
|
||||
@@ -40,4 +40,12 @@ export class Upload {
|
||||
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())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -278,3 +278,13 @@ def get_file_info(file_url):
|
||||
"File", {"file_url": file_url}, ["file_name", "file_size", "file_url"], as_dict=1
|
||||
)
|
||||
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"),
|
||||
}
|
||||
|
||||
@@ -15,7 +15,11 @@ class LMSCertificate(Document):
|
||||
|
||||
def after_insert(self):
|
||||
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):
|
||||
subject = _("Congratulations on getting certified!")
|
||||
|
||||
@@ -161,7 +161,6 @@ def get_lesson_details(chapter):
|
||||
)
|
||||
lesson_details.number = f"{chapter.idx}.{row.idx}"
|
||||
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)
|
||||
return lessons
|
||||
|
||||
|
||||
@@ -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.") }}
|
||||
</p>
|
||||
<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>
|
||||
<p>
|
||||
{{ _("Once again, congratulations on this significant accomplishment.")}}
|
||||
|
||||
Reference in New Issue
Block a user