refactor: renamed app to lms
This commit is contained in:
0
lms/www/batch/__init__.py
Normal file
0
lms/www/batch/__init__.py
Normal file
0
lms/www/batch/__pycache__/__init__.py
Normal file
0
lms/www/batch/__pycache__/__init__.py
Normal file
72
lms/www/batch/join.html
Normal file
72
lms/www/batch/join.html
Normal file
@@ -0,0 +1,72 @@
|
||||
% 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 joining the course {{ batch.course_title }}.</div>
|
||||
<a type="submit" id="login" class="btn btn-primary w-100"
|
||||
href="/login?redirect-to=/courses/{{ batch.course }}/join?batch={{ batch.name }}">{{_("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="">{{_("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">{{_("Confirm")}}</a>
|
||||
</div>
|
||||
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block script %}
|
||||
<script>
|
||||
frappe.ready(() => {
|
||||
$("#confirm").click((e) => {
|
||||
frappe.call({
|
||||
"method": "lms.lms.doctype.lms_batch_membership.lms_batch_membership.create_membership",
|
||||
"args": {
|
||||
"batch": {{ batch.name }},
|
||||
"course": {{ batch.course }}
|
||||
},
|
||||
"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/{{ batch.course }}/home";
|
||||
}, 2000);
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
</script>
|
||||
{% endblock %}
|
||||
8
lms/www/batch/join.py
Normal file
8
lms/www/batch/join.py
Normal 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")
|
||||
177
lms/www/batch/learn.html
Normal file
177
lms/www/batch/learn.html
Normal file
@@ -0,0 +1,177 @@
|
||||
{% extends "templates/base.html" %}
|
||||
{% from "www/macros/livecode.html" import LiveCodeEditorJS, LiveCodeEditor with context %}
|
||||
{% block title %} {{ lesson.title }} - {{ course.title }} {% endblock %}
|
||||
|
||||
{% block head_include %}
|
||||
{% include "public/icons/symbol-defs.svg" %}
|
||||
<link rel="stylesheet" href="/assets/frappe/css/hljs-night-owl.css">
|
||||
|
||||
{% for ext in page_extensions %}
|
||||
{{ ext.render_header() }}
|
||||
{% endfor %}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="common-page-style">
|
||||
<div class="container course-details-page">
|
||||
{{ BreadCrumb(course, lesson) }}
|
||||
<div class="course-content-parent">
|
||||
<div class="course-details-outline">
|
||||
{{ widgets.CourseOutline(course=course, membership=membership) }}
|
||||
</div>
|
||||
<div class="lesson-pagination-parent">
|
||||
{{ LessonContent(lesson) }}
|
||||
{% if membership %}
|
||||
{{ pagination(prev_url, next_url) }}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{{ Discussions() }}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% macro BreadCrumb(course, lesson) %}
|
||||
<div class="breadcrumb">
|
||||
<a class="dark-links" href="/courses">{{ _("All Courses") }}</a>
|
||||
<img class="ml-1 mr-1" src="/assets/lms/icons/chevron-right.svg">
|
||||
<a class="dark-links" href="/courses">{{ course.title }}</a>
|
||||
<img class="ml-1 mr-1" src="/assets/lms/icons/chevron-right.svg">
|
||||
<span class="breadcrumb-destination">{{ lesson.title }}</span>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro LessonContent(lesson) %}
|
||||
{% set instructors = get_instructors(course.name) %}
|
||||
{% set is_instructor = is_instructor(course.name) %}
|
||||
<div class="lesson-content">
|
||||
<div class="lesson-title">
|
||||
<div class="course-home-headings title mb-0
|
||||
{% if membership %} is-member {% endif %}
|
||||
{% if membership or is_instructor %} eligible-for-submission {% endif %}" data-lesson="{{ lesson.name }}"
|
||||
data-course="{{ course.name }}">{{ lesson.title }}</div>
|
||||
<span class="lesson-progress {{hide if get_progress(course.name, lesson.name) != 'Complete' else ''}}">COMPLETED</span>
|
||||
|
||||
{% if is_instructor %}
|
||||
<a class="button is-default button-links ml-auto" href="/lesson?name={{ lesson.name }}"> {{ _("Edit") }} </a>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="d-flex align-items-center">
|
||||
{% set ins_len = instructors | length %}
|
||||
{% for instructor in instructors %}
|
||||
{% if ins_len > 1 and loop.index == 1 %}
|
||||
<div class="avatar-group overlap">
|
||||
{% endif %}
|
||||
{{ widgets.Avatar(member=instructor, avatar_class="avatar-small") }}
|
||||
|
||||
{% if ins_len > 1 and loop.index == ins_len %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
<a class="button-links ml-1" href="{{ get_profile_url(instructors[0].username) }}">
|
||||
<span class="course-meta">
|
||||
{% if ins_len == 1 %}
|
||||
{{ instructors[0].full_name }}
|
||||
{% else %}
|
||||
{% set suffix = "other" if ins_len - 1 == 1 else "others" %}
|
||||
{{ instructors[0].full_name.split(" ")[0] }} and {{ ins_len - 1 }} {{ suffix }}
|
||||
{% endif %}
|
||||
</span>
|
||||
</a>
|
||||
<div class="ml-5 course-meta"> {{ frappe.utils.format_date(lesson.creation, "medium") }} </div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="markdown-source lesson-content-card">
|
||||
{% if membership or lesson.include_in_preview or is_instructor %}
|
||||
{% if is_instructor and not lesson.include_in_preview %}
|
||||
<div class="small alert alert-secondary alert-dismissible mt-4 mb-4">
|
||||
This lesson is not available for preview. As you are the Instructor of the course only you can see it.
|
||||
<a href="#" class="close" data-dismiss="alert" aria-label="close">×</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
{{ render_html(lesson.body) }}
|
||||
{% else %}
|
||||
<div class="">
|
||||
<a class="button is-primary pull-right" href="/courses/{{ course.name }}"> Start Learning </a>
|
||||
<div class="">This lesson is not available for preview. Please join the course to access it.</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro pagination(prev_url, next_url) %}
|
||||
<div class="lesson-pagination">
|
||||
|
||||
<div>
|
||||
{% if prev_url %}
|
||||
<a class="button is-secondary dark-links prev" href="{{ prev_url }}">
|
||||
<img class="mr-2" src="/assets/lms/icons/left-arrow.svg">
|
||||
Prev
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
|
||||
{% if not is_mentor(course.name, frappe.session.user) and membership %}
|
||||
{% set progress = get_progress(course.name, lesson.name) %}
|
||||
<div class="custom-checkbox {% if progress == 'Complete' %} hide {% endif %}">
|
||||
<label class="quiz-label">
|
||||
<input class="mark-progress" type="checkbox" checked>
|
||||
<img class="empty-checkbox" />
|
||||
<span class="small">{{ _("Mark as complete on moving to the next lesson") }}</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="button is-secondary mark-progress {{ progress }} {% if progress == 'Incomplete' or progress == None %} hide {% endif %}"
|
||||
data-progress="Incomplete">
|
||||
Mark as Incomplete
|
||||
</div>
|
||||
|
||||
{% endif %}
|
||||
|
||||
<div>
|
||||
<a class="button is-primary next {% if membership.progress|int == 100 and not next_url %} hide {% endif %}"
|
||||
{% if next_url %} data-href="{{ next_url }}" {% endif %} href="">
|
||||
{% if next_url %} Next {% else %} Mark as Complete {% endif %}
|
||||
<img class="ml-2" src="/assets/lms/icons/side-arrow-white.svg">
|
||||
</a>
|
||||
{% if course.enable_certification %}
|
||||
<div class="button is-primary {% if membership.progress|int != 100 or next_url %} hide {% endif %}" id="certification">
|
||||
Get Certificate
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro Discussions() %}
|
||||
{% set is_instructor = frappe.session.user == course.instructor %}
|
||||
{% set condition = is_instructor if is_instructor else membership %}
|
||||
{% set doctype, docname = "Course Lesson", lesson.name %}
|
||||
{% set title = "Questions" %}
|
||||
{% set cta_title = "Ask a Question" %}
|
||||
{% set button_name = "Start Learning" %}
|
||||
{% set redirect_to = "/courses/" + course.name %}
|
||||
{% set empty_state_title = "Have a doubt?" %}
|
||||
{% set empty_state_subtitle = "Post it here, our mentors will help you out." %}
|
||||
|
||||
{% include "frappe/templates/discussions/discussions_section.html" %}
|
||||
{% endmacro %}
|
||||
|
||||
{%- block script %}
|
||||
{{ super() }}
|
||||
|
||||
<script type="text/javascript">
|
||||
var page_context = {{ page_context | tojson }};
|
||||
</script>
|
||||
|
||||
{% for ext in page_extensions %}
|
||||
{{ ext.render_footer() }}
|
||||
{% endfor %}
|
||||
{%- endblock %}
|
||||
383
lms/www/batch/learn.js
Normal file
383
lms/www/batch/learn.js
Normal file
@@ -0,0 +1,383 @@
|
||||
frappe.ready(() => {
|
||||
|
||||
localStorage.removeItem($("#quiz-title").text());
|
||||
fetch_assignments();
|
||||
|
||||
save_current_lesson();
|
||||
|
||||
$(".option").click((e) => {
|
||||
enable_check(e);
|
||||
})
|
||||
|
||||
$(".mark-progress").click((e) => {
|
||||
mark_progress(e);
|
||||
});
|
||||
|
||||
$(".next").click((e) => {
|
||||
mark_progress(e);
|
||||
});
|
||||
|
||||
$("#summary").click((e) => {
|
||||
quiz_summary(e);
|
||||
});
|
||||
|
||||
$("#check").click((e) => {
|
||||
check_answer(e);
|
||||
});
|
||||
|
||||
$("#next").click((e) => {
|
||||
mark_active_question(e);
|
||||
});
|
||||
|
||||
$("#try-again").click((e) => {
|
||||
try_quiz_again(e);
|
||||
});
|
||||
|
||||
$("#certification").click((e) => {
|
||||
create_certificate(e);
|
||||
});
|
||||
|
||||
$(".submit-work").click((e) => {
|
||||
attach_work(e);
|
||||
});
|
||||
|
||||
$(".clear-work").click((e) => {
|
||||
clear_work(e);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
const save_current_lesson = () => {
|
||||
if ($(".title").hasClass("is-member")) {
|
||||
frappe.call("lms.lms.api.save_current_lesson", {
|
||||
course_name: $(".title").attr("data-course"),
|
||||
lesson_name: $(".title").attr("data-lesson")
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
const enable_check = (e) => {
|
||||
if ($(".option:checked").length) {
|
||||
$("#check").removeAttr("disabled");
|
||||
$(".custom-checkbox").removeClass("active-option");
|
||||
$(".option:checked").closest(".custom-checkbox").addClass("active-option");
|
||||
}
|
||||
};
|
||||
|
||||
const mark_active_question = (e = undefined) => {
|
||||
var current_index;
|
||||
var next_index = 1;
|
||||
if (e) {
|
||||
e.preventDefault();
|
||||
current_index = $(".active-question").attr("data-qt-index");
|
||||
next_index = parseInt(current_index) + 1;
|
||||
}
|
||||
$(".question").addClass("hide").removeClass("active-question");
|
||||
$(`.question[data-qt-index='${next_index}']`).removeClass("hide").addClass("active-question");
|
||||
$(".current-question").text(`${next_index}`);
|
||||
$("#check").removeClass("hide").attr("disabled", true);
|
||||
$("#next").addClass("hide");
|
||||
$(".explanation").addClass("hide");
|
||||
};
|
||||
|
||||
const mark_progress = (e) => {
|
||||
/* Prevent default only for Next button anchor tag and not for progress checkbox */
|
||||
if ($(e.currentTarget).prop("nodeName") != "INPUT")
|
||||
e.preventDefault();
|
||||
else
|
||||
return
|
||||
|
||||
const target = $(e.currentTarget).attr("data-progress") ? $(e.currentTarget) : $("input.mark-progress");
|
||||
const current_status = $(".lesson-progress").hasClass("hide") ? "Incomplete": "Complete";
|
||||
|
||||
let status = "Incomplete";
|
||||
if (target.prop("nodeName") == "INPUT" && target.prop("checked")) {
|
||||
status = "Complete";
|
||||
}
|
||||
|
||||
if (status != current_status) {
|
||||
frappe.call({
|
||||
method: "lms.lms.doctype.course_lesson.course_lesson.save_progress",
|
||||
args: {
|
||||
lesson: $(".title").attr("data-lesson"),
|
||||
course: $(".title").attr("data-course"),
|
||||
status: status
|
||||
},
|
||||
callback: (data) => {
|
||||
change_progress_indicators(status, e);
|
||||
show_certificate_if_course_completed(data);
|
||||
move_to_next_lesson(status, e);
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
move_to_next_lesson(e);
|
||||
};
|
||||
|
||||
const change_progress_indicators = (status, e) => {
|
||||
if (status == "Complete") {
|
||||
$(".lesson-progress").removeClass("hide");
|
||||
$(".active-lesson .lesson-progress-tick").removeClass("hide");
|
||||
}
|
||||
else {
|
||||
$(".lesson-progress").addClass("hide");
|
||||
$(".active-lesson .lesson-progress-tick").addClass("hide");
|
||||
}
|
||||
if (status == "Incomplete" && !$(e.currentTarget).hasClass("next")) {
|
||||
$(e.currentTarget).addClass("hide");
|
||||
$("input.mark-progress").prop("checked", false).closest(".custom-checkbox").removeClass("hide");
|
||||
}
|
||||
};
|
||||
|
||||
const show_certificate_if_course_completed = (data) => {
|
||||
if (data.message == 100 && !$(".next").attr("data-next") && $("#certification").hasClass("hide")) {
|
||||
$("#certification").removeClass("hide");
|
||||
$(".next").addClass("hide");
|
||||
}
|
||||
};
|
||||
|
||||
const move_to_next_lesson = (status, e) => {
|
||||
if ($(e.currentTarget).hasClass("next") && $(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) => {
|
||||
e.preventDefault();
|
||||
var quiz_name = $("#quiz-title").text();
|
||||
var total_questions = $(".question").length;
|
||||
|
||||
frappe.call({
|
||||
method: "lms.lms.doctype.lms_quiz.lms_quiz.quiz_summary",
|
||||
args: {
|
||||
"quiz": quiz_name,
|
||||
"results": localStorage.getItem(quiz_name)
|
||||
},
|
||||
callback: (data) => {
|
||||
var message = data.message == total_questions ? "Excellent Work" : "You were almost there."
|
||||
$(".question").addClass("hide");
|
||||
$("#summary").addClass("hide");
|
||||
$("#quiz-form").parent().prepend(
|
||||
`<div class="text-center summary"><h2>${message} 👏 </h2>
|
||||
<div class="font-weight-bold">${data.message}/${total_questions} correct.</div></div>`);
|
||||
$("#try-again").removeClass("hide");
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
const try_quiz_again = (e) => {
|
||||
window.location.reload();
|
||||
};
|
||||
|
||||
const check_answer = (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
var quiz_name = $("#quiz-title").text();
|
||||
var total_questions = $(".question").length;
|
||||
var current_index = $(".active-question").attr("data-qt-index");
|
||||
|
||||
$(".explanation").removeClass("hide");
|
||||
$("#check").addClass("hide");
|
||||
|
||||
if (current_index == total_questions) {
|
||||
if ($(".eligible-for-submission").length) {
|
||||
$("#summary").removeClass("hide")
|
||||
}
|
||||
else {
|
||||
$("#submission-message").removeClass("hide");
|
||||
}
|
||||
}
|
||||
else {
|
||||
$("#next").removeClass("hide")
|
||||
}
|
||||
|
||||
var [answer, is_correct] = parse_options();
|
||||
add_to_local_storage(quiz_name, current_index, answer, is_correct)
|
||||
};
|
||||
|
||||
const parse_options = () => {
|
||||
var answer = [];
|
||||
var is_correct = [];
|
||||
$(".active-question input").each((i, element) => {
|
||||
var correct = parseInt($(element).attr("data-correct"));
|
||||
if ($(element).prop("checked")) {
|
||||
answer.push(decodeURIComponent($(element).val()));
|
||||
correct && is_correct.push(1);
|
||||
correct ? add_icon(element, "check") : add_icon(element, "wrong");
|
||||
}
|
||||
else {
|
||||
correct && is_correct.push(0);
|
||||
correct ? add_icon(element, "minus-circle-green") : add_icon(element, "minus-circle");
|
||||
}
|
||||
})
|
||||
return [answer, is_correct];
|
||||
};
|
||||
|
||||
const add_icon = (element, icon) => {
|
||||
$(element).closest(".custom-checkbox").removeClass("active-option");
|
||||
var label = $(element).siblings(".option-text").text();
|
||||
$(element).parent().empty().html(`<div class="option-text"><img class="mr-3" src="/assets/lms/icons/${icon}.svg"> ${label}</div>`);
|
||||
};
|
||||
|
||||
const add_to_local_storage = (quiz_name, current_index, answer, is_correct) => {
|
||||
var quiz_stored = JSON.parse(localStorage.getItem(quiz_name));
|
||||
var quiz_obj = {
|
||||
"question_index": current_index,
|
||||
"answer": answer.join(),
|
||||
"is_correct": is_correct
|
||||
}
|
||||
quiz_stored ? quiz_stored.push(quiz_obj) : quiz_stored = [quiz_obj]
|
||||
localStorage.setItem(quiz_name, JSON.stringify(quiz_stored))
|
||||
};
|
||||
|
||||
const create_certificate = (e) => {
|
||||
e.preventDefault();
|
||||
course = $(".title").attr("data-course");
|
||||
frappe.call({
|
||||
method: "lms.lms.doctype.lms_certification.lms_certification.create_certificate",
|
||||
args: {
|
||||
"course": course
|
||||
},
|
||||
callback: (data) => {
|
||||
window.location.href = `/courses/${course}/${data.message.name}`;
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
const attach_work = (e) => {
|
||||
const target = $(e.currentTarget);
|
||||
let files = target.siblings(".attach-file").prop("files")
|
||||
if (files && files.length) {
|
||||
files = add_files(files)
|
||||
return_as_dataurl(files)
|
||||
files.map((file) => {
|
||||
upload_file(file, target);
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
const upload_file = (file, target) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
let xhr = new XMLHttpRequest();
|
||||
|
||||
xhr.onreadystatechange = () => {
|
||||
if (xhr.readyState == XMLHttpRequest.DONE) {
|
||||
if (xhr.status === 200) {
|
||||
let response = JSON.parse(xhr.responseText)
|
||||
create_lesson_work(response.message, target);
|
||||
} else if (xhr.status === 403) {
|
||||
let response = JSON.parse(xhr.responseText);
|
||||
frappe.msgprint(`Not permitted. ${response._error_message || ''}`);
|
||||
|
||||
} else if (xhr.status === 413) {
|
||||
frappe.msgprint('Size exceeds the maximum allowed file size.');
|
||||
|
||||
} else {
|
||||
frappe.msgprint(xhr.status === 0 ? 'XMLHttpRequest Error' : `${xhr.status} : ${xhr.statusText}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
xhr.open('POST', '/api/method/upload_file', true);
|
||||
xhr.setRequestHeader('Accept', 'application/json');
|
||||
xhr.setRequestHeader('X-Frappe-CSRF-Token', frappe.csrf_token);
|
||||
|
||||
let form_data = new FormData();
|
||||
if (file.file_obj) {
|
||||
form_data.append('file', file.file_obj, `${frappe.session.user}-${file.name}`);
|
||||
form_data.append('folder', `${$(".title").attr("data-lesson")} ${$(".title").attr("data-course")}`)
|
||||
}
|
||||
|
||||
xhr.send(form_data);
|
||||
});
|
||||
}
|
||||
|
||||
const create_lesson_work = (file, target) => {
|
||||
frappe.call({
|
||||
method: "lms.lms.doctype.lesson_assignment.lesson_assignment.upload_assignment",
|
||||
args: {
|
||||
assignment: file.file_url,
|
||||
lesson: $(".title").attr("data-lesson"),
|
||||
identifier: target.siblings(".attach-file").attr("id")
|
||||
},
|
||||
callback: (data) => {
|
||||
target.siblings(".attach-file").addClass("hide");
|
||||
target.siblings(".preview-work").removeClass("hide");
|
||||
target.siblings(".preview-work").find("a").attr("href", file.file_url).text(file.file_name)
|
||||
target.addClass("hide");
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const return_as_dataurl = (files) => {
|
||||
let promises = files.map(file =>
|
||||
frappe.dom.file_to_base64(file.file_obj)
|
||||
.then(dataurl => {
|
||||
file.dataurl = dataurl;
|
||||
this.on_success && this.on_success(file);
|
||||
})
|
||||
);
|
||||
return Promise.all(promises);
|
||||
}
|
||||
|
||||
const add_files = (files) => {
|
||||
files = Array.from(files).map(file => {
|
||||
let is_image = file.type.startsWith('image');
|
||||
return {
|
||||
file_obj: file,
|
||||
cropper_file: file,
|
||||
crop_box_data: null,
|
||||
optimize: this.attach_doc_image ? true : false,
|
||||
name: file.name,
|
||||
doc: null,
|
||||
progress: 0,
|
||||
total: 0,
|
||||
failed: false,
|
||||
request_succeeded: false,
|
||||
error_message: null,
|
||||
uploading: false,
|
||||
private: !is_image
|
||||
}
|
||||
});
|
||||
return files
|
||||
};
|
||||
|
||||
const clear_work = (e) => {
|
||||
const target = $(e.currentTarget);
|
||||
const parent = target.closest(".preview-work");
|
||||
parent.addClass("hide");
|
||||
parent.siblings(".attach-file").removeClass("hide").val(null);
|
||||
parent.siblings(".submit-work").removeClass("hide");
|
||||
};
|
||||
|
||||
const fetch_assignments = () => {
|
||||
if ($(".attach-file").length <= 0)
|
||||
return;
|
||||
frappe.call({
|
||||
method: "lms.lms.doctype.lesson_assignment.lesson_assignment.get_assignment",
|
||||
args: {
|
||||
"lesson": $(".title").attr("data-lesson")
|
||||
},
|
||||
callback: (data) => {
|
||||
if (data.message && data.message.length) {
|
||||
const assignments = data.message;
|
||||
for (let i in assignments) {
|
||||
let target = $(`#${assignments[i]["id"]}`);
|
||||
target.addClass("hide");
|
||||
target.siblings(".submit-work").addClass("hide");
|
||||
target.siblings(".preview-work").removeClass("hide");
|
||||
target.siblings(".preview-work").find("a").attr("href", assignments[i]["assignment"]).text(assignments[i]["file_name"]);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
69
lms/www/batch/learn.py
Normal file
69
lms/www/batch/learn.py
Normal file
@@ -0,0 +1,69 @@
|
||||
from re import I
|
||||
import frappe
|
||||
from lms.www.utils import get_common_context, redirect_to_lesson
|
||||
from lms.lms.utils import get_lesson_url
|
||||
from frappe.utils import cstr, flt
|
||||
from lms.www import batch
|
||||
|
||||
def get_context(context):
|
||||
get_common_context(context)
|
||||
|
||||
chapter_index = frappe.form_dict.get("chapter")
|
||||
lesson_index = frappe.form_dict.get("lesson")
|
||||
lesson_number = f"{chapter_index}.{lesson_index}"
|
||||
if not chapter_index or not lesson_index:
|
||||
if context.batch:
|
||||
index_ = get_lesson_index(context.course, context.batch, frappe.session.user) or "1.1"
|
||||
else:
|
||||
index_ = "1.1"
|
||||
redirect_to_lesson(context.course, index_)
|
||||
|
||||
context.lesson = get_current_lesson_details(lesson_number, context)
|
||||
neighbours = get_neighbours(lesson_number, context.lessons)
|
||||
context.next_url = get_url(neighbours["next"], context.course)
|
||||
context.prev_url = get_url(neighbours["prev"], context.course)
|
||||
|
||||
meta_info = context.lesson.title + " - " + context.course.title
|
||||
context.metatags = {
|
||||
"title": meta_info,
|
||||
"keywords": meta_info,
|
||||
"description": meta_info
|
||||
}
|
||||
|
||||
context.page_extensions = get_page_extensions(context)
|
||||
context.page_context = {
|
||||
"course": context.course.name,
|
||||
"batch": context.get("batch") and context.batch.name,
|
||||
"lesson": context.lesson.name,
|
||||
"is_member": context.membership is not None
|
||||
}
|
||||
|
||||
def get_current_lesson_details(lesson_number, context):
|
||||
details_list = list(filter(lambda x: cstr(x.number) == lesson_number, context.lessons))
|
||||
if not len(details_list):
|
||||
redirect_to_lesson(context.course)
|
||||
return details_list[0]
|
||||
|
||||
def get_url(lesson_number, course):
|
||||
return get_lesson_url(course.name, lesson_number) and get_lesson_url(course.name, lesson_number) + course.query_parameter
|
||||
|
||||
def get_lesson_index(course, batch, user):
|
||||
lesson = batch.get_current_lesson(user)
|
||||
return lesson and course.get_lesson_index(lesson)
|
||||
|
||||
def get_page_extensions(context):
|
||||
default_value = ["lms.plugins.PageExtension"]
|
||||
classnames = frappe.get_hooks("lms_lesson_page_extensions") or default_value
|
||||
extensions = [frappe.get_attr(name)() for name in classnames]
|
||||
for e in extensions:
|
||||
e.set_context(context)
|
||||
return extensions
|
||||
|
||||
def get_neighbours(current, lessons):
|
||||
current = flt(current)
|
||||
numbers = sorted(lesson.number for lesson in lessons)
|
||||
index = numbers.index(current)
|
||||
return {
|
||||
"prev": numbers[index-1] if index-1 >= 0 else None,
|
||||
"next": numbers[index+1] if index+1 < len(numbers) else None
|
||||
}
|
||||
Reference in New Issue
Block a user