Compare commits

...

24 Commits

Author SHA1 Message Date
pateljannat
671b4a0650 fix: api and orm 2021-06-02 20:19:36 +05:30
pateljannat
4fd7af053b fix: tests 2021-06-02 16:47:17 +05:30
pateljannat
5fd1143f76 feat: lesson progress 2021-06-02 13:52:50 +05:30
Jannat Patel
9c65ff8ae6 Merge pull request #113 from fossunited/invite-based-membership
feat: Invite based membership
2021-05-31 13:41:12 +05:30
pateljannat
bb0aa09b4e fix: messages and url 2021-05-31 13:39:31 +05:30
pateljannat
a8752afb3b feat: invite based membership become a member page 2021-05-28 13:53:34 +05:30
pateljannat
327bde870b Merge branch 'main' of https://github.com/frappe/community into invite-based-membership 2021-05-27 17:32:48 +05:30
Jannat Patel
640ead4922 Merge pull request #109 from fossunited/style-fixes
fix: Style fixes
2021-05-27 11:54:08 +05:30
pateljannat
687f7f7f7b fix: minor home page issues 2021-05-27 11:25:05 +05:30
Anand Chitipothu
527a563e4a chore: added "programming" to the hero title 2021-05-27 09:39:07 +05:30
pateljannat
5bc9a7fe37 Merge branch 'style-fixes' of https://github.com/frappe/community into invite-based-membership 2021-05-26 19:10:38 +05:30
pateljannat
24835acd9c fix: jinja 2021-05-26 19:10:08 +05:30
pateljannat
3648b3ab47 Merge branch 'style-fixes' of https://github.com/frappe/community into invite-based-membership 2021-05-26 19:08:38 +05:30
pateljannat
914f8504a0 fix: added class in lms_message 2021-05-26 18:56:57 +05:30
pateljannat
ab8546a121 fix: course outline, discussion, lms batch 2021-05-26 17:16:00 +05:30
pateljannat
f327c6fb10 fix: tests for course description 2021-05-26 12:38:50 +05:30
pateljannat
c7ccefa632 fix: discussion, batch home page, new fields for batches 2021-05-26 12:13:04 +05:30
Anand Chitipothu
823cf4e431 style: fixed word-wrap of output 2021-05-25 16:06:12 +05:30
pateljannat
18f074d8ac fix: ignore user permission for membership 2021-05-24 19:35:26 +05:30
pateljannat
c9185ae68c fix: tabs and learn page 2021-05-24 19:24:07 +05:30
Anand Chitipothu
82fa0fa4d7 fix: error in loading the progress page 2021-05-24 13:46:59 +05:30
Jannat Patel
64752433d2 Merge pull request #106 from fossunited/issue-103
Redirect the learn page to the current lesson of the user
2021-05-24 13:40:50 +05:30
pateljannat
ca42c32f54 Merge branch 'main' of https://github.com/frappe/community into style-fixes 2021-05-24 11:57:53 +05:30
pateljannat
631275e9a8 refactor: course and sidebar cleanup 2021-05-24 10:28:02 +05:30
53 changed files with 792 additions and 395 deletions

View File

@@ -136,13 +136,15 @@ primary_rules = [
{"from_route": "/hackathons/<hackathon>/<project>", "to_route": "hackathons/project"}, {"from_route": "/hackathons/<hackathon>/<project>", "to_route": "hackathons/project"},
{"from_route": "/dashboard", "to_route": ""}, {"from_route": "/dashboard", "to_route": ""},
{"from_route": "/add-a-new-batch", "to_route": "add-a-new-batch"}, {"from_route": "/add-a-new-batch", "to_route": "add-a-new-batch"},
{"from_route": "/courses/<course>/<batch>/home", "to_route": "batch/home"},
{"from_route": "/courses/<course>/<batch>/learn", "to_route": "batch/learn"}, {"from_route": "/courses/<course>/<batch>/learn", "to_route": "batch/learn"},
{"from_route": "/courses/<course>/<batch>/learn/<int:chapter>.<int:lesson>", "to_route": "batch/learn"}, {"from_route": "/courses/<course>/<batch>/learn/<int:chapter>.<int:lesson>", "to_route": "batch/learn"},
{"from_route": "/courses/<course>/<batch>/schedule", "to_route": "batch/schedule"}, {"from_route": "/courses/<course>/<batch>/schedule", "to_route": "batch/schedule"},
{"from_route": "/courses/<course>/<batch>/members", "to_route": "batch/members"}, {"from_route": "/courses/<course>/<batch>/members", "to_route": "batch/members"},
{"from_route": "/courses/<course>/<batch>/discuss", "to_route": "batch/discuss"}, {"from_route": "/courses/<course>/<batch>/discuss", "to_route": "batch/discuss"},
{"from_route": "/courses/<course>/<batch>/about", "to_route": "batch/about"}, {"from_route": "/courses/<course>/<batch>/about", "to_route": "batch/about"},
{"from_route": "/courses/<course>/<batch>/progress", "to_route": "batch/progress"} {"from_route": "/courses/<course>/<batch>/progress", "to_route": "batch/progress"},
{"from_route": "/courses/<course>/<batch>/join", "to_route": "batch/join"}
] ]
# Any frappe default URL is blocked by profile-rules, add it here to unblock it # Any frappe default URL is blocked by profile-rules, add it here to unblock it

View File

@@ -43,7 +43,7 @@ def save_current_lesson(batch_name, lesson_name):
doctype="LMS Batch Membership", doctype="LMS Batch Membership",
filters={ filters={
"batch": batch_name, "batch": batch_name,
"member_email": frappe.session.user "member": frappe.session.user
}, },
fieldname="name") fieldname="name")
if not name: if not name:

View File

@@ -55,5 +55,6 @@ class Exercise(Document):
image=image, image=image,
solution=code) solution=code)
doc.insert(ignore_permissions=True) doc.insert(ignore_permissions=True)
return doc return doc

View File

