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) macros = find_macros(lesson_details.body)
for macro in macros: for macro in macros:
if macro[0] == "YouTubeVideo": if macro[0] == "YouTubeVideo" or macro[0] == "Video":
lesson_details.icon = "icon-video" lesson_details.icon = "icon-youtube"
elif macro[0] == "Quiz": elif macro[0] == "Quiz":
lesson_details.icon = "icon-quiz" lesson_details.icon = "icon-quiz"
lessons.append(lesson_details) 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.') }}" title="{{ _('This lesson is not available for preview. As you are the Instructor of the course only you can see it.') }}"
{% endif %}> {% endif %}>
<svg class="icon icon-sm mr-2"> <svg class="icon icon-md mr-2">
<use class="" href="#{{ lesson.icon }}"> <use class="" href="#{{ lesson.icon }}">
</svg> </svg>

View File

@@ -181,9 +181,24 @@ textarea.field-input {
text-decoration: none; text-decoration: none;
} }
.form-checkbox { .codex-editor path {
stroke: var(--gray-800);
}
.drag-handle {
cursor: grabbing;
}
.edit-header {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between;
}
.btn-default:not(:disabled):not(.disabled).active {
color: white;
background-color: var(--primary);
border: none;
} }
body { body {
@@ -610,6 +625,7 @@ input[type=checkbox] {
.lesson-links { .lesson-links {
display: flex; display: flex;
align-items: center;
padding: 0.5rem; padding: 0.5rem;
color: var(--gray-900); color: var(--gray-900);
font-size: var(--text-base); 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"> <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"/> <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>
<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"> <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"/> <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> </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); show_no_preview_dialog(e);
}); });
$(".lesson-dropzone").each((i, el) => {
setSortable(el);
});
$(".chapter-dropzone").each((i, el) => {
setSortable(el);
});
$("#create-class").click((e) => { $("#create-class").click((e) => {
open_class_dialog(e); open_class_dialog(e);
}); });

View File

@@ -22,24 +22,50 @@
<header class="sticky"> <header class="sticky">
<div class="container form-width"> <div class="container form-width">
<button class="btn btn-primary btn-sm pull-right mt-1" id="save-lesson"> <div class="edit-header">
<span>
{{ _("Save") }}
</span>
</button>
<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="align-self-center">
{% if lesson.name %} {% if lesson.name %}
<a class="btn btn-default btn-sm pull-right mt-1 mr-2" href="{{ get_lesson_url(course.name, lesson_number) }}"> <a class="btn btn-default btn-sm mr-2" href="{{ get_lesson_url(course.name, lesson_number) }}">
<span> <span>
{{ _("Preview") }} {{ _("Preview") }}
</span> </span>
</a> </a>
{% endif %} {% endif %}
<div class="page-title"> <button class="btn btn-primary btn-sm" id="save-lesson">
{{ course.title if course.name else _("Course Outline") }} <span>
{{ _("Save") }}
</span>
</button>
</div> </div>
</div> </div>
</div>
</header> </header>
{% endmacro %} {% endmacro %}
@@ -60,7 +86,7 @@
</div> </div>
<div class="field-group"> <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 %}> <input type="checkbox" id="preview" {% if lesson.include_in_preview %} checked {% endif %}>
<span>{{ _("Show preview of this lesson to Guest users.") }}</span> <span>{{ _("Show preview of this lesson to Guest users.") }}</span>
</label> </label>

View File

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

View File

