fix: course details interactions

This commit is contained in:
pateljannat
2021-07-08 10:55:03 +05:30
parent 0ed5309b97
commit 27c01b3b0c
13 changed files with 307 additions and 102 deletions

View File

@@ -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

View File

@@ -283,6 +283,15 @@ class LMSCourse(Document):
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

View File

@@ -1,21 +1,34 @@
<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/side-arrow.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="lessons">
{% for lesson in chapter.get_lessons() %}
<div class="lesson-info">
{% if show_link or lesson.include_in_preview %}
<a class="dark-links"
href="{{ course.get_learn_url(course.get_lesson_index(lesson.name)) }}{{course.query_parameter}}"
data-course="{{ course.name }}">
{{ lesson.title }}</a>
{% if show_link %}
<img class="lesson-progress-tick {{ course.get_progress(lesson.name) != 'Complete' and 'hide' }}"
src="/assets/community/icons/white-tick.svg">
{% endif %}
{% else %}
<div title="This lesson is not available for preview">
<span class="dark-links">
@@ -24,11 +37,14 @@
<i class="fa fa-lock ml-2"></i>
</div>
{% endif %}
</div>
{% endfor %}
</div>
</div>
</div>
{% if index != course.get_chapters() | length %}
<div class="card-divider"></div>
{% endif %}
@@ -36,16 +52,28 @@
<script>
frappe.ready(() => {
expand_the_first_chapter();
expand_the_active_chapter();
})
var expand_the_first_chapter = () => {
var elements = $(".collapse");
elements.each((i, elem) => {
elements.each((i, element) => {
if (i <= 1) {
$(elem).addClass("show");
$(elem).siblings(".chapter-title").children(".chapter-icon").css("transform", "rotate(90deg)");
show_section(element);
}
});
}
var expand_the_active_chapter = () => {
var selector = $(`a[href="${decodeURIComponent(window.location.pathname)}"]`).parent();
if (selector.length) {
show_section(selector.parent().parent());
}
}
var show_section = (element) => {
$(element).addClass("show");
$(element).siblings(".chapter-title").children(".chapter-icon").css("transform", "rotate(90deg)");
}
</script>

View File

@@ -11,4 +11,5 @@
Created {{ course_count }} {{ suffix }}
</div>
{% endif %}
<a class="stretched-link" href="/{{ member.username }}"></a>
</div>

View File

@@ -77,7 +77,7 @@
</form>
</div>
<div class="modal-footer">
<div class="wide-button submit-review is-primary" data-course="{{ course.name | urlencode}}" id="submit-review">Submit</div>
<div class="button wide-button submit-review is-primary" data-course="{{ course.name | urlencode}}" id="submit-review">Submit</div>
</div>
</div>
</div>

View File

@@ -489,6 +489,7 @@ div.custom-checkbox>label>input:checked+img {
padding: 24px;
background: #E2E6E9;
border-radius: 12px;
margin-top: 16px;
}
@media (max-width: 768px) {
@@ -543,8 +544,8 @@ div.custom-checkbox>label>input:checked+img {
@media (max-width: 768px) {
.course-home-page {
padding: 0px;
width: 688px;
padding: 0px 0px 30px;
width: 90%;
}
}
@@ -605,17 +606,26 @@ div.custom-checkbox>label>input:checked+img {
}
}
.wide-button {
.button {
box-shadow: 0px 1px 2px rgba(0, 0, 0, 0.13), 0px 0px 0.5px rgba(0, 0, 0, 0.5);
border-radius: 6px;
padding: 12px 24px 12px;
margin-right: 16px;
height: 48px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
width: fit-content;
padding: 8px 12px 8px;
font-size: 12px;
line-height: 135%;
letter-spacing: -0.011em;
}
.wide-button {
padding: 12px 24px 12px;
height: 48px;
margin-right: 16px;
font-size: 16px;
line-height: 150%;
}
@media (max-width: 375px) {
@@ -634,7 +644,7 @@ div.custom-checkbox>label>input:checked+img {
}
@media (max-width: 375px) {
.is-secondary {
.video-preview {
margin-top: 16px;
}
}
@@ -712,6 +722,7 @@ div.custom-checkbox>label>input:checked+img {
line-height: 250%;
letter-spacing: -0.011em;
margin-bottom: 2px;
padding-left: 10px;
}
.chapter-content {
@@ -910,11 +921,24 @@ div.custom-checkbox>label>input:checked+img {
}
.breadcrumb {
margin: 16px 10px 16px;
margin: 16px 10px 0px;
}
.course-details-outline {
width: 352px;
margin-top: 16px;
}
@media (max-width: 768px) {
.course-details-outline {
width: 688px;
}
}
@media (max-width: 375px) {
.course-details-outline {
width: 312px;
}
}
.lesson-content-card {
@@ -922,13 +946,20 @@ div.custom-checkbox>label>input:checked+img {
flex-direction: column;
}
.lesson-content {
width: 736px;
}
.course-content-parent {
display: flex;
justify-content: space-between;
flex-wrap: wrap-reverse;
}
@media (max-width: 375px) {
.course-content-parent {
justify-content: center;
}
}
.course-content-parent .course-home-headings {
margin: 0px 0px 16px;
}
.lesson-pagination {
@@ -936,3 +967,65 @@ div.custom-checkbox>label>input:checked+img {
justify-content: space-between;
margin: 24px 0px 0px;
}
.lesson-pagination-parent {
width: 736px;
margin-top: 16px;
}
@media (max-width: 768px) {
.lesson-pagination-parent {
width: 690px;
}
}
@media (max-width: 375px) {
.lesson-pagination-parent {
width: 312px;
}
}
.course-details-page {
padding: 0px 0px 80px;
display: flex;
flex-direction: column;
width: 1120px;
margin: 0 auto;
}
@media (max-width: 768px) {
.course-details-page {
padding: 24px 0px 24px;
width: 90%;
}
}
@media (max-width: 375px) {
.course-details-page {
width: 100%;
}
}
.active-lesson {
background-color: #EBF5FF;
border-radius: 4px;
}
.lesson-progress {
background: #BFDDF7;
padding: 4px 8px 4px;
font-size: 10px;
line-height: 120%;
margin: 0px 10px 20px;
border-radius: 4px;
font-weight: bold;
}
.lesson-progress-tick {
width: 16px;
height: 16px;
background: #4C5A67;
border-radius: 2px;
padding: 2px;
margin: 0px 4px 4px;
}

View 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

View File

@@ -21,7 +21,7 @@
{% block content %}
<div class="common-page-style">
<div class="course-home-page">
<div class="course-details-page">
{{ widgets.Breadcrumb(course=course, lesson=lesson) }}
<div class="course-content-parent">
<div class="course-details-outline">
@@ -40,9 +40,10 @@
{% macro LessonContent(lesson) %}
<div class="lesson-content">
<div class="course-home-headings 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 }}</div>
<div class="course-home-headings title" 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>
@@ -52,6 +53,7 @@
<a href="/courses/{{ course.name }}">Checkout Course Details.</a>
</div>
{% endif %}
</div>
{% endmacro %}
@@ -59,22 +61,33 @@
<div class="lesson-pagination">
{% if prev_url %}
<a class="wide-button is-secondary dark-links" href="{{ 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 class="wide-button is-primary">
{% 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 %}
{% endif %}
{% if next_url %}
<a class="wide-button is-secondary dark-links" href="{{ next_url }}">
<a class="button is-primary" href="{{ next_url }}">
Next
<img class="ml-2" src="/assets/community/icons/side-arrow.svg">
<img class="ml-2" src="/assets/community/icons/side-arrow-white.svg">
</a>
{% endif %}
</div>
{% endmacro %}

View File

@@ -1,69 +1,111 @@
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")
}
})
}
highlight_active_lesson();
/* Save Current Lesson */
save_current_lesson();
$("#progress").click((e) => {
mark_progress(e);
});
$("#submit-quiz").click((e) => {
submit_quiz(e);
});
$("#try-again").click((e) => {
try_quiz_again(e);
});
})
var highlight_active_lesson = () => {
var selector = $(`a[href="${decodeURIComponent(window.location.pathname)}"]`).parent();
if (selector.length) {
selector.addClass('active-lesson');
}
}
var save_current_lesson = () => {
if ($(".title").attr("data-membership")) {
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");
}

View File

@@ -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():

View File

@@ -40,7 +40,7 @@
</div>
<div class="course-buttons">
{% if not course.disable_self_learning and not membership %}
<div class="wide-button start-learning is-primary join-batch" data-course="{{ course.name | urlencode }}">
<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>
@@ -49,13 +49,13 @@
{% set lesson_index = course.get_lesson_index(membership.current_lesson) if membership and
membership.current_lesson
else '1.1' %}
<a class="wide-button is-primary" id="continue-learning"
<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="wide-button is-secondary video-preview">
<div class="button wide-button is-secondary video-preview">
Watch Video Preview
<img class="ml-2" src="/assets/community/images/play.png" />
</div>

View File

@@ -132,7 +132,7 @@ var join_course = (e) => {
if (data.message == "OK") {
frappe.msgprint(__("You are now a student of this course."));
setTimeout(function () {
window.location.href = `/courses/${course}/home`;
window.location.href = `/courses/${course}/learn/1.1`;
}, 2000);
}
}

View File

@@ -65,17 +65,30 @@
</span>
{% endif %}
</div>
{% if course.get_membership(frappe.session.user) %}
{% 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>
<a class="stretched-link" href="/courses/{{ course.name }}"></a>
</div>
{% endmacro %}