@@ -14,7 +14,9 @@ class TestExercise(unittest.TestCase):
course = frappe.get_doc({ course = frappe.get_doc({
"doctype": "LMS Course", "doctype": "LMS Course",
"name": "test-course", "name": "test-course",
"title": "Test Course" "title": "Test Course",
"short_introduction": "Test Course",
"description": "Test Course"
}) })
course.insert() course.insert()
e = frappe.get_doc({ e = frappe.get_doc({

View File

@@ -1,8 +1,13 @@
# Copyright (c) 2021, FOSS United and contributors # Copyright (c) 2021, FOSS United and contributors
# For license information, please see license.txt # For license information, please see license.txt
# import frappe import frappe
from frappe.model.document import Document from frappe.model.document import Document
from ..lesson.lesson import update_progress
class ExerciseSubmission(Document): class ExerciseSubmission(Document):
pass
def after_insert(self):
course_details = frappe.get_doc("LMS Course", self.course)
if not (course_details.is_mentor(frappe.session.user) or frappe.flags.in_test):
update_progress(self.lesson)

View File

@@ -43,3 +43,65 @@ class Lesson(Document):
The return value would be like 1.2, 2.1 etc. The return value would be like 1.2, 2.1 etc.
It will be None if there is no next lesson. It will be None if there is no next lesson.
""" """
def get_progress(self):
return frappe.db.get_value("LMS Course Progress", {"lesson": self.name, "owner": frappe.session.user}, "status")
def get_slugified_class(self):
if self.get_progress():
return ("").join([ s for s in self.get_progress().lower().split() ])
return
@frappe.whitelist()
def save_progress(lesson, batch):
if not frappe.db.exists("LMS Batch Membership",
{
"member": frappe.session.user,
"batch": batch
}):
return
if frappe.db.exists("LMS Course Progress",
{
"lesson": lesson,
"owner": frappe.session.user
}):
return
lesson_details = frappe.get_doc("Lesson", lesson)
dynamic_content = frappe.db.count("LMS Section",
filters={
"type": ["not in", ["example", "text"]],
"parent": lesson_details.name
})
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)
def update_progress(lesson):
user = frappe.session.user
if not all_dynamic_content_submitted(lesson, user):
return
if frappe.db.exists("LMS Course Progress", {"lesson": lesson, "owner": user}):
course_progress = frappe.get_doc("LMS Course Progress", {"lesson": lesson, "owner": user})
course_progress.status = "Complete"
course_progress.save()
def all_dynamic_content_submitted(lesson, user):
exercise_names = frappe.get_list("Exercise", {"lesson": lesson}, ["name"], pluck="name")
all_exercises_submitted = False
print(exercise_names)
query = {
"exercise": ["in", exercise_names],
"owner": user
}
if frappe.db.count("Exercise Submission", query) == len(exercise_names):
all_exercises_submitted = True
return all_exercises_submitted

View File

@@ -6,11 +6,10 @@
"engine": "InnoDB", "engine": "InnoDB",
"field_order": [ "field_order": [
"course", "course",
"title",
"start_date", "start_date",
"start_time", "start_time",
"column_break_3", "column_break_3",
"code", "title",
"sessions_on", "sessions_on",
"end_time", "end_time",
"section_break_5", "section_break_5",
@@ -31,13 +30,6 @@
"label": "Course", "label": "Course",
"options": "LMS Course" "options": "LMS Course"
}, },
{
"fieldname": "code",
"fieldtype": "Data",
"label": "Code",
"read_only": 1,
"unique": 1
},
{ {
"fieldname": "title", "fieldname": "title",
"fieldtype": "Data", "fieldtype": "Data",
@@ -84,7 +76,8 @@
}, },
{ {
"fieldname": "section_break_5", "fieldname": "section_break_5",
"fieldtype": "Section Break" "fieldtype": "Section Break",
"label": "Batch Description"
}, },
{ {
"fieldname": "column_break_9", "fieldname": "column_break_9",
@@ -92,7 +85,8 @@
}, },
{ {
"fieldname": "section_break_7", "fieldname": "section_break_7",
"fieldtype": "Section Break" "fieldtype": "Section Break",
"label": "Batch Settings"
}, },
{ {
"fieldname": "start_date", "fieldname": "start_date",
@@ -126,7 +120,7 @@
"link_fieldname": "batch" "link_fieldname": "batch"
} }
], ],
"modified": "2021-05-06 05:46:38.469120", "modified": "2021-05-26 16:43:57.399747",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "LMS", "module": "LMS",
"name": "LMS Batch", "name": "LMS Batch",

View File

@@ -12,8 +12,6 @@ from community.query import find, find_all
class LMSBatch(Document): class LMSBatch(Document):
def validate(self): def validate(self):
self.validate_if_mentor() self.validate_if_mentor()
if not self.code:
self.generate_code()
def validate_if_mentor(self): def validate_if_mentor(self):
course = frappe.get_doc("LMS Course", self.course) course = frappe.get_doc("LMS Course", self.course)
@@ -23,11 +21,6 @@ class LMSBatch(Document):
def after_insert(self): def after_insert(self):
create_membership(batch=self.name, member_type="Mentor") create_membership(batch=self.name, member_type="Mentor")
def generate_code(self):
short_code = frappe.db.get_value("LMS Course", self.course, "short_code")
course_batches = frappe.get_all("LMS Batch",{"course":self.course})
self.code = short_code + str(len(course_batches) + 1)
def get_mentors(self): def get_mentors(self):
memberships = frappe.get_all( memberships = frappe.get_all(
"LMS Batch Membership", "LMS Batch Membership",
@@ -87,6 +80,11 @@ class LMSBatch(Document):
membership = self.get_membership(user) membership = self.get_membership(user)
return membership and membership.current_lesson return membership and membership.current_lesson
def get_learn_url(self, lesson_number):
if not lesson_number:
return
return f"/courses/{self.course}/{self.name}/learn/{lesson_number}"
@frappe.whitelist() @frappe.whitelist()
def save_message(message, batch): def save_message(message, batch):
doc = frappe.get_doc({ doc = frappe.get_doc({

View File

@@ -2,7 +2,13 @@
// For license information, please see license.txt // For license information, please see license.txt
frappe.ui.form.on('LMS Batch Membership', { frappe.ui.form.on('LMS Batch Membership', {
// refresh: function(frm) { onload: function(frm) {
frm.set_query('member', function(doc) {
// } return {
filters: {
"ignore_user_type": 1,
}
};
});
},
}); });

View File

@@ -34,7 +34,7 @@ class LMSBatchMembership(Document):
"member": self.member, "member": self.member,
"name": ["!=", self.name] "name": ["!=", self.name]
}, },
fields=["batch", "member_type"] fields=["batch", "member_type", "name"]
) )
for membership in previous_membership: for membership in previous_membership:

View File

@@ -19,7 +19,9 @@ class TestLMSBatchMembership(unittest.TestCase):
"doctype": "LMS Course", "doctype": "LMS Course",
"name": "test-course", "name": "test-course",
"title": "Test Course", "title": "Test Course",
"short_code": "XX" "short_code": "XX",
"short_introduction": "Test Course",
"description": "Test Course"
}) })
course.insert() course.insert()

View File

@@ -28,7 +28,8 @@
{ {
"fieldname": "description", "fieldname": "description",
"fieldtype": "Markdown Editor", "fieldtype": "Markdown Editor",
"label": "Description" "label": "Description",
"reqd": 1
}, },
{ {
"default": "0", "default": "0",
@@ -57,7 +58,8 @@
{ {
"fieldname": "short_introduction", "fieldname": "short_introduction",
"fieldtype": "Small Text", "fieldtype": "Small Text",
"label": "Short Introduction" "label": "Short Introduction",
"reqd": 1
} }
], ],
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
@@ -84,7 +86,7 @@
"link_fieldname": "course" "link_fieldname": "course"
} }
], ],
"modified": "2021-05-06 13:37:03.318829", "modified": "2021-05-23 18:14:32.602647",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "LMS", "module": "LMS",
"name": "LMS Course", "name": "LMS Course",

View File

@@ -15,7 +15,9 @@ class TestLMSCourse(unittest.TestCase):
def new_course(self, title): def new_course(self, title):
doc = frappe.get_doc({ doc = frappe.get_doc({
"doctype": "LMS Course", "doctype": "LMS Course",
"title": title "title": title,
"short_introduction": title,
"description": title
}) })
doc.insert() doc.insert()
return doc return doc

View File

@@ -0,0 +1,8 @@
// Copyright (c) 2021, FOSS United and contributors
// For license information, please see license.txt
frappe.ui.form.on('LMS Course Progress', {
// refresh: function(frm) {
// }
});

View File

@@ -0,0 +1,78 @@
{
"actions": [],
"creation": "2021-05-31 17:20:13.388453",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"status",
"column_break_3",
"lesson",
"chapter",
"course"
],
"fields": [
{
"fetch_from": "chapter.course",
"fieldname": "course",
"fieldtype": "Link",
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Course",
"options": "LMS Course",
"read_only": 1
},
{
"fetch_from": "lesson.chapter",
"fieldname": "chapter",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Chapter",
"options": "Chapter",
"read_only": 1
},
{
"fieldname": "lesson",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Lesson",
"options": "Lesson"
},
{
"fieldname": "status",
"fieldtype": "Select",
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Status",
"options": "Complete\nPartially Complete\nIncomplete"
},
{
"fieldname": "column_break_3",
"fieldtype": "Column Break"
}
],
"index_web_pages_for_search": 1,
"links": [],
"modified": "2021-06-02 13:05:31.114939",
"modified_by": "Administrator",
"module": "LMS",
"name": "LMS Course Progress",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"share": 1,
"write": 1
}
],
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View File

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

View File

@@ -0,0 +1,8 @@
# Copyright (c) 2021, FOSS United and Contributors
# See license.txt
# import frappe
import unittest
class TestLMSCourseProgress(unittest.TestCase):
pass

View File

