Merge branch 'main' of https://github.com/frappe/school into profile-redesign

This commit is contained in:
Jannat Patel
2022-02-22 11:34:22 +05:30
14 changed files with 317 additions and 171 deletions

View File

@@ -183,7 +183,9 @@ jinja = {
"school.lms.utils.get_reviews", "school.lms.utils.get_reviews",
"school.lms.utils.is_eligible_to_review", "school.lms.utils.is_eligible_to_review",
"school.lms.utils.get_initial_members", "school.lms.utils.get_initial_members",
"school.lms.utils.get_sorted_reviews" "school.lms.utils.get_sorted_reviews",
"school.lms.utils.is_instructor",
"school.lms.utils.convert_number_to_character"
], ],
"filters": [] "filters": []
} }

View File

@@ -2,6 +2,7 @@ import re
import frappe import frappe
from frappe.utils import flt, cint, cstr from frappe.utils import flt, cint, cstr
from school.lms.md import markdown_to_html from school.lms.md import markdown_to_html
import string
RE_SLUG_NOTALLOWED = re.compile("[^a-z0-9]+") RE_SLUG_NOTALLOWED = re.compile("[^a-z0-9]+")
@@ -81,7 +82,7 @@ def get_lesson_details(chapter):
for row in lesson_list: for row in lesson_list:
lesson_details = frappe.db.get_value("Course Lesson", row.lesson, lesson_details = frappe.db.get_value("Course Lesson", row.lesson,
["name", "title", "include_in_preview", "body"], as_dict=True) ["name", "title", "include_in_preview", "body", "creation"], as_dict=True)
lesson_details.number = flt("{}.{}".format(chapter.idx, row.idx)) lesson_details.number = flt("{}.{}".format(chapter.idx, row.idx))
lessons.append(lesson_details) lessons.append(lesson_details)
return lessons return lessons
@@ -289,3 +290,9 @@ def get_initial_members(course):
member.member, ["name", "username", "full_name", "user_image"], as_dict=True)) member.member, ["name", "username", "full_name", "user_image"], as_dict=True))
return member_details return member_details
def is_instructor(course):
return len(list(filter(lambda x: x.name == frappe.session.user, get_instructors(course)))) > 0
def convert_number_to_character(number):
return string.ascii_uppercase[number]

View File

