Compare commits
21 Commits
sketch-cle
...
disable-ex
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
51d5db01e9 | ||
|
|
0fd760df81 | ||
|
|
71d0a89968 | ||
|
|
d939a63412 | ||
|
|
daaa2d2fe2 | ||
|
|
6dd7cb19df | ||
|
|
b1de2481a8 | ||
|
|
27c01b3b0c | ||
|
|
b7aa9aff51 | ||
|
|
524a041fb9 | ||
|
|
9de0203914 | ||
|
|
0ed5309b97 | ||
|
|
68fd32d536 | ||
|
|
5ea3b25d21 | ||
|
|
45d88bdc08 | ||
|
|
ee8273fd30 | ||
|
|
60c1449f40 | ||
|
|
5e916dc2c8 | ||
|
|
0c64d46e99 | ||
|
|
3aa974f8bd | ||
|
|
9bc5408a44 |
3
.gitignore
vendored
@@ -5,3 +5,6 @@
|
||||
tags
|
||||
community/docs/current
|
||||
community/public/dist
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
108
community/fixtures/custom_field.json
Normal file
@@ -0,0 +1,108 @@
|
||||
[
|
||||
{
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"collapsible_depends_on": null,
|
||||
"columns": 0,
|
||||
"default": null,
|
||||
"depends_on": null,
|
||||
"description": null,
|
||||
"docstatus": 0,
|
||||
"doctype": "Custom Field",
|
||||
"dt": "User",
|
||||
"fetch_from": null,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "linkedin",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"hide_border": 0,
|
||||
"hide_days": 0,
|
||||
"hide_seconds": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_preview": 0,
|
||||
"in_standard_filter": 0,
|
||||
"insert_after": "mobile_no",
|
||||
"label": "LinkedIn ID",
|
||||
"length": 0,
|
||||
"mandatory_depends_on": null,
|
||||
"modified": "2021-06-30 14:46:55.834145",
|
||||
"name": "User-linkedin",
|
||||
"no_copy": 0,
|
||||
"non_negative": 0,
|
||||
"options": null,
|
||||
"parent": null,
|
||||
"parentfield": null,
|
||||
"parenttype": null,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"print_width": null,
|
||||
"read_only": 0,
|
||||
"read_only_depends_on": null,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"translatable": 1,
|
||||
"unique": 0,
|
||||
"width": null
|
||||
},
|
||||
{
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"collapsible_depends_on": null,
|
||||
"columns": 0,
|
||||
"default": null,
|
||||
"depends_on": null,
|
||||
"description": null,
|
||||
"docstatus": 0,
|
||||
"doctype": "Custom Field",
|
||||
"dt": "User",
|
||||
"fetch_from": null,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "github",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"hide_border": 0,
|
||||
"hide_days": 0,
|
||||
"hide_seconds": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_preview": 0,
|
||||
"in_standard_filter": 0,
|
||||
"insert_after": "linkedin",
|
||||
"label": "Github ID",
|
||||
"length": 0,
|
||||
"mandatory_depends_on": null,
|
||||
"modified": "2021-06-30 14:46:55.834145",
|
||||
"name": "User-github",
|
||||
"no_copy": 0,
|
||||
"non_negative": 0,
|
||||
"options": null,
|
||||
"parent": null,
|
||||
"parentfield": null,
|
||||
"parenttype": null,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"print_width": null,
|
||||
"read_only": 0,
|
||||
"read_only_depends_on": null,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"translatable": 1,
|
||||
"unique": 0,
|
||||
"width": null
|
||||
}
|
||||
]
|
||||
@@ -104,6 +104,8 @@ doc_events = {
|
||||
# ]
|
||||
#}
|
||||
|
||||
fixtures = ["Custom Field"]
|
||||
|
||||
# Testing
|
||||
# -------
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe.model.document import Document
|
||||
from ...utils import slugify
|
||||
|
||||
class Chapter(Document):
|
||||
def get_lessons(self):
|
||||
@@ -13,3 +14,6 @@ class Chapter(Document):
|
||||
fields='name',
|
||||
order_by="index_")
|
||||
return [frappe.get_doc('Lesson', row['name']) for row in rows]
|
||||
|
||||
def get_slugified_chapter_title(self):
|
||||
return slugify(self.title)
|
||||
|
||||
@@ -63,32 +63,35 @@ class Lesson(Document):
|
||||
return
|
||||
|
||||
@frappe.whitelist()
|
||||
def save_progress(lesson, course):
|
||||
def save_progress(lesson, course, status):
|
||||
if not frappe.db.exists("LMS Batch Membership",
|
||||
{
|
||||
"member": frappe.session.user,
|
||||
"course": course
|
||||
}):
|
||||
return
|
||||
|
||||
if frappe.db.exists("LMS Course Progress",
|
||||
{
|
||||
"lesson": lesson,
|
||||
"owner": frappe.session.user
|
||||
"owner": frappe.session.user,
|
||||
"course": course
|
||||
}):
|
||||
return
|
||||
|
||||
lesson_details = frappe.get_doc("Lesson", lesson)
|
||||
dynamic_content = find_macros(lesson_details.body)
|
||||
|
||||
status = "Complete"
|
||||
if dynamic_content:
|
||||
status = "Partially Complete"
|
||||
|
||||
frappe.get_doc({
|
||||
"doctype": "LMS Course Progress",
|
||||
"lesson": lesson_details.name,
|
||||
"status": status
|
||||
}).save(ignore_permissions=True)
|
||||
doc = frappe.get_doc("LMS Course Progress",
|
||||
{
|
||||
"lesson": lesson,
|
||||
"owner": frappe.session.user,
|
||||
"course": course
|
||||
})
|
||||
doc.status = status
|
||||
doc.save(ignore_permissions=True)
|
||||
else:
|
||||
frappe.get_doc({
|
||||
"doctype": "LMS Course Progress",
|
||||
"lesson": lesson,
|
||||
"status": status,
|
||||
}).save(ignore_permissions=True)
|
||||
return "OK"
|
||||
|
||||
def update_progress(lesson):
|
||||
user = frappe.session.user
|
||||
|
||||
@@ -10,5 +10,5 @@ frappe.ui.form.on('LMS Batch Membership', {
|
||||
}
|
||||
};
|
||||
});
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
@@ -65,8 +65,7 @@
|
||||
"fieldname": "course",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Course",
|
||||
"read_only": 1
|
||||
"label": "Course"
|
||||
},
|
||||
{
|
||||
"fieldname": "current_lesson",
|
||||
@@ -84,7 +83,7 @@
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2021-06-21 12:10:28.808803",
|
||||
"modified": "2021-07-06 20:50:46.885325",
|
||||
"modified_by": "Administrator",
|
||||
"module": "LMS",
|
||||
"name": "LMS Batch Membership",
|
||||
|
||||
@@ -21,12 +21,14 @@
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"title",
|
||||
"is_published",
|
||||
"disable_self_learning",
|
||||
"column_break_3",
|
||||
"short_code",
|
||||
"video_link",
|
||||
"column_break_3",
|
||||
"is_published",
|
||||
"disable_self_learning",
|
||||
"image",
|
||||
"section_break_5",
|
||||
"tags",
|
||||
"short_introduction",
|
||||
"description"
|
||||
],
|
||||
@@ -80,6 +82,16 @@
|
||||
"fieldname": "disable_self_learning",
|
||||
"fieldtype": "Check",
|
||||
"label": "Disable Self Learning"
|
||||
},
|
||||
{
|
||||
"fieldname": "image",
|
||||
"fieldtype": "Attach Image",
|
||||
"label": "Preview Image"
|
||||
},
|
||||
{
|
||||
"fieldname": "tags",
|
||||
"fieldtype": "Data",
|
||||
"label": "Tags"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
@@ -106,7 +118,7 @@
|
||||
"link_fieldname": "course"
|
||||
}
|
||||
],
|
||||
"modified": "2021-06-21 11:34:04.552376",
|
||||
"modified": "2021-07-09 15:05:05.372430",
|
||||
"modified_by": "Administrator",
|
||||
"module": "LMS",
|
||||
"name": "LMS Course",
|
||||
|
||||
@@ -8,7 +8,7 @@ from frappe.model.document import Document
|
||||
import json
|
||||
from ...utils import slugify
|
||||
from community.query import find, find_all
|
||||
from frappe.utils import flt
|
||||
from frappe.utils import flt, cint
|
||||
|
||||
class LMSCourse(Document):
|
||||
@staticmethod
|
||||
@@ -115,6 +115,26 @@ class LMSCourse(Document):
|
||||
# TODO: chapters should have a way to specify the order
|
||||
return find_all("Chapter", course=self.name, order_by="index_")
|
||||
|
||||
def get_lessons(self):
|
||||
""" Returns all lessons of this course """
|
||||
lessons = []
|
||||
chapters = self.get_chapters()
|
||||
for chapter in chapters:
|
||||
lessons.append(frappe.get_all("Lesson", {"chapter": chapter.name}))
|
||||
return lessons
|
||||
|
||||
def get_course_progress(self):
|
||||
""" Returns the course progress of the session user """
|
||||
lesson_count = len(self.get_lessons())
|
||||
completed_lessons = frappe.db.count("LMS Course Progress",
|
||||
{
|
||||
"course": self.name,
|
||||
"owner": frappe.session.user,
|
||||
"status": "Complete"
|
||||
})
|
||||
precision = cint(frappe.db.get_default("float_precision")) or 3
|
||||
return flt(((completed_lessons/lesson_count) * 100), precision)
|
||||
|
||||
def get_batch(self, batch_name):
|
||||
return find("LMS Batch", name=batch_name, course=self.name)
|
||||
|
||||
@@ -199,7 +219,12 @@ class LMSCourse(Document):
|
||||
}
|
||||
if batch:
|
||||
filters["batch"] = batch
|
||||
membership = frappe.db.get_value("LMS Batch Membership", filters, ["name","batch", "current_lesson"], as_dict=True)
|
||||
|
||||
membership = frappe.db.get_value("LMS Batch Membership",
|
||||
filters,
|
||||
["name", "batch", "current_lesson", "member_type"],
|
||||
as_dict=True)
|
||||
|
||||
if membership and membership.batch:
|
||||
membership.batch_title = frappe.db.get_value("LMS Batch", membership.batch, "title")
|
||||
return membership
|
||||
@@ -241,9 +266,52 @@ class LMSCourse(Document):
|
||||
member_names = [m['member'] for m in memberships]
|
||||
return find_all("User", name=["IN", member_names])
|
||||
|
||||
def get_tags(self):
|
||||
return self.tags.split(",") if self.tags else []
|
||||
|
||||
def get_reviews(self):
|
||||
reviews = frappe.get_all("LMS Course Review",
|
||||
{
|
||||
"course": self.name
|
||||
},
|
||||
["review", "rating", "owner"],
|
||||
order_by= "creation desc")
|
||||
|
||||
for review in reviews:
|
||||
review.owner_details = frappe.get_doc("User", review.owner)
|
||||
|
||||
return reviews
|
||||
|
||||
def is_eligible_to_review(self, membership):
|
||||
""" Checks if user is eligible to review the course """
|
||||
if not membership:
|
||||
return False
|
||||
if frappe.db.count("LMS Course Review",
|
||||
{
|
||||
"course": self.name,
|
||||
"owner": frappe.session.user
|
||||
}):
|
||||
return False
|
||||
return True
|
||||
|
||||
def get_average_rating(self):
|
||||
ratings = [review.rating for review in self.get_reviews()]
|
||||
if not len(ratings):
|
||||
return None
|
||||
return sum(ratings)/len(ratings)
|
||||
|
||||
def get_outline(self):
|
||||
return CourseOutline(self)
|
||||
|
||||
def get_progress(self, lesson):
|
||||
return frappe.db.get_value("LMS Course Progress",
|
||||
{
|
||||
"course": self.name,
|
||||
"owner": frappe.session.user,
|
||||
"lesson": lesson
|
||||
},
|
||||
["status"])
|
||||
|
||||
class CourseOutline:
|
||||
def __init__(self, course):
|
||||
self.course = course
|
||||
|
||||
@@ -2,7 +2,13 @@
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('LMS Course Mentor Mapping', {
|
||||
// refresh: function(frm) {
|
||||
|
||||
// }
|
||||
onload: function(frm) {
|
||||
frm.set_query('mentor', function(doc) {
|
||||
return {
|
||||
filters: {
|
||||
"ignore_user_type": 1,
|
||||
}
|
||||
};
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
0
community/lms/doctype/lms_course_review/__init__.py
Normal file
@@ -0,0 +1,8 @@
|
||||
// Copyright (c) 2021, FOSS United and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('LMS Course Review', {
|
||||
// refresh: function(frm) {
|
||||
|
||||
// }
|
||||
});
|
||||
@@ -0,0 +1,57 @@
|
||||
{
|
||||
"actions": [],
|
||||
"creation": "2021-06-28 13:36:36.146718",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"review",
|
||||
"rating",
|
||||
"course"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "review",
|
||||
"fieldtype": "Small Text",
|
||||
"in_list_view": 1,
|
||||
"label": "Review"
|
||||
},
|
||||
{
|
||||
"fieldname": "rating",
|
||||
"fieldtype": "Rating",
|
||||
"in_list_view": 1,
|
||||
"label": "Rating"
|
||||
},
|
||||
{
|
||||
"fieldname": "course",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Course",
|
||||
"options": "LMS Course"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2021-07-05 14:57:03.841430",
|
||||
"modified_by": "Administrator",
|
||||
"module": "LMS",
|
||||
"name": "LMS Course Review",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
||||
18
community/lms/doctype/lms_course_review/lms_course_review.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# Copyright (c) 2021, FOSS United and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
class LMSCourseReview(Document):
|
||||
pass
|
||||
|
||||
@frappe.whitelist()
|
||||
def submit_review(rating, review, course):
|
||||
frappe.get_doc({
|
||||
"doctype": "LMS Course Review",
|
||||
"rating": rating,
|
||||
"review": review,
|
||||
"course": course
|
||||
}).save(ignore_permissions=True)
|
||||
return "OK"
|
||||
@@ -0,0 +1,8 @@
|
||||
# Copyright (c) 2021, FOSS United and Contributors
|
||||
# See license.txt
|
||||
|
||||
# import frappe
|
||||
import unittest
|
||||
|
||||
class TestLMSCourseReview(unittest.TestCase):
|
||||
pass
|
||||
0
community/lms/web_form/profile/__init__.py
Normal file
3
community/lms/web_form/profile/profile.js
Normal file
@@ -0,0 +1,3 @@
|
||||
frappe.ready(function() {
|
||||
// bind events here
|
||||
})
|
||||
125
community/lms/web_form/profile/profile.json
Normal file
@@ -0,0 +1,125 @@
|
||||
{
|
||||
"accept_payment": 0,
|
||||
"allow_comments": 0,
|
||||
"allow_delete": 0,
|
||||
"allow_edit": 1,
|
||||
"allow_incomplete": 0,
|
||||
"allow_multiple": 0,
|
||||
"allow_print": 0,
|
||||
"amount": 0.0,
|
||||
"amount_based_on_field": 0,
|
||||
"apply_document_permissions": 0,
|
||||
"breadcrumbs": "",
|
||||
"button_label": "Save",
|
||||
"creation": "2021-06-30 13:48:13.682851",
|
||||
"doc_type": "User",
|
||||
"docstatus": 0,
|
||||
"doctype": "Web Form",
|
||||
"idx": 0,
|
||||
"is_standard": 1,
|
||||
"login_required": 1,
|
||||
"max_attachment_size": 0,
|
||||
"modified": "2021-06-30 15:53:20.967466",
|
||||
"modified_by": "Administrator",
|
||||
"module": "LMS",
|
||||
"name": "profile",
|
||||
"owner": "Administrator",
|
||||
"payment_button_label": "Buy Now",
|
||||
"published": 1,
|
||||
"route": "profile",
|
||||
"route_to_success_link": 0,
|
||||
"show_attachments": 0,
|
||||
"show_in_grid": 0,
|
||||
"show_sidebar": 0,
|
||||
"sidebar_items": [],
|
||||
"success_url": "/profile",
|
||||
"title": "Profile",
|
||||
"web_form_fields": [
|
||||
{
|
||||
"allow_read_on_all_link_options": 0,
|
||||
"fieldname": "first_name",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"label": "First Name",
|
||||
"max_length": 0,
|
||||
"max_value": 0,
|
||||
"read_only": 0,
|
||||
"reqd": 1,
|
||||
"show_in_filter": 0
|
||||
},
|
||||
{
|
||||
"allow_read_on_all_link_options": 0,
|
||||
"fieldname": "middle_name",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"label": "Middle Name (Optional)",
|
||||
"max_length": 0,
|
||||
"max_value": 0,
|
||||
"read_only": 0,
|
||||
"reqd": 0,
|
||||
"show_in_filter": 0
|
||||
},
|
||||
{
|
||||
"allow_read_on_all_link_options": 0,
|
||||
"fieldname": "last_name",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"label": "Last Name",
|
||||
"max_length": 0,
|
||||
"max_value": 0,
|
||||
"read_only": 0,
|
||||
"reqd": 0,
|
||||
"show_in_filter": 0
|
||||
},
|
||||
{
|
||||
"allow_read_on_all_link_options": 0,
|
||||
"description": "Get your globally recognized avatar from Gravatar.com",
|
||||
"fieldname": "user_image",
|
||||
"fieldtype": "Attach Image",
|
||||
"hidden": 0,
|
||||
"label": "User Image",
|
||||
"max_length": 0,
|
||||
"max_value": 0,
|
||||
"read_only": 0,
|
||||
"reqd": 0,
|
||||
"show_in_filter": 0
|
||||
},
|
||||
{
|
||||
"allow_read_on_all_link_options": 0,
|
||||
"fieldname": "username",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"label": "Username",
|
||||
"max_length": 0,
|
||||
"max_value": 0,
|
||||
"read_only": 0,
|
||||
"reqd": 0,
|
||||
"show_in_filter": 0
|
||||
},
|
||||
{
|
||||
"allow_read_on_all_link_options": 0,
|
||||
"fieldname": "mobile_no",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"label": "Mobile No",
|
||||
"max_length": 0,
|
||||
"max_value": 0,
|
||||
"options": "Phone",
|
||||
"read_only": 0,
|
||||
"reqd": 0,
|
||||
"show_in_filter": 0
|
||||
},
|
||||
{
|
||||
"allow_read_on_all_link_options": 0,
|
||||
"fieldname": "bio",
|
||||
"fieldtype": "Small Text",
|
||||
"hidden": 0,
|
||||
"label": "Bio",
|
||||
"max_length": 0,
|
||||
"max_value": 0,
|
||||
"read_only": 0,
|
||||
"reqd": 0,
|
||||
"show_in_filter": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
5
community/lms/web_form/profile/profile.py
Normal file
@@ -0,0 +1,5 @@
|
||||
import frappe
|
||||
|
||||
def get_context(context):
|
||||
# do your magic here
|
||||
pass
|
||||
19
community/lms/widgets/BreadCrumb.html
Normal file
@@ -0,0 +1,19 @@
|
||||
<div class="course-instructor breadcrumb">
|
||||
<a class="dark-links" href="/courses">All Courses</a>
|
||||
<img class="ml-1 mr-1" src="/assets/community/icons/chevron-right.svg">
|
||||
|
||||
{% if course %}
|
||||
{% if lesson %}
|
||||
<a class="dark-links" href="/courses/{{ course.name }}">{{ course.title }}</a>
|
||||
<img class="ml-1 mr-1" src="/assets/community/icons/chevron-right.svg">
|
||||
<span class="muted-text"> {{ lesson.title }}</span>
|
||||
{% else %}
|
||||
<span class="muted-text">{{ course.title }}</span>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% if member_name %}
|
||||
<span class="muted-text">{{ member_name }}</span>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
@@ -1,28 +1,98 @@
|
||||
<div class="chapter-teaser">
|
||||
<div class="teaser-body">
|
||||
<div class="chapter-title mb-5 font-weight-bold"><span class="mr-1">{{index}}.</span> {{ chapter.title }}</div>
|
||||
<div class="chapter-description">
|
||||
{{ chapter.description or "" }}
|
||||
<div>
|
||||
|
||||
<div class="small-title chapter-title" data-target="#{{ chapter.get_slugified_chapter_title() }}"
|
||||
data-toggle="collapse" aria-expanded="false">
|
||||
<img class="chapter-icon" src="/assets/community/icons/chevron-right.svg">
|
||||
{{ index }}. {{ chapter.title }}
|
||||
</div>
|
||||
|
||||
<div class="chapter-content collapse navbar-collapse" id="{{ chapter.get_slugified_chapter_title() }}">
|
||||
|
||||
<div class="chapter-description muted-text">
|
||||
{{ chapter.description }}
|
||||
</div>
|
||||
<div class="chapter-lessons">
|
||||
|
||||
<div class="lessons">
|
||||
|
||||
{% for lesson in chapter.get_lessons() %}
|
||||
<div class="lesson-teaser">
|
||||
{% if show_link or lesson.include_in_preview %}
|
||||
<a class="" href="{{ course.get_learn_url(course.get_lesson_index(lesson.name)) }}{{course.query_parameter}}"
|
||||
data-course="{{ course.name }}">{{ lesson.title }}</a>
|
||||
|
||||
<div class="lesson-info {% if membership.current_lesson == lesson.name %} active-lesson {% endif %}">
|
||||
|
||||
{% if membership or lesson.include_in_preview %}
|
||||
<a class="lesson-links"
|
||||
href="{{ course.get_learn_url(course.get_lesson_index(lesson.name)) }}{{course.query_parameter}}"
|
||||
data-course="{{ course.name }}">
|
||||
{{ lesson.title }}
|
||||
|
||||
{% if membership %}
|
||||
<img class="{{ course.get_progress(lesson.name) != 'Complete' and 'hide' }}"
|
||||
src="/assets/community/icons/check.svg">
|
||||
{% endif %}
|
||||
|
||||
</a>
|
||||
|
||||
{% else %}
|
||||
<div class="no-preview" title="This lesson is not available for preview">
|
||||
<span style="color: #2490ef;">
|
||||
<div class="lesson-links">
|
||||
{{ lesson.title }}
|
||||
</span>
|
||||
<i class="fa fa-lock ml-2"></i>
|
||||
<img class="ml-2" src="/assets/community/icons/lock.svg">
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if show_progress and not course.is_mentor(frappe.session.user) and lesson.get_progress() %}
|
||||
<span class="ml-5 badge p-2 {{ lesson.get_slugified_class() }}"> {{ lesson.get_progress() }}</span>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if index != course.get_chapters() | length %}
|
||||
<div class="card-divider"></div>
|
||||
{% endif %}
|
||||
|
||||
<script>
|
||||
frappe.ready(() => {
|
||||
expand_the_active_chapter();
|
||||
})
|
||||
|
||||
var expand_the_first_chapter = () => {
|
||||
var elements = $(".collapse");
|
||||
elements.each((i, element) => {
|
||||
if (i <= 1) {
|
||||
show_section(element);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
var expand_the_active_chapter = () => {
|
||||
|
||||
/* Find anchor matching the URL for course details page */
|
||||
var selector = $(`a[href="${decodeURIComponent(window.location.pathname)}"]`).parent();
|
||||
if (!selector.length) {
|
||||
selector = $(`a[href^="${decodeURIComponent(window.location.pathname)}"]`).parent();
|
||||
}
|
||||
if (selector.length && $(".course-details-page").length) {
|
||||
$(".lesson-info").removeClass("active-lesson")
|
||||
selector.addClass("active-lesson");
|
||||
show_section(selector.parent().parent());
|
||||
}
|
||||
|
||||
/* For course home page */
|
||||
else if ($(".active-lesson").length) {
|
||||
selector = $(".active-lesson")
|
||||
show_section(selector.parent().parent());
|
||||
}
|
||||
|
||||
/* If no active chapter then exapand the first chapter */
|
||||
else {
|
||||
expand_the_first_chapter();
|
||||
}
|
||||
}
|
||||
|
||||
var show_section = (element) => {
|
||||
$(element).addClass("show");
|
||||
$(element).siblings(".chapter-title").children(".chapter-icon").css("transform", "rotate(90deg)");
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
63
community/lms/widgets/CourseCard.html
Normal file
@@ -0,0 +1,63 @@
|
||||
<div class="common-card-style course-card">
|
||||
<div class="course-image" style="background-image: url({{ course.image }});">
|
||||
<div class="course-tags">
|
||||
{% for tag in course.get_tags() %}
|
||||
<div class="course-card-pills">{{ tag }}</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="course-card-content">
|
||||
<div class="course-card-meta muted-text">
|
||||
{% if course.get_chapters() | length %}
|
||||
<span>
|
||||
{{ course.get_chapters() | length }} Chapters
|
||||
</span>
|
||||
{% endif %}
|
||||
{% if course.get_chapters() | length and course.get_upcoming_batches() | length %}
|
||||
<span class="font-weight-bold ml-3 mr-3"> . </span>
|
||||
{% endif %}
|
||||
{% if course.get_upcoming_batches() | length %}
|
||||
<span class="">
|
||||
{{ course.get_upcoming_batches() | length }} Open Batches
|
||||
</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="course-card-title">{{ course.title }}</div>
|
||||
<div class="card-divider"></div>
|
||||
<div class="course-card-meta-2">
|
||||
{{ widgets.Avatar(member=course.get_instructor(), avatar_class="avatar-small") }}
|
||||
<span class="course-instructor">
|
||||
{{ course.get_instructor().full_name }}
|
||||
</span>
|
||||
{% if course.get_students() | length %}
|
||||
<span class="course-student-count">
|
||||
<img class="icon-background mr-1" src="/assets/community/icons/user.svg" />
|
||||
{{ course.get_students() | length }}
|
||||
</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% set membership = course.get_membership(frappe.session.user) %}
|
||||
|
||||
{% set lesson_index = course.get_lesson_index(membership.current_lesson) if membership and
|
||||
membership.current_lesson
|
||||
else '1.1' %}
|
||||
|
||||
{% set query_parameter = "?batch=" + membership.batch if membership and membership.batch else "" %}
|
||||
|
||||
{% if membership %}
|
||||
|
||||
<div class="view-course-link is-primary">
|
||||
Continue Course <img class="ml-3" src="/assets/community/icons/white-arrow.svg" />
|
||||
</div>
|
||||
<a class="stretched-link" href="{{ course.get_learn_url(lesson_index) }}{{ query_parameter }}"></a>
|
||||
|
||||
{% else %}
|
||||
|
||||
<div class="view-course-link">
|
||||
View Course <img class="ml-3" src="/assets/community/icons/black-arrow.svg" />
|
||||
</div>
|
||||
<a class="stretched-link" href="/courses/{{ course.name }}"></a>
|
||||
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,7 +1,12 @@
|
||||
<div class="mt-5">
|
||||
<h3> Course Outline </h3>
|
||||
|
||||
{% for chapter in course.get_chapters() %}
|
||||
{{ widgets.ChapterTeaser(index=loop.index, chapter=chapter, course=course, batch=batch, show_link=show_link, show_progress=show_progress)}}
|
||||
{% endfor %}
|
||||
{% if course.get_chapters() | length %}
|
||||
<div class="">
|
||||
<div class="course-home-headings">
|
||||
Course Outline
|
||||
</div>
|
||||
<div class="coure-outline">
|
||||
{% for chapter in course.get_chapters() %}
|
||||
{{ widgets.ChapterTeaser(index=loop.index, chapter=chapter, course=course, batch=batch, membership=membership) }}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
15
community/lms/widgets/MemberCard.html
Normal file
@@ -0,0 +1,15 @@
|
||||
<div class="common-card-style member-card {{dimension_class}} ">
|
||||
{% set avatar_class = "avatar-large" if dimension_class == "member-card-medium" else "avatar-xl"%}
|
||||
{{ widgets.Avatar(member=member, avatar_class=avatar_class) }}
|
||||
<div class="small-title member-card-title">
|
||||
{{ member.full_name }}
|
||||
</div>
|
||||
{% set course_count = member.get_authored_courses() | length %}
|
||||
{% if show_course_count and course_count > 0 %}
|
||||
{% set suffix = "Courses" if course_count > 1 else "Course" %}
|
||||
<div class="small-title">
|
||||
Created {{ course_count }} {{ suffix }}
|
||||
</div>
|
||||
{% endif %}
|
||||
<a class="stretched-link" href="/{{ member.username }}"></a>
|
||||
</div>
|
||||
85
community/lms/widgets/Reviews.html
Normal file
@@ -0,0 +1,85 @@
|
||||
{% if course.get_reviews() | length %}
|
||||
<div class="reviews-parent">
|
||||
<div class="reviews-heading">
|
||||
<div class="course-home-headings">Review</div>
|
||||
{% if course.is_eligible_to_review(membership) %}
|
||||
<a class="review-link" href="">
|
||||
Provide your Feedback
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="mentors-section">
|
||||
{% for review in course.get_reviews() %}
|
||||
<div class="review-card">
|
||||
<div class="common-card-style review-content small-title"> {{ review.review }} </div>
|
||||
<div class="review-card-footer">
|
||||
<div>
|
||||
{{ widgets.Avatar(member=review.owner_details, avatar_class="avatar-medium") }}
|
||||
<span class="course-instructor">
|
||||
{{ review.owner_details.full_name }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="rating">
|
||||
{% for i in [1, 2, 3, 4, 5] %}
|
||||
<svg class="icon icon-md {% if i <= review.rating %} star-click {% endif %}" data-rating="{{ i }}">
|
||||
<use href="#icon-star"></use>
|
||||
</svg>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal fade review-modal" id="review-modal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel"
|
||||
aria-hidden="true">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<div class="course-home-headings modal-headings">Review</div>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form class="review-form" id="review-form">
|
||||
<div class="form-group">
|
||||
<div class="clearfix">
|
||||
<label class="control-label reqd" style="padding-right: 0px;">Rating</label>
|
||||
</div>
|
||||
<div class="control-input-wrapper">
|
||||
<div class="control-input">
|
||||
<div class="rating rating-field" id="rating">
|
||||
{% for i in [1, 2, 3, 4, 5] %}
|
||||
<svg class="icon icon-md icon-rating" data-rating="{{ i }}">
|
||||
<use href="#icon-star"></use>
|
||||
</svg>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="clearfix">
|
||||
<label class="control-label reqd" style="padding-right: 0px;">Review</label>
|
||||
</div>
|
||||
<div class="control-input-wrapper">
|
||||
<div class="control-input">
|
||||
<textarea type="text" autocomplete="off" class="input-with-feedback form-control review-field" data-fieldtype="Text"
|
||||
data-fieldname="feedback_comments" placeholder="" style="height: 300px;"
|
||||
spellcheck="false"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="error-field muted-text"></p>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<div class="button wide-button submit-review is-primary" data-course="{{ course.name | urlencode}}" id="submit-review">Submit</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
@@ -5,15 +5,18 @@ import hashlib
|
||||
|
||||
class CustomUser(User):
|
||||
|
||||
def get_course_count(self) -> int:
|
||||
def get_authored_courses(self) -> int:
|
||||
"""Returns the number of courses authored by this user.
|
||||
"""
|
||||
return frappe.db.count(
|
||||
return frappe.get_all(
|
||||
'LMS Course', {
|
||||
'owner': self.email
|
||||
'owner': self.name
|
||||
})
|
||||
|
||||
def get_palette(self):
|
||||
"""
|
||||
Returns a color unique to each member for Avatar """
|
||||
|
||||
palette = [
|
||||
['--orange-avatar-bg', '--orange-avatar-color'],
|
||||
['--pink-avatar-bg', '--pink-avatar-color'],
|
||||
@@ -40,3 +43,19 @@ class CustomUser(User):
|
||||
'member_type': 'Mentor'
|
||||
})
|
||||
|
||||
def get_user_reviews(self):
|
||||
""" Returns the reviews created by user """
|
||||
return frappe.get_all("LMS Course Review",
|
||||
{
|
||||
"owner": self.name
|
||||
})
|
||||
|
||||
def get_course_membership(self, member_type=None):
|
||||
""" Returns all memberships of the user """
|
||||
filters = {
|
||||
"member": self.name
|
||||
}
|
||||
if member_type:
|
||||
filters["member_type"] = member_type
|
||||
|
||||
return frappe.get_all("LMS Batch Membership", filters, ["name", "course"])
|
||||
|
||||
@@ -138,39 +138,6 @@ section.lightgray {
|
||||
color: var(--tag-color);
|
||||
}
|
||||
|
||||
.course-header {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
/*
|
||||
.course-header {
|
||||
margin-top: 20px;
|
||||
padding: 20px;
|
||||
background: var(--header-bg);
|
||||
color: var(--header-color);
|
||||
border-radius: 9px;
|
||||
}
|
||||
|
||||
.course-author-avatar {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 50%;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.course-header h1 {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
// .gray-section {
|
||||
// background:#F6F6F6;
|
||||
// border: 1px solid #C4C4C4;
|
||||
// padding: 20px;
|
||||
// margin: 20px 0px;
|
||||
// }
|
||||
|
||||
.instructor-title {
|
||||
color: black;
|
||||
}
|
||||
@@ -289,10 +256,6 @@ section.lightgray {
|
||||
}
|
||||
}
|
||||
|
||||
.chapter-description {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.lesson-teaser {
|
||||
line-height: 40px;
|
||||
}
|
||||
@@ -305,10 +268,6 @@ section.lightgray {
|
||||
margin: 20px 0px;
|
||||
}
|
||||
|
||||
.lesson-pagination {
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.exercise-image svg {
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
|
||||
3
community/public/icons/black-arrow.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="13" height="8" viewBox="0 0 13 8" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.9649 0.313118C8.76964 0.117856 8.45306 0.117856 8.2578 0.313118C8.06253 0.50838 8.06253 0.824963 8.2578 1.02023L10.7374 3.49985L1.16683 3.49719C0.890684 3.49712 0.666764 3.72091 0.666687 3.99705C0.66661 4.2732 0.890406 4.49712 1.16655 4.49719L10.7377 4.49985L8.2578 6.97978C8.06253 7.17505 8.06253 7.49163 8.2578 7.68689C8.45306 7.88215 8.76964 7.88215 8.9649 7.68689L12.2982 4.35356C12.4935 4.1583 12.4935 3.84171 12.2982 3.64645L8.9649 0.313118Z" fill="#4C5A67"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 619 B |
3
community/public/icons/calendar.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M15.8947 2.47619C15.8947 2.2132 15.6885 2 15.4342 2C15.1798 2 14.9737 2.2132 14.9737 2.47619V3.90476H8.52631V2.47619C8.52631 2.2132 8.32013 2 8.06579 2C7.81145 2 7.60526 2.2132 7.60526 2.47619V3.90476H5.30263C4.03092 3.90476 3 4.97074 3 6.28571V10.0952V19.619C3 20.934 4.03092 22 5.30263 22H18.1974C19.4691 22 20.5 20.934 20.5 19.619V10.0952V6.28571C20.5 4.97074 19.4691 3.90476 18.1974 3.90476H15.8947V2.47619ZM19.579 9.61905V6.28571C19.579 5.49673 18.9604 4.85714 18.1974 4.85714H15.8947V6.28572C15.8947 6.54871 15.6885 6.76191 15.4342 6.76191C15.1798 6.76191 14.9737 6.54871 14.9737 6.28572V4.85714H8.52631V6.28572C8.52631 6.54871 8.32013 6.76191 8.06579 6.76191C7.81145 6.76191 7.60526 6.54871 7.60526 6.28572V4.85714H5.30263C4.53961 4.85714 3.92105 5.49673 3.92105 6.28571V9.61905H19.579ZM3.92105 10.5714H19.579V19.619C19.579 20.408 18.9604 21.0476 18.1974 21.0476H5.30263C4.53961 21.0476 3.92105 20.408 3.92105 19.619V10.5714Z" fill="#4C5A67"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
3
community/public/icons/check.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M22 12C22 17.5228 17.5228 22 12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2C17.5228 2 22 6.47715 22 12ZM16.8734 10.1402C17.264 9.74969 17.264 9.11652 16.8734 8.726C16.4829 8.33547 15.8498 8.33547 15.4592 8.726L14.6259 9.55933L12.9592 11.226L10.333 13.8522L9.37345 12.8927L8.54011 12.0593C8.14959 11.6688 7.51643 11.6688 7.1259 12.0593C6.73538 12.4499 6.73538 13.083 7.1259 13.4735L7.95923 14.3069L9.6259 15.9735C9.81344 16.1611 10.0678 16.2664 10.333 16.2664C10.5982 16.2664 10.8526 16.1611 11.0401 15.9735L14.3734 12.6402L16.0401 10.9735L16.8734 10.1402Z" fill="#68D391"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 736 B |
3
community/public/icons/chevron-right.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M10 16L14 12L10 8" stroke="#4C5A67" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 197 B |
3
community/public/icons/chevron.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8 10L12 14L16 10" stroke="#4C5A67" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 220 B |
3
community/public/icons/clock.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M3 11.9999C3 7.02937 7.02937 3 11.9999 3C16.9703 3 20.9997 7.02937 20.9997 11.9999C20.9997 16.9703 16.9703 20.9997 11.9999 20.9997C7.02937 20.9997 3 16.9703 3 11.9999ZM11.9999 2C6.47709 2 2 6.47709 2 11.9999C2 17.5226 6.47709 21.9997 11.9999 21.9997C17.5226 21.9997 21.9997 17.5226 21.9997 11.9999C21.9997 6.47709 17.5226 2 11.9999 2ZM12.5002 6.30027C12.5002 6.02413 12.2763 5.80027 12.0002 5.80027C11.724 5.80027 11.5002 6.02413 11.5002 6.30027V12.0002C11.5002 12.1896 11.6072 12.3627 11.7766 12.4474L15.5765 14.3474C15.8235 14.4709 16.1238 14.3708 16.2473 14.1238C16.3708 13.8768 16.2707 13.5764 16.0237 13.4529L12.5002 11.6912V6.30027Z" fill="#4C5A67"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 809 B |
3
community/public/icons/down-arrow.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="10" height="6" viewBox="0 0 10 6" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M1 1L5 5L9 1" stroke="#4C5A67" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 213 B |
3
community/public/icons/left-arrow.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="4" height="8" viewBox="0 0 4 8" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M3.33331 6.66663L0.666646 3.99996L3.33331 1.33329" stroke="#4C5A67" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 225 B |
3
community/public/icons/lock.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.7114 3.0002H11.6325C10.4336 2.98895 9.27915 3.45397 8.42278 4.29322C7.5658 5.13306 7.07747 6.27888 7.06517 7.47871L7.06514 7.47871V7.48384V8.17399H5C3.89543 8.17399 3 9.06943 3 10.174V18.9996C3 20.1041 3.89543 20.9996 5 20.9996H18.2602C19.3647 20.9996 20.2602 20.1041 20.2602 18.9996V10.174C20.2602 9.06942 19.3647 8.17399 18.2602 8.17399H16.195V7.56755C16.2063 6.36863 15.7412 5.21421 14.902 4.35784C14.0622 3.50086 12.9163 3.01253 11.7165 3.00023V3.0002H11.7114ZM15.195 8.17399V7.56514V7.56016H15.195C15.2043 6.62548 14.842 5.72537 14.1878 5.05777C13.5341 4.39076 12.6425 4.01043 11.7087 4.0002H11.6301H11.6251V4.00017C10.6904 3.99087 9.79031 4.35318 9.12271 5.00743C8.4557 5.6611 8.07537 6.55271 8.06514 7.48649V8.17399H15.195ZM5 9.17399C4.44772 9.17399 4 9.62171 4 10.174V18.9996C4 19.5518 4.44772 19.9996 5 19.9996H18.2602C18.8124 19.9996 19.2602 19.5518 19.2602 18.9996V10.174C19.2602 9.62171 18.8124 9.17399 18.2602 9.17399H5ZM11.6303 15.804C12.3026 15.804 12.8476 15.259 12.8476 14.5867C12.8476 13.9145 12.3026 13.3694 11.6303 13.3694C10.958 13.3694 10.413 13.9145 10.413 14.5867C10.413 15.259 10.958 15.804 11.6303 15.804ZM13.8476 14.5867C13.8476 15.8113 12.8549 16.804 11.6303 16.804C10.4057 16.804 9.413 15.8113 9.413 14.5867C9.413 13.3622 10.4057 12.3694 11.6303 12.3694C12.8549 12.3694 13.8476 13.3622 13.8476 14.5867Z" fill="#4C5A67"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
3
community/public/icons/play.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M8 18L16 12L8 6" stroke="#4C5A67" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 237 B |
3
community/public/icons/rating-filled.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M12 1.5C12.1903 1.5 12.3641 1.60804 12.4484 1.77869L15.4221 7.80323L22.0723 8.77526C22.2606 8.80278 22.4169 8.93478 22.4756 9.11578C22.5343 9.29679 22.4852 9.49541 22.3489 9.62818L17.5373 14.3147L18.6728 20.9355C18.705 21.1231 18.6279 21.3127 18.4739 21.4245C18.3199 21.5364 18.1157 21.5511 17.9473 21.4625L12 18.3349L6.05273 21.4625C5.88427 21.5511 5.68012 21.5364 5.52613 21.4245C5.37214 21.3127 5.29502 21.1231 5.3272 20.9355L6.46274 14.3147L1.65113 9.62818C1.51482 9.49541 1.46569 9.29679 1.52438 9.11578C1.58307 8.93478 1.73941 8.80278 1.92769 8.77526L8.57787 7.80323L11.5516 1.77869C11.6359 1.60804 11.8097 1.5 12 1.5Z" fill="#192734"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 795 B |
3
community/public/icons/rating.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12 2L15.09 8.26L22 9.27L17 14.14L18.18 21.02L12 17.77L5.82 21.02L7 14.14L2 9.27L8.91 8.26L12 2Z" stroke="#192734" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 278 B |
3
community/public/icons/side-arrow-white.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="4" height="8" viewBox="0 0 4 8" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M0.666626 6.66675L3.33329 4.00008L0.666626 1.33341" stroke="white" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 224 B |
3
community/public/icons/tick.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg class="icon">
|
||||
<use href="#icon-tick"></use>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 58 B |
4
community/public/icons/user.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M20 21V19C20 17.9391 19.5786 16.9217 18.8284 16.1716C18.0783 15.4214 17.0609 15 16 15H8C6.93913 15 5.92172 15.4214 5.17157 16.1716C4.42143 16.9217 4 17.9391 4 19V21" stroke="#192734" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M12 11C14.2091 11 16 9.20914 16 7C16 4.79086 14.2091 3 12 3C9.79086 3 8 4.79086 8 7C8 9.20914 9.79086 11 12 11Z" stroke="#192734" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 532 B |
3
community/public/icons/white-arrow.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="13" height="8" viewBox="0 0 13 8" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.96484 0.313118C8.76958 0.117856 8.453 0.117856 8.25773 0.313118C8.06247 0.50838 8.06247 0.824963 8.25773 1.02023L10.7374 3.49985L1.16676 3.49719C0.890623 3.49712 0.666703 3.72091 0.666626 3.99705C0.666549 4.2732 0.890345 4.49712 1.16649 4.49719L10.7377 4.49985L8.25773 6.97978C8.06247 7.17505 8.06247 7.49163 8.25773 7.68689C8.453 7.88215 8.76958 7.88215 8.96484 7.68689L12.2982 4.35356C12.4934 4.1583 12.4934 3.84171 12.2982 3.64645L8.96484 0.313118Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 621 B |
3
community/public/icons/white-tick.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="12" height="10" viewBox="0 0 12 10" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.2414 1.09763C11.3639 1.22781 11.3639 1.43886 11.2414 1.56904L4.33944 8.90237C4.21693 9.03254 4.01829 9.03254 3.89577 8.90237L0.758514 5.56904C0.635997 5.43886 0.635997 5.22781 0.758514 5.09763C0.881031 4.96746 1.07967 4.96746 1.20219 5.09763L4.11761 8.19526L10.7977 1.09763C10.9202 0.967456 11.1189 0.967456 11.2414 1.09763Z" fill="white" stroke="white" stroke-width="0.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 578 B |
|
Before Width: | Height: | Size: 206 B After Width: | Height: | Size: 200 B |
BIN
community/public/images/play.png
Normal file
|
After Width: | Height: | Size: 326 B |
@@ -14,9 +14,12 @@
|
||||
{% set options = [question.option_1, question.option_2, question.option_3, question.option_4] %}
|
||||
{% for option in options %}
|
||||
{% if option %}
|
||||
<div class="checkbox mb-2">
|
||||
<input {% if question.multiple %} type="checkbox" {% else %} type="radio"
|
||||
name="{{ question.question | urlencode }}" {% endif %} class="option" value="{{ option | urlencode }}">
|
||||
<div class="custom-checkbox mb-2">
|
||||
<label>
|
||||
<input {% if question.multiple %} type="checkbox" {% else %} type="radio"
|
||||
name="{{ question.question | urlencode }}" {% endif %} class="option" value="{{ option | urlencode }}">
|
||||
<img />
|
||||
</label>
|
||||
<span class="label-area">{{ option }}</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
0
community/www/__init__.py
Normal file
0
community/www/__pycache__/__init__.py
Normal file
0
community/www/batch/__pycache__/__init__.py
Normal file
@@ -20,42 +20,80 @@
|
||||
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="container">
|
||||
{{ widgets.BatchTabs(course=course, membership=membership) }}
|
||||
<div class="lesson-page">
|
||||
|
||||
<h2 class="title {% if course.is_mentor(frappe.session.user) %} is_mentor {% endif %}" data-lesson="{{ lesson.name }}"
|
||||
data-course="{{ course.name }}" {% if membership%} data-membership="{{membership.name}}" {% endif %}>{{ lesson.title }}</h2>
|
||||
|
||||
{% if membership or lesson.include_in_preview %}
|
||||
{{ lesson.render_html() }}
|
||||
{% else %}
|
||||
<div class="no-preview-message">
|
||||
<span>This lesson is not available for Preview. Please join the course to access this lesson.</span>
|
||||
<a href="/courses/{{ course.name }}">Checkout Course Details.</a>
|
||||
<div class="common-page-style">
|
||||
<div class="course-details-page">
|
||||
{{ widgets.BreadCrumb(course=course, lesson=lesson) }}
|
||||
<div class="course-content-parent">
|
||||
<div class="course-details-outline">
|
||||
{{ widgets.CourseOutline(course=course, membership=membership) }}
|
||||
</div>
|
||||
<div class="lesson-pagination-parent">
|
||||
{{ LessonContent(lesson) }}
|
||||
{% if membership %}
|
||||
{{ pagination(prev_chap, prev_url, next_chap, next_url) }}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if membership %}
|
||||
{{ pagination(prev_chap, prev_url, next_chap, next_url) }}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% macro LessonContent(lesson) %}
|
||||
<div class="lesson-content">
|
||||
<div class="course-home-headings title {% if membership %} is-member {% endif %}" data-lesson="{{ lesson.name }}"
|
||||
data-course="{{ course.name }}">
|
||||
{{ lesson.title }}
|
||||
<span class="lesson-progress {{hide if course.get_progress(lesson.name) != 'Complete' else ''}}">COMPLETED</span>
|
||||
</div>
|
||||
|
||||
{% if membership or lesson.include_in_preview %}
|
||||
<div class="common-card-style lesson-content-card">{{ lesson.render_html() }}</div>
|
||||
{% else %}
|
||||
<div class="no-preview-message">
|
||||
<span>This lesson is not available for Preview. Please join the course to access this lesson.</span>
|
||||
<a href="/courses/{{ course.name }}">Checkout Course Details.</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro pagination(prev_chap, prev_url, next_chap, next_url) %}
|
||||
<div class="lesson-pagination">
|
||||
{% if prev_url %}
|
||||
<span>
|
||||
Prev: <a href="{{prev_url}}">{{prev_chap}}</a>
|
||||
</span>
|
||||
|
||||
<div>
|
||||
{% if prev_url %}
|
||||
<a class="button is-secondary dark-links" href="{{ prev_url }}">
|
||||
<img class="mr-2" src="/assets/community/icons/left-arrow.svg">
|
||||
Prev
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
|
||||
{% if not course.is_mentor(frappe.session.user) and membership %}
|
||||
|
||||
{% if course.get_progress(lesson.name) != "Complete" %}
|
||||
<div class="button is-secondary" id="progress" data-progress="Complete">
|
||||
Mark as Complete
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="button is-secondary" id="progress" data-progress="Incomplete">
|
||||
Mark as Incomplete
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if next_url %}
|
||||
<span class="pull-right">
|
||||
Next: <a href="{{next_url}}">{{next_chap}}</a>
|
||||
</span>
|
||||
|
||||
{% endif %}
|
||||
<div style="clear: both;"></div>
|
||||
|
||||
<div>
|
||||
{% if next_url %}
|
||||
<a class="button is-primary" href="{{ next_url }}">
|
||||
Next
|
||||
<img class="ml-2" src="/assets/community/icons/side-arrow-white.svg">
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
|
||||
@@ -1,69 +1,102 @@
|
||||
frappe.ready(() => {
|
||||
|
||||
/* Save Lesson Progress */
|
||||
if ($(".title").attr("data-membership") && !$(".title").hasClass("is_mentor")) {
|
||||
frappe.call({
|
||||
method: "community.lms.doctype.lesson.lesson.save_progress",
|
||||
args: {
|
||||
lesson: $(".title").attr("data-lesson"),
|
||||
course: $(".title").attr("data-course")
|
||||
}
|
||||
})
|
||||
}
|
||||
save_current_lesson();
|
||||
|
||||
/* Save Current Lesson */
|
||||
if ($(".title").attr("data-membership")) {
|
||||
$("#progress").click((e) => {
|
||||
mark_progress(e);
|
||||
});
|
||||
|
||||
$("#submit-quiz").click((e) => {
|
||||
submit_quiz(e);
|
||||
});
|
||||
|
||||
$("#try-again").click((e) => {
|
||||
try_quiz_again(e);
|
||||
});
|
||||
|
||||
})
|
||||
|
||||
var save_current_lesson = () => {
|
||||
if ($(".title").hasClass("is-member")) {
|
||||
frappe.call("community.lms.api.save_current_lesson", {
|
||||
course_name: $(".title").attr("data-course"),
|
||||
lesson_name: $(".title").attr("data-lesson")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/* Submit Quiz */
|
||||
$("#submit-quiz").click((e) => {
|
||||
e.preventDefault();
|
||||
console.log("click")
|
||||
var result = [];
|
||||
$('.question').each((i, element) => {
|
||||
var options = $(element).find(".option");
|
||||
var answers = [];
|
||||
options.filter((i, op) => $(op).prop("checked")).each((i, elem) => answers.push(decodeURIComponent(elem.value)));
|
||||
result.push({
|
||||
"question": element.dataset.question,
|
||||
"answer": answers
|
||||
});
|
||||
});
|
||||
frappe.call({
|
||||
method: "community.lms.doctype.lms_quiz.lms_quiz.submit",
|
||||
args: {
|
||||
quiz: $("#title").text(),
|
||||
result: result
|
||||
},
|
||||
callback: (data) => {
|
||||
$("#submit-quiz").addClass("hide");
|
||||
$("#try-again").removeClass("hide");
|
||||
$(":input[type='checkbox']").prop("disabled", true);
|
||||
$(":input[type='radio']").prop("disabled", true);
|
||||
if (data.message == result.length) {
|
||||
$(".success-message").text("Congratulations, you cleared the quiz!");
|
||||
}
|
||||
else {
|
||||
$(".success-message").text("Some of your answers weren't correct. You can give it another shot.");
|
||||
}
|
||||
$(".score").text(`Score: ${data.message}/${result.length}`);
|
||||
var mark_progress = (e) => {
|
||||
var status = $(e.currentTarget).attr("data-progress");
|
||||
frappe.call({
|
||||
method: "community.lms.doctype.lesson.lesson.save_progress",
|
||||
args: {
|
||||
lesson: $(".title").attr("data-lesson"),
|
||||
course: $(".title").attr("data-course"),
|
||||
status: status
|
||||
},
|
||||
callback: (data) => {
|
||||
if (data.message == "OK") {
|
||||
change_progress_indicators(status, e);
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/* Try the quiz again */
|
||||
$("#try-again").click((e) => {
|
||||
e.preventDefault();
|
||||
$(":input[type='checkbox']").prop("disabled", false);
|
||||
$(":input[type='radio']").prop("disabled", false);
|
||||
$("#quiz-form").trigger("reset");
|
||||
$(".success-message").text("");
|
||||
$(".score").text("");
|
||||
$("#submit-quiz").removeClass("hide");
|
||||
$("#try-again").addClass("hide");
|
||||
var change_progress_indicators = (status, e) => {
|
||||
if (status == "Complete") {
|
||||
$(".lesson-progress").removeClass("hide");
|
||||
$(".active-lesson .lesson-progress-tick").removeClass("hide");
|
||||
}
|
||||
else {
|
||||
$(".lesson-progress").addClass("hide");
|
||||
$(".active-lesson .lesson-progress-tick").addClass("hide");
|
||||
}
|
||||
var label = status != "Complete" ? "Mark as Complete" : "Mark as Incomplete";
|
||||
var data_progress = status != "Complete" ? "Complete" : "Incomplete";
|
||||
$(e.currentTarget).text(label).attr("data-progress", data_progress);
|
||||
}
|
||||
|
||||
var submit_quiz = (e) => {
|
||||
e.preventDefault();
|
||||
var result = [];
|
||||
$('.question').each((i, element) => {
|
||||
var options = $(element).find(".option");
|
||||
var answers = [];
|
||||
options.filter((i, op) => $(op).prop("checked")).each((i, elem) => answers.push(decodeURIComponent(elem.value)));
|
||||
result.push({
|
||||
"question": element.dataset.question,
|
||||
"answer": answers
|
||||
});
|
||||
});
|
||||
frappe.call({
|
||||
method: "community.lms.doctype.lms_quiz.lms_quiz.submit",
|
||||
args: {
|
||||
quiz: $("#title").text(),
|
||||
result: result
|
||||
},
|
||||
callback: (data) => {
|
||||
$("#submit-quiz").addClass("hide");
|
||||
$("#try-again").removeClass("hide");
|
||||
$(":input[type='checkbox']").prop("disabled", true);
|
||||
$(":input[type='radio']").prop("disabled", true);
|
||||
if (data.message == result.length) {
|
||||
$(".success-message").text("Congratulations, you cleared the quiz!");
|
||||
}
|
||||
else {
|
||||
$(".success-message").text("Some of your answers weren't correct. You can give it another shot.");
|
||||
}
|
||||
$(".score").text(`Score: ${data.message}/${result.length}`);
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
var try_quiz_again = (e) => {
|
||||
e.preventDefault();
|
||||
$(":input[type='checkbox']").prop("disabled", false);
|
||||
$(":input[type='radio']").prop("disabled", false);
|
||||
$("#quiz-form").trigger("reset");
|
||||
$(".success-message").text("");
|
||||
$(".score").text("");
|
||||
$("#submit-quiz").removeClass("hide");
|
||||
$("#try-again").addClass("hide");
|
||||
}
|
||||
|
||||
@@ -38,7 +38,8 @@ def get_context(context):
|
||||
context.page_context = {
|
||||
"course": context.course.name,
|
||||
"batch": context.get("batch") and context.batch.name,
|
||||
"lesson": context.lesson.name
|
||||
"lesson": context.lesson.name,
|
||||
"is_member": context.membership is not None
|
||||
}
|
||||
|
||||
def get_chapter_title(course_name, lesson_number):
|
||||
|
||||
@@ -20,19 +20,16 @@
|
||||
<div class="mt-5">
|
||||
{% for member in members %}
|
||||
<div class="d-flex align-items-center">
|
||||
{{ widgets.Avatar(member=member, avatar_class="avatar-large") }}
|
||||
{{ widgets.Avatar(member=member, avatar_class="avatar-medium") }}
|
||||
<div class="d-flex flex-column ml-2">
|
||||
<div class="d-flex">
|
||||
<a class="anchor_style ml-2" href="/{{member.username}}">
|
||||
<h3>{{ member.full_name }}</h3>
|
||||
<div class="review-content">{{ member.full_name }}</div>
|
||||
</a>
|
||||
{% if course.is_mentor(member.name) %}
|
||||
<div class="badge badge-success ml-2 align-self-start">Mentor</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if member.bio %}
|
||||
<i class="ml-2">{{member.bio}}</i>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% if loop.index != member_count %}
|
||||
|
||||
@@ -17,8 +17,8 @@ def get_common_context(context):
|
||||
context.course = course
|
||||
|
||||
membership = course.get_membership(frappe.session.user, batch_name)
|
||||
context.membership = membership
|
||||
if membership:
|
||||
context.membership = membership
|
||||
batch = course.get_batch(membership.batch)
|
||||
|
||||
if batch:
|
||||
@@ -27,7 +27,7 @@ def get_common_context(context):
|
||||
context.members = course.get_mentors(membership.batch) + course.get_students(membership.batch)
|
||||
context.member_count = len(context.members)
|
||||
|
||||
context.course.query_parameter = "?batch=" + membership.batch if membership and membership.batch else " "
|
||||
context.course.query_parameter = "?batch=" + membership.batch if membership and membership.batch else ""
|
||||
context.livecode_url = get_livecode_url()
|
||||
|
||||
def get_livecode_url():
|
||||
|
||||
0
community/www/courses/__init__.py
Normal file
0
community/www/courses/__pycache__/__init__.py
Normal file
@@ -1,60 +1,186 @@
|
||||
{% extends "templates/base.html" %}
|
||||
{% from "www/macros/common_macro.html" import MentorsSection %}
|
||||
{% block title %}{{ course.title }}{% endblock %}
|
||||
{% block title %}{{ course.title }}
|
||||
{% endblock %}
|
||||
{% block head_include %}
|
||||
<meta name="description" content="Courses" />
|
||||
<meta name="keywords" content="Courses {{course.title}}" />
|
||||
<link rel="stylesheet" href="/assets/frappe/css/font-awesome.css">
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container">
|
||||
<div class="course-header">
|
||||
<div class="mb-5">
|
||||
<a class="anchor_style" href="/courses">Courses</a> / <span class="text-muted">{{ course.title }}</span>
|
||||
</div>
|
||||
<div class="d-flex justify-content-between align-items-end">
|
||||
<h2 id="course-title" data-course="{{course.name}}">{{course.title}}</h2>
|
||||
{% if not course.disable_self_learning and not course.is_mentor(frappe.session.user) %}
|
||||
<div>
|
||||
<button class="btn btn-primary join-batch" data-course="{{ course.name | urlencode }}"> Start Learning </button>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="course-short-intro">{{ course.short_introduction }}</div>
|
||||
</div>
|
||||
|
||||
<div class="">
|
||||
<div class="">
|
||||
<div class="course-details">
|
||||
{{ CourseVideo(course) }}
|
||||
{{ CourseDescription(course) }}
|
||||
{{ widgets.InstructorSection(instructor=course.get_instructor()) }}
|
||||
{{ BatchSection(course) }}
|
||||
{{ widgets.CourseOutline(course=course, show_link=membership) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="common-page-style">
|
||||
<div class="course-home-page">
|
||||
{{ widgets.BreadCrumb(course=course) }}
|
||||
{{ CourseCardWide(course) }}
|
||||
{{ CourseOutlineAndCreator(course) }}
|
||||
{{ Mentors(course) }}
|
||||
{{ CourseDescriptionAndOverview(course) }}
|
||||
{{ widgets.Reviews(course=course, membership=membership) }}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% macro CourseVideo(course) %}
|
||||
{% if course.video_link %}
|
||||
<div class="preview-video">
|
||||
<iframe width="560" height="315" src="{{course.video_link}}" title="YouTube video player" frameborder="0"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowfullscreen></iframe>
|
||||
<!-- Course Card -->
|
||||
|
||||
{% macro CourseCardWide(course) %}
|
||||
<div class="common-card-style course-card-wide">
|
||||
<div class="course-image-wide" style="background-image: url({{ course.image }});">
|
||||
<div class="course-tags">
|
||||
{% for tag in course.get_tags() %}
|
||||
<div class="course-card-pills">{{ tag }}</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="course-card-wide-content">
|
||||
<div class="course-card-wide-title">
|
||||
{{ course.title }}
|
||||
</div>
|
||||
<div class="course-card-wide-intro">
|
||||
{{ course.short_introduction }}
|
||||
</div>
|
||||
<div class="course-buttons">
|
||||
{% if not course.disable_self_learning and not membership %}
|
||||
<div class="button wide-button start-learning is-primary join-batch" data-course="{{ course.name | urlencode }}">
|
||||
Start Learning
|
||||
<img class="ml-2" src="/assets/community/icons/white-arrow.svg" />
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if membership %}
|
||||
{% set lesson_index = course.get_lesson_index(membership.current_lesson) if membership and
|
||||
membership.current_lesson
|
||||
else '1.1' %}
|
||||
<a class="button wide-button is-primary" id="continue-learning"
|
||||
href="{{ course.get_learn_url(lesson_index) }}{{ course.query_parameter }}">
|
||||
Continue Learning <img class="ml-2" src="/assets/community/icons/white-arrow.svg" />
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if course.video_link %}
|
||||
<div class="button wide-button is-secondary video-preview">
|
||||
Watch Video Preview
|
||||
<img class="ml-2" src="/assets/community/images/play.png" />
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal fade preview-modal" id="video-modal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel"
|
||||
aria-hidden="true">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<div class="course-home-headings modal-headings">{{ course.title }}</div>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<iframe class="video-iframe" frameborder="0"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowfullscreen src="{{ course.video_link }}"></iframe>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endmacro%}
|
||||
|
||||
<!-- Course Outline and Creator -->
|
||||
|
||||
{% macro CourseOutlineAndCreator(course) %}
|
||||
<div class="course-outline-instructor-parent">
|
||||
<div class="course-home-outline">
|
||||
{{ widgets.CourseOutline(course=course, membership=membership) }}
|
||||
</div>
|
||||
<div class="course-creator-progress-parent">
|
||||
<div class="course-creator-section">
|
||||
<div class="course-home-headings">
|
||||
Creator
|
||||
</div>
|
||||
{{ widgets.MemberCard(member=course.get_instructor(), show_course_count=True, dimension_class="member-card-large") }}
|
||||
</div>
|
||||
{% if course.get_course_progress() %}
|
||||
<div class="course-progress-section">
|
||||
<div class="course-home-headings">
|
||||
Your Progress
|
||||
</div>
|
||||
<div class="common-card-style progress-card">
|
||||
<p class="small-title">
|
||||
Great work so far!
|
||||
</p>
|
||||
<p class="progress-text">
|
||||
Challenge yourself to complete the lessons and grow professionally.
|
||||
</p>
|
||||
<div class="progress-percentage">
|
||||
{{ frappe.utils.rounded(course.get_course_progress()) }}%
|
||||
</div>
|
||||
<div class="progress">
|
||||
<div class="progress-bar" role="progressbar" style="width: {{ course.get_course_progress() }}%"
|
||||
aria-valuenow="{{ course.get_course_progress() }}" aria-valuemin="0" aria-valuemax="100"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
<!-- Mentors -->
|
||||
|
||||
{% macro Mentors(course) %}
|
||||
{% if course.get_mentors() | length %}
|
||||
<div class="course-home-mentors">
|
||||
<div class="course-home-headings">
|
||||
Mentors
|
||||
</div>
|
||||
<div class="mentors-section">
|
||||
{% for mentor in course.get_mentors() %}
|
||||
{{ widgets.MemberCard(member=mentor, show_course_count=False, dimension_class="member-card-medium") }}
|
||||
{% endfor %}
|
||||
<div class="view-all-mentors">
|
||||
<span class="card-divider-dark flex-one"></span>
|
||||
<span class="course-instructor"><span class="all-mentors-text">View all mentors</span> <img class="mentor-icon"
|
||||
src="/assets/community/icons/down-arrow.svg" /></span>
|
||||
<span class="card-divider-dark flex-one"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro CourseDescription(course) %}
|
||||
<div class="mt-5">
|
||||
<h3>Course Description</h3>
|
||||
<!-- Course Description and Overview -->
|
||||
|
||||
<div class="course-description text-justify">
|
||||
{{ frappe.utils.md_to_html(course.description) }}
|
||||
{% macro CourseDescriptionAndOverview(course) %}
|
||||
<div class="course-outline-instructor-parent">
|
||||
<div class="course-description-section">
|
||||
<div class="course-home-headings">
|
||||
Course Description
|
||||
</div>
|
||||
<div class="common-card-style description-card small-title">
|
||||
{{ frappe.utils.md_to_html(course.description) }}
|
||||
</div>
|
||||
</div>
|
||||
{% set avg_rating = course.get_average_rating() %}
|
||||
{% if course.get_students() | length or avg_rating %}
|
||||
<div class="course-overview-section">
|
||||
<div class="course-home-headings">
|
||||
Overview
|
||||
</div>
|
||||
<div class="common-card-style overview-card small-title">
|
||||
{% if course.get_students() | length %}
|
||||
<div class="overtime-item">
|
||||
<img class="icon-background mr-1" src="/assets/community/icons/user.svg" />
|
||||
{{ course.get_students() | length }} Enrolled
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if avg_rating %}
|
||||
<div class="overtime-item">
|
||||
<img class="icon-background mr-1" src="/assets/community/icons/rating.svg" />
|
||||
{{ avg_rating }} Rating
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
@@ -69,7 +195,6 @@
|
||||
</div>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro BatchSectionForMentors(course, mentor_batches) %}
|
||||
<h2>Your Batches</h2>
|
||||
|
||||
@@ -83,11 +208,12 @@
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<a class="add-batch margin-bottom" href="/add-a-new-batch?new=1&course={{course.name}}">Add a new
|
||||
batch</a>
|
||||
<a class="add-batch margin-bottom" href="/add-a-new-batch?new=1&course={{course.name}}">Add a new batch</a>
|
||||
{% else %}
|
||||
<div class="mentor_message">
|
||||
<p> You are a mentor for this course. </p>
|
||||
<p>
|
||||
You are a mentor for this course.
|
||||
</p>
|
||||
<a class="" href="/add-a-new-batch?new=1&course={{course.name}}">Create your first batch</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
@@ -1,78 +1,211 @@
|
||||
frappe.ready(() => {
|
||||
if (frappe.session.user != "Guest") {
|
||||
frappe.call({
|
||||
'method': 'community.lms.doctype.lms_mentor_request.lms_mentor_request.has_requested',
|
||||
'args': {
|
||||
course: decodeURIComponent($("#course-title").attr("data-course")),
|
||||
},
|
||||
'callback': (data) => {
|
||||
if (data.message > 0) {
|
||||
$("#mentor-request").addClass("hide");
|
||||
$("#already-applied").removeClass("hide")
|
||||
}
|
||||
}
|
||||
})
|
||||
check_mentor_request();
|
||||
}
|
||||
|
||||
hide_wrapped_mentor_cards();
|
||||
|
||||
$("#apply-now").click((e) => {
|
||||
e.preventDefault();
|
||||
if (frappe.session.user == "Guest") {
|
||||
window.location.href = `/login?redirect-to=/courses/${$(e.currentTarget).attr("data-course")}`;
|
||||
return;
|
||||
}
|
||||
frappe.call({
|
||||
"method": "community.lms.doctype.lms_mentor_request.lms_mentor_request.create_request",
|
||||
"args": {
|
||||
"course": decodeURIComponent($(e.currentTarget).attr("data-course"))
|
||||
},
|
||||
"callback": (data) => {
|
||||
if (data.message == "OK") {
|
||||
$("#mentor-request").addClass("hide");
|
||||
$("#already-applied").removeClass("hide")
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
create_mentor_request(e);
|
||||
});
|
||||
|
||||
$("#cancel-request").click((e) => {
|
||||
e.preventDefault()
|
||||
frappe.call({
|
||||
"method": "community.lms.doctype.lms_mentor_request.lms_mentor_request.cancel_request",
|
||||
"args": {
|
||||
"course": decodeURIComponent($(e.currentTarget).attr("data-course"))
|
||||
},
|
||||
"callback": (data) => {
|
||||
if (data.message == "OK") {
|
||||
$("#mentor-request").removeClass("hide");
|
||||
$("#already-applied").addClass("hide")
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
cancel_mentor_request(e);
|
||||
});
|
||||
|
||||
$(".join-batch").click((e) => {
|
||||
e.preventDefault();
|
||||
var course = $(e.currentTarget).attr("data-course")
|
||||
if (frappe.session.user == "Guest") {
|
||||
window.location.href = `/login?redirect-to=/courses/${course}`;
|
||||
return;
|
||||
}
|
||||
var batch = $(e.currentTarget).attr("data-batch");
|
||||
batch = batch ? decodeURIComponent(batch) : "";
|
||||
frappe.call({
|
||||
"method": "community.lms.doctype.lms_batch_membership.lms_batch_membership.create_membership",
|
||||
"args": {
|
||||
"batch": batch ? batch : "",
|
||||
"course": course
|
||||
},
|
||||
"callback": (data) => {
|
||||
if (data.message == "OK") {
|
||||
frappe.msgprint(__("You are now a student of this course."));
|
||||
setTimeout(function () {
|
||||
window.location.href = `/courses/${course}/home`;
|
||||
}, 2000);
|
||||
}
|
||||
}
|
||||
})
|
||||
join_course(e)
|
||||
});
|
||||
|
||||
$(".view-all-mentors").click((e) => {
|
||||
view_all_mentors(e);
|
||||
});
|
||||
|
||||
$(".video-preview").click((e) => {
|
||||
show_video_dialog(e);
|
||||
});
|
||||
|
||||
$(".review-link").click((e) => {
|
||||
show_review_dialog(e);
|
||||
});
|
||||
|
||||
$(".chapter-title").click((e) => {
|
||||
rotate_chapter_icon(e);
|
||||
});
|
||||
|
||||
$(".icon-rating").click((e) => {
|
||||
highlight_rating(e);
|
||||
});
|
||||
|
||||
$("#submit-review").click((e) => {
|
||||
submit_review(e);
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
var check_mentor_request = () => {
|
||||
frappe.call({
|
||||
'method': 'community.lms.doctype.lms_mentor_request.lms_mentor_request.has_requested',
|
||||
'args': {
|
||||
course: decodeURIComponent($("#course-title").attr("data-course")),
|
||||
},
|
||||
'callback': (data) => {
|
||||
if (data.message > 0) {
|
||||
$("#mentor-request").addClass("hide");
|
||||
$("#already-applied").removeClass("hide")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
var hide_wrapped_mentor_cards = () => {
|
||||
var offset_top_prev;
|
||||
|
||||
$('.member-card-medium').each(function () {
|
||||
var offset_top = $(this).offset().top;
|
||||
if (offset_top > offset_top_prev) {
|
||||
$(this).addClass('wrapped').slideUp("fast");
|
||||
}
|
||||
if (!offset_top_prev) {
|
||||
offset_top_prev = offset_top;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
if ($(".wrapped").length < 1) {
|
||||
$(".view-all-mentors").hide();
|
||||
}
|
||||
}
|
||||
|
||||
var create_mentor_request = (e) => {
|
||||
e.preventDefault();
|
||||
if (frappe.session.user == "Guest") {
|
||||
window.location.href = `/login?redirect-to=/courses/${$(e.currentTarget).attr("data-course")}`;
|
||||
return;
|
||||
}
|
||||
frappe.call({
|
||||
"method": "community.lms.doctype.lms_mentor_request.lms_mentor_request.create_request",
|
||||
"args": {
|
||||
"course": decodeURIComponent($(e.currentTarget).attr("data-course"))
|
||||
},
|
||||
"callback": (data) => {
|
||||
if (data.message == "OK") {
|
||||
$("#mentor-request").addClass("hide");
|
||||
$("#already-applied").removeClass("hide")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
var cancel_mentor_request = (e) => {
|
||||
e.preventDefault()
|
||||
frappe.call({
|
||||
"method": "community.lms.doctype.lms_mentor_request.lms_mentor_request.cancel_request",
|
||||
"args": {
|
||||
"course": decodeURIComponent($(e.currentTarget).attr("data-course"))
|
||||
},
|
||||
"callback": (data) => {
|
||||
if (data.message == "OK") {
|
||||
$("#mentor-request").removeClass("hide");
|
||||
$("#already-applied").addClass("hide")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
var join_course = (e) => {
|
||||
e.preventDefault();
|
||||
var course = $(e.currentTarget).attr("data-course")
|
||||
if (frappe.session.user == "Guest") {
|
||||
window.location.href = `/login?redirect-to=/courses/${course}`;
|
||||
return;
|
||||
}
|
||||
var batch = $(e.currentTarget).attr("data-batch");
|
||||
batch = batch ? decodeURIComponent(batch) : "";
|
||||
frappe.call({
|
||||
"method": "community.lms.doctype.lms_batch_membership.lms_batch_membership.create_membership",
|
||||
"args": {
|
||||
"batch": batch ? batch : "",
|
||||
"course": course
|
||||
},
|
||||
"callback": (data) => {
|
||||
if (data.message == "OK") {
|
||||
frappe.msgprint(__("You are now a student of this course."));
|
||||
setTimeout(function () {
|
||||
window.location.href = `/courses/${course}/learn/1.1`;
|
||||
}, 2000);
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
var view_all_mentors = (e) => {
|
||||
$(".wrapped").each((i, element) => {
|
||||
$(element).slideToggle("slow");
|
||||
})
|
||||
var text_element = $(".view-all-mentors .course-instructor .all-mentors-text");
|
||||
var text = text_element.text() == "View all mentors" ? "View less" : "View all mentors";
|
||||
text_element.text(text);
|
||||
|
||||
if ($(".mentor-icon").css("transform") == "none") {
|
||||
$(".mentor-icon").css("transform", "rotate(180deg)");
|
||||
} else {
|
||||
$(".mentor-icon").css("transform", "");
|
||||
}
|
||||
}
|
||||
|
||||
var show_video_dialog = (e) => {
|
||||
e.preventDefault();
|
||||
$("#video-modal").modal("show");
|
||||
}
|
||||
|
||||
var show_review_dialog = (e) => {
|
||||
e.preventDefault();
|
||||
$("#review-modal").modal("show");
|
||||
}
|
||||
|
||||
var rotate_chapter_icon = (e) => {
|
||||
var icon = $(e.currentTarget).children(".chapter-icon");
|
||||
if (icon.css("transform") == "none") {
|
||||
icon.css("transform", "rotate(90deg)");
|
||||
} else {
|
||||
icon.css("transform", "none");
|
||||
}
|
||||
}
|
||||
|
||||
var highlight_rating = (e) => {
|
||||
var rating = $(e.currentTarget).attr("data-rating");
|
||||
$(".icon-rating").removeClass("star-click");
|
||||
$(".icon-rating").each((i, elem) => {
|
||||
if (i <= rating-1) {
|
||||
$(elem).addClass("star-click");
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
var submit_review = (e) => {
|
||||
e.preventDefault();
|
||||
var rating = $(".rating-field").children(".star-click").length;
|
||||
var review = $(".review-field").val();
|
||||
if (!review || !rating) {
|
||||
$(".error-field").text("Both Rating and Review are required.");
|
||||
return;
|
||||
}
|
||||
frappe.call({
|
||||
method: "community.lms.doctype.lms_course_review.lms_course_review.submit_review",
|
||||
args: {
|
||||
"rating": rating,
|
||||
"review": review,
|
||||
"course": decodeURIComponent($(e.currentTarget).attr("data-course"))
|
||||
},
|
||||
callback: (data) => {
|
||||
if (data.message == "OK") {
|
||||
$(".review-modal").modal("hide");
|
||||
frappe.msgprint("Thanks for providing your feedback!");
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 2000);
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -19,6 +19,3 @@ def get_context(context):
|
||||
membership = course.get_membership(frappe.session.user)
|
||||
context.course.query_parameter = "?batch=" + membership.batch if membership and membership.batch else ""
|
||||
context.membership = membership
|
||||
if not course.is_mentor(frappe.session.user) and membership:
|
||||
frappe.local.flags.redirect_location = f"/courses/{course.name}/learn"
|
||||
raise frappe.Redirect
|
||||
|
||||
@@ -9,37 +9,29 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<section class="top-section" style="padding: 1rem 0rem;">
|
||||
<div class='container'>
|
||||
<h4 class="mt-5">{{ 'All Courses' }}</h4>
|
||||
<div class="row mt-5">
|
||||
<div class="common-page-style">
|
||||
<div class="course-top-section">
|
||||
<div class="courses-header">
|
||||
{{ 'All Courses' }}
|
||||
</div>
|
||||
<div class="cards-parent">
|
||||
{% for course in courses %}
|
||||
{{ course_card(course) }}
|
||||
{{ widgets.CourseCard(course=course) }}
|
||||
{% endfor %}
|
||||
<!-- {% if courses %}
|
||||
{% for n in range( (3 - (courses|length)) %3) %}
|
||||
{{ null_card() }}
|
||||
{% endfor %}
|
||||
{% endif %} -->
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% macro course_card(course) %}
|
||||
<div class="col-sm-4 mb-4 text-left">
|
||||
<a class="anchor_style" style="color: inherit;" href="/courses/{{course.name}}">
|
||||
<div class="card h-100" style="box-shadow: 0px 5px 10px rgb(0 0 0 / 10%);">
|
||||
<div class='card-body'>
|
||||
<h5 class='card-title'>{{ course.title }}</h5>
|
||||
{% if course.description %}
|
||||
<div class="mt-4">
|
||||
{{ frappe.utils.md_to_html(course.description[:200]) }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
{% block script %}
|
||||
<script>
|
||||
frappe.ready(() => {
|
||||
$(".course-card-title").each((i, element) => {
|
||||
var title = $(element).text();
|
||||
var length = $(window).width() <= 375 ? 60 : 65;
|
||||
var suffix = title.length > length ? "..." : "";
|
||||
$(element).text(title.substring(0, length) + suffix);
|
||||
})
|
||||
})
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
@@ -5,8 +5,8 @@ def get_context(context):
|
||||
context.courses = get_courses()
|
||||
|
||||
def get_courses():
|
||||
courses = frappe.get_all(
|
||||
"LMS Course",
|
||||
fields=['name', 'title', 'description']
|
||||
)
|
||||
course_names = frappe.get_all("LMS Course", pluck="name")
|
||||
courses = []
|
||||
for course in course_names:
|
||||
courses.append(frappe.get_doc("LMS Course", course))
|
||||
return courses
|
||||
|
||||
0
community/www/dashboard/__init__.py
Normal file
0
community/www/dashboard/__pycache__/__init__.py
Normal file
0
community/www/hackathons/__init__.py
Normal file
0
community/www/hackathons/__pycache__/__init__.py
Normal file
0
community/www/hackathons/macros/__init__.py
Normal file
0
community/www/home/__init__.py
Normal file
0
community/www/home/__pycache__/__init__.py
Normal file
0
community/www/macros/__init__.py
Normal file
0
community/www/my-courses/__init__.py
Normal file
0
community/www/my-courses/__pycache__/__init__.py
Normal file
0
community/www/profiles/__init__.py
Normal file
0
community/www/profiles/__pycache__/__init__.py
Normal file
@@ -1,100 +1,116 @@
|
||||
{% extends "templates/web.html" %}
|
||||
{% extends "templates/base.html" %}
|
||||
{% block head_include %}
|
||||
<meta name="description" content="{{ 'Community' }}" />
|
||||
<meta name="keywords" content="An app that supports Communities." />
|
||||
<style>
|
||||
section {
|
||||
padding: 2rem;
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
svg {
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
}
|
||||
|
||||
.dashboard__parent {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.dashboard__name {
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
font-size: 36px;
|
||||
line-height: 42px;
|
||||
}
|
||||
|
||||
.dashboard__details {
|
||||
padding-top: 2rem;
|
||||
}
|
||||
|
||||
.dashboard__course {
|
||||
border: 1px solid black;
|
||||
padding: 1rem;
|
||||
margin: 0.5rem;
|
||||
width: 48%;
|
||||
}
|
||||
|
||||
.dashboard__courseHeader {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
height: 50px;
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
|
||||
.dashboard__badge {
|
||||
background: #D6D6FF;
|
||||
border-radius: 20px;
|
||||
color: #1712FE;
|
||||
padding: 0.5rem;
|
||||
height: fit-content;
|
||||
}
|
||||
|
||||
.dashboard__description {
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.dashboard__parent {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
{% block page_content %}
|
||||
<div class="dashboard__parent">
|
||||
<div class="dashboard__photo mr-5">
|
||||
{{ widgets.Avatar(member=member, avatar_class="avatar-xl") }}
|
||||
</div>
|
||||
<div class="dashboard__details">
|
||||
<div class="dashboard__name">
|
||||
<a class="anchor_style" href="/{{member.username}}">{{ member.full_name }}</a>
|
||||
</div>
|
||||
<div>
|
||||
<ul class="nav nav-tabs mt-4" id="myTab" role="tablist">
|
||||
{% for tab in profile_tabs %}
|
||||
<li class="nav-item">
|
||||
{% set slug = title.lower().replace(" ", "-") %}
|
||||
{% set selected = loop.index == 1 %}
|
||||
{% set active = 'active' if loop.index == 1 else '' %}
|
||||
<a class="nav-link {{ active }}" id="{{ slug }}-tab" data-toggle="tab" href="#{{ slug }}" role="tab" aria-controls="{{ slug }}"
|
||||
aria-selected="{{ selected }}">Sketches</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
{% for tab in profile_tabs %}
|
||||
{% set slug = title.lower().replace(" ", "-") %}
|
||||
<div class="tab-content">
|
||||
<div class="tab-pane fade py-4 show active" role="tabpanel" id="slug">
|
||||
{{ tab.render() }}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
{% block content %}
|
||||
<div class="common-page-style">
|
||||
<div class="course-home-page">
|
||||
{{ widgets.MemberCard(member=member, show_course_count=True, dimension_class="member-card-xl") }}
|
||||
{{ AboutOverviewSection(member) }}
|
||||
<div class="profile-courses">
|
||||
{{ CoursesCreated(member) }}
|
||||
{{ CoursesMentored(member) }}
|
||||
{{ CoursesEnrolled(member) }}
|
||||
</div>
|
||||
{{ ProfileTabs(profile_tabs) }}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
<!-- this is a sample default web page template -->
|
||||
{% macro AboutOverviewSection(member) %}
|
||||
<div class="course-outline-instructor-parent">
|
||||
{% if member.bio %}
|
||||
<div class="course-overview-section">
|
||||
<div class="course-home-headings">
|
||||
About
|
||||
</div>
|
||||
<div class="common-card-style description-card">
|
||||
{{ member.bio }}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="course-overview-section">
|
||||
<div class="course-home-headings">
|
||||
Overview
|
||||
</div>
|
||||
<div class="common-card-style overview-card small-title">
|
||||
{% if member.get_course_membership("Student") | length %}
|
||||
<div class="overtime-item">
|
||||
<img class="icon-background mr-1" src="/assets/community/icons/user.svg" />
|
||||
{{ member.get_course_membership("Student") | length }} Enrolled
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if member.get_user_reviews() | length %}
|
||||
<div class="overtime-item">
|
||||
<img class="icon-background mr-1" src="/assets/community/icons/rating.svg" />
|
||||
{{ member.get_user_reviews() | length }} Created
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if member.get_course_membership("Mentor") | length%}
|
||||
<div class="overtime-item">
|
||||
<img class="icon-background mr-1" src="/assets/community/icons/calendar.svg" />
|
||||
{{ member.get_course_membership("Mentor") | length }} Mentored
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
|
||||
{% macro CoursesCreated(member) %}
|
||||
{% if member.get_authored_courses() | length %}
|
||||
<div class="course-home-headings">
|
||||
Courses Created
|
||||
</div>
|
||||
<div class="cards-parent">
|
||||
{% for course in member.get_authored_courses() %}
|
||||
{% set course_details = frappe.get_doc("LMS Course", course) %}
|
||||
{{ widgets.CourseCard(course=course_details) }}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro CoursesMentored(member) %}
|
||||
{% if member.get_course_membership("Mentor") | length %}
|
||||
<div class="course-home-headings">
|
||||
Courses Mentored
|
||||
</div>
|
||||
<div class="cards-parent">
|
||||
{% for membership in member.get_course_membership("Mentor") %}
|
||||
{% set course_details = frappe.get_doc("LMS Course", membership.course) %}
|
||||
{{ widgets.CourseCard(course=course_details) }}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro CoursesEnrolled(member) %}
|
||||
{% if member.get_course_membership("Student") | length %}
|
||||
<div class="course-home-headings">
|
||||
Courses Enrolled
|
||||
</div>
|
||||
<div class="cards-parent">
|
||||
{% for membership in member.get_course_membership("Student") %}
|
||||
{% set course_details = frappe.get_doc("LMS Course", membership.course) %}
|
||||
{{ widgets.CourseCard(course=course_details) }}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro ProfileTabs(profile_tabs) %}
|
||||
<div>
|
||||
{% for tab in profile_tabs %}
|
||||
{% set slug = title.lower().replace(" ", "-") %}
|
||||
<div class="tab-content">
|
||||
<div class="tab-pane fade py-4 show active" role="tabpanel" id="slug">
|
||||
{{ tab.render() }}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import frappe
|
||||
from community.lms.models import Sketch
|
||||
|
||||
def get_context(context):
|
||||
context.no_cache = 1
|
||||
|
||||