style: course home

This commit is contained in:
Jannat Patel
2022-02-16 19:12:25 +05:30
parent 5218e134a9
commit a23a356bf6
28 changed files with 789 additions and 515 deletions

View File

@@ -26,6 +26,7 @@
{% endfor %} {% endfor %}
</div> </div>
{% if testimonials_table | length > 3 %}
<div class="slider-controls"> <div class="slider-controls">
<a class="carousel-control-prev" href="#carouselExampleControls" role="button" data-slide="prev"> <a class="carousel-control-prev" href="#carouselExampleControls" role="button" data-slide="prev">
<span class="carousel-control-prev-icon" aria-hidden="true"></span> <span class="carousel-control-prev-icon" aria-hidden="true"></span>
@@ -44,4 +45,6 @@
<span class="sr-only">Next</span> <span class="sr-only">Next</span>
</a> </a>
</div> </div>
{% endif %}
</div> </div>

View File

@@ -12,10 +12,10 @@
<div class="common-card-style member-card"> <div class="common-card-style member-card">
{{ widgets.Avatar(member=member, avatar_class="avatar-large")}} {{ widgets.Avatar(member=member, avatar_class="avatar-large")}}
<div class="small-title member-card-title"> <div class=" member-card-title">
{{ member.full_name }} {{ member.full_name }}
</div> </div>
<div class="small-title"> <div class="">
{{exhibitor_doc.company}} {{exhibitor_doc.company}}
</div> </div>
<a class="stretched-link" href=""></a> <a class="stretched-link" href=""></a>

View File

@@ -12,10 +12,10 @@
<div class="common-card-style member-card"> <div class="common-card-style member-card">
{{ widgets.Avatar(member=member, avatar_class="avatar-large") }} {{ widgets.Avatar(member=member, avatar_class="avatar-large") }}
<div class="small-title member-card-title"> <div class=" member-card-title">
{{ member.full_name }} {{ member.full_name }}
</div> </div>
<div class="small-title"> <div class="">
{{speaker_doc.company}} {{speaker_doc.company}}
</div> </div>
<a class="stretched-link" href=""></a> <a class="stretched-link" href=""></a>

View File

@@ -60,7 +60,7 @@
<div class="course-card-meta-2"> <div class="course-card-meta-2">
{{ widgets.Avatar(member=member, avatar_class="avatar-small")}} {{ widgets.Avatar(member=member, avatar_class="avatar-small")}}
<span class="course-instructor"> {{ member.full_name }} </span> <span class="course-instructor"> {{ member.full_name }} </span>
<span class="small-title company-name"></span> <span class=" company-name"></span>
</div> </div>
<div class="view-talk-link"> <div class="view-talk-link">
Vew Talk Vew Talk

View File

@@ -182,6 +182,8 @@ jinja = {
"school.lms.utils.get_mentors", "school.lms.utils.get_mentors",
"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_sorted_reviews"
], ],
"filters": [] "filters": []
} }

View File

@@ -6,6 +6,7 @@ from frappe.model.document import Document
from frappe.utils import nowdate, add_years from frappe.utils import nowdate, add_years
from frappe import _ from frappe import _
from frappe.utils.pdf import get_pdf from frappe.utils.pdf import get_pdf
from school.lms.utils import is_certified
class LMSCertification(Document): class LMSCertification(Document):
@@ -22,8 +23,7 @@ class LMSCertification(Document):
@frappe.whitelist() @frappe.whitelist()
def create_certificate(course): def create_certificate(course):
course_details = frappe.get_doc("LMS Course", course) certificate = is_certified()
certificate = course_details.is_certified()
if certificate: if certificate:
return certificate return certificate

View File

@@ -20,6 +20,14 @@ frappe.ui.form.on('LMS Course', {
} }
}; };
}); });
frm.set_query("course", "related_courses", function () {
return {
filters: {
"is_published": true,
}
};
});
} }
}); });

View File

@@ -10,7 +10,7 @@
"allow_guest_to_view": 1, "allow_guest_to_view": 1,
"allow_import": 1, "allow_import": 1,
"allow_rename": 1, "allow_rename": 1,
"creation": "2021-03-01 16:49:33.622422", "creation": "2022-02-08 16:34:42.721203",
"doctype": "DocType", "doctype": "DocType",
"editable_grid": 1, "editable_grid": 1,
"engine": "InnoDB", "engine": "InnoDB",
@@ -31,6 +31,7 @@
"short_introduction", "short_introduction",
"description", "description",
"chapters", "chapters",
"related_courses",
"certification_section", "certification_section",
"enable_certification", "enable_certification",
"expiry" "expiry"
@@ -141,6 +142,12 @@
"fieldtype": "Select", "fieldtype": "Select",
"label": "Certification Expires After Years", "label": "Certification Expires After Years",
"options": "0\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10" "options": "0\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10"
},
{
"fieldname": "related_courses",
"fieldtype": "Table",
"label": "Related Courses",
"options": "Related Courses"
} }
], ],
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
@@ -167,7 +174,7 @@
"link_fieldname": "course" "link_fieldname": "course"
} }
], ],
"modified": "2022-02-07 11:41:39.735325", "modified": "2022-02-16 11:50:20.661085",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "LMS", "module": "LMS",
"name": "LMS Course", "name": "LMS Course",
@@ -192,4 +199,4 @@
"states": [], "states": [],
"title_field": "title", "title_field": "title",
"track_changes": 1 "track_changes": 1
} }

View File

@@ -0,0 +1,8 @@
// Copyright (c) 2022, Frappe and contributors
// For license information, please see license.txt
frappe.ui.form.on('Related Courses', {
// refresh: function(frm) {
// }
});

View File