@@ -5,96 +5,128 @@
{% block content %} {% block content %}
<div class="common-page-style" style="background-color: var(--fg-color);"> <div class="common-page-style">
<div class="container"> {{ Header() }}
{{ BreadCrumb(quiz) }} <div class="container form-width">
{{ QuizCard(quiz) }} {{ QuizCard(quiz) }}
</div> </div>
</div> </div>
{% endblock %} {% endblock %}
{% macro Header() %}
{% macro BreadCrumb(quiz) %} <header class="sticky">
<div class="breadcrumb"> <div class="container form-width">
<a class="dark-links" href="/quizzes">{{ _("Quizzes") }}</a> <div class="edit-header">
<img class="ml-1 mr-1" src="/assets/lms/icons/chevron-right.svg"> <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> <span class="breadcrumb-destination">{{ quiz.title if quiz.title else _("New Quiz") }}</span>
</div> </div>
</div>
<button class="btn btn-primary btn-sm btn-save-question align-self-center">
{{ _("Save") }}
</button>
</div>
</div>
</header>
{% endmacro %} {% endmacro %}
{% macro QuizCard(quiz) %} {% macro QuizCard(quiz) %}
<div style="width: 60%;"> <div>
<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 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 %} {% if quiz.questions %}
{% for question in quiz.questions %} {% for question in quiz.questions %}
<div class="quiz-card"> <div class="common-card-style column-card field-parent">
<div contenteditable="true" data-placeholder="{{ _('Question') }}" data-question="{{ question.name }}" <div class="field-group">
class="question mb-4">{% if question.question %} {{ question.question }} {% endif %}</div> <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"> <div class="field-group">
{% for option in ["Choices", "User Input"] %} <div class="vertically-center justify-content-between">
<option value="{{ option }}" {% if question.type == option %} selected {% endif %} > {{ _(option) }} </option> <div class="field-label">
{% endfor %} {{ _("Question Type") }}
</select> </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>
<div class="">
{% for i in range(1,5) %} {% for i in range(1,5) %}
{% set num = frappe.utils.cstr(i) %} {% set num = frappe.utils.cstr(i) %}
{% set option = question["option_" + num] %} {% set option = question["option_" + num] %}
{% set explanation = question["explanation_" + num] %} {% set explanation = question["explanation_" + num] %}
{% set possible_answer = question["possibility_" + num] %}
<div class="option-group mt-4 {% if question.type == 'User Input' %} hide {% endif %} "> <div class="field-group">
<label class=""> {{ _("Option") }} {{ num }} </label>
<div class="d-flex justify-content-between option-{{ num }}"> <div class="options-group {% if question.type == 'User Input' %} hide {% endif %}">
<div contenteditable="true" data-placeholder="{{ _('Option') }}" <input type="text" placeholder="Option" class="field-input option-{{ num }}" {% if option %} value="{{ option }}" {% endif %}>
class="option-input">{% if option %}{{ option }}{% endif %}</div> <input type="text" placeholder="Explanation" class="field-input explanation-{{ num }}" {% if explanation %} value="{{ explanation }}" {% endif %}>
<div contenteditable="true" data-placeholder="{{ _('Explain the option') }}" <label class="vertically-center mt-1">
class="option-input">{% if explanation %}{{ explanation }}{% endif %}</div> <input type="checkbox" class="correct-{{ num }}" {% if question['is_correct_' + num] %} checked {% endif %}>
<div class="option-checkbox">
<label class="mb-0">
<input type="checkbox" {% if question['is_correct_' + num] %} checked {% endif %}>
{{ _("Is Correct") }} {{ _("Is Correct") }}
</label> </label>
</div> </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>
</div> </div>
{% set possible_answer = question["possibility_" + num] %}
<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>
</div>
</div>
</div>
{% endfor %} {% endfor %}
</div> </div>
</div>
{% endfor %} {% endfor %}
{% endif %} {% 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> </div>
{% endmacro %} {% endmacro %}

View File

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

View File

@@ -6,48 +6,47 @@
{% block content %} {% block content %}
<div class="common-page-style"> <div class="common-page-style">
<div class="container"> <div class="container form-width">
{{ Header() }}
{% if quiz_list | length %}
{{ QuizList(quiz_list) }}
{% else %}
{{ EmptyState() }}
{% endif %}
</div>
</div>
{% endblock %}
<a class="btn btn-secondary btn-sm pull-right" href="/quizzes/new-quiz"> {% macro Header() %}
{{ _("Add Quiz") }} <header class="sticky">
</a> <div class="edit-header">
<div class="course-home-headings"> <div class="page-title">
{{ _("Quiz List") }} {{ _("Quiz List") }}
</div> </div>
{% if quiz_list | length %} <a class="btn btn-primary btn-sm align-self-center" href="/quizzes/new-quiz">
<div class="common-card-style"> {{ _("Add Quiz") }}
<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> </a>
</td>
</tr>
{% endfor %}
</table>
</div> </div>
</header>
{% endmacro %}
{% else %} {% macro QuizList(quiz_list) %}
<div class="empty-state"> <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"> <img class="icon icon-xl" src="/assets/lms/icons/comment.svg">
<div class="empty-state-text"> <div class="empty-state-text">
<div class="empty-state-heading"> <div class="empty-state-heading">
@@ -57,12 +56,8 @@
{{ _("Create a quiz and add it to your course to engage your users.") }} {{ _("Create a quiz and add it to your course to engage your users.") }}
</div> </div>
</div> </div>
</div>
{% endif %}
</div>
</div> </div>
{% endblock %} {% endmacro %}
{% block script %} {% block script %}
<script> <script>

View File

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

View File

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

View File

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

View File

@@ -10,6 +10,14 @@ frappe.ready(() => {
$("#save-chapter").click((e) => { $("#save-chapter").click((e) => {
save_chapter(e); save_chapter(e);
}); });
$(".lesson-dropzone").each((i, el) => {
setSortable(el);
});
$(".chapter-dropzone").each((i, el) => {
setSortable(el);
});
}); });
const show_chapter_modal = (e) => { 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();
},
});
};