Compare commits
15 Commits
remove-bra
...
gitignore-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
573019bbcc | ||
|
|
f1b3ee19b6 | ||
|
|
9cb9fad05c | ||
|
|
0859afdf34 | ||
|
|
6407b24324 | ||
|
|
34e993cf86 | ||
|
|
8c889ffb92 | ||
|
|
a67ad67be1 | ||
|
|
646a7b723f | ||
|
|
6f7011ca58 | ||
|
|
d61acb552a | ||
|
|
265c78e76e | ||
|
|
7d180e141c | ||
|
|
29f9141ad8 | ||
|
|
e6f58f56e0 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -4,3 +4,4 @@
|
|||||||
*.swp
|
*.swp
|
||||||
tags
|
tags
|
||||||
community/docs/current
|
community/docs/current
|
||||||
|
community/public/dist
|
||||||
|
|||||||
@@ -15,11 +15,11 @@ app_license = "AGPL"
|
|||||||
# ------------------
|
# ------------------
|
||||||
|
|
||||||
# include js, css files in header of desk.html
|
# include js, css files in header of desk.html
|
||||||
app_include_css = "/assets/community/css/community.css"
|
# app_include_css = "/assets/community/css/community.css"
|
||||||
app_include_js = "/assets/community/js/community.js"
|
# app_include_js = "/assets/community/js/community.js"
|
||||||
|
|
||||||
# include js, css files in header of web template
|
# include js, css files in header of web template
|
||||||
web_include_css = "/assets/css/community.css"
|
web_include_css = "community.bundle.css"
|
||||||
# web_include_css = "/assets/community/css/community.css"
|
# web_include_css = "/assets/community/css/community.css"
|
||||||
# web_include_js = "/assets/community/js/community.js"
|
# web_include_js = "/assets/community/js/community.js"
|
||||||
|
|
||||||
|
|||||||
@@ -21,3 +21,16 @@ def get_section(name):
|
|||||||
"""
|
"""
|
||||||
doc = frappe.get_doc("LMS Section", name)
|
doc = frappe.get_doc("LMS Section", name)
|
||||||
return doc and doc.as_dict()
|
return doc and doc.as_dict()
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def submit_solution(exercise, code):
|
||||||
|
"""Submits a solution.
|
||||||
|
|
||||||
|
@exerecise: name of the exercise to submit
|
||||||
|
@code: solution to the exercise
|
||||||
|
"""
|
||||||
|
ex = frappe.get_doc("Exercise", exercise)
|
||||||
|
if not ex:
|
||||||
|
return
|
||||||
|
doc = ex.submit(code)
|
||||||
|
return {"name": doc.name, "creation": doc.creation}
|
||||||
|
|||||||
0
community/lms/doctype/exercise/__init__.py
Normal file
0
community/lms/doctype/exercise/__init__.py
Normal file
8
community/lms/doctype/exercise/exercise.js
Normal file
8
community/lms/doctype/exercise/exercise.js
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
// Copyright (c) 2021, FOSS United and contributors
|
||||||
|
// For license information, please see license.txt
|
||||||
|
|
||||||
|
frappe.ui.form.on('Exercise', {
|
||||||
|
// refresh: function(frm) {
|
||||||
|
|
||||||
|
// }
|
||||||
|
});
|
||||||
106
community/lms/doctype/exercise/exercise.json
Normal file
106
community/lms/doctype/exercise/exercise.json
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"allow_rename": 1,
|
||||||
|
"creation": "2021-05-19 17:43:39.923430",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"editable_grid": 1,
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"title",
|
||||||
|
"description",
|
||||||
|
"code",
|
||||||
|
"answer",
|
||||||
|
"column_break_4",
|
||||||
|
"course",
|
||||||
|
"hints",
|
||||||
|
"tests",
|
||||||
|
"image",
|
||||||
|
"lesson"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "title",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Title"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "course",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Course",
|
||||||
|
"options": "LMS Course"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"columns": 4,
|
||||||
|
"fieldname": "description",
|
||||||
|
"fieldtype": "Small Text",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Description"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"columns": 4,
|
||||||
|
"fieldname": "answer",
|
||||||
|
"fieldtype": "Code",
|
||||||
|
"label": "Answer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "tests",
|
||||||
|
"fieldtype": "Code",
|
||||||
|
"label": "Tests"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_4",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"columns": 4,
|
||||||
|
"fieldname": "hints",
|
||||||
|
"fieldtype": "Small Text",
|
||||||
|
"label": "Hints"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"columns": 4,
|
||||||
|
"fieldname": "code",
|
||||||
|
"fieldtype": "Code",
|
||||||
|
"label": "Code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "image",
|
||||||
|
"fieldtype": "Code",
|
||||||
|
"label": "Image",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "lesson",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Lesson",
|
||||||
|
"options": "Lesson"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2021-05-20 13:23:12.340928",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "LMS",
|
||||||
|
"name": "Exercise",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "System Manager",
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"search_fields": "title",
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"title_field": "title",
|
||||||
|
"track_changes": 1
|
||||||
|
}
|
||||||
54
community/lms/doctype/exercise/exercise.py
Normal file
54
community/lms/doctype/exercise/exercise.py
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
# Copyright (c) 2021, FOSS United and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
import frappe
|
||||||
|
from frappe.model.document import Document
|
||||||
|
from ..lms_sketch.livecode import livecode_to_svg
|
||||||
|
|
||||||
|
class Exercise(Document):
|
||||||
|
def before_save(self):
|
||||||
|
self.image = livecode_to_svg(None, self.answer)
|
||||||
|
|
||||||
|
def get_user_submission(self):
|
||||||
|
"""Returns the latest submission for this user.
|
||||||
|
"""
|
||||||
|
user = frappe.session.user
|
||||||
|
if not user or user == "Guest":
|
||||||
|
return
|
||||||
|
|
||||||
|
result = frappe.get_all('Exercise Submission',
|
||||||
|
fields="*",
|
||||||
|
filters={
|
||||||
|
"owner": user,
|
||||||
|
"exercise": self.name
|
||||||
|
},
|
||||||
|
order_by="creation desc",
|
||||||
|
page_length=1)
|
||||||
|
if result:
|
||||||
|
return result[0]
|
||||||
|
|
||||||
|
def submit(self, code):
|
||||||
|
"""Submits the given code as solution to exercise.
|
||||||
|
"""
|
||||||
|
user = frappe.session.user
|
||||||
|
if not user or user == "Guest":
|
||||||
|
return
|
||||||
|
|
||||||
|
old_submission = self.get_user_submission()
|
||||||
|
if old_submission and old_submission.solution == code:
|
||||||
|
return old_submission
|
||||||
|
|
||||||
|
course = frappe.get_doc("LMS Course", self.course)
|
||||||
|
batch = course.get_student_batch(user)
|
||||||
|
|
||||||
|
doc = frappe.get_doc(
|
||||||
|
doctype="Exercise Submission",
|
||||||
|
exercise=self.name,
|
||||||
|
exercise_title=self.title,
|
||||||
|
course=self.course,
|
||||||
|
lesson=self.lesson,
|
||||||
|
batch=batch and batch.name,
|
||||||
|
solution=code)
|
||||||
|
doc.insert()
|
||||||
|
return doc
|
||||||
|
|
||||||
47
community/lms/doctype/exercise/test_exercise.py
Normal file
47
community/lms/doctype/exercise/test_exercise.py
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
# Copyright (c) 2021, FOSS United and Contributors
|
||||||
|
# See license.txt
|
||||||
|
|
||||||
|
import frappe
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
class TestExercise(unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
frappe.db.sql('delete from `tabExercise Submission`')
|
||||||
|
frappe.db.sql('delete from `tabExercise`')
|
||||||
|
frappe.db.sql('delete from `tabLMS Course`')
|
||||||
|
|
||||||
|
def new_exercise(self):
|
||||||
|
course = frappe.get_doc({
|
||||||
|
"doctype": "LMS Course",
|
||||||
|
"name": "test-course",
|
||||||
|
"title": "Test Course"
|
||||||
|
})
|
||||||
|
course.insert()
|
||||||
|
e = frappe.get_doc({
|
||||||
|
"doctype": "Exercise",
|
||||||
|
"name": "test-problem",
|
||||||
|
"course": course.name,
|
||||||
|
"title": "Test Problem",
|
||||||
|
"description": "draw a circle",
|
||||||
|
"code": "# draw a single cicle",
|
||||||
|
"answer": (
|
||||||
|
"# draw a single circle\n" +
|
||||||
|
"circle(100, 100, 50)")
|
||||||
|
})
|
||||||
|
e.insert()
|
||||||
|
return e
|
||||||
|
|
||||||
|
def test_exercise(self):
|
||||||
|
e = self.new_exercise()
|
||||||
|
assert e.get_user_submission() is None
|
||||||
|
|
||||||
|
def test_exercise_submission(self):
|
||||||
|
e = self.new_exercise()
|
||||||
|
submission = e.submit("circle(100, 100, 50)")
|
||||||
|
assert submission is not None
|
||||||
|
assert submission.exercise == e.name
|
||||||
|
assert submission.course == e.course
|
||||||
|
|
||||||
|
user_submission = e.get_user_submission()
|
||||||
|
assert user_submission is not None
|
||||||
|
assert user_submission.name == submission.name
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
// Copyright (c) 2021, FOSS United and contributors
|
||||||
|
// For license information, please see license.txt
|
||||||
|
|
||||||
|
frappe.ui.form.on('Exercise Submission', {
|
||||||
|
// refresh: function(frm) {
|
||||||
|
|
||||||
|
// }
|
||||||
|
});
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"creation": "2021-05-19 11:41:18.108316",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"editable_grid": 1,
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"exercise",
|
||||||
|
"solution",
|
||||||
|
"exercise_title",
|
||||||
|
"course",
|
||||||
|
"batch",
|
||||||
|
"lesson"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "exercise",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Exercise",
|
||||||
|
"options": "Exercise"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "solution",
|
||||||
|
"fieldtype": "Code",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Solution"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fetch_from": "exercise.title",
|
||||||
|
"fieldname": "exercise_title",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Exercise Title",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fetch_from": "exercise.course",
|
||||||
|
"fieldname": "course",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Course",
|
||||||
|
"options": "LMS Course",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "batch",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Batch",
|
||||||
|
"options": "LMS Batch"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fetch_from": "exercise.lesson",
|
||||||
|
"fieldname": "lesson",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Lesson",
|
||||||
|
"options": "Lesson"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2021-05-20 13:30:16.349278",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "LMS",
|
||||||
|
"name": "Exercise Submission",
|
||||||
|
"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
|
||||||
|
}
|
||||||
@@ -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 ExerciseSubmission(Document):
|
||||||
|
pass
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
# Copyright (c) 2021, FOSS United and Contributors
|
||||||
|
# See license.txt
|
||||||
|
|
||||||
|
# import frappe
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
class TestExerciseSubmission(unittest.TestCase):
|
||||||
|
pass
|
||||||
@@ -11,6 +11,11 @@ class Lesson(Document):
|
|||||||
def before_save(self):
|
def before_save(self):
|
||||||
sections = SectionParser().parse(self.body or "")
|
sections = SectionParser().parse(self.body or "")
|
||||||
self.sections = [self.make_lms_section(i, s) for i, s in enumerate(sections)]
|
self.sections = [self.make_lms_section(i, s) for i, s in enumerate(sections)]
|
||||||
|
for s in self.sections:
|
||||||
|
if s.type == "exercise":
|
||||||
|
e = s.get_exercise()
|
||||||
|
e.lesson = self.name
|
||||||
|
e.save()
|
||||||
|
|
||||||
def get_sections(self):
|
def get_sections(self):
|
||||||
return sorted(self.get('sections'), key=lambda s: s.index)
|
return sorted(self.get('sections'), key=lambda s: s.index)
|
||||||
@@ -18,6 +23,7 @@ class Lesson(Document):
|
|||||||
def make_lms_section(self, index, section):
|
def make_lms_section(self, index, section):
|
||||||
s = frappe.new_doc('LMS Section', parent_doc=self, parentfield='sections')
|
s = frappe.new_doc('LMS Section', parent_doc=self, parentfield='sections')
|
||||||
s.type = section.type
|
s.type = section.type
|
||||||
|
s.id = section.id
|
||||||
s.label = section.label
|
s.label = section.label
|
||||||
s.contents = section.contents
|
s.contents = section.contents
|
||||||
s.index = index
|
s.index = index
|
||||||
|
|||||||
@@ -9,7 +9,8 @@
|
|||||||
"contents",
|
"contents",
|
||||||
"code",
|
"code",
|
||||||
"attrs",
|
"attrs",
|
||||||
"index"
|
"index",
|
||||||
|
"id"
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
@@ -43,12 +44,17 @@
|
|||||||
"fieldname": "index",
|
"fieldname": "index",
|
||||||
"fieldtype": "Int",
|
"fieldtype": "Int",
|
||||||
"label": "Index"
|
"label": "Index"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "id",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "id"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-03-12 17:56:23.118854",
|
"modified": "2021-05-19 18:55:26.019625",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "LMS",
|
"module": "LMS",
|
||||||
"name": "LMS Section",
|
"name": "LMS Section",
|
||||||
|
|||||||
@@ -10,6 +10,10 @@ class LMSSection(Document):
|
|||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"<LMSSection {self.label!r}>"
|
return f"<LMSSection {self.label!r}>"
|
||||||
|
|
||||||
|
def get_exercise(self):
|
||||||
|
if self.type == "exercise":
|
||||||
|
return frappe.get_doc("Exercise", self.id)
|
||||||
|
|
||||||
def get_latest_code_for_user(self):
|
def get_latest_code_for_user(self):
|
||||||
"""Returns the latest code for the logged in user.
|
"""Returns the latest code for the logged in user.
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import websocket
|
|||||||
import json
|
import json
|
||||||
from .svg import SVG
|
from .svg import SVG
|
||||||
import frappe
|
import frappe
|
||||||
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
# Files to pass to livecode server
|
# Files to pass to livecode server
|
||||||
# The same code is part of livecode-canvas.js
|
# The same code is part of livecode-canvas.js
|
||||||
@@ -60,9 +61,21 @@ def clear():
|
|||||||
clear()
|
clear()
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
def get_livecode_url():
|
||||||
|
doc = frappe.get_cached_doc("LMS Settings")
|
||||||
|
return doc.livecode_url
|
||||||
|
|
||||||
|
def get_livecode_ws_url():
|
||||||
|
url = urlparse(get_livecode_url())
|
||||||
|
protocol = "wss" if url.scheme == "https" else "ws"
|
||||||
|
return protocol + "://" + url.netloc + "/livecode"
|
||||||
|
|
||||||
def livecode_to_svg(livecode_ws_url, code, *, timeout=3):
|
def livecode_to_svg(livecode_ws_url, code, *, timeout=3):
|
||||||
"""Renders the code as svg.
|
"""Renders the code as svg.
|
||||||
"""
|
"""
|
||||||
|
if livecode_ws_url is None:
|
||||||
|
livecode_ws_url = get_livecode_ws_url()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
ws = websocket.WebSocket()
|
ws = websocket.WebSocket()
|
||||||
ws.settimeout(timeout)
|
ws.settimeout(timeout)
|
||||||
|
|||||||
18
community/lms/widgets/Exercise.html
Normal file
18
community/lms/widgets/Exercise.html
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
{% from "www/macros/livecode.html" import LiveCodeEditorJS, LiveCodeEditor with context %}
|
||||||
|
|
||||||
|
<div class="exercise">
|
||||||
|
<h2>{{ exercise.title }}</h2>
|
||||||
|
<div class="exercise-description">{{frappe.utils.md_to_html(exercise.description)}}</div>
|
||||||
|
|
||||||
|
{% if exercise.image %}
|
||||||
|
<div class="exercise-image">{{exercise.image}}</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% set submission = exercise.get_user_submission() %}
|
||||||
|
|
||||||
|
{{ LiveCodeEditor(exercise.name,
|
||||||
|
code=exercise.code,
|
||||||
|
reset_code=exercise.code,
|
||||||
|
is_exercise=True,
|
||||||
|
last_submitted=submission and submission.creation) }}
|
||||||
|
</div>
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
{
|
|
||||||
"css/lms.css": [
|
|
||||||
"public/css/lms.css"
|
|
||||||
],
|
|
||||||
"css/community.css": [
|
|
||||||
"public/css/style.css",
|
|
||||||
"public/css/vars.css",
|
|
||||||
"public/css/style.less"
|
|
||||||
],
|
|
||||||
"js/livecode-canvas.js": [
|
|
||||||
"public/js/livecode-canvas.js"
|
|
||||||
]
|
|
||||||
|
|
||||||
}
|
|
||||||
4
community/public/css/community.bundle.less
Normal file
4
community/public/css/community.bundle.less
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
|
||||||
|
@import "./style.css";
|
||||||
|
@import "./vars.css";
|
||||||
|
@import "./style.less";
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
|
|
||||||
.heading {
|
|
||||||
background: #eee;
|
|
||||||
padding: 10px;
|
|
||||||
clear: both;
|
|
||||||
color: #212529;
|
|
||||||
border: 1px solid #ddd;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sketch-header h1 {
|
|
||||||
font-size: 1.5em;
|
|
||||||
margin-bottom: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sketch-header {
|
|
||||||
margin-bottom: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sketch-card .sketch-title a {
|
|
||||||
font-weight: bold;
|
|
||||||
color: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hidden {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,3 @@
|
|||||||
@import url("https://cdn.jsdelivr.net/npm/bootstrap-icons@1.4.1/font/bootstrap-icons.css");
|
|
||||||
@import url("https://use.fontawesome.com/releases/v5.13.0/css/all.css");
|
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--c1: #fefae0;
|
--c1: #fefae0;
|
||||||
@@ -26,7 +24,7 @@
|
|||||||
--send-message: var(--c7);
|
--send-message: var(--c7);
|
||||||
--received-message: var(--c8);
|
--received-message: var(--c8);
|
||||||
|
|
||||||
--primary-color: #08B74F;
|
--primary-color: #08B74F !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
|
|||||||
@@ -1,9 +1,4 @@
|
|||||||
@primary-color: #08B74F;
|
@primary-color: #08B74F;
|
||||||
@import url('https://rsms.me/inter/inter.css');
|
|
||||||
|
|
||||||
body {
|
|
||||||
font-family: "Inter", sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
h2 {
|
h2 {
|
||||||
margin: 20px 0px;
|
margin: 20px 0px;
|
||||||
@@ -306,3 +301,14 @@ section.lightgray {
|
|||||||
.lesson-page {
|
.lesson-page {
|
||||||
margin: 20px 0px;
|
margin: 20px 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.lesson-pagination {
|
||||||
|
clear: both;
|
||||||
|
}
|
||||||
|
|
||||||
|
.exercise-image svg {
|
||||||
|
width: 200px;
|
||||||
|
height: 200px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,6 +6,8 @@
|
|||||||
{% block head_include %}
|
{% block head_include %}
|
||||||
<meta name="description" content="Courses" />
|
<meta name="description" content="Courses" />
|
||||||
<meta name="keywords" content="" />
|
<meta name="keywords" content="" />
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="/assets/frappe/css/font-awesome.css">
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|||||||
@@ -6,6 +6,8 @@
|
|||||||
{% block head_include %}
|
{% block head_include %}
|
||||||
<meta name="description" content="Courses" />
|
<meta name="description" content="Courses" />
|
||||||
<meta name="keywords" content="" />
|
<meta name="keywords" content="" />
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="/assets/frappe/css/font-awesome.css">
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|||||||
@@ -7,6 +7,6 @@ def get_context(context):
|
|||||||
def get_courses():
|
def get_courses():
|
||||||
courses = frappe.get_all(
|
courses = frappe.get_all(
|
||||||
"LMS Course",
|
"LMS Course",
|
||||||
fields=['name', 'slug', 'title', 'description']
|
fields=['name', 'title', 'description']
|
||||||
)
|
)
|
||||||
return courses
|
return courses
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
<style>
|
<style>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<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">
|
||||||
|
|
||||||
@@ -26,14 +27,7 @@
|
|||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="lesson-page">
|
<div class="lesson-page">
|
||||||
<div class="lesson-pagination">
|
{{ pagination(prev_url, next_url) }}
|
||||||
{% if prev_url %}
|
|
||||||
<a href="{{prev_url}}" class="btn">← Prev</a>
|
|
||||||
{% endif %}
|
|
||||||
{% if next_url %}
|
|
||||||
<a href="{{next_url}}" class="btn pull-right">Next →</a>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h2>{{ lesson.title }}</h2>
|
<h2>{{ lesson.title }}</h2>
|
||||||
|
|
||||||
@@ -43,10 +37,8 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
<div class="lesson-pagination">
|
{{ pagination(prev_url, next_url) }}
|
||||||
<a href="#" class="btn">← Prev</a>
|
|
||||||
<a href="#" class="btn pull-right">Next →</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@@ -55,8 +47,14 @@
|
|||||||
{% 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" or s.type == "exercise" %}
|
{% elif s.type == "example" or s.type == "code" %}
|
||||||
{{ LiveCodeEditor(s.name, s.get_latest_code_for_user(), s.type=="exercise", "2 hours ago") }}
|
{{ LiveCodeEditor(s.name,
|
||||||
|
code=s.get_latest_code_for_user(),
|
||||||
|
reset_code=s.contents,
|
||||||
|
is_exercise=False)
|
||||||
|
}}
|
||||||
|
{% elif s.type == "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 %}
|
||||||
@@ -70,6 +68,18 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
|
||||||
|
{% macro pagination(prev_url, next_url) %}
|
||||||
|
<div class="lesson-pagination">
|
||||||
|
{% if prev_url %}
|
||||||
|
<a href="{{prev_url}}" class="btn">← Prev</a>
|
||||||
|
{% endif %}
|
||||||
|
{% if next_url %}
|
||||||
|
<a href="{{next_url}}" class="btn pull-right">Next →</a>
|
||||||
|
{% endif %}
|
||||||
|
<div style="clear: both;"></div>
|
||||||
|
</div>
|
||||||
|
{% endmacro %}
|
||||||
|
|
||||||
{%- block script %}
|
{%- block script %}
|
||||||
{{ super() }}
|
{{ super() }}
|
||||||
{{ LiveCodeEditorJS() }}
|
{{ LiveCodeEditorJS() }}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
{% block head_include %}
|
{% block head_include %}
|
||||||
<meta name="description" content="Courses" />
|
<meta name="description" content="Courses" />
|
||||||
<meta name="keywords" content="" />
|
<meta name="keywords" content="" />
|
||||||
|
<link rel="stylesheet" href="/assets/frappe/css/font-awesome.css">
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
{% block head_include %}
|
{% block head_include %}
|
||||||
<meta name="description" content="Courses" />
|
<meta name="description" content="Courses" />
|
||||||
<meta name="keywords" content="" />
|
<meta name="keywords" content="" />
|
||||||
|
<link rel="stylesheet" href="/assets/frappe/css/font-awesome.css">
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
|
||||||
{% macro LiveCodeEditor(name, code, is_exercise, last_submitted) %}
|
{% macro LiveCodeEditor(name, code, reset_code, is_exercise=False, last_submitted=None) %}
|
||||||
<div class="livecode-editor livecode-editor-inline" id="editor-{{name}}">
|
<div class="livecode-editor livecode-editor-inline" id="editor-{{name}}">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-lg-8 col-md-6">
|
<div class="col-lg-8 col-md-6">
|
||||||
@@ -34,10 +34,13 @@
|
|||||||
{% if is_exercise %}
|
{% if is_exercise %}
|
||||||
<button class="submit pull-right btn-primary">Submit</button>
|
<button class="submit pull-right btn-primary">Submit</button>
|
||||||
{% if last_submitted %}
|
{% if last_submitted %}
|
||||||
<span class="pull-right" style="padding-right: 10px;">Submitted <span class="human-time" data-timestamp="{{last_submitted}}">on {{last_submitted}}</span></span>
|
<span class="pull-right" style="padding-right: 10px;"><span class="human-time" data-timestamp="{{last_submitted}}"></span></span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
<div style="display: none">
|
||||||
|
<pre class="reset-code">{{reset_code}}</pre>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="code-editor">
|
<div class="code-editor">
|
||||||
@@ -59,20 +62,67 @@
|
|||||||
|
|
||||||
|
|
||||||
{% macro LiveCodeEditorJS(name, code) %}
|
{% macro LiveCodeEditorJS(name, code) %}
|
||||||
|
|
||||||
|
<script type="text/javascript" src="/assets/frappe/node_modules/moment/min/moment-with-locales.min.js"></script>
|
||||||
|
<script type="text/javascript" src="/assets/frappe/node_modules/moment-timezone/builds/moment-timezone-with-data.min.js"></script>
|
||||||
|
<script type="text/javascript" src="/assets/frappe/js/frappe/utils/datetime.js"></script>
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
// comment_when is failing because of this
|
||||||
|
if (!frappe.sys_defaults) {
|
||||||
|
frappe.sys_defaults = {}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
<script type="text/javascript" src="{{ livecode_url }}/static/livecode.js"></script>
|
<script type="text/javascript" src="{{ livecode_url }}/static/livecode.js"></script>
|
||||||
<script type="text/javascript" src="/assets/community/js/livecode-canvas.js"></script>
|
<script type="text/javascript" src="/assets/community/js/livecode-canvas.js"></script>
|
||||||
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
var livecodeEditors = [];
|
var livecodeEditors = [];
|
||||||
|
var livecodeEditorsMap = {};
|
||||||
|
|
||||||
$(function() {
|
$(function() {
|
||||||
$(".livecode-editor").each((i, e) => {
|
$(".livecode-editor").each((i, e) => {
|
||||||
|
var name = e.id.replace("editor-", "");
|
||||||
var editor = new LiveCodeEditor(e, {
|
var editor = new LiveCodeEditor(e, {
|
||||||
base_url: "{{ livecode_url }}",
|
base_url: "{{ livecode_url }}",
|
||||||
...getLiveCodeOptions()
|
...getLiveCodeOptions()
|
||||||
})
|
})
|
||||||
livecodeEditors.push(editor);
|
livecodeEditors.push(editor);
|
||||||
})
|
livecodeEditorsMap[e.id] = editor;
|
||||||
})
|
|
||||||
|
$(e).find(".reset").on('click', function() {
|
||||||
|
let code = $(e).find(".reset-code").html();
|
||||||
|
editor.codemirror.doc.setValue(code);
|
||||||
|
});
|
||||||
|
|
||||||
|
$(e).find(".submit").on('click', function() {
|
||||||
|
let code = editor.codemirror.doc.getValue();
|
||||||
|
console.log("submit", name, code);
|
||||||
|
frappe.call("community.lms.api.submit_solution", {
|
||||||
|
"exercise": name,
|
||||||
|
"code": code
|
||||||
|
}).then(r => {
|
||||||
|
if (r.message.name) {
|
||||||
|
frappe.msgprint("Submitted successfully!");
|
||||||
|
|
||||||
|
let d = r.message.creation;
|
||||||
|
$(e).find(".human-time").html(__("Submitted {0}", [comment_when(d)]));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function updateSubmitTimes() {
|
||||||
|
$(".human-time").each(function(i, e) {
|
||||||
|
var d = $(e).data().timestamp;
|
||||||
|
$(e).html(__("Submitted {0}", [comment_when(d)]));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
updateSubmitTimes();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
{% macro Sidebar(course, batch_code) %}
|
{% macro Sidebar(course, batch_code) %}
|
||||||
<div class="sidebar-batch">
|
<div class="sidebar-batch">
|
||||||
<a href=""><i class="fas fa-bars fa-lg"></i></a>
|
<a href=""><i class="fa fa-bars fa-lg"></i></a>
|
||||||
<br>
|
<br>
|
||||||
<a href="/courses/{{course}}/{{batch_code}}/learn"><i class="fas fa-book fa-lg"></i></a>
|
<a href="/courses/{{course}}/{{batch_code}}/learn"><i class="fa fa-book fa-lg"></i></a>
|
||||||
<a href="/courses/{{course}}/{{batch_code}}/schedule"><i class="fas fa-calendar-alt fa-lg"></i></a>
|
<a href="/courses/{{course}}/{{batch_code}}/schedule"><i class="fa fa-calendar fa-lg"></i></a>
|
||||||
<a href="/courses/{{course}}/{{batch_code}}/members"><i class="fas fa-users fa-lg"></i></a>
|
<a href="/courses/{{course}}/{{batch_code}}/members"><i class="fa fa-users fa-lg"></i></a>
|
||||||
<a href="/courses/{{course}}/{{batch_code}}/discuss"><i class="fas fa-comments fa-lg"></i></a>
|
<a href="/courses/{{course}}/{{batch_code}}/discuss"><i class="fa fa-comments fa-lg"></i></a>
|
||||||
<a href="/courses/{{course}}/{{batch_code}}/about"><i class="fas fa-info-circle fa-lg"></i></a>
|
<a href="/courses/{{course}}/{{batch_code}}/about"><i class="fa fa-info-circle fa-lg"></i></a>
|
||||||
<!-- <a href="#contact"><i class="fas fa-home fa-lg"></i></a> -->
|
<!-- <a href="#contact"><i class="fas fa-home fa-lg"></i></a> -->
|
||||||
</div>
|
</div>
|
||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
Reference in New Issue
Block a user