@@ -0,0 +1,32 @@
{
"actions": [],
"allow_rename": 1,
"creation": "2022-02-16 11:45:07.200407",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"course"
],
"fields": [
{
"fieldname": "course",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Course",
"options": "LMS Course"
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2022-02-16 11:48:30.964916",
"modified_by": "Administrator",
"module": "LMS",
"name": "Related Courses",
"owner": "Administrator",
"permissions": [],
"sort_field": "modified",
"sort_order": "DESC",
"states": []
}

View File

@@ -0,0 +1,8 @@
# Copyright (c) 2022, Frappe and contributors
# For license information, please see license.txt
# import frappe
from frappe.model.document import Document
class RelatedCourses(Document):
pass

View File

@@ -0,0 +1,8 @@
# Copyright (c) 2022, Frappe and Contributors
# See license.txt
# import frappe
import unittest
class TestRelatedCourses(unittest.TestCase):
pass

View File

@@ -105,7 +105,7 @@ def sanitize_html(html, macro):
any broken tags. This makes sures that all those things are fixed any broken tags. This makes sures that all those things are fixed
before passing to the etree parser. before passing to the etree parser.
""" """
soup = BeautifulSoup(html, features="lxml") soup = BeautifulSoup(html, features="html5lib")
nodes = soup.body.children nodes = soup.body.children
classname = "" classname = ""
if macro == "YouTubeVideo": if macro == "YouTubeVideo":

View File

@@ -1,6 +1,6 @@
import re import re
import frappe import frappe
from frappe.utils import flt, cint from frappe.utils import flt, cint, cstr
from school.lms.md import markdown_to_html from school.lms.md import markdown_to_html
RE_SLUG_NOTALLOWED = re.compile("[^a-z0-9]+") RE_SLUG_NOTALLOWED = re.compile("[^a-z0-9]+")
@@ -128,7 +128,7 @@ def get_reviews(course):
{ {
"course": course "course": course
}, },
["review", "rating", "owner"], ["review", "rating", "owner", "creation"],
order_by= "creation desc") order_by= "creation desc")
out_of_ratings = frappe.db.get_all("DocField", out_of_ratings = frappe.db.get_all("DocField",
{ {
@@ -144,6 +144,22 @@ def get_reviews(course):
return reviews return reviews
def get_sorted_reviews(course):
rating_count = rating_percent = frappe._dict()
keys = ["5.0", "4.0", "3.0", "2.0", "1.0"]
for key in keys:
rating_count[key] = 0
reviews = get_reviews(course)
for review in reviews:
rating_count[cstr(review.rating)] += 1
for key in keys:
rating_percent[key] = (rating_count[key]/len(reviews) * 100)
return rating_percent
def is_certified(course): def is_certified(course):
certificate = frappe.get_all("LMS Certification", certificate = frappe.get_all("LMS Certification",
{ {
@@ -258,3 +274,18 @@ def get_course_progress(course, member=None):
}) })
precision = cint(frappe.db.get_default("float_precision")) or 3 precision = cint(frappe.db.get_default("float_precision")) or 3
return flt(((completed_lessons/lesson_count) * 100), precision) return flt(((completed_lessons/lesson_count) * 100), precision)
def get_initial_members(course):
members = frappe.get_all("LMS Batch Membership",
{
"course": course
},
["member"],
limit=3)
member_details = []
for member in members:
member_details.append(frappe.db.get_value("User",
member.member, ["name", "username", "full_name", "user_image"], as_dict=True))
return member_details

View File

@@ -7,10 +7,8 @@
{{ widgets.CourseCard(course=course, read_only=False) }} {{ widgets.CourseCard(course=course, read_only=False) }}
{% endfor %} {% endfor %}
</div> </div>
<a class="button-links d-flex justify-content-center mt-12 intercative-link" style="color: var(--gray-800);" href="/courses"> <a class="d-flex justify-content-center align-items-center mt-12" href="/courses">
<div> <span>{{ _("Explore More") }}</span>
Explore More <img src="/assets/school/icons/blue-arrow.svg" class="ml-2"/>
</div>
<img src="/assets/school/icons/arrow.svg" class="ml-2"/>
</a> </a>
</div> </div>

View File

@@ -1,123 +0,0 @@
<div>
<div class="chapter-title small-title" data-target="#{{ get_slugified_chapter_title(chapter.title) }}"
data-toggle="collapse" aria-expanded="false">
<img class="chapter-icon" src="/assets/school/icons/chevron-right.svg">
{{ index }}. {{ chapter.title }}
</div>
<div class="chapter-content collapse navbar-collapse" id="{{ get_slugified_chapter_title(chapter.title) }}">
{% if chapter.description %}
<div class="chapter-description muted-text">
{{ chapter.description }}
</div>
{% endif %}
{% set is_instructor = frappe.session.user == course.instructor %}
<div class="lessons">
{% for lesson in get_lessons(course.name, chapter) %}
<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="{{ get_lesson_url(course.name, lesson.number) }}{{course.query_parameter}}"
data-course="{{ course.name }}">
{{ lesson.title }}
{% if membership %}
<img class="ml-1 lesson-progress-tick {{ get_progress(course.name, lesson.name) != 'Complete' and 'hide' }}"
src="/assets/school/icons/check.svg">
{% endif %}
</a>
{% elif is_instructor and not lesson.include_in_preview %}
<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."
href="{{ course.get_lesson_url(lesson.number) }}{{course.query_parameter}}"
data-course="{{ course.name }}">
{{ lesson.title }}
<img class="ml-2" src="/assets/school/icons/lock.svg">
</a>
{% else %}
<div class="no-preview" title="This lesson is not available for preview">
<div class="lesson-links">
{{ lesson.title }}
<img class="ml-2" src="/assets/school/icons/lock.svg">
</div>
</div>
{% endif %}
</div>
{% endfor %}
</div>
</div>
</div>
{% if index != get_chapters(course.name) | length %}
<div class="card-divider"></div>
{% endif %}
<script>
frappe.ready(() => {
expand_the_active_chapter();
$(".chapter-title").unbind().click((e) => {
rotate_chapter_icon(e);
});
});
var expand_the_first_chapter = () => {
var elements = $(".course-outline .collapse");
elements.each((i, element) => {
if (i < 1) {
show_section(element);
return false;
}
});
}
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)");
}
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");
}
}
</script>

View File

@@ -58,11 +58,11 @@
{% endif %} {% endif %}
</span> </span>
</a> </a>
</span> </span>
{% set student_count = get_students(course.name) | length %} {% set student_count = get_students(course.name) | length %}
<span class="course-student-count"> <span class="course-student-count">
{% if student_count %} {% if student_count %}
<span class="vertically-center mr-4"> <span class="vertically-center mr-3">
<img class="icon-background" src="/assets/school/icons/user.svg" /> <img class="icon-background" src="/assets/school/icons/user.svg" />
{{ student_count }} {{ student_count }}
</span> </span>
@@ -85,15 +85,8 @@
membership.current_lesson else '1.1' %} membership.current_lesson else '1.1' %}
{% set query_parameter = "?batch=" + membership.batch if membership and {% set query_parameter = "?batch=" + membership.batch if membership and
membership.batch else "" %} membership.batch else "" %}
{% set certificate = is_certified(course.name) %}
{% if certificate %} {% if progress == 100 %}
<a class="stretched-link" href="/courses/{{ course.name }}/{{ certificate }}"></a>
{% elif course.enable_certification and progress == 100 %}
<a class="stretched-link" id="certification" data-course="{{ course.name }}"></a>
{% elif progress == 100 %}
<a class="stretched-link" href="/courses/{{ course.name }}"></a> <a class="stretched-link" href="/courses/{{ course.name }}"></a>
{% elif course.upcoming %} {% elif course.upcoming %}
@@ -109,27 +102,3 @@
{% endif %} {% endif %}
</div> </div>
</div> </div>
<script>
frappe.ready(() => {
$("#certification").unbind().click((e) => {
create_certificate(e);
});
})
var create_certificate = (e) => {
e.preventDefault();
course = $(e.currentTarget).attr("data-course");
frappe.call({
method: "school.lms.doctype.lms_certification.lms_certification.create_certificate",
args: {
"course": course
},
callback: (data) => {
window.location.href = `/courses/${course}/${data.message}`;
}
})
}
</script>

