feat: move chapters and lessons

This commit is contained in:
Jannat Patel
2023-03-31 16:30:38 +05:30
parent de399166f1
commit f361c42a30
5 changed files with 223 additions and 103 deletions

2
.gitignore vendored
View File

@@ -8,3 +8,5 @@ lms/public/dist
__pycache__/
*.py[cod]
*$py.class
node_modules
package-lock.json

View File

@@ -306,3 +306,43 @@ def save_lesson(
lesson_reference.save(ignore_permissions=True)
return doc.name
@frappe.whitelist()
def reorder_lesson(old_chapter, old_lesson_array, new_chapter, new_lesson_array):
if old_chapter == new_chapter:
sort_lessons(new_chapter, new_lesson_array)
else:
sort_lessons(old_chapter, old_lesson_array)
sort_lessons(new_chapter, new_lesson_array)
def sort_lessons(chapter, lesson_array):
lesson_array = json.loads(lesson_array)
for les in lesson_array:
ref = frappe.get_all("Lesson Reference", {"lesson": les}, ["name", "idx"])
if ref:
frappe.db.set_value(
"Lesson Reference",
ref[0].name,
{
"parent": chapter,
"idx": lesson_array.index(les) + 1,
},
)
@frappe.whitelist()
def reorder_chapter(chapter_array):
chapter_array = json.loads(chapter_array)
for chap in chapter_array:
ref = frappe.get_all("Chapter Reference", {"chapter": chap}, ["name", "idx"])
if ref:
frappe.db.set_value(
"Chapter Reference",
ref[0].name,
{
"idx": chapter_array.index(chap) + 1,
},
)

View File

@@ -9,7 +9,7 @@
{% endif %}
{% if course.name and (course.edit_mode or chapters | length) %}
<div class="course-home-headings" id="outline-heading">
<div class="course-home-headings" id="outline-heading" data-course="{{ course.name }}">
{{ _("Course Content") }}
</div>
{% endif %}
@@ -23,9 +23,9 @@
{% endif %}
{% if chapters | length %}
<div class="chapter-dropzone">
{% for chapter in chapters %}
<div class="chapter-parent {% if course.edit_mode %} chapter-edit {% endif %} ">
<div class="chapter-parent {% if course.edit_mode %} chapter-edit {% endif %}" data-chapter="{{ chapter.name }}">
<div class="chapter-title" {% if not course.edit_mode %} data-toggle="collapse" aria-expanded="false"
data-target="#{{ get_slugified_chapter_title(chapter.title) }}" {% endif %} >
{% if not course.edit_mode %}
@@ -42,13 +42,13 @@
{% if chapter.description or course.edit_mode %}
<div {% if course.edit_mode %} contenteditable="true" {% endif %} class="chapter-description
{% if not course.edit_mode %} mx-8 mb-2 {% endif %} "
data-placeholder="{{ _('Short Description') }}">{{ chapter.description }}</div>
data-placeholder="{{ _('Short Description') }}">{% if chapter.description %}{{ chapter.description }}{% endif %}</div>
{% endif %}
{% if course.edit_mode %}
<div class="mt-2">
<button class="btn btn-sm btn-secondary btn-save-chapter"
data-index="{{ loop.index }}" data-chapter="{{ chapter.name }}"> {{ _('Save') }} </button>
data-index="{{ loop.index }}"> {{ _('Save') }} </button>
<a class="btn btn-sm btn-secondary btn-lesson ml-2"
href="/courses/{{ course.name }}/learn/{{loop.index}}.{{ lessons | length + 1 }}?edit=1"> {{ _("New Lesson") }} </a>
</div>
@@ -56,19 +56,22 @@
{% set is_instructor = is_instructor(course.name) %}
{% if lessons | length %}
<div class="lessons">
{% if course.edit_mode %}
<div class="course-meta mt-8 font-weight-bold"> {{ _("Lessons") }}: </div>
{% endif %}
{% if course.edit_mode %}
<b class="course-meta"> {{ _("Lessons") }}: </b>
{% endif %}
<div class="lessons {% if course.edit_mode %} lesson-dropzone {% endif %}">
{% if lessons | length %}
{% for lesson in lessons %}
{% set active = membership.current_lesson == lesson.name %}
<div class="lesson-info {% if active and not course.edit_mode %} active-lesson {% endif %}">
<div data-lesson="{{ lesson.name }}" class="lesson-info {% if active and not course.edit_mode %} active-lesson {% endif %}">
{% if membership or lesson.include_in_preview or is_instructor or has_course_moderator_role() %}
<a class="lesson-links" data-course="{{ course.name }}"
<a class="lesson-links"
{% if is_instructor and not lesson.include_in_preview %}
title="{{ _('This lesson is not available for preview. As you are the Instructor of the course only you can see it.') }}"
{% endif %}
@@ -89,7 +92,7 @@
</a>
{% else %}
<div class="no-preview" title="This lesson is not available for preview" data-course="{{ course.name }}">
<div class="no-preview" title="This lesson is not available for preview">
<div class="lesson-links">
<svg class="icon icon-sm mr-2">
<use class="" href="#icon-lock-gray">
@@ -102,12 +105,13 @@
</div>
{% endfor %}
</div>
{% endif %}
</div>
</div>
</div>
{% endfor %}
</div>
{% endif %}
@@ -119,87 +123,3 @@
{{ widgets.NoPreviewModal(course=course, membership=membership) }}
{% endif %}
<script>
frappe.ready(() => {
expand_the_active_chapter();
$(".chapter-title").unbind().click((e) => {
rotate_chapter_icon(e);
});
$(".no-preview").click((e) => {
show_no_preview_dialog(e);
});
});
const expand_the_first_chapter = () => {
let elements = $(".course-home-outline .collapse");
elements.each((i, element) => {
if (i < 1) {
show_section(element);
return false;
}
});
};
const expand_the_active_chapter = () => {
/* Find anchor matching the URL for course details page */
let selector = $(`a[href="${decodeURIComponent(window.location.pathname)}"]`).parent();
if (!selector.length) {
selector = $(`a[href^="${decodeURIComponent(window.location.pathname)}"]`).parent();
}
if (selector.length && $(".course-details-page").length) {
$(".lesson-info").removeClass("active-lesson");
$(".lesson-info").each((i, elem) => {
let href = $(elem).find("use").attr("href");
href.endsWith("blue") && $(elem).find("use").attr("href", href.substring(0, href.length - 5));
})
selector.addClass("active-lesson");
show_section(selector.parent().parent());
}
/* For course home page */
else if ($(".active-lesson").length) {
selector = $(".active-lesson")
show_section(selector.parent().parent());
}
/* If no active chapter then exapand the first chapter */
else {
expand_the_first_chapter();
}
};
const show_section = (element) => {
$(element).addClass("show");
$(element).siblings(".chapter-title").children(".chapter-icon").css("transform", "rotate(90deg)");
$(element).siblings(".chapter-title").attr("aria-expanded", true);
};
const rotate_chapter_icon = (e) => {
let icon = $(e.currentTarget).children(".chapter-icon");
if (icon.css("transform") == "none") {
icon.css("transform", "rotate(90deg)");
} else {
icon.css("transform", "none");
}
};
const show_no_preview_dialog = (e) => {
$("#no-preview-modal").modal("show");
};
</script>

View File

@@ -1593,7 +1593,6 @@ li {
.chapter-edit .lessons {
margin-left: 0;
margin-top: 2rem;
}
.chapter-parent {

View File

@@ -31,8 +31,54 @@ frappe.ready(() => {
generate_graph("Lesson Completion", "#lesson-completion");
generate_course_completion_graph();
}
expand_the_active_chapter();
$(".chapter-title")
.unbind()
.click((e) => {
rotate_chapter_icon(e);
});
$(".no-preview").click((e) => {
show_no_preview_dialog(e);
});
$(".lesson-dropzone").each((i, el) => {
setSortable(el);
});
$(".chapter-dropzone").each((i, el) => {
setSortable(el);
});
});
const setSortable = (el) => {
new Sortable(el, {
group: {
name: "les",
pull: "les",
put: "les",
},
onEnd: (e) => {
if ($(e.item).hasClass("lesson-info")) reorder_lesson(e);
else reorder_chapter(e);
},
onMove: (e) => {
if (
$(e.dragged).hasClass("lesson-info") &&
$(e.to).hasClass("chapter-dropzone")
)
return false;
if (
$(e.dragged).hasClass("chapter-edit") &&
$(e.to).hasClass("lesson-dropzone")
)
return false;
},
});
};
const setup_file_size = () => {
frappe.provide("frappe.form.formatters");
frappe.form.formatters.FileSize = file_size;
@@ -49,7 +95,7 @@ const file_size = (value) => {
const join_course = (e) => {
e.preventDefault();
let course = $(e.currentTarget).attr("data-course");
let course = $("#outline-heading").attr("data-course");
if (frappe.session.user == "Guest") {
window.location.href = `/login?redirect-to=/courses/${course}`;
return;
@@ -83,7 +129,7 @@ const join_course = (e) => {
const notify_user = (e) => {
e.preventDefault();
var course = decodeURIComponent($(e.currentTarget).attr("data-course"));
var course = decodeURIComponent($("#outline-heading").attr("data-course"));
if (frappe.session.user == "Guest") {
window.location.href = `/login?redirect-to=/courses/${course}`;
return;
@@ -120,7 +166,7 @@ const add_chapter = (e) => {
let next_index = $("[data-index]").last().data("index") + 1 || 1;
let add_after = $(`.chapter-parent:last`).length
? $(`.chapter-parent:last`)
? $(`.chapter-dropzone`)
: $("#outline-heading");
$(`<div class="chapter-parent chapter-edit new-chapter">
@@ -157,7 +203,7 @@ const save_chapter = (e) => {
title: parent.find(".chapter-title-main").text(),
chapter_description: parent.find(".chapter-description").text(),
idx: target.data("index"),
chapter: target.data("chapter") ? target.data("chapter") : "",
chapter: parent.data("chapter") ? parent.data("chapter") : "",
},
callback: (data) => {
frappe.show_alert({
@@ -226,3 +272,116 @@ const change_hash = (e) => {
const open_tab = () => {
$(`a[href="${window.location.hash}"]`).click();
};
const expand_the_first_chapter = () => {
let elements = $(".course-home-outline .collapse");
elements.each((i, element) => {
if (i < 1) {
show_section(element);
return false;
}
});
};
const expand_the_active_chapter = () => {
/* Find anchor matching the URL for course details page */
let selector = $(
`a[href="${decodeURIComponent(window.location.pathname)}"]`
).parent();
if (!selector.length) {
selector = $(
`a[href^="${decodeURIComponent(window.location.pathname)}"]`
).parent();
}
if (selector.length && $(".course-details-page").length) {
expand_for_course_details(selector);
} else if ($(".active-lesson").length) {
/* For course home page */
selector = $(".active-lesson");
show_section(selector.parent().parent());
} else {
/* If no active chapter then exapand the first chapter */
expand_the_first_chapter();
}
};
const expand_for_course_details = (selector) => {
$(".lesson-info").removeClass("active-lesson");
$(".lesson-info").each((i, elem) => {
let href = $(elem).find("use").attr("href");
href.endsWith("blue") &&
$(elem)
.find("use")
.attr("href", href.substring(0, href.length - 5));
});
selector.addClass("active-lesson");
show_section(selector.parent().parent());
};
const show_section = (element) => {
$(element).addClass("show");
$(element)
.siblings(".chapter-title")
.children(".chapter-icon")
.css("transform", "rotate(90deg)");
$(element).siblings(".chapter-title").attr("aria-expanded", true);
};
const rotate_chapter_icon = (e) => {
let icon = $(e.currentTarget).children(".chapter-icon");
if (icon.css("transform") == "none") {
icon.css("transform", "rotate(90deg)");
} else {
icon.css("transform", "none");
}
};
const show_no_preview_dialog = (e) => {
$("#no-preview-modal").modal("show");
};
const reorder_lesson = (e) => {
let old_chapter = $(e.from).closest(".chapter-edit").data("chapter");
let new_chapter = $(e.to).closest(".chapter-edit").data("chapter");
if (old_chapter == new_chapter && e.oldIndex == e.newIndex) return;
frappe.call({
method: "lms.lms.doctype.lms_course.lms_course.reorder_lesson",
args: {
old_chapter: old_chapter,
old_lesson_array: $(e.from)
.children()
.map((i, e) => $(e).data("lesson"))
.get(),
new_chapter: new_chapter,
new_lesson_array: $(e.to)
.children()
.map((i, e) => $(e).data("lesson"))
.get(),
},
callback: (data) => {
window.location.reload();
},
});
};
const reorder_chapter = (e) => {
if (e.oldIndex == e.newIndex) return;
frappe.call({
method: "lms.lms.doctype.lms_course.lms_course.reorder_chapter",
args: {
new_index: e.newIndex + 1,
chapter_array: $(e.to)
.children()
.map((i, e) => $(e).data("chapter"))
.get(),
},
callback: (data) => {
window.location.reload();
},
});
};