feat: reorder chapters, lessons

This commit is contained in:
Jannat Patel
2023-05-08 19:07:28 +05:30
parent 211c69bb41
commit 752fe5b4ba
17 changed files with 425 additions and 217 deletions

View File

@@ -141,8 +141,8 @@ def get_lesson_details(chapter):
macros = find_macros(lesson_details.body)
for macro in macros:
if macro[0] == "YouTubeVideo":
lesson_details.icon = "icon-video"
if macro[0] == "YouTubeVideo" or macro[0] == "Video":
lesson_details.icon = "icon-youtube"
elif macro[0] == "Quiz":
lesson_details.icon = "icon-quiz"
lessons.append(lesson_details)

View File

@@ -62,7 +62,7 @@
title="{{ _('This lesson is not available for preview. As you are the Instructor of the course only you can see it.') }}"
{% endif %}>
<svg class="icon icon-sm mr-2">
<svg class="icon icon-md mr-2">
<use class="" href="#{{ lesson.icon }}">
</svg>

View File

@@ -181,9 +181,24 @@ textarea.field-input {
text-decoration: none;
}
.form-checkbox {
.codex-editor path {
stroke: var(--gray-800);
}
.drag-handle {
cursor: grabbing;
}
.edit-header {
display: flex;
align-items: center;
justify-content: space-between;
}
.btn-default:not(:disabled):not(.disabled).active {
color: white;
background-color: var(--primary);
border: none;
}
body {
@@ -610,6 +625,7 @@ input[type=checkbox] {
.lesson-links {
display: flex;
align-items: center;
padding: 0.5rem;
color: var(--gray-900);
font-size: var(--text-base);

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" id="icon-quiz" viewBox="0 0 1024 1024" stroke="#1F272E">
<path d="M512 0C229.232 0 0 229.232 0 512c0 282.784 229.232 512 512 512 282.784 0 512.017-229.216 512.017-512C1024.017 229.232 794.785 0 512 0zm0 961.008c-247.024 0-448-201.984-448-449.01 0-247.024 200.976-448 448-448s448.017 200.977 448.017 448S759.025 961.009 512 961.009zm-47.056-160.529h80.512v-81.248h-80.512zm46.112-576.944c-46.88 0-85.503 12.64-115.839 37.889-30.336 25.263-45.088 75.855-44.336 117.775l1.184 2.336h73.44c0-25.008 8.336-60.944 25.008-73.84 16.656-12.88 36.848-19.328 60.56-19.328 27.328 0 48.336 7.424 63.073 22.271 14.72 14.848 22.063 36.08 22.063 63.664 0 23.184-5.44 42.976-16.368 59.376-10.96 16.4-29.328 39.841-55.088 70.322-26.576 23.967-42.992 43.231-49.232 57.807-6.256 14.592-9.504 40.768-9.744 78.512h76.96c0-23.68 1.503-41.136 4.496-52.336 2.975-11.184 11.504-23.823 25.568-37.888 30.224-29.152 54.496-57.664 72.88-85.551 18.336-27.857 27.52-58.593 27.52-92.193 0-46.88-14.176-83.408-42.577-109.568-28.416-26.176-68.272-39.248-119.568-39.248z" fill="#1F272E"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -5,6 +5,16 @@
<svg id="icon-video-blue" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M1 9C1 4.58172 4.58172 1 9 1C11.1217 1 13.1566 1.84286 14.6569 3.34315C16.1571 4.84343 17 6.87827 17 9C17 13.4182 13.4182 17 9 17C4.58172 17 1 13.4182 1 9ZM8.00636 12.0679L11.8766 9.51133C12.0614 9.40191 12.174 9.2084 12.174 9C12.174 8.79161 12.0614 8.59809 11.8766 8.48867L8.00636 5.932C7.79102 5.78453 7.51 5.75869 7.2694 5.86422C7.0288 5.96977 6.86529 6.1906 6.84063 6.44334V11.5567C6.86529 11.8094 7.0288 12.0302 7.2694 12.1358C7.51 12.2413 7.79102 12.2155 8.00636 12.0679Z" fill="#2D95F0"/>
</svg>
<svg id="icon-youtube" width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_779_38008)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.125 13.625H14.875C15.4273 13.625 15.875 13.1773 15.875 12.625V3.125C15.875 2.57272 15.4273 2.125 14.875 2.125H3.125C2.57272 2.125 2.125 2.57272 2.125 3.125V12.625C2.125 13.1773 2.57272 13.625 3.125 13.625ZM14.875 14.625C15.9796 14.625 16.875 13.7296 16.875 12.625V3.125C16.875 2.02043 15.9796 1.125 14.875 1.125H3.125C2.02043 1.125 1.125 2.02043 1.125 3.125V12.625C1.125 13.7296 2.02043 14.625 3.125 14.625H14.875ZM6.74988 5.35507C6.74988 4.74831 7.43289 4.39269 7.92997 4.74065L11.619 7.32298C12.0456 7.62156 12.0456 8.25325 11.619 8.55183L7.92998 11.1342C7.43289 11.4821 6.74988 11.1265 6.74988 10.5197V5.35507ZM7.74988 5.83524V10.0396L10.753 7.93741L7.74988 5.83524ZM5.625 15.25C5.34886 15.25 5.125 15.4739 5.125 15.75C5.125 16.0261 5.34886 16.25 5.625 16.25H12.375C12.6511 16.25 12.875 16.0261 12.875 15.75C12.875 15.4739 12.6511 15.25 12.375 15.25H5.625Z" fill="#171717"/>
</g>
<defs>
<clipPath id="clip0_779_38008">
<rect width="18" height="18" fill="white"/>
</clipPath>
</defs>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" id="icon-quiz" viewBox="0 0 1024 1024" stroke="#1F272E">
<path d="M512 0C229.232 0 0 229.232 0 512c0 282.784 229.232 512 512 512 282.784 0 512.017-229.216 512.017-512C1024.017 229.232 794.785 0 512 0zm0 961.008c-247.024 0-448-201.984-448-449.01 0-247.024 200.976-448 448-448s448.017 200.977 448.017 448S759.025 961.009 512 961.009zm-47.056-160.529h80.512v-81.248h-80.512zm46.112-576.944c-46.88 0-85.503 12.64-115.839 37.889-30.336 25.263-45.088 75.855-44.336 117.775l1.184 2.336h73.44c0-25.008 8.336-60.944 25.008-73.84 16.656-12.88 36.848-19.328 60.56-19.328 27.328 0 48.336 7.424 63.073 22.271 14.72 14.848 22.063 36.08 22.063 63.664 0 23.184-5.44 42.976-16.368 59.376-10.96 16.4-29.328 39.841-55.088 70.322-26.576 23.967-42.992 43.231-49.232 57.807-6.256 14.592-9.504 40.768-9.744 78.512h76.96c0-23.68 1.503-41.136 4.496-52.336 2.975-11.184 11.504-23.823 25.568-37.888 30.224-29.152 54.496-57.664 72.88-85.551 18.336-27.857 27.52-58.593 27.52-92.193 0-46.88-14.176-83.408-42.577-109.568-28.416-26.176-68.272-39.248-119.568-39.248z" fill="#1F272E"/>
</svg>

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View File

@@ -0,0 +1,7 @@
<svg width="16" height="16" viewBox="0 0 16 16" id="icon-upload" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8 14.5C11.5899 14.5 14.5 11.5899 14.5 8C14.5 4.41015 11.5899 1.5 8 1.5C4.41015 1.5 1.5 4.41015 1.5 8C1.5 11.5899
4.41015 14.5 8 14.5Z" stroke="#505A62" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M8 4.75V11.1351" stroke="#505A62" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M5.29102 7.45833L7.99935 4.75L10.7077 7.45833" stroke="#505A62" stroke-miterlimit="10" stroke-linecap="round"
stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 637 B

View File

@@ -0,0 +1,10 @@
<svg id="icon-youtube" width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_779_38008)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.125 13.625H14.875C15.4273 13.625 15.875 13.1773 15.875 12.625V3.125C15.875 2.57272 15.4273 2.125 14.875 2.125H3.125C2.57272 2.125 2.125 2.57272 2.125 3.125V12.625C2.125 13.1773 2.57272 13.625 3.125 13.625ZM14.875 14.625C15.9796 14.625 16.875 13.7296 16.875 12.625V3.125C16.875 2.02043 15.9796 1.125 14.875 1.125H3.125C2.02043 1.125 1.125 2.02043 1.125 3.125V12.625C1.125 13.7296 2.02043 14.625 3.125 14.625H14.875ZM6.74988 5.35507C6.74988 4.74831 7.43289 4.39269 7.92997 4.74065L11.619 7.32298C12.0456 7.62156 12.0456 8.25325 11.619 8.55183L7.92998 11.1342C7.43289 11.4821 6.74988 11.1265 6.74988 10.5197V5.35507ZM7.74988 5.83524V10.0396L10.753 7.93741L7.74988 5.83524ZM5.625 15.25C5.34886 15.25 5.125 15.4739 5.125 15.75C5.125 16.0261 5.34886 16.25 5.625 16.25H12.375C12.6511 16.25 12.875 16.0261 12.875 15.75C12.875 15.4739 12.6511 15.25 12.375 15.25H5.625Z" fill="#171717"/>
</g>
<defs>
<clipPath id="clip0_779_38008">
<rect width="18" height="18" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -45,14 +45,6 @@ frappe.ready(() => {
show_no_preview_dialog(e);
});
$(".lesson-dropzone").each((i, el) => {
setSortable(el);
});
$(".chapter-dropzone").each((i, el) => {
setSortable(el);
});
$("#create-class").click((e) => {
open_class_dialog(e);
});

View File

@@ -22,23 +22,49 @@
<header class="sticky">
<div class="container form-width">
<button class="btn btn-primary btn-sm pull-right mt-1" id="save-lesson">
<span>
{{ _("Save") }}
</span>
</button>
<div class="edit-header">
{% if lesson.name %}
<a class="btn btn-default btn-sm pull-right mt-1 mr-2" href="{{ get_lesson_url(course.name, lesson_number) }}">
<span>
{{ _("Preview") }}
</span>
</a>
{% endif %}
<div>
<div class="page-title">
{{ course.title if course.name else _("Course Outline") }}
</div>
<div class="vertically-center small">
<a class="dark-links" href="/courses/{{ course.name }}/edit">
{{ _("Course Details") }}
</a>
<img class="icon icon-sm mr-0" src="/assets/lms/icons/chevron-right.svg">
<a class="dark-links" href="/courses/{{ course.name }}/outline">
{{ _("Course Outline") }}
</a>
<img class="icon icon-sm mr-0" src="/assets/lms/icons/chevron-right.svg">
<span class="breadcrumb-destination">
{{ _("New Lesson") }}
</span>
</div>
</div>
<div class="page-title">
{{ course.title if course.name else _("Course Outline") }}
<div class="align-self-center">
{% if lesson.name %}
<a class="btn btn-default btn-sm mr-2" href="{{ get_lesson_url(course.name, lesson_number) }}">
<span>
{{ _("Preview") }}
</span>
</a>
{% endif %}
<button class="btn btn-primary btn-sm" id="save-lesson">
<span>
{{ _("Save") }}
</span>
</button>
</div>
</div>
</div>
</header>
{% endmacro %}
@@ -60,7 +86,7 @@
</div>
<div class="field-group">
<label for="published" class="form-checkbox">
<label for="published" class="vertically-center">
<input type="checkbox" id="preview" {% if lesson.include_in_preview %} checked {% endif %}>
<span>{{ _("Show preview of this lesson to Guest users.") }}</span>
</label>

View File

@@ -23,6 +23,9 @@ const setup_editor = () => {
levels: [4, 5, 6],
defaultLevel: 5,
},
icon: `<svg class="icon icon-sm" style="">
<use class="" href="#icon-header"></use>
</svg>`,
},
paragraph: {
class: Paragraph,
@@ -177,6 +180,7 @@ class YouTubeVideo {
static get toolbox() {
return {
title: "YouTube Video",
icon: `<img src="/assets/lms/icons/video.svg" width="15" height="15">`,
};
}
@@ -234,6 +238,7 @@ class Quiz {
static get toolbox() {
return {
title: "Quiz",
icon: `<img src="/assets/lms/icons/quiz.svg" width="15" height="15">`,
};
}
@@ -270,10 +275,14 @@ class Quiz {
quizdialog.hide();
$(self.wrapper).html(self.render_quiz(self.quiz));
},
secondary_action_label: __("Create New"),
secondary_action: () => {
window.location.href = `/quizzes`;
},
});
quizdialog.show();
setTimeout(() => {
$(".modal-body").css("min-height", "300px");
$(".modal-body").css("min-height", "200px");
$(".modal-body input").focus();
}, 1000);
}
@@ -295,6 +304,7 @@ class Upload {
static get toolbox() {
return {
title: "Upload",
icon: `<img src="/assets/lms/icons/upload.svg" width="15" height="15">`,
};
}

View File

@@ -5,96 +5,128 @@
{% block content %}
<div class="common-page-style" style="background-color: var(--fg-color);">
<div class="container">
{{ BreadCrumb(quiz) }}
<div class="common-page-style">
{{ Header() }}
<div class="container form-width">
{{ QuizCard(quiz) }}
</div>
</div>
{% endblock %}
{% macro Header() %}
<header class="sticky">
<div class="container form-width">
<div class="edit-header">
<div>
<div class="page-title">
{{ _("Quiz Details") }}
</div>
<div class="vertically-center small">
<a class="dark-links" href="/quizzes">
{{ _("Quiz List") }}
</a>
<img class="icon icon-sm mr-0" src="/assets/lms/icons/chevron-right.svg">
<span class="breadcrumb-destination">{{ quiz.title if quiz.title else _("New Quiz") }}</span>
</div>
</div>
{% macro BreadCrumb(quiz) %}
<div class="breadcrumb">
<a class="dark-links" href="/quizzes">{{ _("Quizzes") }}</a>
<img class="ml-1 mr-1" src="/assets/lms/icons/chevron-right.svg">
<span class="breadcrumb-destination">{{ quiz.title if quiz.title else _("New Quiz") }}</span>
</div>
{% endmacro %}
<button class="btn btn-primary btn-sm btn-save-question align-self-center">
{{ _("Save") }}
</button>
</div>
</div>
</header>
{% endmacro %}
{% macro QuizCard(quiz) %}
<div style="width: 60%;">
<div class="course-home-headings mb-2" data-placeholder="{{ _('Quiz Title') }}" id="quiz-title"
{% if quiz.name %} data-name="{{ quiz.name }}" {% endif %}
contenteditable="true" >{% if quiz.title %}{{ quiz.title }}{% endif %}</div>
<div>
<div class="field-parent">
<div class="field-group">
<div>
<div class="field-label">
{{ _("Title") }}
</div>
<div class="field-description">
{{ _("Give your quiz a title") }}
</div>
</div>
<div class="">
<input type="text" class="field-input" id="quiz-title" {% if quiz.name %} value="{{ quiz.title }}" data-name="{{ quiz.name }}" {% endif %}>
</div>
</div>
</div>
{% if quiz.questions %}
{% for question in quiz.questions %}
<div class="quiz-card">
<div contenteditable="true" data-placeholder="{{ _('Question') }}" data-question="{{ question.name }}"
class="question mb-4">{% if question.question %} {{ question.question }} {% endif %}</div>
<div class="common-card-style column-card field-parent">
<div class="field-group">
<div>
<div class="field-label">
{{ _("Question") }} {{ loop.index }}
</div>
</div>
<div class="">
<input type="text" class="field-input question" {% if question.name %} value="{{ question.question }}" data-question="{{ question.name }}" {% endif %}>
</div>
</div>
<select value="{{ question.type }}" class="input-with-feedback form-control ellipsis type" maxlength="140" data-fieldtype="Select" data-fieldname="type" placeholder="" data-doctype="LMS Quiz Question">
{% for option in ["Choices", "User Input"] %}
<option value="{{ option }}" {% if question.type == option %} selected {% endif %} > {{ _(option) }} </option>
{% endfor %}
</select>
{% for i in range(1,5) %}
{% set num = frappe.utils.cstr(i) %}
{% set option = question["option_" + num] %}
{% set explanation = question["explanation_" + num] %}
<div class="option-group mt-4 {% if question.type == 'User Input' %} hide {% endif %} ">
<label class=""> {{ _("Option") }} {{ num }} </label>
<div class="d-flex justify-content-between option-{{ num }}">
<div contenteditable="true" data-placeholder="{{ _('Option') }}"
class="option-input">{% if option %}{{ option }}{% endif %}</div>
<div contenteditable="true" data-placeholder="{{ _('Explain the option') }}"
class="option-input">{% if explanation %}{{ explanation }}{% endif %}</div>
<div class="option-checkbox">
<label class="mb-0">
<input type="checkbox" {% if question['is_correct_' + num] %} checked {% endif %}>
{{ _("Is Correct") }}
<div class="field-group">
<div class="vertically-center justify-content-between">
<div class="field-label">
{{ _("Question Type") }}
</div>
<div class="btn-group btn-group-toggle type align-self-center" data-toggle="buttons">
<label class="btn btn-default btn-sm active question-type">
<input type="radio" name="type-{{ loop.index }}" data-type="Choices" {% if question.type == "Choices" %} checked {% endif %}>
{{ _("Choices") }}
</label>
<label class="btn btn-default btn-sm question-type">
<input type="radio" name="type-{{ loop.index }}" data-type="User Input" {% if question.type == "User Input" %} checked {% endif %}>
{{ _("User Input") }}
</label>
</div>
</div>
</div>
{% set possible_answer = question["possibility_" + num] %}
<div class="">
<div class="possibility-group mt-4 {% if question.type == 'Choices' %} hide {% endif %}">
<label class=""> {{ _("Possible Answer") }} {{ num }} </label>
<div class="control-input-wrapper">
<div class="control-input">
<div contenteditable="true" class="input-with-feedback form-control bold possibility-{{ num }}" style="height: 100px;" spellcheck="false">{% if possible_answer %}{{possible_answer}}{% endif %}</div>
{% for i in range(1,5) %}
{% set num = frappe.utils.cstr(i) %}
{% set option = question["option_" + num] %}
{% set explanation = question["explanation_" + num] %}
{% set possible_answer = question["possibility_" + num] %}
<div class="field-group">
<div class="options-group {% if question.type == 'User Input' %} hide {% endif %}">
<input type="text" placeholder="Option" class="field-input option-{{ num }}" {% if option %} value="{{ option }}" {% endif %}>
<input type="text" placeholder="Explanation" class="field-input explanation-{{ num }}" {% if explanation %} value="{{ explanation }}" {% endif %}>
<label class="vertically-center mt-1">
<input type="checkbox" class="correct-{{ num }}" {% if question['is_correct_' + num] %} checked {% endif %}>
{{ _("Is Correct") }}
</label>
</div>
<div class="answers-group {% if question.type == 'Choices' %} hide {% endif %}">
<div class="field-label">
{{ _("Possible Answers") }} {{ num }}
</div>
<textarea class="field-input possibility-{{ num }}"
style="height: 100px;">{% if possible_answer %}{{ possible_answer }}{% endif %}</textarea>
</div>
</div>
{% endfor %}
</div>
{% endfor %}
</div>
{% endfor %}
{% endif %}
<div class="mt-4">
<button class="btn btn-secondary btn-sm btn-question"> {{ _("New Question") }} </button>
{% if quiz.name %}
<button class="btn btn-secondary btn-sm copy-quiz-id ml-2" data-name="'{{ quiz.name }}'">
{{ _("Copy Quiz ID") }}
</button>
{% endif %}
<button class="btn btn-primary btn-sm btn-save-question ml-2 {% if not quiz.name %} hide {% endif %}">
{{ _("Save Quiz") }}
</button>
</div>
</div>
{% endmacro %}

View File

@@ -1,8 +1,4 @@
frappe.ready(() => {
if (!$(".quiz-card").length) {
add_question();
}
$(".btn-question").click((e) => {
add_question();
});
@@ -15,21 +11,35 @@ frappe.ready(() => {
frappe.utils.copy_to_clipboard($(e.currentTarget).data("name"));
});
$(document).on("change", ".type", function () {
toggle_form($(this));
$(".question-type").click((e) => {
toggle_form($(e.currentTarget));
});
get_questions();
});
const toggle_form = (el) => {
let type = el.val();
if (type === "Choices") {
el.siblings(".option-group").removeClass("hide");
el.siblings(".possibility-group").addClass("hide");
} else if (type === "User Input") {
el.siblings(".option-group").addClass("hide");
el.siblings(".possibility-group").removeClass("hide");
if ($(el).hasClass("active")) {
let type = $(el).find("input").data("type");
if (type == "Choices") {
$(el)
.closest(".field-parent")
.find(".options-group")
.removeClass("hide");
$(el)
.closest(".field-parent")
.find(".answers-group")
.addClass("hide");
} else {
$(el)
.closest(".field-parent")
.find(".options-group")
.addClass("hide");
$(el)
.closest(".field-parent")
.find(".answers-group")
.removeClass("hide");
}
}
};
@@ -102,19 +112,19 @@ const get_option_template = (num) => {
};
const save_question = (e) => {
if (!$("#quiz-title").text()) {
if (!$("#quiz-title").val()) {
frappe.throw(__("Quiz Title is mandatory."));
}
frappe.call({
method: "lms.lms.doctype.lms_quiz.lms_quiz.save_quiz",
args: {
quiz_title: $("#quiz-title").text(),
quiz_title: $("#quiz-title").val(),
questions: get_questions(),
quiz: $("#quiz-title").data("name") || "",
},
callback: (data) => {
window.location.href = "/quizzes";
window.location.reload();
},
});
};
@@ -122,41 +132,37 @@ const save_question = (e) => {
const get_questions = () => {
let questions = [];
$(".quiz-card").each((i, el) => {
if (!$(el).find(".question").text()) return;
$(".field-parent").each((i, el) => {
if (!$(el).find(".question").val()) return;
let details = {};
let correct_options = 0;
let possibilities = 0;
details["element"] = el;
details["question"] = $(el).find(".question").text();
details["question"] = $(el).find(".question").val();
details["question_name"] =
$(el).find(".question").data("question") || "";
details["type"] = $(el).find(".type").val();
details["type"] = $(el).find("label.active").find("input").data("type");
Array.from({ length: 4 }, (x, i) => {
let num = i + 1;
if (details.type == "Choices") {
details[`option_${num}`] = $(el)
.find(`.option-${num} .option-input:first`)
.text();
details[`explanation_${num}`] = $(el)
.find(`.option-${num} .option-input:last`)
.text();
details[`option_${num}`] = $(el).find(`.option-${num}`).val();
details[`explanation_${num}`] = $(el)
.find(`.explanation-${num}`)
.val();
let is_correct = $(el).find(`.correct-${num}`).prop("checked");
let is_correct = $(el)
.find(`.option-${num} .option-checkbox`)
.find("input")
.prop("checked");
if (is_correct) correct_options += 1;
details[`is_correct_${num}`] = is_correct;
} else {
let possible_answer = $(el)
.find(`.possibility-${num}`)
.text()
.val()
.trim();
if (possible_answer) possibilities += 1;
details[`possibility_${num}`] = possible_answer;

View File

@@ -6,63 +6,58 @@
{% block content %}
<div class="common-page-style">
<div class="container">
<a class="btn btn-secondary btn-sm pull-right" href="/quizzes/new-quiz">
{{ _("Add Quiz") }}
</a>
<div class="course-home-headings">
{{ _("Quiz List") }}
</div>
<div class="container form-width">
{{ Header() }}
{% if quiz_list | length %}
<div class="common-card-style">
<table class="table">
<tr style="background-color: var(--fg-hover-color); font-weight: bold">
<td style="width: 10%;"> {{ _("No.") }} </td>
<td style="width: 45%;"> {{ _("Title") }} </td>
<td> {{ _("ID") }} </td>
<td> </td>
</tr>
{% for quiz in quiz_list %}
<tr class="quiz-row" data-name="{{ quiz.name }}">
<td> {{ loop.index }} </td>
<td>
{{ quiz.title }}
</td>
<td>
{{ quiz.name }}
</td>
<td>
<a class="btn btn-secondary btn-sm copy-quiz-id" data-name="{{ quiz.name }}">
{{ _("Copy Quiz ID") }}
</a>
</td>
</tr>
{% endfor %}
</table>
</div>
{{ QuizList(quiz_list) }}
{% else %}
<div class="empty-state">
<img class="icon icon-xl" src="/assets/lms/icons/comment.svg">
<div class="empty-state-text">
<div class="empty-state-heading">
{{ _("You have not created any quiz yet.") }}
</div>
<div class="course-meta ">
{{ _("Create a quiz and add it to your course to engage your users.") }}
</div>
</div>
</div>
{{ EmptyState() }}
{% endif %}
</div>
</div>
{% endblock %}
{% macro Header() %}
<header class="sticky">
<div class="edit-header">
<div class="page-title">
{{ _("Quiz List") }}
</div>
<a class="btn btn-primary btn-sm align-self-center" href="/quizzes/new-quiz">
{{ _("Add Quiz") }}
</a>
</div>
</header>
{% endmacro %}
{% macro QuizList(quiz_list) %}
<div class="mt-5">
<ul class="list-unstyled">
{% for quiz in quiz_list %}
<li>
<a class="clickable" href="/quizzes/{{ quiz.name }}">
{{ quiz.title }}
</a>
</li>
{% endfor %}
</ul>
</div>
{% endmacro %}
{% macro EmptyState() %}
<div class="empty-state">
<img class="icon icon-xl" src="/assets/lms/icons/comment.svg">
<div class="empty-state-text">
<div class="empty-state-heading">
{{ _("You have not created any quiz yet.") }}
</div>
<div class="course-meta ">
{{ _("Create a quiz and add it to your course to engage your users.") }}
</div>
</div>
</div>
{% endmacro %}
{% block script %}
<script>

View File

@@ -16,17 +16,27 @@
{% macro Header() %}
<header class="sticky">
<div class="container form-width">
<button class="btn btn-primary btn-sm btn-save-course pull-right mt-1">
{{ _("Save") }}
</button>
{% if course.name %}
<a class="btn btn-default btn-sm pull-right mt-1 mr-2" href="/courses/{{ course.name }}">
{{ _("Preview") }}
</a>
{% endif %}
<div class="edit-header">
<div class="page-title"> {{ _("Course Details") }} </div>
<div class="align-self-center">
{% if course.name %}
<a class="btn btn-default btn-sm mr-2" href="/courses/{{ course.name }}">
{{ _("Preview") }}
</a>
<a class="btn btn-default btn-sm mr-2" href="/courses/{{ course.name }}/outline">
{{ _("Course Outline") }}
</a>
{% endif %}
<button class="btn btn-primary btn-sm btn-save-course">
{{ _("Save") }}
</button>
</div>
</div>
<div class="page-title"> {{ _("Course Details") }} </div>
</div>
</header>
{% endmacro %}

View File

@@ -75,7 +75,7 @@ const save_course = (e) => {
indicator: "green",
});
setTimeout(() => {
window.location.href = `/courses/${data.message}/outline`;
window.location.reload();
}, 1000);
},
});

View File

@@ -23,15 +23,26 @@
{% macro Header() %}
<header class="sticky">
<div class="container form-width">
<button class="btn btn-primary btn-sm pull-right mt-1 btn-add-chapter">
<span>
{{ _("Add Chapter") }}
</span>
</button>
<div class="page-title">
{{ course.title if course.name else _("Course Outline") }}
<div class="edit-header">
<div>
<div class="page-title">
{{ course.title if course.name else _("Course Outline") }}
</div>
<div class="vertically-center small">
<a class="dark-links" href="/courses/{{ course.name }}/edit">{{ _("Course Details") }}</a>
<img class="icon icon-sm mr-0" src="/assets/lms/icons/chevron-right.svg">
<span class="breadcrumb-destination">{{ _("Course Outline") }}</span>
</div>
</div>
<button class="btn btn-primary btn-sm btn-add-chapter align-self-center">
<span>
{{ _("Add Chapter") }}
</span>
</button>
</div>
</div>
</header>
{% endmacro %}
@@ -39,15 +50,17 @@
{% macro Outline(chapters) %}
{% if chapters %}
{% for chapter in chapters %}
{% set chapter_index = loop.index %}
{% set lessons = get_lessons(course.name, chapter) %}
<article>
<div class="chapter-dropzone">
{% for chapter in chapters %}
{% set chapter_index = loop.index %}
{% set lessons = get_lessons(course.name, chapter) %}
<div class="common-card-style column-card chapter-container p-4 my-5" data-chapter="{{ chapter.name }}" data-idx="{{ loop.index }}">
<div class="level">
<svg class="icon icon-xs level-item mr-2">
<use class="" href="#icon-drag"></use>
</svg>
<div class="drag-handle">
<svg class="icon icon-xs level-item mr-2">
<use class="" href="#icon-drag"></use>
</svg>
</div>
<div class="bold-heading chapters-title">
{{ chapter.title }}
</div>
@@ -59,21 +72,25 @@
</div>
{% endif %}
{% for lesson in lessons %}
<div class="outline-lesson level">
<svg class="icon icon-xs level-item mr-2">
<use class="" href="#icon-drag"></use>
</svg>
<!-- <div class="icon-bg">
<svg class="icon icon-sm level-item">
<use class="" href="#{{ lesson.icon }}">
{% if lessons | length %}
<div class="lesson-dropzone">
{% for lesson in lessons %}
<div class="outline-lesson level" data-lesson="{{ lesson.name }}">
<div class="drag-handle">
<svg class="icon icon-xs level-item mr-2">
<use class="" href="#icon-drag"></use>
</svg>
</div> -->
<a class="clickable" href="/courses/{{ course.name }}/learn/{{ chapter_index }}.{{ loop.index }}/edit">
{{ lesson.title }}
</a>
</div>
<div>
<a class="clickable" href="/courses/{{ course.name }}/learn/{{ chapter_index }}.{{ loop.index }}/edit">
{{ lesson.title }}
</a>
</div>
</div>
{% endfor %}
{% endfor %}
</div>
{% endif %}
<div class="align-self-start mt-4">
<a class="btn btn-secondary btn-sm" href="/courses/{{ course.name }}/learn/{{ loop.index }}.{{ lessons | length + 1 }}/edit">
@@ -88,8 +105,8 @@
</button>
</div>
</div>
</article>
{% endfor %}
{% endfor %}
</div>
{% endif %}
{% endmacro %}
@@ -141,7 +158,6 @@
</article>
</div>
<div class="modal-footer">
<button class="btn btn-secondary btn-sm mr-2" data-dismiss="modal" aria-label="Close">
{{ _("Discard") }}
@@ -168,9 +184,6 @@
</div>
<div class="mt-4">
<button class="btn btn-default btn-sm btn-add-chapter">
<!-- <svg class="icon icon-xs">
<use class="" href="#icon-add"></use>
</svg> -->
<span>
{{ _("Add Chapter") }}
</span>

View File

@@ -10,6 +10,14 @@ frappe.ready(() => {
$("#save-chapter").click((e) => {
save_chapter(e);
});
$(".lesson-dropzone").each((i, el) => {
setSortable(el);
});
$(".chapter-dropzone").each((i, el) => {
setSortable(el);
});
});
const show_chapter_modal = (e) => {
@@ -49,3 +57,73 @@ const save_chapter = (e) => {
},
});
};
const setSortable = (el) => {
new Sortable(el, {
group: "drag",
handle: ".drag-handle",
animation: 150,
fallbackOnBody: true,
swapThreshold: 0.65,
onEnd: (e) => {
if ($(e.item).hasClass("outline-lesson")) reorder_lesson(e);
else reorder_chapter(e);
},
onMove: (e) => {
if (
$(e.dragged).hasClass("outline-lesson") &&
$(e.to).hasClass("chapter-dropzone")
)
return false;
if (
$(e.dragged).hasClass("chapter-edit") &&
$(e.to).hasClass("lesson-dropzone")
)
return false;
},
});
};
const reorder_lesson = (e) => {
let old_chapter = $(e.from).closest(".chapter-container").data("chapter");
let new_chapter = $(e.to).closest(".chapter-container").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();
},
});
};