View File

@@ -1,12 +1,131 @@
{% if get_chapters(course.name) | length %} {% if get_chapters(course.name) | length %}
<div class=""> <div class="course-home-outline">
<div class="course-home-headings"> <div class="course-home-headings">
Course Outline {{ _("Course Content") }}
</div> </div>
<div class="common-card-style course-outline"> {% for chapter in get_chapters(course.name) %}
{% for chapter in get_chapters(course.name) %} <div class="mb-2">
{{ widgets.ChapterTeaser(index=loop.index, chapter=chapter, course=course, batch=batch, membership=membership) }} <div class="chapter-title" data-target="#{{ get_slugified_chapter_title(chapter.title) }}"
{% endfor %} data-toggle="collapse" aria-expanded="false">
<img class="chapter-icon" src="/assets/school/icons/chevron-right.svg">
<div>{{ chapter.title }}</div>
</div>
<div class="chapter-content collapse navbar-collapse" id="{{ get_slugified_chapter_title(chapter.title) }}">
{% if chapter.description %}
<div class="chapter-description muted-text">
{{ chapter.description }}
</div>
{% endif %}
{% set is_instructor = frappe.session.user == course.instructor %}
<div class="lessons">
{% for lesson in get_lessons(course.name, chapter) %}
<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="{{ get_lesson_url(course.name, lesson.number) }}{{course.query_parameter}}"
data-course="{{ course.name }}">
{{ lesson.title }}
{% if membership %}
<img class="ml-1 lesson-progress-tick {{ get_progress(course.name, lesson.name) != 'Complete' and 'hide' }}"
src="/assets/school/icons/check.svg">
{% endif %}
</a>
{% elif is_instructor and not lesson.include_in_preview %}
<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."
href="{{ course.get_lesson_url(lesson.number) }}{{course.query_parameter}}"
data-course="{{ course.name }}">
<img class="mr-3" src="/assets/school/icons/lock.svg">
<div>{{ lesson.title }}</div>
</a>
{% else %}
<div class="no-preview" title="This lesson is not available for preview">
<div class="lesson-links">
<img class="mr-3" src="/assets/school/icons/lock.svg">
<div>{{ lesson.title }}</div>
</div>
</div>
{% endif %}
</div>
{% endfor %}
</div>
</div>
</div> </div>
{% endfor %}
</div> </div>
{% endif %} {% endif %}
<script>
frappe.ready(() => {
expand_the_active_chapter();
$(".chapter-title").unbind().click((e) => {
rotate_chapter_icon(e);
});
})
var expand_the_first_chapter = () => {
var elements = $(".course-outline .collapse");
elements.each((i, element) => {
if (i < 1) {
show_section(element);
return false;
}
});
}
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)");
$(element).siblings(".chapter-title").attr("aria-expanded", true);
}
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");
}
}
</script>

View File

@@ -1,12 +1,12 @@
<div class="common-card-style member-card"> <div class="common-card-style member-card">
{{ widgets.Avatar(member=member, avatar_class=avatar_class) }} {{ widgets.Avatar(member=member, avatar_class=avatar_class) }}
<div class="small-title member-card-title {% if show_course_count %} font-weight-bold {% endif %}"> <div class=" member-card-title {% if show_course_count %} font-weight-bold {% endif %}">
{{ member.full_name }} {{ member.full_name }}
</div> </div>
{% set course_count = get_authored_courses(member.name) | length %} {% set course_count = get_authored_courses(member.name) | length %}
{% if show_course_count and course_count > 0 %} {% if show_course_count and course_count > 0 %}
{% set suffix = "Courses" if course_count > 1 else "Course" %} {% set suffix = "Courses" if course_count > 1 else "Course" %}
<div class="small-title"> <div class="">
Created {{ course_count }} {{ suffix }} Created {{ course_count }} {{ suffix }}
</div> </div>
{% endif %} {% endif %}

View File

@@ -10,29 +10,76 @@
{% endif %} {% endif %}
</div> </div>
{% if reviews | length %} {% set avg_rating = get_average_rating(course.name) %}
<div class="reviews-section"> <div class="reviews-header">
{% for review in reviews %} <div class="text-center">
<div class="review-card"> <div class="avg-rating"> {{ avg_rating }} </div>
<div class="common-card-style review-content small-title"> {{ review.review }} </div> <div class="course-meta"> {{ reviews | length }} {{ _("ratings") }} </div>
<div class="review-card-footer"> <div class="avg-rating-stars">
<div>
{{ widgets.Avatar(member=review.owner_details, avatar_class="avatar-medium") }}
<a class="button-links" href="{{get_profile_url(review.owner_details.username) }}">
<span class="course-instructor">
{{ review.owner_details.full_name }}
</span>
</a>
</div>
<div class="rating"> <div class="rating">
{% for i in [1, 2, 3, 4, 5] %} {% for i in [1, 2, 3, 4, 5] %}
<svg class="icon icon-md {% if i <= review.rating %} star-click {% endif %}" data-rating="{{ i }}"> <svg class="icon icon-md {% if i <= avg_rating %} star-click {% endif %}" data-rating="{{ i }}">
<use href="#icon-star"></use> <use href="#icon-star"></use>
</svg> </svg>
{% endfor %} {% endfor %}
</div> </div>
</div> </div>
<div class="course-meta"> {{ avg_rating }} {{ _("out of 5 ") }} </div>
</div> </div>
<div class="vertical-divider"></div>
{% set sorted_reviews = get_sorted_reviews(course.name) %}
<div>
{% for review in sorted_reviews %}
<div class="d-flex align-items-center mb-3">
<div class="course-meta mr-2"> {{ frappe.utils.cint(review) }} {{ _("stars") }} </div>
<div class="progress">
<div class="progress-bar" role="progressbar" aria-valuenow="{{ sorted_reviews[review] }}"
aria-valuemin="0" aria-valuemax="100" style="width:{{ sorted_reviews[review] }}%">
<span class="sr-only"> {{ sorted_reviews[review] }} Complete</span>
</div>
</div>
<div class="course-meta ml-3"> {{ frappe.utils.cint(sorted_reviews[review]) }}% </div>
</div>
{% endfor %}
</div>
</div>
{% if reviews | length %}
<div class="mt-12">
{% for review in reviews %}
<div class="">
<div class="review-card-footer">
<div class="d-flex align-items-center">
<div class="mr-5">
{{ widgets.Avatar(member=review.owner_details, avatar_class="avatar-medium") }}
</div>
<div>
<a class="button-links" href="{{get_profile_url(review.owner_details.username) }}">
<span class="review-author">
{{ review.owner_details.full_name }}
</span>
</a>
<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>
<div class="ml-16 mt-4">
<div> {{ review.review }} </div>
<div class="frappe-timestamp mt-2 mb-6 course-meta" data-timestamp="{{ review.creation }}"> frappe.utils.pretty_date(review.creation) </div>
</div>
</div>
</div>
{% if loop.index != reviews | length %}
<div class="card-divider"></div>
{% endif %}
{% endfor %} {% endfor %}
</div> </div>