@@ -18,7 +18,7 @@ class LMSMessage(Document):
template = self.get_message_template() template = self.get_message_template()
message = frappe._dict({ message = frappe._dict({
"author_name": self.author_name, "author_name": self.author_name,
"message_time": frappe.utils.pretty_date(self.creation), "message_time": frappe.utils.format_datetime(self.creation, "dd-mm-yyyy HH:mm"),
"message": frappe.utils.md_to_html(self.message) "message": frappe.utils.md_to_html(self.message)
}) })
@@ -32,26 +32,28 @@ class LMSMessage(Document):
template = frappe.render_template(template, {{ template = frappe.render_template(template, {{
"message": message "message": message
}}) }})
$(".message-section").append(template); $(".messages").append(template);
var message_element = document.getElementsByClassName("messages")[0]
message_element.scrollTo(0, message_element.scrollHeight);
""".format(template, message, self.owner) """.format(template, message, self.owner)
frappe.publish_realtime(event="eval_js", message=js, after_commit=True) frappe.publish_realtime(event="eval_js", message=js, after_commit=True)
def get_message_template(self): def get_message_template(self):
return """ return """
<div class="discussion {% if message.is_author %} is-author {% endif %}"> <li class="{% if message.is_author %} ours {% endif %}">
<div class="d-flex justify-content-between"> <div class="d-flex justify-content-between">
<div class="font-weight-bold"> <div class="font-weight-bold">
{{ message.author_name }} {{ message.author_name }}
</div> </div>
<div class="text-muted"> <small class="">
{{ message.message_time }} {{ message.message_time }}
</small>
</div> </div>
</div> <div class="message-para">
<div class="mt-5">
{{ message.message }} {{ message.message }}
</div> </div>
</div> </li>
""" """
def send_email(self): def send_email(self):
@@ -106,4 +108,3 @@ def send_daily_digest():
}, },
delayed = False delayed = False
) )

View File

@@ -19,7 +19,7 @@
"is_standard": 1, "is_standard": 1,
"login_required": 1, "login_required": 1,
"max_attachment_size": 0, "max_attachment_size": 0,
"modified": "2021-04-30 11:22:18.188712", "modified": "2021-06-02 15:52:06.383260",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "LMS", "module": "LMS",
"name": "add-a-new-batch", "name": "add-a-new-batch",
@@ -38,13 +38,13 @@
{ {
"allow_read_on_all_link_options": 0, "allow_read_on_all_link_options": 0,
"fieldname": "course", "fieldname": "course",
"fieldtype": "Data", "fieldtype": "Link",
"hidden": 0, "hidden": 1,
"label": "Course", "label": "Course",
"max_length": 0, "max_length": 0,
"max_value": 0, "max_value": 0,
"options": "", "options": "LMS Course",
"read_only": 1, "read_only": 0,
"reqd": 0, "reqd": 0,
"show_in_filter": 0 "show_in_filter": 0
}, },

View File

@@ -0,0 +1,4 @@
<div class="p-5 batch-header">
<h3>{{batch_name}}</h3>
<div class="text-muted">{{member_count}} members</div>
</div>

View File

@@ -0,0 +1,39 @@
<div class="mt-5">
<a class="anchor_style" href="/courses">Courses</a> /{% if course.is_mentor(frappe.session.user) %} <a class="anchor_style" href="/courses/{{ course.name }}"> {{ course.title }}</a> {% else %} <span class="text-muted"> {{ course.title }}</span> {% endif %}
</div>
<ul class="nav nav-tabs mt-4">
<li class="nav-item">
<a class="nav-link" id="home" href="/courses/{{course.name}}/{{batch.name}}/home">Home</a>
</li>
<li class="nav-item">
<a class="nav-link" id="learn" href="/courses/{{course.name}}/{{batch.name}}/learn">Learn</a>
</li>
<!-- <li class="nav-item">
<a class="nav-link" id="schedule" href="/courses/{{course.name}}/{{batch.name}}/schedule">Schedule</a>
</li> -->
<li class="nav-item">
<a class="nav-link" id="members" href="/courses/{{course.name}}/{{batch.name}}/members">Members</a>
</li>
<li class="nav-item">
<a class="nav-link" id="discussion" href="/courses/{{course.name}}/{{batch.name}}/discuss">Discussion</a>
</li>
<!-- <li class="nav-item">
<a class="nav-link" id="about" href="/courses/{{course.name}}/{{batch.name}}/about">About</a>
</li> -->
{% if batch.is_member(frappe.session.user, member_type="Mentor") %}
<li class="nav-item">
<a class="nav-link" id="progress" href="/courses/{{course.name}}/{{batch.name}}/progress">Progress</a>
</li>
{% endif %}
</ul>
<script>
frappe.ready(() => {
var selector = document.querySelector(`a[href="${decodeURIComponent(window.location.pathname)}"]`)
if (selector) {
selector.classList.add('active');
}
else {
$("#learn").addClass('active')
}
})
</script>

View File

@@ -1,13 +1,16 @@
<div class="chapter-teaser"> <div class="chapter-teaser">
<div class="teaser-body"> <div class="teaser-body">
<h3 class="chapter-title"><span class="chapter-number">{{index}}</span> {{ chapter.title }}</h3> <h3 class="chapter-title"><span class="mr-1">{{index}}.</span> {{ chapter.title }}</h3>
<div class="chapter-description"> <div class="chapter-description">
{{ chapter.description or "" }} {{ chapter.description or "" }}
</div> </div>
<div class="chapter-lessons"> <div class="chapter-lessons">
{% for lesson in chapter.get_lessons() %} {% for lesson in chapter.get_lessons() %}
<div class="lesson-teaser"> <div class="lesson-teaser">
{{lesson.title}} <a {% if show_link %} class="anchor_style" href="{{ batch.get_learn_url(course.get_lesson_index(lesson.name)) }}" {% endif %}>{{ lesson.title }}</a>
{% if show_progress and not course.is_mentor(frappe.session.user) and lesson.get_progress() %}
<a class="pull-right badge p-1 {{ lesson.get_slugified_class() }}"> <img class="progress-image" src="/assets/community/images/Vector.png"> {{ lesson.get_progress() }}</a>
{% endif %}
</div> </div>
{% endfor %} {% endfor %}
</div> </div>

View File

@@ -0,0 +1,5 @@
<h2>Course Outline</h2>
{% for chapter in course.get_chapters() %}
{{ widgets.ChapterTeaser(index=loop.index, chapter=chapter, course=course, batch=batch, show_link=show_link, show_progress=show_progress)}}
{% endfor %}

View File

@@ -0,0 +1,5 @@
<h3>Instructor</h3>
<div class="instructor">
<div class="instructor-title">{{instructor.full_name}}</div>
<div class="instructor-subtitle">Created {{instructor.get_course_count()}} courses</div>
</div>

View File

@@ -0,0 +1,29 @@
<div class="batch">
<div class="batch-details">
<div>Session every {{batch.sessions_on}}</div>
<div>{{frappe.utils.format_time(batch.start_time, "short")}} -
{{frappe.utils.format_time(batch.end_time, "short")}}
</div>
<div>Starting {{frappe.utils.format_date(batch.start_date, "medium")}}</div>
<div class="course-type" style="color: #888; padding: 10px 0px;">mentors</div>
{% for m in batch.get_mentors() %}
<div>
{{ widgets.Avatar(member=m, avatar_class="avatar-medium" ) }}
<span class="instructor-title">{{m.full_name}}</span>
</div>
{% endfor %}
</div>
{% if can_manage or can_join %}
<div class="cta">
<div class="">
{% if can_manage %}
<a href="/courses/{{course.name}}/{{batch.name}}/home" class="btn btn-primary">Manage</a>
{% elif can_join %}
<button class="join-batch btn btn-primary" data-batch="{{ batch.name | urlencode }}"
data-course="{{ course.name | urlencode }}">Join this Batch</button>
{% endif %}
</div>
</div>
{% endif %}
</div>

View File