@@ -4,7 +4,7 @@
{{ _("Course Content") }} {{ _("Course Content") }}
</div> </div>
{% for chapter in get_chapters(course.name) %} {% for chapter in get_chapters(course.name) %}
<div class="mb-2"> <div class="">
<div class="chapter-title" data-target="#{{ get_slugified_chapter_title(chapter.title) }}" <div class="chapter-title" data-target="#{{ get_slugified_chapter_title(chapter.title) }}"
data-toggle="collapse" aria-expanded="false"> data-toggle="collapse" aria-expanded="false">
<img class="chapter-icon" src="/assets/school/icons/chevron-right.svg"> <img class="chapter-icon" src="/assets/school/icons/chevron-right.svg">
@@ -41,14 +41,14 @@
{% elif is_instructor and not lesson.include_in_preview %} {% elif is_instructor and not lesson.include_in_preview %}
<a class="lesson-links" <a class="lesson-links"
title="This lesson is not available for preview. As you are the Instructor of the course only you can see it." title="This lesson is not available for preview. As you are the Instructor of the course only you can see it."
href="{{ course.get_lesson_url(lesson.number) }}{{course.query_parameter}}" href="{{ get_lesson_url(lesson.number) }}{{course.query_parameter}}"
data-course="{{ course.name }}"> data-course="{{ course.name }}">
<img class="mr-3" src="/assets/school/icons/lock.svg"> <img class="mr-3" src="/assets/school/icons/lock.svg">
<div>{{ lesson.title }}</div> <div>{{ lesson.title }}</div>
</a> </a>
{% else %} {% else %}
<div class="no-preview" title="This lesson is not available for preview"> <div class="no-preview" title="This lesson is not available for preview" data-course="{{ course.name }}">
<div class="lesson-links"> <div class="lesson-links">
<img class="mr-3" src="/assets/school/icons/lock.svg"> <img class="mr-3" src="/assets/school/icons/lock.svg">
<div>{{ lesson.title }}</div> <div>{{ lesson.title }}</div>
@@ -76,9 +76,13 @@ frappe.ready(() => {
rotate_chapter_icon(e); rotate_chapter_icon(e);
}); });
}) $(".no-preview").click((e) => {
show_no_preview_dialog(e);
});
var expand_the_first_chapter = () => { });
const expand_the_first_chapter = () => {
var elements = $(".course-outline .collapse"); var elements = $(".course-outline .collapse");
elements.each((i, element) => { elements.each((i, element) => {
if (i < 1) { if (i < 1) {
@@ -86,9 +90,9 @@ var expand_the_first_chapter = () => {
return false; return false;
} }
}); });
} };
var expand_the_active_chapter = () => { const expand_the_active_chapter = () => {
/* Find anchor matching the URL for course details page */ /* Find anchor matching the URL for course details page */
var selector = $(`a[href="${decodeURIComponent(window.location.pathname)}"]`).parent(); var selector = $(`a[href="${decodeURIComponent(window.location.pathname)}"]`).parent();
@@ -111,21 +115,30 @@ var expand_the_active_chapter = () => {
else { else {
expand_the_first_chapter(); expand_the_first_chapter();
} }
} };
var show_section = (element) => { const show_section = (element) => {
$(element).addClass("show"); $(element).addClass("show");
$(element).siblings(".chapter-title").children(".chapter-icon").css("transform", "rotate(90deg)"); $(element).siblings(".chapter-title").children(".chapter-icon").css("transform", "rotate(90deg)");
$(element).siblings(".chapter-title").attr("aria-expanded", true); $(element).siblings(".chapter-title").attr("aria-expanded", true);
} };
var rotate_chapter_icon = (e) => { const rotate_chapter_icon = (e) => {
var icon = $(e.currentTarget).children(".chapter-icon"); var icon = $(e.currentTarget).children(".chapter-icon");
if (icon.css("transform") == "none") { if (icon.css("transform") == "none") {
icon.css("transform", "rotate(90deg)"); icon.css("transform", "rotate(90deg)");
} else { } else {
icon.css("transform", "none"); icon.css("transform", "none");
} }
} };
const show_no_preview_dialog = (e) => {
frappe.warn(__("Not available for preview"),
__("This lesson is not available for preview. Please join the course to access it."),
() => {
window.location.href = `/courses/${ $(e.currentTarget).data("course") }`
},
__("Start Learning"), false);
};
</script> </script>

View File

@@ -11,6 +11,7 @@
</div> </div>
{% set avg_rating = get_average_rating(course.name) %} {% set avg_rating = get_average_rating(course.name) %}
{% if avg_rating %}
<div class="reviews-header"> <div class="reviews-header">
<div class="text-center"> <div class="text-center">
<div class="avg-rating"> {{ avg_rating }} </div> <div class="avg-rating"> {{ avg_rating }} </div>
@@ -43,9 +44,7 @@
{% endfor %} {% endfor %}
</div> </div>
</div> </div>
{% endif %}
{% if reviews | length %} {% if reviews | length %}
<div class="mt-12"> <div class="mt-12">
@@ -58,7 +57,7 @@
</div> </div>
<div> <div>
<a class="button-links" href="{{get_profile_url(review.owner_details.username) }}"> <a class="button-links" href="{{get_profile_url(review.owner_details.username) }}">
<span class="review-author"> <span class="bold-heading">
{{ review.owner_details.full_name }} {{ review.owner_details.full_name }}
</span> </span>
</a> </a>
@@ -84,19 +83,25 @@
</div> </div>
{% else %} {% else %}
<div class="empty-state text-center"> <div class="empty-state-new">
<img class="icon icon-xl" src="/assets/frappe/icons/timeless/message.svg"> <div>
<div class="course-home-headings mt-4 mb-0" style="color: inherit;"> {{ _("Review the course") }} </div> <img class="icon icon-xl" src="/assets/school/icons/comment.svg">
<div class="small mb-6"> {{ _("Help us improve our course material.") }} </div> </div>
{% if is_eligible_to_review(course.name, membership) %} <div class="empty-state-text">
<span class="review-link button is-secondary ml-auto mr-auto mt-3"> <div class="empty-state-heading">{{ _("Review the course") }}</div>
<div class="course-meta">{{ _("Help us improve our course material.") }}</div>
</div>
<div>
{% if is_eligible_to_review(course.name, membership) %}
<span class="review-link button is-secondary">
{{ _("Write a review") }} {{ _("Write a review") }}
</span> </span>
{% elif frappe.session.user == "Guest" %} {% elif frappe.session.user == "Guest" %}
<a class="button is-secondary dark-links ml-auto mr-auto mt-3" href="/login?redirect-to=/courses/{{ course.name }}"> {{ _("Login") }} </a> <a class="button is-primary" href="/login?redirect-to=/courses/{{ course.name }}"> {{ _("Login") }} </a>
{% elif not membership %} {% elif not membership %}
<div class="button is-secondary join-batch ml-auto mr-auto mt-3" data-course="{{ course.name | urlencode }}"> {{ _("Start Learning") }} </div> <div class="button is-primary join-batch" data-course="{{ course.name | urlencode }}"> {{ _("Start Learning") }} </div>
{% endif %} {% endif %}
</div>
</div> </div>
{% endif %} {% endif %}
</div> </div>

View File

@@ -114,10 +114,11 @@ def exercise_renderer(argument):
def youtube_video_renderer(video_id): def youtube_video_renderer(video_id):
return f""" return f"""
<iframe width="100%" height="315" <iframe width="100%" height="400"
src="https://www.youtube.com/embed/{video_id}" src="https://www.youtube.com/embed/{video_id}"
title="YouTube video player" title="YouTube video player"
frameborder="0" frameborder="0"
style="border-radius: var(--border-radius-lg)"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowfullscreen> allowfullscreen>
</iframe> </iframe>

View File

@@ -308,11 +308,6 @@ input[type=checkbox] {
width: 100px; width: 100px;
} }
.custom-checkbox {
display: flex;
align-items: center;
}
.custom-checkbox>label>input { .custom-checkbox>label>input {
visibility: hidden; visibility: hidden;
} }
@@ -324,6 +319,10 @@ input[type=checkbox] {
border-radius: var(--border-radius-md); border-radius: var(--border-radius-md);
} }
.empty-checkbox {
margin-right: 0.5rem;
}
.custom-checkbox>label>input:checked+.empty-checkbox { .custom-checkbox>label>input:checked+.empty-checkbox {
background: url(/assets/school/icons/tick.svg); background: url(/assets/school/icons/tick.svg);
background-repeat: no-repeat; background-repeat: no-repeat;
@@ -331,6 +330,8 @@ input[type=checkbox] {
} }
.quiz-label { .quiz-label {
display: flex;
align-items: center;
margin-bottom: 0; margin-bottom: 0;
cursor: pointer; cursor: pointer;
} }
@@ -403,6 +404,8 @@ input[type=checkbox] {
font-size: var(--text-md); font-size: var(--text-md);
line-height: 20px; line-height: 20px;
box-shadow: var(--btn-shadow); box-shadow: var(--btn-shadow);
border: none;
width: fit-content;
} }
.button:disabled { .button:disabled {
@@ -412,6 +415,7 @@ input[type=checkbox] {
.wide-button { .wide-button {
padding: 0.5rem 6rem; padding: 0.5rem 6rem;
font-weight: 500; font-weight: 500;
width: inherit;
} }
@media (max-width: 768px) { @media (max-width: 768px) {
@@ -435,10 +439,8 @@ input[type=checkbox] {
color: var(--gray-700); color: var(--gray-700);
} }
@media (max-width: 600px) { .is-default:disabled {
.video-preview { color: var(--gray-500);
margin-top: 16px;
}
} }
.is-primary { .is-primary {
@@ -465,10 +467,6 @@ input[type=checkbox] {
align-items: center; align-items: center;
} }
.chapter-title[aria-expanded="true"] {
background: var(--gray-100);
}
.chapter-description { .chapter-description {
margin: 0.75rem 3rem 1rem; margin: 0.75rem 3rem 1rem;
} }
@@ -478,7 +476,7 @@ input[type=checkbox] {
} }
.chapter-icon { .chapter-icon {
margin: 0 1.25rem; margin-right: 1.25rem;
} }
.course-outline-instructor-parent { .course-outline-instructor-parent {
@@ -542,14 +540,12 @@ input[type=checkbox] {
.lesson-info { .lesson-info {
font-size: 16px; font-size: 16px;
line-height: 250%;
letter-spacing: -0.011em; letter-spacing: -0.011em;
margin-top: 0.5rem;
} }
.lesson-links { .lesson-links {
display: flex; display: flex;
padding: 0 1rem; padding: 0.75rem 1rem;
color: inherit; color: inherit;
} }
@@ -560,16 +556,14 @@ input[type=checkbox] {
border-radius: var(--border-radius-md); border-radius: var(--border-radius-md);
} }
.course-content-parent .lesson-links { .course-content-parent .chapter-title {
padding: 0 0 0 1rem; font-size: var(--text-md);
margin-bottom: 0.75rem;
font-size: 0.85rem;
line-height: 200%;
} }
.chapter-content { .course-content-parent .lesson-links {
margin: 0; padding: 0.5rem 1rem;
margin-left: .875rem; font-size: var(--text-md);
line-height: 200%;
} }
.course-outline { .course-outline {
@@ -578,7 +572,7 @@ input[type=checkbox] {
} }
.lessons { .lessons {
margin-left: 2rem; margin-left: 2.5rem;
} }
.course-buttons { .course-buttons {
@@ -669,7 +663,7 @@ input[type=checkbox] {
font-weight: 600; font-weight: 600;
font-size: var(--text-3-5xl); font-size: var(--text-3-5xl);
letter-spacing: -0.0175em; letter-spacing: -0.0175em;
color: var(--gray-800); color: var(--gray-900);
margin-bottom: 1rem; margin-bottom: 1rem;
} }
@@ -831,12 +825,16 @@ input[type=checkbox] {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
margin-top: 5rem;
} }
.question { .question {
flex-direction: column; flex-direction: column;
width: 85%; }
margin: 0 auto;
.question-card {
flex-direction: column;
padding: 1.25rem;
} }
.question p { .question p {
@@ -868,8 +866,7 @@ input[type=checkbox] {
} }
.lesson-content-card { .lesson-content-card {
padding: 24px; margin-top: 2rem;
flex-direction: column;
} }
.lesson-content-card .alert-dismissible .close { .lesson-content-card .alert-dismissible .close {
@@ -896,6 +893,7 @@ input[type=checkbox] {
.lesson-pagination { .lesson-pagination {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center;
margin: 24px 0px 0px; margin: 24px 0px 0px;
} }
@@ -914,8 +912,15 @@ input[type=checkbox] {
} }
.active-lesson { .active-lesson {
background-color: var(--gray-100); background-color: var(--blue-100);
border-radius: var(--border-radius-md); border-radius: var(--border-radius-md);
color: var(--blue-500);
}
.lesson-title {
display: flex;
align-items: center;
margin-bottom: 1rem;
} }
.lesson-progress { .lesson-progress {
@@ -923,7 +928,7 @@ input[type=checkbox] {
padding: 4px 8px 4px; padding: 4px 8px 4px;
font-size: 10px; font-size: 10px;
line-height: 120%; line-height: 120%;
margin: 0px 10px 20px; margin-left: 1rem;
border-radius: var(--border-radius-md); border-radius: var(--border-radius-md);
font-weight: bold; font-weight: bold;
} }
@@ -1271,6 +1276,26 @@ pre {
padding: 2.5rem; padding: 2.5rem;
} }
.empty-state-new {
background: var(--gray-50);
border-radius: var(--border-radius-lg);
padding: 2rem;
display: flex;
justify-content: space-between;
align-items: center;
}
.empty-state-text {
flex: 1;
margin-left: 1.25rem;
}
.empty-state-heading {
font-size: var(--text-xl);
color: var(--gray-900);
font-weight: 600;
}
.vertically-center { .vertically-center {
display: flex; display: flex;
align-items: center; align-items: center;
@@ -1361,6 +1386,8 @@ pre {
.related-courses { .related-courses {
background: var(--gray-50); background: var(--gray-50);
padding: 5rem 0; padding: 5rem 0;
position: relative;
z-index: 5;
} }
.carousel-indicators { .carousel-indicators {
@@ -1555,10 +1582,11 @@ pre {
box-shadow: var(--shadow-sm); box-shadow: var(--shadow-sm);
overflow: auto; overflow: auto;
width: fit-content; width: fit-content;
position: absolute; position: fixed;
top: 40%; top: 40%;
right: 7%; right: 7%;
max-width: 400px; max-width: 400px;
z-index: 4;
} }
@media (max-width: 768px) { @media (max-width: 768px) {
@@ -1612,8 +1640,8 @@ pre {
.overlay-student-count { .overlay-student-count {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center;
margin-bottom: 1.5rem; margin-bottom: 1.5rem;
margin-top: 1rem;
} }
.course-creators-card { .course-creators-card {
@@ -1664,7 +1692,7 @@ pre {
} }
} }
.review-author { .bold-heading {
font-size: var(--text-lg); font-size: var(--text-lg);
color: var(--gray-900); color: var(--gray-900);
font-weight: 600; font-weight: 600;
@@ -1696,3 +1724,40 @@ pre {
.reviews-parent .progress-bar { .reviews-parent .progress-bar {
background-color: var(--gray-600); background-color: var(--gray-600);
} }
.course-home-top-container {
position: relative;
}
.question-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 2rem;
}
.question-number {
padding: 0.25rem 0.75rem;
border-radius: 50%;
box-shadow: var(--shadow-sm);
}
.option-text {
padding: 0.75rem;
box-shadow: var(--shadow-sm);
border-radius: var(--border-radius-md);
flex: 1;
}
.active-option .option-text {
background-color: var(--blue-50);
border: 1px solid var(--blue-500);
}
.question-text {
font-size: var(--text-lg);
color: var(--gray-900);
font-weight: 600;
flex-grow: 1;
margin-left: 1rem;
}

View File

@@ -0,0 +1,6 @@
<svg width="44" height="44" viewBox="0 0 44 44" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M28.875 3.85022H6.875C5.78098 3.85022 4.73177 4.28482 3.95818 5.0584C3.1846 5.83199 2.75 6.8812 2.75 7.97522V21.7252C2.75 22.8192 3.1846 23.8684 3.95818 24.642C4.73177 25.4156 5.78098 25.8502 6.875 25.8502H11V34.1002L19.25 25.8502H28.875C29.969 25.8502 31.0182 25.4156 31.7918 24.642C32.5654 23.8684 33 22.8192 33 21.7252V7.97522C33 6.8812 32.5654 5.83199 31.7918 5.0584C31.0182 4.28482 29.969 3.85022 28.875 3.85022V3.85022Z" stroke="#687178" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M8.5249 10.7252H27.7749" stroke="#687178" stroke-width="2" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M8.5249 16.2252H19.5249" stroke="#687178" stroke-width="2" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M16.5 31.9003H24.75L33 40.1503V31.9003H37.125C38.219 31.9003 39.2682 31.4657 40.0418 30.6921C40.8154 29.9186 41.25 28.8693 41.25 27.7753V14.0253C41.25 12.9313 40.8154 11.8821 40.0418 11.1085C39.2682 10.3349 38.219 9.90033 37.125 9.90033H35.75" stroke="#687178" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -2,43 +2,40 @@
<div class="card-divider"></div> <div class="card-divider"></div>
<div class="mt-5"> <div class="common-card-style question-card">
<form id="quiz-form"> <form id="quiz-form">
<div class="questions"> <div class="questions">
{% for question in quiz.questions %} {% for question in quiz.questions %}
{% set instruction = _("Choose all answers that apply") if question.multiple else _("Choose 1 answer") %}
<div class="question {% if loop.index == 1 %} active-question {% else %} hide {% endif %}" <div class="question {% if loop.index == 1 %} active-question {% else %} hide {% endif %}"
data-question="{{ question.question }}" data-multi="{{ question.multiple}}" data-qt-index="{{ loop.index }}"> data-question="{{ question.question }}" data-multi="{{ question.multiple}}" data-qt-index="{{ loop.index }}">
<p>{{ frappe.utils.md_to_html(question.question) }}</p> <div class="question-header">
<div class="question-number">{{ loop.index }}</div>
{% if question.multiple %} <div class="question-text">
<small class="font-weight-bold">Choose all answers that apply:</small> <div class="course-meta pull-right ml-2"> {{ instruction }} </div>
{% else %} {{ frappe.utils.md_to_html(question.question) }}
<small class="font-weight-bold">Choose 1 answer:</small> </div>
{% endif %}
<div class="card-divider"></div>
{% set options = [question.option_1, question.option_2, question.option_3, question.option_4] %}
{% for option in options %}
{% if option %}
<div class="custom-checkbox">
<label class="quiz-label">
<input class="option" value="{{ option | urlencode }}"
data-correct="{{ question['is_correct_' + loop.index | string] }}" {% if question.multiple %}
type="checkbox" {% else %} type="radio" name="{{ question.question | urlencode }}" {% endif %}>
<img class="empty-checkbox mr-3" />
<span class="label-area">{{ frappe.utils.md_to_html(option) }}</span>
</label>
</div> </div>
{% set explanation = question['explanation_' + loop.index | string] %} {% set options = [question.option_1, question.option_2, question.option_3, question.option_4] %}
{% if explanation %} {% for option in options %}
<small class="explanation muted-text hide">{{ explanation }}</small> {% if option %}
{% endif %} <div class="mb-2">
<div class="custom-checkbox">
<label class="quiz-label">
<div class="course-meta font-weight-bold ml-3"> {{ convert_number_to_character(loop.index - 1) }}</div>
<input class="option" value="{{ option | urlencode }}"
data-correct="{{ question['is_correct_' + loop.index | string] }}" {% if question.multiple %}
type="checkbox" {% else %} type="radio" name="{{ question.question | urlencode }}" {% endif %}>
<div class="option-text">{{ frappe.utils.md_to_html(option) }}</div>
</label>
</div>
<div class="card-divider"></div> {% set explanation = question['explanation_' + loop.index | string] %}
{% if explanation %}
<small class="explanation ml-3 hide">{{ explanation }}</small>
{% endif %}
</div>
{% endif %} {% endif %}
{% endfor %} {% endfor %}
@@ -47,12 +44,12 @@
</div> </div>
<div class="quiz-footer"> <div class="quiz-footer">
<span class="font-weight-bold"> <span class="current-question">1</span> of {{ quiz.questions | length }}</span> <span class="font-weight-bold"> <span class="current-question">1</span> of {{ quiz.questions | length }}</span>
<button class="button pull-right" id="check" disabled>Check</button> <button class="button pull-right is-default" id="check" disabled>{{ _("Check") }}</button>
<button class="button hide" id="next">Next Question</button> <div class="button is-secondary hide" id="next">{{ _("Next Question") }}</div>
<button class="button is-default hide" id="summary">Summary</button> <div class="button is-secondary is-default hide" id="summary">{{ _("Summary") }}</div>
<small id="submission-message" class="font-weight-bold hide"> Please join the course to submit the Quiz.</small> <small id="submission-message" class="font-weight-bold hide"> {{ _("Please join the course to submit the Quiz.") }} </small>
<div class="button is-secondary hide" id="try-again">{{ _("Try Again") }}</div>
</div> </div>
<div class="button is-secondary pull-right hide" id="try-again">Try Again</div>
<h4 class="success-message"></h4> <h4 class="success-message"></h4>
<h5 class="score text-muted"></h5> <h5 class="score text-muted"></h5>
</form> </form>

View File

@@ -33,34 +33,60 @@
{% endblock %} {% endblock %}
{% macro LessonContent(lesson) %} {% macro LessonContent(lesson) %}
{% set is_instructor = frappe.session.user == course.instructor %} {% set instructors = get_instructors(course.name) %}
{% set is_instructor = is_instructor(course.name) %}
<div class="lesson-content"> <div class="lesson-content">
<div class="course-home-headings title <div class="lesson-title">
{% if membership %} is-member {% endif %} <div class="course-home-headings title mb-0
{% if membership or is_instructor %} eligible-for-submission {% endif %}" data-lesson="{{ lesson.name }}" {% if membership %} is-member {% endif %}
data-course="{{ course.name }}"> {% if membership or is_instructor %} eligible-for-submission {% endif %}" data-lesson="{{ lesson.name }}"
{{ lesson.title }} data-course="{{ course.name }}">{{ lesson.title }}</div>
<span class="lesson-progress {{hide if get_progress(course.name, lesson.name) != 'Complete' else ''}}">COMPLETED</span> <span class="lesson-progress {{hide if get_progress(course.name, lesson.name) != 'Complete' else ''}}">COMPLETED</span>
</div> </div>
<div class="d-flex align-items-center">
{% set instructors = instructors %}
{% set ins_len = instructors | length %}
{% for instructor in instructors %}
{% if ins_len > 1 and loop.index == 1 %}
<div class="avatar-group overlap">
{% endif %}
{{ widgets.Avatar(member=instructor, avatar_class="avatar-small") }}
{% if ins_len > 1 and loop.index == ins_len %}
</div>
{% endif %}
{% endfor %}
<a class="button-links ml-2" href="{{ get_profile_url(instructors[0].username) }}">
<span class="course-creator-name">
{% if ins_len == 1 %}
{{ instructors[0].full_name }}
{% else %}
{% set suffix = "other" if ins_len - 1 == 1 else "others" %}
{{ instructors[0].full_name.split(" ")[0] }} and {{ ins_len - 1 }} {{ suffix }}
{% endif %}
</span>
</a>
<div class="ml-3 course-meta"> {{ frappe.utils.format_date(lesson.creation, "medium") }} </div>
</div>
<div class="markdown-source lesson-content-card">
{% if membership or lesson.include_in_preview or is_instructor %} {% if membership or lesson.include_in_preview or is_instructor %}
<div class="common-card-style lesson-content-card markdown-source"> {% if is_instructor and not lesson.include_in_preview %}
{% if is_instructor and not lesson.include_in_preview %} <div class="small alert alert-secondary alert-dismissible mt-4 mb-4">
<small class="alert alert-secondary alert-dismissible">
This lesson is not available for preview. As you are the Instructor of the course only you can see it. This lesson is not available for preview. As you are the Instructor of the course only you can see it.
<a href="#" class="close" data-dismiss="alert" aria-label="close">&times;</a> <a href="#" class="close" data-dismiss="alert" aria-label="close">&times;</a>
</small>
{% endif %}
{{ render_html(lesson.body) }}</div>
{% else %}
<div class="common-card-style lesson-content-card">
<div class="w-25 text-center" style="margin: 0 auto;">
<small>This lesson is not available for preview. Please join the course to access it.</small>
<a class="button is-primary ml-auto mr-auto mt-3" href="/courses/{{ course.name }}"> Start Learning </a>
</div> </div>
</div>
{% endif %} {% endif %}
{{ render_html(lesson.body) }}
{% else %}
<div class="">
<a class="button is-primary pull-right" href="/courses/{{ course.name }}"> Start Learning </a>
<div class="">This lesson is not available for preview. Please join the course to access it.</div>
</div>
{% endif %}
</div>
</div> </div>
{% endmacro %} {% endmacro %}
@@ -82,7 +108,7 @@
{% set progress = get_progress(course.name, lesson.name) %} {% set progress = get_progress(course.name, lesson.name) %}
<div class="custom-checkbox {% if progress == 'Complete' %} hide {% endif %}"> <div class="custom-checkbox {% if progress == 'Complete' %} hide {% endif %}">
<label class="quiz-label"> <label class="quiz-label">
<input class="option mark-progress" type="checkbox" checked> <input class="mark-progress" type="checkbox" checked>
<img class="empty-checkbox" /> <img class="empty-checkbox" />
<span class="small">{{ _("Mark as complete on moving to the next lesson") }}</span> <span class="small">{{ _("Mark as complete on moving to the next lesson") }}</span>
</label> </label>

View File

@@ -43,7 +43,7 @@ frappe.ready(() => {
$(".clear-work").click((e) => { $(".clear-work").click((e) => {
clear_work(e); clear_work(e);
}) });
}); });
@@ -57,8 +57,10 @@ const save_current_lesson = () => {
}; };
const enable_check = (e) => { const enable_check = (e) => {
if ($(".option:checked").length && $("#check").attr("disabled")) { if ($(".option:checked").length) {
$("#check").removeAttr("disabled"); $("#check").removeAttr("disabled");
$(".custom-checkbox").removeClass("active-option");
$(".option:checked").closest(".custom-checkbox").addClass("active-option");
} }
}; };
@@ -164,7 +166,7 @@ const quiz_summary = (e) => {
callback: (data) => { callback: (data) => {
var message = data.message == total_questions ? "Excellent Work" : "You were almost there." var message = data.message == total_questions ? "Excellent Work" : "You were almost there."
$(".question").addClass("hide"); $(".question").addClass("hide");
$(".quiz-footer").addClass("hide"); $("#summary").addClass("hide");
$("#quiz-form").parent().prepend( $("#quiz-form").parent().prepend(
`<div class="text-center summary"><h2>${message} 👏 </h2> `<div class="text-center summary"><h2>${message} 👏 </h2>
<div class="font-weight-bold">${data.message}/${total_questions} correct.</div></div>`); <div class="font-weight-bold">${data.message}/${total_questions} correct.</div></div>`);
@@ -222,8 +224,9 @@ const parse_options = () => {
}; };
const add_icon = (element, icon) => { const add_icon = (element, icon) => {
var label = $(element).parent().find(".label-area p").text(); $(element).closest(".custom-checkbox").removeClass("active-option");
$(element).parent().empty().html(`<img class="mr-3" src="/assets/school/icons/${icon}.svg"> ${label}`); var label = $(element).siblings(".option-text").text();
$(element).parent().empty().html(`<div class="option-text"><img class="mr-3" src="/assets/school/icons/${icon}.svg"> ${label}</div>`);
}; };
const add_to_local_storage = (quiz_name, current_index, answer, is_correct) => { const add_to_local_storage = (quiz_name, current_index, answer, is_correct) => {
@@ -354,27 +357,27 @@ const clear_work = (e) => {
parent.addClass("hide"); parent.addClass("hide");
parent.siblings(".attach-file").removeClass("hide").val(null); parent.siblings(".attach-file").removeClass("hide").val(null);
parent.siblings(".submit-work").removeClass("hide"); parent.siblings(".submit-work").removeClass("hide");
} };
const fetch_assignments = () => { const fetch_assignments = () => {
if ($(".attach-file").length > 0) { if ($(".attach-file").length <= 0)
frappe.call({ return;
method: "school.lms.doctype.lesson_assignment.lesson_assignment.get_assignment", frappe.call({
args: { method: "school.lms.doctype.lesson_assignment.lesson_assignment.get_assignment",
"lesson": $(".title").attr("data-lesson") args: {
}, "lesson": $(".title").attr("data-lesson")
callback: (data) => { },
if (data.message && data.message.length) { callback: (data) => {
const assignments = data.message; if (data.message && data.message.length) {
for (let i in assignments) { const assignments = data.message;
let target = $(`#${assignments[i]["id"]}`); for (let i in assignments) {
target.addClass("hide"); let target = $(`#${assignments[i]["id"]}`);
target.siblings(".submit-work").addClass("hide"); target.addClass("hide");
target.siblings(".preview-work").removeClass("hide"); target.siblings(".submit-work").addClass("hide");
target.siblings(".preview-work").find("a").attr("href", assignments[i]["assignment"]).text(assignments[i]["file_name"]); target.siblings(".preview-work").removeClass("hide");
} target.siblings(".preview-work").find("a").attr("href", assignments[i]["assignment"]).text(assignments[i]["file_name"]);
} }
} }
}) }
} });
} };

View File

@@ -37,7 +37,6 @@ def get_context(context):
"lesson": context.lesson.name, "lesson": context.lesson.name,
"is_member": context.membership is not None "is_member": context.membership is not None
} }
print(context)
def get_current_lesson_details(lesson_number, context): def get_current_lesson_details(lesson_number, context):
details_list = list(filter(lambda x: cstr(x.number) == lesson_number, context.lessons)) details_list = list(filter(lambda x: cstr(x.number) == lesson_number, context.lessons))

View File

@@ -6,13 +6,15 @@
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<div class="common-page-style pt-0"> <div class="common-page-style pt-0">
{{ CourseHomeHeader(course) }} <div class="course-home-top-container">
<div class="container course-home-page"> {{ CourseHomeHeader(course) }}
<div class="course-body-container"> {{ CourseHeaderOverlay(course) }}
{{ Description(course) }} <div class="container course-home-page">
{{ widgets.CourseOutline(course=course, membership=membership) }} <div class="course-body-container">
{{ CourseCreator(course) }} {{ Description(course) }}
{{ widgets.Reviews(course=course, membership=membership) }} {{ widgets.CourseOutline(course=course, membership=membership) }}
{{ widgets.Reviews(course=course, membership=membership) }}
</div>
</div> </div>
</div> </div>
{{ RelatedCourses(course) }} {{ RelatedCourses(course) }}
@@ -26,7 +28,6 @@
<div class="container pt-10 pb-10"> <div class="container pt-10 pb-10">
{{ BreadCrumb(course) }} {{ BreadCrumb(course) }}
{{ CourseCardWide(course) }} {{ CourseCardWide(course) }}
{{ CourseHeaderOverlay(course) }}
</div> </div>
</div> </div>
@@ -125,6 +126,14 @@
You have opted to be notified for this course. You will receive an email when the course becomes available. You have opted to be notified for this course. You will receive an email when the course becomes available.
</div> </div>
<div class="course-home-headings"> {{ course.title }} </div>
{% if get_lessons(course.name) | length %}
<div class="mt-3">
<img class="mr-3" src="/assets/school/icons/book.svg">
{{ get_lessons(course.name) | length }} {{ _("Lessons") }}
</div>
{% endif %}
{% if get_students(course.name) | length %} {% if get_students(course.name) | length %}
{% set initial_members = get_initial_members(course.name) %} {% set initial_members = get_initial_members(course.name) %}
<div class="overlay-student-count"> <div class="overlay-student-count">
@@ -188,13 +197,6 @@
</div> </div>
{% endif %} {% endif %}
<div class="overlay-heading"> {{ _("Course Include:") }} </div>
{% if get_lessons(course.name) | length %}
<div class="mt-3">
<img class="mr-3" src="/assets/school/icons/book.svg">
{{ get_lessons(course.name) | length }} {{ _("Lessons") }}
</div>
{% endif %}
</div> </div>
</div> </div>

View File

@@ -21,10 +21,6 @@ frappe.ready(() => {
view_all_mentors(e); view_all_mentors(e);
}); });
$(".video-preview").click((e) => {
show_video_dialog(e);
});
$(".review-link").click((e) => { $(".review-link").click((e) => {
show_review_dialog(e); show_review_dialog(e);
}); });
@@ -45,6 +41,12 @@ frappe.ready(() => {
create_certificate(e); create_certificate(e);
}); });
$(document).scroll(function() {
let timer;
clearTimeout(timer);
timer = setTimeout(() => { handle_overlay_display.apply(this, arguments); }, 500);
});
}) })
var check_mentor_request = () => { var check_mentor_request = () => {
@@ -158,11 +160,6 @@ var view_all_mentors = (e) => {
} }
} }
var show_video_dialog = (e) => {
e.preventDefault();
$("#video-modal").modal("show");
}
var show_review_dialog = (e) => { var show_review_dialog = (e) => {
e.preventDefault(); e.preventDefault();
$("#review-modal").modal("show"); $("#review-modal").modal("show");
@@ -235,3 +232,27 @@ const create_certificate = (e) => {
} }
}) })
}; };
const element_not_in_viewport = (el) => {
const rect = el.getBoundingClientRect();
return rect.bottom < 0 || rect.right < 0 || rect.left > window.innerWidth || rect.top > window.innerHeight;
}
const handle_overlay_display = () => {
const element = $(".related-courses").length && $(".related-courses")[0];
if (element && element_not_in_viewport(element)) {
$(".course-overlay-card").css({
"position": "fixed",
"top": "30%",
"bottom": "inherit"
});
}
else if (element && !element_not_in_viewport(element)) {
$(".course-overlay-card").css({
"position": "absolute",
"top": "inherit",
"bottom": "5%"
});
}
}

View File

@@ -1,6 +1,5 @@
import frappe import frappe
from school.lms.utils import slugify, get_membership, get_lessons, get_batch from school.lms.utils import slugify, get_membership, get_lessons, get_batch, get_lesson_url
def get_common_context(context): def get_common_context(context):
context.no_cache = 1 context.no_cache = 1