View File

@@ -8,10 +8,15 @@
--text-2xl: 20px; --text-2xl: 20px;
--text-3xl: 22px; --text-3xl: 22px;
--text-3-5xl: 24px; --text-3-5xl: 24px;
--text-3-8xl: 34px;
--text-4xl: 44px; --text-4xl: 44px;
--navbar-shadow: 0px 1px 8px rgba(0, 0, 0, 0.08); --navbar-shadow: 0px 1px 8px rgba(0, 0, 0, 0.08);
} }
body {
background-color: #FFFFFF;
}
input[type=checkbox] { input[type=checkbox] {
appearance: auto; appearance: auto;
} }
@@ -19,8 +24,6 @@ input[type=checkbox] {
.course-image { .course-image {
height: 168px; height: 168px;
width: 100%; width: 100%;
border-top-left-radius: 8px;
border-top-right-radius: 8px;
background-size: cover; background-size: cover;
background-position: center; background-position: center;
background-repeat: no-repeat; background-repeat: no-repeat;
@@ -66,6 +69,7 @@ input[type=checkbox] {
font-weight: 600; font-weight: 600;
box-shadow: var(--popover-box-shadow); box-shadow: var(--popover-box-shadow);
color: var(--gray-900); color: var(--gray-900);
width: fit-content;
} }
.dark-pills { .dark-pills {
@@ -96,6 +100,7 @@ input[type=checkbox] {
flex-direction: column; flex-direction: column;
height: 100%; height: 100%;
min-height: 350px; min-height: 350px;
overflow: auto;
} }
.muted-text { .muted-text {
@@ -120,6 +125,12 @@ input[type=checkbox] {
flex: 1 1 auto; flex: 1 1 auto;
} }
@media (max-width: 400px) {
.course-card-content {
padding: 0 0.5rem 1.25rem;
}
}
@media (max-width: 350px) { @media (max-width: 350px) {
.course-card-content { .course-card-content {
padding: 0px 10px 10px; padding: 0px 10px 10px;
@@ -155,6 +166,12 @@ input[type=checkbox] {
color: var(--gray-700); color: var(--gray-700);
} }
@media (max-width: 400px) {
.course-instructor {
margin-left: 0;
}
}
.course-student-count { .course-student-count {
display: flex; display: flex;
font-size: 12px; font-size: 12px;
@@ -279,7 +296,6 @@ input[type=checkbox] {
} }
.review-card-footer { .review-card-footer {
display: flex;
margin-top: 20px; margin-top: 20px;
justify-content: space-between; justify-content: space-between;
} }
@@ -293,10 +309,6 @@ input[type=checkbox] {
width: 100px; width: 100px;
} }
.rating .star-click {
--star-fill: #74808B;
}
.custom-checkbox { .custom-checkbox {
display: flex; display: flex;
align-items: center; align-items: center;
@@ -329,26 +341,13 @@ input[type=checkbox] {
} }
.course-card-wide { .course-card-wide {
display: flex; margin-top: 1rem;
flex-direction: row; width: 60%;
padding: 24px;
background: #E2E6E9;
border-radius: var(--border-radius-md);
margin-top: 16px;
box-shadow: 0px 2px 1px rgba(0, 0, 0, 0.02), 0px 0px 8px rgba(0, 0, 0, 0.04);
} }
@media (max-width: 768px) { @media (max-width: 768px) {
.course-card-wide { .course-card-wide {
flex-direction: column; width: 100%;
}
}
@media (max-width: 600px) {
.course-card-wide {
padding: 40px 24px 40px;
border-radius: 0px;
align-items: center;
} }
} }
@@ -380,13 +379,6 @@ input[type=checkbox] {
} }
} }
@media (max-width: 500px) {
.course-home-page {
padding-right: 0;
padding-left: 0;
}
}
.course-card-wide-content { .course-card-wide-content {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@@ -394,49 +386,12 @@ input[type=checkbox] {
justify-content: space-between; justify-content: space-between;
} }
@media (max-width: 768px) {
.course-card-wide-content {
width: 100%;
}
}
@media (max-width: 600px) {
.course-card-wide-content {
width: 90%;
align-items: center;
}
}
.course-card-wide-title { .course-card-wide-title {
font-style: normal; font-weight: bold;
font-weight: 600; font-size: 36px;
font-size: 40px; line-height: 44px;
line-height: 120%; color: var(--gray-900);
color: var(--gray-800); margin-bottom: 0.75rem;
margin-bottom: 8px;
}
@media (max-width: 768px) {
.course-card-wide-title {
margin-top: 24px;
font-size: 36px;
}
}
@media (max-width: 600px) {
.course-card-wide-title {
margin-top: 16px;
font-size: 28px;
}
}
.course-card-wide-intro {
font-size: 16px;
line-height: 172%;
letter-spacing: -0.011em;
margin-bottom: 16px;
display: flex;
flex-wrap: wrap;
} }
.button { .button {
@@ -449,6 +404,7 @@ input[type=checkbox] {
padding: 0.25rem 1.25rem; padding: 0.25rem 1.25rem;
font-size: var(--text-md); font-size: var(--text-md);
line-height: 20px; line-height: 20px;
box-shadow: var(--btn-shadow);
} }
.button:disabled { .button:disabled {
@@ -456,16 +412,13 @@ input[type=checkbox] {
} }
.wide-button { .wide-button {
padding: 12px 24px 12px; padding: 0.5rem 6rem;
height: 48px; font-weight: 500;
margin-right: 16px;
font-size: 16px;
line-height: 150%;
} }
@media (max-width: 600px) { @media (max-width: 768px) {
.wide-button { .wide-button {
width: 264px; padding: 0.5rem 4rem;
} }
} }
@@ -499,28 +452,26 @@ input[type=checkbox] {
color: #FFFFFF; color: #FFFFFF;
} }
.course-home-outline { .course-home-page .course-home-outline {
margin-top: 3rem; margin: 5rem 0 4rem;
}
.small-title {
letter-spacing: -0.011em;
} }
.chapter-title { .chapter-title {
font-weight: bold; font-weight: 500;
margin: 0 .25rem 0;
cursor: pointer; cursor: pointer;
border-radius: var(--border-radius-lg);
padding: 0.75rem 0;
color: var(--gray-900);
display: flex; display: flex;
align-items: center; align-items: center;
padding-bottom: 1rem; }
.chapter-title[aria-expanded="true"] {
background: var(--gray-100);
} }
.chapter-description { .chapter-description {
height: fit-content; margin: 0.75rem 3rem 1rem;
padding-left: 1rem;
padding-right: 1rem;
margin-bottom: 0.75rem;
} }
.course-content-parent .chapter-description { .course-content-parent .chapter-description {
@@ -528,7 +479,7 @@ input[type=checkbox] {
} }
.chapter-icon { .chapter-icon {
margin-right: .25rem; margin: 0 1.25rem;
} }
.course-outline-instructor-parent { .course-outline-instructor-parent {
@@ -579,17 +530,11 @@ input[type=checkbox] {
} }
.reviews-parent { .reviews-parent {
margin-top: 3rem; margin: 5rem 0;
}
@media (max-width: 600px) {
.reviews-parent {
padding: 0px 24px 0px;
}
} }
.course-description-section { .course-description-section {
margin-top: 3rem; margin-top: 3.75rem;
} }
.course-overview-section { .course-overview-section {
@@ -600,21 +545,20 @@ input[type=checkbox] {
font-size: 16px; font-size: 16px;
line-height: 250%; 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 1rem;
margin-bottom: .25rem;
color: inherit; color: inherit;
} }
.lesson-links:hover { .lesson-links:hover {
cursor: pointer; cursor: pointer;
text-decoration: none; text-decoration: none;
background: #F4F5F6;
color: inherit; color: inherit;
border-radius: var(--border-radius-sm); border-radius: var(--border-radius-md);
} }
.course-content-parent .lesson-links { .course-content-parent .lesson-links {
@@ -635,7 +579,7 @@ input[type=checkbox] {
} }
.lessons { .lessons {
margin-bottom: 1rem; margin-left: 2rem;
} }
.course-buttons { .course-buttons {
@@ -906,9 +850,9 @@ input[type=checkbox] {
.breadcrumb { .breadcrumb {
display: flex; display: flex;
align-items: center; align-items: center;
font-size: 12px; font-size: var(--text-md);
line-height: 135%; line-height: 20px;
color: var(--gray-800); color: var(--gray-900);
} }
.course-details-outline { .course-details-outline {
@@ -962,8 +906,8 @@ input[type=checkbox] {
} }
.active-lesson { .active-lesson {
background-color: #EBF5FF; background-color: var(--gray-100);
border-radius: var(--border-radius-sm); border-radius: var(--border-radius-md);
} }
.lesson-progress { .lesson-progress {
@@ -1192,6 +1136,7 @@ input[type=checkbox] {
.progress { .progress {
width: 100%; width: 100%;
height: 4px; height: 4px;
background-color: var(--gray-500);
} }
.progress-bar { .progress-bar {
@@ -1324,19 +1269,6 @@ pre {
.navbar { .navbar {
box-shadow: var(--navbar-shadow); box-shadow: var(--navbar-shadow);
} }
.interactive-arrow {
background-image: url("/assets/school/icons/arrow.svg");
width: 1.5rem;
height: 1.5rem;
margin-left: 0.5rem;
}
.intercative-link:hover .interactive-arrow{
background-image: url("/assets/school/icons/blue-arrow.svg");
margin-left: 1.5rem;
}
.search { .search {
background-image: url(/assets/frappe/icons/timeless/search.svg); background-image: url(/assets/frappe/icons/timeless/search.svg);
border: 1px solid #C8CFD5; border: 1px solid #C8CFD5;
@@ -1403,8 +1335,17 @@ pre {
} }
.carousel-control-prev, .carousel-control-next { .carousel-control-prev, .carousel-control-next {
position: inherit;
width: auto; width: auto;
top: 30%;
height: fit-content;
background: white;
border-radius: 50%;
box-shadow: var(--shadow-sm);
opacity: 1;
}
.related-courses .carousel-control-prev, .related-courses .carousel-control-next {
top: 40%;
} }
.carousel-indicators { .carousel-indicators {
@@ -1412,6 +1353,16 @@ pre {
margin: 0; margin: 0;
} }
.carousel {
padding: 0 1rem;
}
@media (max-width: 500px) {
.carousel {
padding: 0 0.5rem;
}
}
.slider-controls { .slider-controls {
display: flex; display: flex;
align-items: center; align-items: center;
@@ -1563,3 +1514,170 @@ pre {
content: "\00B7"; content: "\00B7";
margin: 0 8px; margin: 0 8px;
} }
.course-head-container {
color: var(--gray-900);
}
.course-intructor-rating-section {
display: flex;
align-items: center;
margin-top: 3.5rem;
}
.course-intructor-rating-section .seperator::before {
content: "\00B7";
margin: 0 1rem;
}
.course-head-container .progress {
margin-top: 2.75rem;
}
.course-overlay-card {
background-color: white;
border-radius: var(--border-radius-lg);
box-shadow: var(--shadow-sm);
overflow: auto;
width: fit-content;
position: absolute;
top: 40%;
right: 7%;
max-width: 400px;
}
@media (max-width: 768px) {
.course-overlay-card {
position: inherit;
margin-top: 1rem;
}
.course-intructor-rating-section .seperator::before {
content: "\00B7";
margin: 0 0.25rem;
}
}
.video-in-overlay {
top: 20%;
}
.course-overlay-content {
padding: 1.25rem;
font-size: var(--text-base);
color: var(--gray-700);
}
.breadcrumb-destination {
color: var(--gray-600);
}
.preview-video {
width: 100%;
height: 190px;
border: none;
}
.course-body-container {
width: 60%;
}
@media (max-width: 768px) {
.course-body-container {
width: 100%;
}
}
.overlay-heading {
margin-top: 2rem;
font-weight: 600;
color: var(--gray-800);
}
.overlay-student-count {
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 1.5rem;
}
.course-creators-card {
display: grid;
grid-gap: 1rem;
padding: 1rem;
background-color: var(--gray-100);
box-shadow: none;
}
.course-creator-name {
font-size: var(--text-xl);
font-weight: 500;
color: var(--gray-900);
}
.course-meta {
font-size: var(--text-base);
color: var(--gray-600);
}
.avg-rating {
font-size: var(--text-3-8xl);
color: var(--gray-900);
font-weight: bold;
}
.reviews-header {
display: flex;
justify-content: space-between;
width: 75%;
}
@media (max-width: 768px) {
.reviews-header {
width: 100%;
}
}
@media (max-width: 500px) {
.reviews-header {
flex-direction: column;
align-items: center;
}
.vertical-divider {
margin: 1rem 0;
}
}
.review-author {
font-size: var(--text-lg);
color: var(--gray-900);
font-weight: 600;
}
.star-click {
--star-fill: var(--yellow-500);
}
.rating {
--star-fill: var(--gray-400);
}
.vertical-divider {
border: 1px solid var(--gray-300);
}
.avg-rating-stars {
background: var(--gray-200);
border-radius: 100px;
padding: 0.5rem 0.75rem;
margin: 1.25rem 0 0.5rem;
}
.reviews-parent .progress {
width: 200px;
}
.reviews-parent .progress-bar {
background-color: var(--gray-600);
}

View File

@@ -0,0 +1,7 @@
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M11.25 7.5375L14.625 6.1875" stroke="#687178" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M3.375 6.1875L6.75 7.5375" stroke="#687178" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M11.25 12.0375L14.625 10.6875" stroke="#687178" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M3.375 10.6875L6.75 12.0375" stroke="#687178" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M0.5625 0.5625L9 3.9375L17.4375 0.5625V14.0625L9 17.4375L0.5625 14.0625V0.5625Z" stroke="#687178" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 790 B

View File

@@ -1,4 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.3333 14V12.6667C13.3333 11.9594 13.0524 11.2811 12.5523 10.781C12.0522 10.281 11.3739 10 10.6667 10H5.33332C4.62608 10 3.9478 10.281 3.4477 10.781C2.94761 11.2811 2.66666 11.9594 2.66666 12.6667V14" stroke="#687178" stroke-linecap="round" stroke-linejoin="round"/> <path d="M13.3334 14V12.6667C13.3334 11.9594 13.0525 11.2811 12.5524 10.781C12.0523 10.281 11.374 10 10.6667 10H5.33341C4.62617 10 3.94789 10.281 3.4478 10.781C2.9477 11.2811 2.66675 11.9594 2.66675 12.6667V14" stroke="#687178" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M8.00001 7.33333C9.47277 7.33333 10.6667 6.13943 10.6667 4.66667C10.6667 3.19391 9.47277 2 8.00001 2C6.52725 2 5.33334 3.19391 5.33334 4.66667C5.33334 6.13943 6.52725 7.33333 8.00001 7.33333Z" stroke="#687178" stroke-linecap="round" stroke-linejoin="round"/> <path d="M7.99992 7.33333C9.47268 7.33333 10.6666 6.13943 10.6666 4.66667C10.6666 3.19391 9.47268 2 7.99992 2C6.52716 2 5.33325 3.19391 5.33325 4.66667C5.33325 6.13943 6.52716 7.33333 7.99992 7.33333Z" stroke="#687178" stroke-linecap="round" stroke-linejoin="round"/>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 649 B

After

Width:  |  Height:  |  Size: 648 B

View File

@@ -104,7 +104,7 @@ const mark_progress = (e) => {
callback: (data) => { callback: (data) => {
change_progress_indicators(status, e); change_progress_indicators(status, e);
show_certificate_if_course_completed(data); show_certificate_if_course_completed(data);
move_to_next_lesson(e); move_to_next_lesson(status, e);
} }
}); });
} }
@@ -134,10 +134,20 @@ const show_certificate_if_course_completed = (data) => {
} }
}; };
const move_to_next_lesson = (e) => { const move_to_next_lesson = (status, e) => {
if ($(e.currentTarget).hasClass("next") && $(e.currentTarget).attr("data-href")) { if ($(e.currentTarget).hasClass("next") && $(e.currentTarget).attr("data-href")) {
window.location.href = $(e.currentTarget).attr("data-href"); window.location.href = $(e.currentTarget).attr("data-href");
} }
else if (status == "Complete") {
$("input.mark-progress").closest(".custom-checkbox").addClass("hide");
$("div.mark-progress").removeClass("hide");
$(".next").addClass("hide");
}
else {
$("input.mark-progress").closest(".custom-checkbox").removeClass("hide");
$("div.mark-progress").addClass("hide");
$(".next").removeClass("hide");
}
}; };
const quiz_summary = (e) => { const quiz_summary = (e) => {

View File

@@ -1,234 +1,246 @@
{% extends "templates/base.html" %} {% extends "templates/base.html" %}
{% from "www/macros/common_macro.html" import MentorsSection %}
{% block title %}{{ course.title }} {% block title %}{{ course.title }}
{% endblock %} {% endblock %}
{% block head_include %} {% block head_include %}
<link rel="stylesheet" href="/assets/frappe/css/font-awesome.css"> <link rel="stylesheet" href="/assets/frappe/css/font-awesome.css">
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<div class="common-page-style"> <div class="common-page-style pt-0">
{{ CourseHomeHeader(course) }}
<div class="container course-home-page"> <div class="container course-home-page">
{{ widgets.BreadCrumb(course=course) }} <div class="course-body-container">
{{ CourseCardWide(course) }} {{ Description(course) }}
{{ CourseSections(course) }} {{ widgets.CourseOutline(course=course, membership=membership) }}
{{ Mentors(course) }} {{ CourseCreator(course) }}
{{ widgets.Reviews(course=course, membership=membership) }} {{ widgets.Reviews(course=course, membership=membership) }}
</div>
{{ RelatedCourses(course) }}
</div> </div>
</div> </div>
{% endblock %} {% endblock %}
{% macro CourseHomeHeader(course) %}
<div class="course-head-container"
style=" {% if course.image %} background-position: center; background-size: cover; background-image: url({{ course.image }});
{% else %} background-color: var(--gray-200) {% endif %}">
<div class="container pt-10 pb-10">
{{ BreadCrumb(course) }}
{{ CourseCardWide(course) }}
{{ CourseHeaderOverlay(course) }}
</div>
</div>
{% endmacro %}
<!-- BreadCrumb -->
{% macro BreadCrumb(course) %}
<div class="breadcrumb">
<a class="dark-links" href="/courses">{{ _("All Courses") }}</a>
<img class="ml-2 mr-2" src="/assets/school/icons/chevron-right.svg">
<span class="breadcrumb-destination">{{ course.title }}</span>
</div>
{% endmacro %}
<!-- Course Card --> <!-- Course Card -->
{% macro CourseCardWide(course) %} {% macro CourseCardWide(course) %}
<div class="common-card-style course-card-wide"> <div class="course-card-wide">
<div class="course-image-wide {% if not course.image %} default-image {% endif %}" {% if course.image <div class="d-flex align-items-center">
%}style="background-image: url({{ course.image }});" {% endif %}> {% for tag in get_tags(course.name) %}
<div class="course-tags"> <div class="course-card-pills">{{ tag }}</div>
{% for tag in course.get_tags() %} {% endfor %}
<div class="course-card-pills">{{ tag }}</div> </div>
<div class="course-card-wide-title">
{{ course.title }}
</div>
<div class="">
{{ course.short_introduction }}
</div>
<div class="course-intructor-rating-section">
<div class="d-flex align-items-center">
{% set instructors = get_instructors(course.name) %}
{% 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" href="{{ get_profile_url(instructors[0].username) }}">
<span class="course-instructor">
{% 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>
<span class="seperator"></span>
{% set avg_rating = get_average_rating(course.name) %}
{% if avg_rating %}
<div class="rating mr-2">
{% for i in [1, 2, 3, 4, 5] %}
<svg class="icon icon-md {% if i <= avg_rating %} star-click {% endif %}" data-rating="{{ i }}">
<use href="#icon-star"></use>
</svg>
{% endfor %} {% endfor %}
</div> </div>
{% if not course.image %} <span> {{ avg_rating }} {{ _(" Rating ") }} </span>
<div class="default-image-text">{{ course.title[0] }}</div>
{% endif %} {% endif %}
</div> </div>
<div class="course-card-wide-content">
<div class="course-info">
<div class="course-card-wide-title">
{{ course.title }}
</div>
<div class="course-card-wide-intro">
{{ course.short_introduction }}
</div>
</div>
<div class="course-buttons">
{% if not course.disable_self_learning and not membership and not course.upcoming and not restriction.restrict %}
<div class="button wide-button start-learning is-primary join-batch" data-course="{{ course.name | urlencode }}">
{{ _("Start Learning") }}
<img class="ml-2" src="/assets/school/icons/white-arrow.svg" />
</div>
{% endif %}
{% if membership %}
{% set lesson_index = 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="{{ get_lesson_url(course.name, lesson_index) }}{{ course.query_parameter }}">
Continue Learning <img class="ml-2" src="/assets/school/icons/white-arrow.svg" />
</a>
{% endif %}
{% if course.upcoming and not is_user_interested %}
<button class="button wide-button is-default"
id="notify-me" data-course="{{course.name | urlencode}}">
Notify me when available
</button>
{% endif %}
{% if course.video_link %}
<div class="button wide-button is-secondary video-preview">
Watch Video Preview
<img class="ml-2" src="/assets/school/images/play.png" />
</div>
{% endif %}
{% if is_cohort_staff(course.name, frappe.session.user) %} {% if membership %}
<a class="button wide-button is-secondary" {% set progress = frappe.utils.cint(membership.progress) %}
href="/courses/{{course.name}}/manage" <div class="progress">
style="color: inherit;"> <div class="progress-bar" role="progressbar" aria-valuenow="{{ progress }}"
Manage the course aria-valuemin="0" aria-valuemax="100" style="width:{{ progress }}%">
</a> <span class="sr-only"> {{ progress }} Complete</span>
{% endif %}
</div> </div>
</div> </div>
<div class="progress-percent">{{ progress }}% Completed</div>
{% endif %}
</div> </div>
<div id="interest-alert" class="alert alert-dismissible empty-state p-4 mt-10 {% if not is_user_interested %} hide {% endif %}">
You have opted to be notified for this course. You will receive an email when the course becomes available.
<a href="#" class="close p-4" data-dismiss="alert" aria-label="close">&times;</a>
</div>
{% if course.video_link %}
<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">&times;</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>
{% endif %}
{% endmacro%} {% endmacro%}
<!-- Course Outline and Creator --> {% macro CourseHeaderOverlay(course) %}
<div class="course-overlay-card {% if course.video_link %} video-in-overlay {% endif %}">
{% macro CourseSections(course) %} {% if course.video_link %}
<div class="course-outline-instructor-parent"> <iframe class="preview-video" src="{{ course.video_link }}"></iframe>
<div class="course-home-outline"> {% endif %}
{{ widgets.CourseOutline(course=course, membership=membership) }}
{{ Description(course) }}
</div>
<div class="course-creator-progress-parent">
{{ CreatorSection(course) }}
{{ Progress(course) }}
{{ Overview(course) }}
</div>
</div>
{% endmacro %}
{% macro CreatorSection(course) %} <div class="course-overlay-content">
<div class="course-creator-section">
<div class="course-home-headings">
Creator
</div>
{% for instructor in get_instructors(course.name) %}
{{ widgets.MemberCard(member=instructor, show_course_count=True, avatar_class="avatar-large") }}
{% endfor %}
</div>
{% endmacro %}
{% macro Progress(course) %} <div id="interest-alert" class="{% if not is_user_interested %} hide {% endif %}">
{% set certificate = is_certified(course.name) %} You have opted to be notified for this course. You will receive an email when the course becomes available.
{% set progress = frappe.utils.cint(membership.progress) %}
{% if 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">
{% if progress != 100 %}
Great work so far!
{% else %}
Excellent work on completing this course 👏
{% endif %}
</p>
<p class="progress-text">
{% if progress != 100 %}
Challenge yourself to complete the lessons and grow professionally.
{% else %}
You have reached a new level in your journey to success!
{% endif %}
</p>
<div class="progress-percentage">
{{ frappe.utils.rounded(progress) }}%
</div> </div>
<div class="progress">
<div class="progress-bar" role="progressbar" style="width: {{ progress }}%" aria-valuenow="{{ progress }}" {% if get_students(course.name) | length %}
aria-valuemin="0" aria-valuemax="100"></div> {% set initial_members = get_initial_members(course.name) %}
<div class="overlay-student-count">
{% for member in initial_members %}
{% if initial_members | length > 1 and loop.index == 1 %}
<div class="avatar-group overlap">
{% endif %}
{{ widgets.Avatar(member=member, avatar_class="avatar-small") }}
{% if initial_members | length > 1 and loop.index == initial_members | length %}
</div>
{% endif %}
{% endfor %}
<div class="course-meta ml-2">{{ get_students(course.name) | length }} {{ _("Enrolled") }} </div>
</div>
{% endif %}
{% if not course.disable_self_learning and not membership and not course.upcoming and not restriction.restrict %}
<div class="button wide-button start-learning is-primary join-batch" data-course="{{ course.name | urlencode }}">
{{ _("Start Learning") }}
<img class="ml-2" src="/assets/school/icons/white-arrow.svg" />
</div>
{% endif %}
{% if membership %}
{% set lesson_index = 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="{{ get_lesson_url(course.name, lesson_index) }}{{ course.query_parameter }}">
Continue Learning <img class="ml-2" src="/assets/school/icons/white-arrow.svg" />
</a>
{% endif %}
{% if course.upcoming and not is_user_interested %}
<div class="button wide-button is-default"
id="notify-me" data-course="{{course.name | urlencode}}">
Notify me when available
</div>
{% endif %}
{% if is_cohort_staff(course.name, frappe.session.user) %}
<a class="button wide-button is-secondary"
href="/courses/{{course.name}}/manage"
style="color: inherit;">
Manage the course
</a>
{% 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> </div>
{% if certificate %}
<a class="muted-text dark-links mt-5 text-center" href="/courses/{{ course.name }}/{{ certificate }}">Get
Certificate</a>
{% endif %} {% endif %}
</div> </div>
</div> </div>
{% endif %}
{% endmacro %} {% endmacro %}
{% macro Description(course) %} {% macro Description(course) %}
<div class="course-description-section"> <div class="course-description-section">
<div class="course-home-headings"> {{ frappe.utils.md_to_html(course.description) }}
Course Description
</div>
<div class="common-card-style description-card">
{{ frappe.utils.md_to_html(course.description) }}
</div>
</div> </div>
{% endmacro %} {% endmacro %}
{% macro Overview(course) %} {% macro CourseCreator(course) %}
{% set avg_rating = get_average_rating(course.name) %} <div class="course-home-headings"> {{ _("Course Creators") }} </div>
{% if get_students(course.name) | length or avg_rating %}
<div class="course-overview-section"> <div class="common-card-style course-creators-card">
<div class="course-home-headings"> {% set instructors = get_instructors(course.name) %}
Overview {% for instructor in instructors %}
<div class="d-flex align-items-center">
{{ widgets.Avatar(member=instructor, avatar_class="avatar-medium") }}
<div class="ml-4">
<div class="course-creator-name"> {{ instructor.full_name }} </div>
<div class="course-meta"> {{ get_authored_courses(instructor.name) | length }} {{ _("Courses Created") }} </div>
</div>
</div> </div>
<div class="common-card-style overview-card small-title"> {% endfor %}
{% if get_students(course.name) | length %} </div>
<div class="overview-item"> {% endmacro %}
<img class="icon-background mr-1" src="/assets/school/icons/user.svg" />
{{ get_students(course.name) | length }} Enrolled {% macro RelatedCourses(course) %}
</div> {% if course.related_courses | length %}
{% endif %} <div class="related-courses">
{% if avg_rating %} <div class="course-home-headings"> {{ _("Other Courses") }} </div>
<div class="overview-item"> <div class="carousel slide" id="carouselExampleControls" data-ride="carousel" data-interval="false">
<img class="icon-background mr-1" src="/assets/school/icons/rating.svg" /> <div class="carousel-inner">
{{ frappe.utils.flt(avg_rating, frappe.get_system_settings("float_precision") or 3) }} Rating {% for crs in course.related_courses %}
{% if loop.index % 3 == 1 %}
<div class="carousel-item {% if loop.index == 1 %} active {% endif %}"><div class="cards-parent">
{% endif %}
{{ widgets.CourseCard(course=crs, read_only=False) }}
{% if loop.index % 3 == 0 or loop.index == course.related_courses | length %} </div> </div> {% endif %}
{% endfor %}
</div>
{% if course.related_courses | length > 3 %}
<div class="slider-controls">
<a class="carousel-control-prev" href="#carouselExampleControls" role="button" data-slide="prev">
<span class="carousel-control-prev-icon" aria-hidden="true"></span>
<span class="sr-only">Previous</span>
</a>
<a class="carousel-control-next" href="#carouselExampleControls" role="button" data-slide="next">
<span class="carousel-control-next-icon" aria-hidden="true"></span>
<span class="sr-only">Next</span>
</a>
</div> </div>
{% endif %} {% endif %}
</div> </div>
</div> </div>
{% endif %} {% endif%}
{% endmacro %}
<!-- Mentors -->
{% macro Mentors(course) %}
{% if get_mentors(course.name) | length %}
<div class="course-home-mentors">
<div class="course-home-headings">
Mentors
</div>
<div class="member-parent">
{% for mentor in get_mentors(course.name) %}
{{ widgets.MemberCard(member=mentor, show_course_count=False) }}
{% endfor %}
</div>
<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/school/icons/down-arrow.svg" /></span>
<span class="card-divider-dark flex-one"></span>
</div>
</div>
{% endif %}
{% endmacro %} {% endmacro %}

View File

@@ -11,7 +11,17 @@ def get_context(context):
frappe.local.flags.redirect_location = "/courses" frappe.local.flags.redirect_location = "/courses"
raise frappe.Redirect raise frappe.Redirect
course = frappe.get_doc("LMS Course", course_name) course = frappe.db.get_value("LMS Course", course_name,
["name", "title", "image", "short_introduction", "description", "is_published", "upcoming",
"disable_self_learning", "video_link"],
as_dict=True)
related_courses = frappe.get_all("Related Courses", {"parent": course.name}, ["course"])
for csr in related_courses:
csr.update(frappe.db.get_value("LMS Course",
csr.course, ["name", "upcoming", "title", "image", "enable_certification"], as_dict=True))
course.related_courses = related_courses
if course is None: if course is None:
frappe.local.flags.redirect_location = "/courses" frappe.local.flags.redirect_location = "/courses"
raise frappe.Redirect raise frappe.Redirect

View File

@@ -78,7 +78,7 @@
<div class="course-home-headings"> <div class="course-home-headings">
Overview Overview
</div> </div>
<div class="common-card-style overview-card small-title"> <div class="common-card-style overview-card ">
{% if enrollment %} {% if enrollment %}
<div class="overview-item"> <div class="overview-item">
<img class="mr-1" src="/assets/school/icons/course.svg" /> <img class="mr-1" src="/assets/school/icons/course.svg" />