@@ -97,14 +97,6 @@ body {
border-top: 1px solid #ddc; border-top: 1px solid #ddc;
} }
.batch .cta button {
background: var(--cta-color);
color: white;
border: none;
border-radius: 5px;
padding: 5px 10px;
}
.batch .right { .batch .right {
float: right; float: right;
} }
@@ -127,44 +119,13 @@ img.profile-photo {
max-width: 100% max-width: 100%
} }
/* override style of base */
.message { .message {
border: 1px dashed var(--text-color); border: 1px dashed var(--text-color);
padding: 20px; padding: 20px;
border-radius: 10px; border-radius: 10px;
} }
.dashboard__profile {
width: 150px;
height: 155px;
border-radius: 50%;
}
.dashboard__profileSmall {
width: 59px;
height: 57px;
border-radius: 50%;
}
.dashboard__abbr {
font-size: 50px;
width: 155px;
height: 155px;
border-radius: 50%;
}
.dashboard__abbrSmall {
font-size: 20px;
width: 59px;
height: 57px;
border-radius: 50%;
}
.msger-inputarea { .msger-inputarea {
position: fixed;
bottom: 0;
width: 100%; width: 100%;
display: flex; display: flex;
padding: 10px; padding: 10px;
@@ -182,37 +143,6 @@ img.profile-photo {
flex: 1; flex: 1;
background: #ddd; background: #ddd;
} }
.msger-send-btn {
margin-left: 10px;
background: var(--cta-color);
color: #fff;
font-weight: bold;
cursor: pointer;
transition: background 0.23s;
}
.discussion {
border: 1px solid var(--text-color);
padding: 10px;
margin: 10px;
border-radius: 10px;
background: var(--received-message);
width: 50%;
display: flex;
flex-direction: column;
}
.is-author {
float: right;
background: var(--send-message);
}
.batch-header {
position: fixed;
top: 0;
background: var(--bg);
width: 100%;
}
.message-section { .message-section {
margin-left: 3%; margin-left: 3%;
@@ -227,6 +157,117 @@ img.profile-photo {
} }
.anchor_style { .anchor_style {
color: inherit;
}
a:hover {
text-decoration: none; text-decoration: none;
color: inherit; color: inherit;
} }
.anchor_style:hover {
text-decoration: underline
}
section {
padding: 5rem 0 5rem 0;
}
.messages-container {
margin: 0 auto;
border: 1px solid black;
}
.messages {
overflow: auto;
height: 450px;
display: flex;
flex-direction: column;
align-items: flex-start;
padding: 8px;
list-style-type: none;
}
.messages li {
background: #F7F5F5;
border-radius: 8px;
padding: 8px;
margin: 2px 8px 2px 0;
width: 40%;
}
.messages li.ours {
align-self: flex-end;
margin: 2px 0 2px 8px;
background: var(--primary-color);
color: #fff
}
.message-para {
font-size: 20px;
}
.batch-header {
background: #eee;
border: 2px solid #ddd;
}
.page-card {
max-width: 360px;
padding: 15px;
margin: 70px auto;
border: 1px solid #d1d8dd;
border-radius: 4px;
background-color: #fff;
box-shadow: 0px 3px 6px rgba(0, 0, 0, 0.1);
}
.page-card .page-card-head {
padding: 10px 15px;
margin: -15px;
margin-bottom: 15px;
border-bottom: 1px solid #d1d8dd;
}
.page-card .page-card-head .indicator {
color: #36414C;
font-size: 14px;
}
.page-card .page-card-head .indicator::before {
margin: 0 6px 0.5px 0px;
}
.page-card .btn {
margin-top: 30px;
}
.partiallycomplete {
background: #FEF4E2;
color: #976417;
}
.partiallycomplete img {
background: #976417;
}
.complete {
background: #EAF5EE;
color: #38A160;
}
.complete img {
background: #38A160;
}
.incomplete {
background: #FEECEC;
color: #E24C4C;
}
.incomplete img {
background: #E24C4C;
}
.progress-image {
margin-right: 3px;
border-radius: 50px;
padding: 5px;
}

View File

@@ -179,11 +179,8 @@ section.lightgray {
.chapter-number { .chapter-number {
background: var(--text-color); background: var(--text-color);
color: white; color: white;
border-radius: 50%;
height: 24px; height: 24px;
min-width: 24px; min-width: 24px;
align-items: center;
padding: 5px 8px 2px 8px;
margin-right: 5px; margin-right: 5px;
} }
@@ -257,6 +254,7 @@ section.lightgray {
background-color: rgba(255, 255, 255, 0); background-color: rgba(255, 255, 255, 0);
max-height: 300px; max-height: 300px;
white-space: pre-wrap; white-space: pre-wrap;
word-wrap: break-word;
margin: 0px; margin: 0px;
margin-left: 20px; margin-left: 20px;
padding: 4px; padding: 4px;
@@ -287,9 +285,7 @@ section.lightgray {
} }
.lesson-teaser { .lesson-teaser {
font-weight: bold; line-height: 40px;
color: black;
padding-left: 20px;
} }
#hero h1 { #hero h1 {

Binary file not shown.

After

Width:  |  Height:  |  Size: 206 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 922 B

View File

@@ -1,7 +1,4 @@
{% extends "templates/base.html" %} {% extends "templates/base.html" %}
{% from "www/macros/sidebar.html" import Sidebar %}
{% from "www/macros/common_macro.html" import InstructorsSection, MentorsSection %}
{% block title %}About{% endblock %} {% block title %}About{% endblock %}
{% block head_include %} {% block head_include %}
<meta name="description" content="Courses" /> <meta name="description" content="Courses" />
@@ -11,12 +8,18 @@
{% endblock %} {% endblock %}
{% block content %} {% block content %}
{{ Sidebar(course, batch) }}
<div class="container"> <div class="container">
{{ widgets.BatchTabs(course=course, batch=batch) }}
<div class="tab-content" id="about">
{{ CourseBasicDetail(course)}} {{ CourseBasicDetail(course)}}
{{ InstructorsSection(course.get_instructor()) }} <div class="d-flex align-items-center">
{{ BatchDetails(batch)}} <div class="col-lg-4 col-md-12">
<div class="sidebar">
{{ widgets.InstructorSection(instructor=course.get_instructor()) }}
</div>
</div>
</div>
</div>
</div> </div>
{% endblock %} {% endblock %}
@@ -35,24 +38,3 @@
<h2>About the Course</h2> <h2>About the Course</h2>
<div>{{frappe.utils.md_to_html(course.description)}}</div> <div>{{frappe.utils.md_to_html(course.description)}}</div>
{% endmacro %} {% endmacro %}
{% macro BatchDetails(batch) %}
<h2>About the Batch</h2>
<div class="batch">
<div class="batch-details">
<div>Session every {{batch.sessions_on}}</div>
<div>{{frappe.utils.format_time(batch.start_time, "short")}} -
{{frappe.utils.format_time(batch.end_time, "short")}}</div>
<div>Starting {{frappe.utils.format_date(batch.start_date, "medium")}}</div>
<div class="course-type" style="color: #888; padding: 10px 0px;">mentors</div>
{% for m in batch.get_mentors() %}
<div>
{{ widgets.Avatar(member=m, avatar_class="avatar-medium" ) }}
<span class="instructor-title">{{m.full_name}}</span>
</div>
{% endfor %}
</div>
</div>
{% endmacro %}

View File

@@ -3,5 +3,3 @@ from . import utils
def get_context(context): def get_context(context):
utils.get_common_context(context) utils.get_common_context(context)
print("context", context)

View File

@@ -1,6 +1,4 @@
{% extends "templates/base.html" %} {% extends "templates/base.html" %}
{% from "www/macros/sidebar.html" import Sidebar %}
{% from "www/macros/common_macro.html" import BatchHearder %}
{% block title %}Discuss{% endblock %} {% block title %}Discuss{% endblock %}
{% block head_include %} {% block head_include %}
@@ -11,16 +9,14 @@
{% endblock %} {% endblock %}
{% block content %} {% block content %}
{{ Sidebar(course, batch) }}
<div class=""> <div class="container">
<div class="batch-header"> {{ widgets.BatchTabs(course=course, batch=batch) }}
{{ BatchHearder(course.title, member_count) }} <div class="messages-container mt-5">
</div> {{ widgets.BatchHeader(batch_name=batch.title, member_count=member_count)}}
<div class="messages"> <ol class="messages">
<div class="message-section">
{{ Messages(messages) }} {{ Messages(messages) }}
</div> </ol>
{{ TextArea() }} {{ TextArea() }}
</div> </div>
</div> </div>
@@ -28,25 +24,25 @@
{% macro Messages(messages) %} {% macro Messages(messages) %}
{% for message in messages %} {% for message in messages %}
<div class="discussion {% if message.is_author %} is-author {% endif %}"> <li class="{% if message.is_author %} ours {% endif %}">
<div class="d-flex justify-content-between"> <div class="d-flex justify-content-between">
<div class="font-weight-bold"> <div class="font-weight-bold">
{{ message.author_name }} {{ message.author_name }}
</div> </div>
<div class="text-muted"> <small class="">
{{ frappe.utils.pretty_date(message.creation) }} {{ frappe.utils.format_datetime(message.creation, "dd-mm-yyyy HH:mm") }}
</small>
</div> </div>
</div> <div class="message-para">
<div class="mt-5">
{{ message.message }} {{ message.message }}
</div> </div>
</div> </li>
{% endfor %} {% endfor %}
{% endmacro %} {% endmacro %}
{% macro TextArea() %} {% macro TextArea() %}
<form class="msger-inputarea"> <form class="msger-inputarea mb-1">
<input type="text" class="msger-input" placeholder="Write your message..."> <input type="text" class="msger-input" placeholder="Write your message...">
<button type="submit" class="msger-send-btn" data-batch="{{batch.name | urlencode }}">Send</button> <button type="submit" class="btn btn-primary msger-send-btn" data-batch="{{batch.name | urlencode }}">Send</button>
</form> </form>
{% endmacro %} {% endmacro %}

View File

@@ -11,7 +11,9 @@ frappe.ready(() => {
}) })
setTimeout(() => { setTimeout(() => {
window.scrollTo(0, document.body.scrollHeight); var message_element = document.getElementsByClassName("messages")[0]
message_element.scrollTo(0, message_element.scrollHeight);
document.getElementsByClassName("messages-container")[0].scrollIntoView({block: "center"})
}, 300); }, 300);
$(".msger-send-btn").click((e) => { $(".msger-send-btn").click((e) => {

View File

@@ -0,0 +1,60 @@
{% extends "templates/base.html" %}
{% block title %} Batch {% endblock %}
{% block head_include %}
<meta name="description" content="Courses" />
<meta name="keywords" content="" />
<link rel="stylesheet" href="/assets/frappe/css/font-awesome.css">
{% endblock %}
{% block content %}
{% set invite_link = frappe.utils.get_url() + "/courses/" + course.name + "/" + batch.name + "/join" %}
<div class="container mt-5">
{{ widgets.BatchTabs(course=course, batch=batch) }}
<div>
<h1 class="mt-5">{{ batch.title }}</h1>
</div>
<div class="course-details">
{{ widgets.CourseOutline(course=course, batch=batch, show_link=True, show_progress=True) }}
</div>
<div class="w-25">
<h2>Batch Schedule</h2>
{{ widgets.RenderBatch(course=course, batch=batch) }}
</div>
{% if batch.description %}
<h2>Batch Details</h2>
{{ frappe.utils.md_to_html(batch.description) }}
{% endif %}
{% if course.is_mentor(frappe.session.user) %}
<div class="">
<h2> Invite Members </h2>
<a href="" class="anchor_style mr-5" id="invite-link" data-link="{{ invite_link }}">Get Batch Invitation
Link</a>
<small id="copy-message" class="text-muted pull-right" style="display: none;">Copied to Clipboard.</small>
</div>
{% endif %}
</div>
<script>
frappe.ready(() => {
$("#invite-link").click((e) => {
e.preventDefault();
var link_element = $("#invite-link");
var input_element = document.createElement("input");
input_element.value = link_element.attr("data-link")
document.body.appendChild(input_element);
input_element.select();
document.execCommand("copy");
input_element.remove();
$("#copy-message").slideDown(function () {
setTimeout(function () {
$("#copy-message").slideUp();
}, 5000);
});
})
})
</script>
{% endblock %}

View File

@@ -0,0 +1,5 @@
import frappe
from . import utils
def get_context(context):
utils.get_common_context(context)

View File

@@ -0,0 +1,74 @@
% extends "templates/base.html" %}
{% block title %}Join a Course{% endblock %}
{% block head_include %}
<meta name="description" content="Join a Course"/>
<meta name="keywords" content="" />
{% endblock %}
{% block content %}
{% if frappe.session.user == "Guest" %}
<div class="page-card">
<div class='page-card-head'>
<span class='indicator blue password-box'>Login Required</span>
</div>
<div class=''>Please log in to confirm to join the course {{ batch.course_title }}.</div>
<a type="submit" id="login" class="btn btn-primary w-100"
href="/login?redirect-to=/courses/{{ batch.course }}/{{ batch.name }}/join">{{_("Login")}}</a>
</div>
{% elif already_a_member %}
<div class="page-card">
<div class='page-card-head'>
<span class='indicator blue password-box'>Already a member</span>
</div>
<div class=''>You are already a member of the batch {{ batch.title }} for the course {{ batch.course_title }}.
</div>
<a type="submit" id="batch-home" class="btn btn-primary w-100" href="/courses/{{batch.course}}/{{batch.name}}/home">{{_("Go to Batch Home")}}</a>
</div>
{% else %}
<div class="page-card">
<div class='page-card-head'>
<span class='indicator blue password-box'>Confirm your membership</span>
</div>
<div>Please provide your confirmation to be a part of the batch {{ batch.title }} for the course
{{ batch.course_title }}.
</div>
<a type="submit" id="confirm" class="btn btn-primary w-100" data-batch="{{ batch.name | urlencode }}"
data-course="{{ batch.course | urlencode }}">{{_("Confirm")}}</a>
</div>
{% endif %}
<script>
frappe.ready(() => {
var confirm_element = $("#confirm");
var batch = decodeURIComponent(confirm_element.attr("data-batch"));
var course = decodeURIComponent(confirm_element.attr("data-course"));
confirm_element.click((e) => {
frappe.call({
"method": "community.lms.doctype.lms_batch_membership.lms_batch_membership.create_membership",
"args": {
"batch": batch
},
"callback": (data) => {
if (data.message == "OK") {
frappe.msgprint({
message: __("You are now a member of this batch!"),
clear: true
});
setTimeout(function () {
window.location.href = "/courses/" + course + "/" + batch + "/home";
}, 2000);
}
}
})
})
})
</script>
{% endblock %}

View File

@@ -0,0 +1,8 @@
import frappe
def get_context(context):
context.no_cache = 1
batch_name = frappe.form_dict["batch"]
context.batch = frappe.get_doc("LMS Batch", batch_name)
context.already_a_member = context.batch.is_member(frappe.session.user)
context.batch.course_title = frappe.db.get_value("LMS Course", context.batch.course, "title")

View File

@@ -1,17 +1,17 @@
{% extends "templates/base.html" %} {% extends "templates/base.html" %}
{% from "www/macros/sidebar.html" import Sidebar %}
{% from "www/macros/livecode.html" import LiveCodeEditorJS, LiveCodeEditor with context %} {% from "www/macros/livecode.html" import LiveCodeEditorJS, LiveCodeEditor with context %}
{% block title %}{{ lesson.title }}{% endblock %} {% block title %}{{ lesson.title }}{% endblock %}
{% block head_include %} {% block head_include %}
<meta name="description" content="{{lesson.title}} - {{course.title}}" /> <meta name="description" content="{{lesson.title}} - {{course.title}}" />
<meta name="keywords" content="{{lesson.title}} - {{course.title}}" /> <meta name="keywords" content="{{lesson.title}} - {{course.title}}" />
<style> <style>
</style> </style>
<link rel="stylesheet" href="/assets/frappe/css/font-awesome.css"> <link rel="stylesheet" href="/assets/frappe/css/font-awesome.css">
<link rel="stylesheet" href="{{ livecode_url }}/static/codemirror/lib/codemirror.css"> <link rel="stylesheet" href="{{ livecode_url }}/static/codemirror/lib/codemirror.css">
<link rel="stylesheet" href="/assets/css/lms.css"> <link rel="stylesheet" href="/assets/css/lms.css">
<link rel="stylesheet" href="/assets/frappe/css/hljs-night-owl.css">
<script src="{{ livecode_url }}/static/codemirror/lib/codemirror.js"></script> <script src="{{ livecode_url }}/static/codemirror/lib/codemirror.js"></script>
<script src="{{ livecode_url }}/static/codemirror/mode/python/python.js"></script> <script src="{{ livecode_url }}/static/codemirror/mode/python/python.js"></script>
@@ -23,13 +23,12 @@
{% block content %} {% block content %}
{{ Sidebar(course, batch) }}
<div class="container"> <div class="container">
{{ widgets.BatchTabs(course=course, batch=batch) }}
<div class="lesson-page"> <div class="lesson-page">
{{ pagination(prev_url, next_url) }}
<h2>{{ lesson.title }}</h2> <h2 class="title {% if course.is_mentor(frappe.session.user) %} is_mentor {% endif %}" data-name="{{ lesson.name }}" data-batch="{{ batch.name }}">{{ lesson.title }}</h2>
{% for s in lesson.get_sections() %} {% for s in lesson.get_sections() %}
<div class="section section-{{ s.type }}"> <div class="section section-{{ s.type }}">
@@ -37,52 +36,55 @@
</div> </div>
{% endfor %} {% endfor %}
{{ pagination(prev_url, next_url) }} {{ pagination(prev_chap, prev_url, next_chap, next_url) }}
</div> </div>
</div> </div>
{% endblock %} {% endblock %}
{% macro render_section(s) %} {% macro render_section(s) %}
{% if s.type == "text" %} {% if s.type == "text" %}
{{ render_section_text(s) }} {{ render_section_text(s) }}
{% elif s.type == "example" or s.type == "code" %} {% elif s.type == "example" or s.type == "code" %}
{{ LiveCodeEditor(s.name, {{ LiveCodeEditor(s.name,
code=s.get_latest_code_for_user(), code=s.get_latest_code_for_user(),
reset_code=s.contents, reset_code=s.contents,
is_exercise=False) is_exercise=False)
}} }}
{% elif s.type == "exercise" %} {% elif s.type == "exercise" %}
{{ widgets.Exercise(exercise=s.get_exercise())}} {{ widgets.Exercise(exercise=s.get_exercise())}}
{% else %} {% else %}
<div>Unknown section type: {{s.type}}</div> <div>Unknown section type: {{s.type}}</div>
{% endif %} {% endif %}
{% endmacro %} {% endmacro %}
{% macro render_section_text(s) %} {% macro render_section_text(s) %}
<div class="row"> <div class="row">
<div class="col-md-9"> <div class="col-md-9">
{{ frappe.utils.md_to_html(s.contents) }} {{ frappe.utils.md_to_html(s.contents) }}
</div> </div>
</div> </div>
{% endmacro %} {% endmacro %}
{% macro pagination(prev_url, next_url) %} {% macro pagination(prev_chap, prev_url, next_chap, next_url) %}
<div class="lesson-pagination"> <div class="lesson-pagination">
{% if prev_url %} {% if prev_url %}
<a href="{{prev_url}}" class="btn">&larr; Prev</a> <span>
Prev: <a href="{{prev_url}}">{{prev_chap}}</a>
</span>
{% endif %} {% endif %}
{% if next_url %} {% if next_url %}
<a href="{{next_url}}" class="btn pull-right">Next &rarr;</a> <span class="pull-right">
Next: <a href="{{next_url}}">{{next_chap}}</a>
</span>
{% endif %} {% endif %}
<div style="clear: both;"></div> <div style="clear: both;"></div>
</div> </div>
{% endmacro %} {% endmacro %}
{%- block script %} {%- block script %}
{{ super() }} {{ super() }}
{{ LiveCodeEditorJS() }} {{ LiveCodeEditorJS() }}
<script type="text/javascript"> <script type="text/javascript">
$(function() { $(function() {

View File

@@ -0,0 +1,11 @@
frappe.ready(() => {
if (!$(".title").hasClass("is_mentor")) {
frappe.call({
method: "community.lms.doctype.lesson.lesson.save_progress",
args: {
lesson: $(".title").attr("data-name"),
batch: $(".title").attr("data-batch")
}
})
}
})

View File

@@ -1,3 +1,4 @@
from re import I
import frappe import frappe
from . import utils from . import utils
@@ -9,11 +10,10 @@ def get_context(context):
lesson_number = f"{chapter_index}.{lesson_index}" lesson_number = f"{chapter_index}.{lesson_index}"
course_name = context.course.name course_name = context.course.name
batch_name = context.batch.name
if not chapter_index or not lesson_index: if not chapter_index or not lesson_index:
index_ = get_lesson_index(context.course, context.batch, frappe.session.user) or "1.1" index_ = get_lesson_index(context.course, context.batch, frappe.session.user) or "1.1"
frappe.local.flags.redirect_location = get_learn_url(course_name, batch_name, index_) frappe.local.flags.redirect_location = context.batch.get_learn_url(index_)
raise frappe.Redirect raise frappe.Redirect
context.lesson = context.course.get_lesson(chapter_index, lesson_index) context.lesson = context.course.get_lesson(chapter_index, lesson_index)
@@ -21,15 +21,22 @@ def get_context(context):
context.chapter_index = chapter_index context.chapter_index = chapter_index
outline = context.course.get_outline() outline = context.course.get_outline()
next_ = outline.get_next(lesson_number)
prev_ = outline.get_prev(lesson_number) prev_ = outline.get_prev(lesson_number)
context.next_url = get_learn_url(course_name, batch_name, next_) next_ = outline.get_next(lesson_number)
context.prev_url = get_learn_url(course_name, batch_name, prev_) context.prev_chap = get_chapter_title(course_name, prev_)
context.next_chap = get_chapter_title(course_name, next_)
context.next_url = context.batch.get_learn_url(next_)
context.prev_url = context.batch.get_learn_url(prev_)
def get_learn_url(course_name, batch_name, lesson_number):
def get_chapter_title(course_name, lesson_number):
if not lesson_number: if not lesson_number:
return return
return f"/courses/{course_name}/{batch_name}/learn/{lesson_number}" chapter_index = lesson_number.split(".")[0]
lesson_index = lesson_number.split(".")[1]
chapter_name = frappe.db.get_value("Chapter", {"course": course_name, "index_": chapter_index}, "name")
return frappe.db.get_value("Lesson", {"chapter": chapter_name, "index_": lesson_index}, "title")
def get_lesson_index(course, batch, user): def get_lesson_index(course, batch, user):
lesson = batch.get_current_lesson(user) lesson = batch.get_current_lesson(user)

View File

@@ -1,6 +1,4 @@
{% extends "templates/base.html" %} {% extends "templates/base.html" %}
{% from "www/macros/sidebar.html" import Sidebar %}
{% from "www/macros/common_macro.html" import BatchHearder %}
{% block title %}Members{% endblock %} {% block title %}Members{% endblock %}
{% block head_include %} {% block head_include %}
@@ -10,10 +8,9 @@
{% endblock %} {% endblock %}
{% block content %} {% block content %}
{{ Sidebar(course, batch) }}
<div class="container"> <div class="container">
{{ BatchHearder(course.title, member_count)}} {{ widgets.BatchTabs(course=course, batch=batch) }}
{{ MembersList(members)}} {{ MembersList(members)}}
</div> </div>
{% endblock %} {% endblock %}
@@ -22,18 +19,29 @@
{% macro MembersList(members) %} {% macro MembersList(members) %}
<div class="mt-5"> <div class="mt-5">
{% for member in members %} {% for member in members %}
<div class="d-flex align-items-center"> <div class="row mb-5">
<div> <div>
{{ widgets.Avatar(member=member, avatar_class="avatar-medium") }} {{ widgets.Avatar(member=member, avatar_class="avatar-large") }}
</div>
<div class="ml-5 mr-5">
<a href="/{{member.username}}">{{ member.full_name }}</a>
</div> </div>
<div class="col">
<div class="row ml-1">
<a class="anchor_style" href="/{{member.username}}">
<h3>{{ member.full_name }}</h3>
</a>
{% if course.is_mentor(member.name) %} {% if course.is_mentor(member.name) %}
<div class="ml-2">
<div class="badge badge-success">Mentor</div> <div class="badge badge-success">Mentor</div>
</div>
{% endif %} {% endif %}
</div> </div>
{% if member.bio %}
<i>{{member.bio}}</i>
{% endif %}
</div>
</div>
{% if loop.index != member_count %}
<hr> <hr>
{% endif %}
{% endfor %} {% endfor %}
</div> </div>
{% endmacro %} {% endmacro %}

View File

@@ -3,3 +3,4 @@ from . import utils
def get_context(context): def get_context(context):
utils.get_common_context(context) utils.get_common_context(context)
print(context.members[0].bio)

View File

@@ -1,13 +1,12 @@
{% extends "templates/base.html" %} {% extends "templates/base.html" %}
{% from "www/macros/sidebar.html" import Sidebar %}
{% from "www/macros/livecode.html" import LiveCodeEditorJS, LiveCodeEditor with context %} {% from "www/macros/livecode.html" import LiveCodeEditorJS, LiveCodeEditor with context %}
{% block title %}{{ course.title }} - Batch Dashboard{% endblock %} {% block title %}{{ course.title }} - Batch Dashboard{% endblock %}
{% block head_include %} {% block head_include %}
<meta name="description" content="{{course.title}} - Batch Dashboard" /> <meta name="description" content="{{course.title}} - Batch Dashboard" />
<meta name="keywords" content="{{course.title}} - Batch Dashboard" /> <meta name="keywords" content="{{course.title}} - Batch Dashboard" />
<style> <style>
</style> </style>
<link rel="stylesheet" href="/assets/frappe/css/font-awesome.css"> <link rel="stylesheet" href="/assets/frappe/css/font-awesome.css">
<link rel="stylesheet" href="{{ livecode_url }}/static/codemirror/lib/codemirror.css"> <link rel="stylesheet" href="{{ livecode_url }}/static/codemirror/lib/codemirror.css">
@@ -23,9 +22,9 @@
{% block content %} {% block content %}
{{ Sidebar(course, batch) }}
<div class="container"> <div class="container">
{{ widgets.BatchTabs(course=course, batch=batch) }}
<div class="mentor-dashboard"> <div class="mentor-dashboard">
<h1>Batch Progress</h1> <h1>Batch Progress</h1>
{% for exercise in report.exercises %} {% for exercise in report.exercises %}
@@ -47,6 +46,6 @@
{%- block script %} {%- block script %}
{{ super() }} {{ super() }}
{{ LiveCodeEditorJS() }} {{ LiveCodeEditorJS() }}
{% endblock %} {% endblock %}

View File

@@ -32,8 +32,7 @@ class BatchReport:
def get_submissions(batch): def get_submissions(batch):
students = batch.get_students() students = batch.get_students()
students_map = {s['email']: s for s in students} students_map = {s.email: s for s in students}
names, values = nparams("s", students_map.keys()) names, values = nparams("s", students_map.keys())
sql = """ sql = """

View File

@@ -1,6 +1,6 @@
{% extends "templates/base.html" %} {% extends "templates/base.html" %}
{% from "www/macros/sidebar.html" import Sidebar %}
{% block title %}Schedule{% endblock %} {% block title %}Schedule{% endblock %}
{% block head_include %} {% block head_include %}
<meta name="description" content="Courses" /> <meta name="description" content="Courses" />
<meta name="keywords" content="" /> <meta name="keywords" content="" />
@@ -8,7 +8,10 @@
{% endblock %} {% endblock %}
{% block content %} {% block content %}
{{ Sidebar(course, batch) }}
<div class="container"> <div class="container">
{{ widgets.BatchTabs(course=course, batch=batch) }}
<h3>
Schedule
</h3>
</div> </div>
{% endblock %} {% endblock %}

View File

@@ -1,5 +1,5 @@
{% extends "templates/base.html" %} {% extends "templates/base.html" %}
{% from "www/macros/common_macro.html" import InstructorsSection, MentorsSection %} {% from "www/macros/common_macro.html" import MentorsSection %}
{% block title %}{{ course.title }}{% endblock %} {% block title %}{{ course.title }}{% endblock %}
{% block head_include %} {% block head_include %}
<meta name="description" content="Courses" /> <meta name="description" content="Courses" />
@@ -9,7 +9,9 @@
{% block content %} {% block content %}
<div class="container"> <div class="container">
<div class="course-header"> <div class="course-header">
<div class="course-type">course</div> <div class="mb-5">
<a class="anchor_style" href="/courses">Courses</a> / <span class="text-muted">{{ course.title }}</span>
</div>
<h1 id="course-title" data-course="{{course.name}}">{{course.title}}</h1> <h1 id="course-title" data-course="{{course.name}}">{{course.title}}</h1>
<div class="course-short-intro">{{ course.short_introduction }}</div> <div class="course-short-intro">{{ course.short_introduction }}</div>
</div> </div>
@@ -21,12 +23,12 @@
{{ CourseDescription(course) }} {{ CourseDescription(course) }}
{{ BatchSection(course) }} {{ BatchSection(course) }}
{{ CourseOutline(course) }} {{ widgets.CourseOutline(course=course, show_link=False) }}
</div> </div>
</div> </div>
<div class="col-lg-4 col-md-12"> <div class="col-lg-4 col-md-12">
<div class="sidebar"> <div class="sidebar">
{{ InstructorsSection(course.get_instructor()) }} {{ widgets.InstructorSection(instructor=course.get_instructor()) }}
</div> </div>
<div class="sidebar"> <div class="sidebar">
{{ MentorsSection(course.get_mentors(), course.is_mentor(frappe.session.user), course.name) }} {{ MentorsSection(course.get_mentors(), course.is_mentor(frappe.session.user), course.name) }}
@@ -37,106 +39,67 @@
{% endblock %} {% endblock %}
{% macro CourseVideo(course) %} {% macro CourseVideo(course) %}
{% if course.video_link %} {% if course.video_link %}
<div class="preview-video"> <div class="preview-video">
<iframe <iframe width="560" height="315" src="{{course.video_link}}" title="YouTube video player" frameborder="0"
width="560"
height="315"
src="{{course.video_link}}"
title="YouTube video player"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowfullscreen></iframe> allowfullscreen></iframe>
</div> </div>
{% endif %} {% endif %}
{% endmacro %} {% endmacro %}
{% macro CourseDescription(course) %} {% macro CourseDescription(course) %}
<h2>Course Description</h2> <h2>Course Description</h2>
<div class="course-description"> <div class="course-description">
{{ frappe.utils.md_to_html(course.description) }} {{ frappe.utils.md_to_html(course.description) }}
</div>
{% endmacro %}
{% macro BatchSection(course) %}
{% if course.is_mentor(frappe.session.user) %}
{{ BatchSectionForMentors(course, course.get_batches(mentor=frappe.session.user)) }}
{% else %}
{{ BatchSectionForStudents(course, course.get_upcoming_batches()) }}
{% endif %}
{% endmacro %}
{% macro RenderBatch(batch, can_manage=False) %}
<div class="batch">
<div class="batch-details">
<div>Session every {{batch.sessions_on}}</div>
<div>{{frappe.utils.format_time(batch.start_time, "short")}} -
{{frappe.utils.format_time(batch.end_time, "short")}}</div>
<div>Starting {{frappe.utils.format_date(batch.start_date, "medium")}}</div>
<div class="course-type" style="color: #888; padding: 10px 0px;">mentors</div>
{% for m in batch.get_mentors() %}
<div>
{{ widgets.Avatar(member=m, avatar_class="avatar-medium" ) }}
<span class="instructor-title">{{m.full_name}}</span>
</div>
{% endfor %}
</div>
<div class="cta">
<div class="">
{% if can_manage %}
<a href="/courses/{{course.name}}/{{batch.name}}/about" class="btn btn-secondary">Manage</a>
{% else %}
<button class="join-batch" data-batch="{{ batch.name | urlencode }}"
data-course="{{ course.name | urlencode }}">Join this Batch</button>
{% endif %}
</div>
</div>
</div> </div>
{% endmacro %} {% endmacro %}
{% macro BatchSection(course) %}
{% if course.is_mentor(frappe.session.user) %}
{{ BatchSectionForMentors(course, course.get_batches(mentor=frappe.session.user)) }}
{% else %}
{{ BatchSectionForStudents(course, course.get_upcoming_batches()) }}
{% endif %}
{% endmacro %}
{% macro BatchSectionForMentors(course, mentor_batches) %} {% macro BatchSectionForMentors(course, mentor_batches) %}
<h2>Your Batches</h2> <h2>Your Batches</h2>
{% if mentor_batches %} {% if mentor_batches %}
<div class="alert alert-secondary"> <!-- <div class="alert alert-secondary">
You are a mentor for this course. Manage your batches or create a new batch from here. You are a mentor for this course. Manage your batches or create a new batch from here.
</div> </div> -->
<div class="row"> <div class="row">
{% for batch in mentor_batches %} {% for batch in mentor_batches %}
<div class="col-lg-4 col-md-6"> <div class="col-lg-4 col-md-6">
{{ RenderBatch(batch, can_manage=True) }} {{ widgets.RenderBatch(course=course, batch=batch, can_manage=True) }}
</div> </div>
{% endfor %} {% endfor %}
</div> </div>
<a class="btn btn-primary add-batch margin-bottom" href="/add-a-new-batch?new=1&course={{course.title}}&slug={{course.name}}">Add a new batch</a> <a class="add-batch margin-bottom" href="/add-a-new-batch?new=1&course={{course.title}}&slug={{course.name}}">Add a new
{% else %} batch</a>
<div class="mentor_message"> {% else %}
<div class="mentor_message">
<p> You are a mentor for this course. </p> <p> You are a mentor for this course. </p>
<a class="btn btn-primary" href="/add-a-new-batch?new=1&course={{course.title}}&slug={{course.name}}" >Create your first batch</a> <a class="" href="/add-a-new-batch?new=1&course={{course.title}}&slug={{course.name}}">Create your first batch</a>
</div> </div>
{% endif %} {% endif %}
{% endmacro %} {% endmacro %}
{% macro BatchSectionForStudents(course, upcoming_batches) %} {% macro BatchSectionForStudents(course, upcoming_batches) %}
<h2>Upcoming Batches</h2> {% if upcoming_batches %}
<h2>Upcoming Batches</h2>
<div class="row"> <div class="row">
{% for batch in upcoming_batches %} {% for batch in upcoming_batches %}
<div class="col-lg-4 col-md-6"> <div class="col-lg-4 col-md-6">
{{ RenderBatch(batch, can_manage=False) }} {{ widgets.RenderBatch(course=course, batch=batch, can_join=True) }}
</div> </div>
{% endfor %} {% endfor %}
</div> </div>
{% endmacro %} {% endif %}
{% macro CourseOutline(course) %}
<h2>Course Outline</h2>
{% for chapter in course.get_chapters() %}
{{ widgets.ChapterTeaser(index=loop.index, chapter=chapter)}}
{% endfor %}
{% endmacro %} {% endmacro %}

View File

@@ -1,10 +1,11 @@
{% extends "templates/base.html" %} {% extends "templates/base.html" %}
{% from "www/hackathons/macros/card.html" import null_card %}
{% block title %}{{ 'Courses' }}{% endblock %} {% block title %}{{ 'Courses' }}{% endblock %}
{% block head_include %} {% block head_include %}
<meta name="description" content="{{ 'Courses' }}" /> <meta name="description" content="{{ 'Courses' }}" />
<meta name="keywords" content="Courses" /> <meta name="keywords" content="Courses" />
<style> <style>
</style> </style>
{% endblock %} {% endblock %}
{% block content %} {% block content %}
@@ -17,6 +18,11 @@
{% for course in courses %} {% for course in courses %}
{{ course_card(course) }} {{ course_card(course) }}
{% endfor %} {% endfor %}
{% if courses %}
{% for n in range( (3 - (courses|length)) %3) %}
{{ null_card() }}
{% endfor %}
{% endif %}
</div> </div>
</div> </div>
</section> </section>
@@ -24,13 +30,18 @@
{% macro course_card(course) %} {% macro course_card(course) %}
<div class="card mb-5 w-100"> <div class="col-sm-4 mb-4 text-left">
<div class="card-body"> <a class="card-links" style="color: inherit;" href="/courses/{{course.name}}">
<h5 class="card-title"><a href="/courses/{{course.name}}">{{course.title}}</a></h5> <div class="card h-100">
<div class='card-body'>
<h5 class='card-title'>{{ course.title }}</h5>
{% if course.description %} {% if course.description %}
<p class="card-text">{{ frappe.utils.md_to_html(course.description[:250]) }}</p> <div class="mt-4">
{% endif %} {{ frappe.utils.md_to_html(course.description[:200]) }}
<a href="/courses/{{course.name}}" class="card-link">See more &rarr;</a>
</div> </div>
{% endif %}
</div>
</div>
</a>
</div> </div>
{% endmacro %} {% endmacro %}

View File

@@ -1,3 +0,0 @@
frappe.ready(() => {
})

View File

@@ -10,8 +10,8 @@
<section id="hero"> <section id="hero">
<div class="container"> <div class="container">
<div class="jumbotron"> <div class="jumbotron">
<h1 class="display-4">Guided online courses, with a <br />mentor at your back.</h1> <h1 class="display-4">Guided online programming courses, with a <br />mentor at your back.</h1>
<p class="lead">Hands-on online courses designed by experts, delivered by passionate mentors.</p> <p class="lead">Hands-on programming courses designed by experts, delivered by passionate mentors.</p>
{{ widgets.RequestInvite() }} {{ widgets.RequestInvite() }}
</div> </div>
</div> </div>

View File

@@ -1,11 +1,3 @@
{% macro InstructorsSection(instructor) %}
<h3>Instructor</h3>
<div class="instructor">
<div class="instructor-title">{{instructor.full_name}}</div>
<div class="instructor-subtitle">Created {{instructor.get_course_count()}} courses</div>
</div>
{% endmacro %}
{% macro MentorsSection(mentors, is_mentor, course_name) %} {% macro MentorsSection(mentors, is_mentor, course_name) %}
<h3>Mentors</h3> <h3>Mentors</h3>
{% for m in mentors %} {% for m in mentors %}
@@ -27,11 +19,3 @@
</div> </div>
{% endif %} {% endif %}
{% endmacro %} {% endmacro %}
{% macro BatchHearder(course_name, member_count) %}
<div class="border p-3">
<h3>{{course_name}}</h3>
<div class="text-muted">{{member_count}} members</div>
</div>
{% endmacro %}

View File

@@ -1,14 +0,0 @@
{% macro Sidebar(course, batch, is_mentor=False) %}
<div class="sidebar-batch">
<a href=""><i class="fa fa-bars fa-lg"></i></a>
<br>
<a href="/courses/{{course.name}}/{{batch.name}}/learn"><i class="fa fa-book fa-lg"></i></a>
<a href="/courses/{{course.name}}/{{batch.name}}/schedule"><i class="fa fa-calendar fa-lg"></i></a>
<a href="/courses/{{course.name}}/{{batch.name}}/members"><i class="fa fa-users fa-lg"></i></a>
<a href="/courses/{{course.name}}/{{batch.name}}/discuss"><i class="fa fa-comments fa-lg"></i></a>
<a href="/courses/{{course.name}}/{{batch.name}}/about"><i class="fa fa-info-circle fa-lg"></i></a>
{% if batch.is_member(frappe.session.user, member_type="Mentor") %}
<a href="/courses/{{course.name}}/{{batch.name}}/progress"><i class="fa fa-flag-checkered fa-lg"></i></a>
{% endif %}
</div>
{% endmacro %}