fix: branding, course, lesson and certificate creation
This commit is contained in:
@@ -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"
|
||||||
|
|||||||
@@ -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)) {
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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)) {
|
||||||
|
|||||||
@@ -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()
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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({
|
||||||
|
|||||||
@@ -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,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"),
|
||||||
|
}
|
||||||
|
|||||||
@@ -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!")
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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.")}}
|
||||||
|
|||||||
Reference in New Issue
Block a user