1 // This file is part of Moodle - http://moodle.org/
3 // Moodle is free software: you can redistribute it and/or modify
4 // it under the terms of the GNU General Public License as published by
5 // the Free Software Foundation, either version 3 of the License, or
6 // (at your option) any later version.
8 // Moodle is distributed in the hope that it will be useful,
9 // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 // GNU General Public License for more details.
13 // You should have received a copy of the GNU General Public License
14 // along with Moodle. If not, see <http://www.gnu.org/licenses/>.
17 * Course section format component.
19 * @module core_courseformat/local/content/section
20 * @class core_courseformat/local/content/section
21 * @copyright 2021 Ferran Recio <ferran@moodle.com>
22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
25 import Header from 'core_courseformat/local/content/section/header';
26 import DndSection from 'core_courseformat/local/courseeditor/dndsection';
27 import Templates from 'core/templates';
29 export default class extends DndSection {
35 // Optional component name for debugging.
36 this.name = 'content_section';
37 // Default query selectors.
39 SECTION_ITEM: `[data-for='section_title']`,
40 CM: `[data-for="cmitem"]`,
41 SECTIONINFO: `[data-for="sectioninfo"]`,
42 SECTIONBADGES: `[data-region="sectionbadges"]`,
43 SHOWSECTION: `[data-action="sectionShow"]`,
44 HIDESECTION: `[data-action="sectionHide"]`,
45 ACTIONTEXT: `.menu-action-text`,
48 // Most classes will be loaded later by DndCmItem.
50 LOCKED: 'editinprogress',
51 HASDESCRIPTION: 'description',
57 // We need our id to watch specific events.
58 this.id = this.element.dataset.id;
62 * Initial state ready method.
64 * @param {Object} state the initial state
67 this.configState(state);
68 // Drag and drop is only available for components compatible course formats.
69 if (this.reactive.isEditing && this.reactive.supportComponents) {
70 // Section zero and other formats sections may not have a title to drag.
71 const sectionItem = this.getElement(this.selectors.SECTION_ITEM);
73 // Init the inner dragable element.
74 const headerComponent = new Header({
77 fullregion: this.element,
79 this.configDragDrop(headerComponent);
87 * @returns {Array} of watchers
91 {watch: `section[${this.id}]:updated`, handler: this._refreshSection},
96 * Validate if the drop data can be dropped over the component.
98 * @param {Object} dropdata the exported drop data.
101 validateDropData(dropdata) {
102 // If the format uses one section per page sections dropping in the content is ignored.
103 if (dropdata?.type === 'section' && this.reactive.sectionReturn !== null) {
106 return super.validateDropData(dropdata);
110 * Get the last CM element of that section.
112 * @returns {element|null}
115 const cms = this.getElements(this.selectors.CM);
116 // DndUpload may add extra elements so :last-child selector cannot be used.
117 if (!cms || cms.length === 0) {
120 return cms[cms.length - 1];
124 * Update a content section using the state information.
126 * @param {object} param
127 * @param {Object} param.element details the update details.
129 _refreshSection({element}) {
131 this.element.classList.toggle(this.classes.DRAGGING, element.dragging ?? false);
132 this.element.classList.toggle(this.classes.LOCKED, element.locked ?? false);
133 this.element.classList.toggle(this.classes.HIDDEN, !element.visible ?? false);
134 this.element.classList.toggle(this.classes.CURRENT, element.current ?? false);
135 this.locked = element.locked;
136 // The description box classes depends on the section state.
137 const sectioninfo = this.getElement(this.selectors.SECTIONINFO);
139 sectioninfo.classList.toggle(this.classes.HASDESCRIPTION, element.hasrestrictions);
141 // Update section badges and menus.
142 this._updateBadges(element);
143 this._updateActionsMenu(element);
147 * Update a section badges using the state information.
149 * @param {object} section the section state.
151 _updateBadges(section) {
152 const current = this.getElement(`${this.selectors.SECTIONBADGES} [data-type='iscurrent']`);
153 current?.classList.toggle(this.classes.HIDE, !section.current);
155 const hiddenFromStudents = this.getElement(`${this.selectors.SECTIONBADGES} [data-type='hiddenfromstudents']`);
156 hiddenFromStudents?.classList.toggle(this.classes.HIDE, section.visible);
160 * Update a section action menus.
162 * @param {object} section the section state.
164 async _updateActionsMenu(section) {
167 if (section.visible) {
168 selector = this.selectors.SHOWSECTION;
169 newAction = 'sectionHide';
171 selector = this.selectors.HIDESECTION;
172 newAction = 'sectionShow';
174 // Find the affected action.
175 const affectedAction = this.getElement(selector);
176 if (!affectedAction) {
180 affectedAction.dataset.action = newAction;
182 const actionText = affectedAction.querySelector(this.selectors.ACTIONTEXT);
183 if (affectedAction.dataset?.swapname && actionText) {
184 const oldText = actionText?.innerText;
185 actionText.innerText = affectedAction.dataset.swapname;
186 affectedAction.dataset.swapname = oldText;
189 const icon = affectedAction.querySelector(this.selectors.ICON);
190 if (affectedAction.dataset?.swapicon && icon) {
191 const newIcon = affectedAction.dataset.swapicon;
193 const pixHtml = await Templates.renderPix(newIcon, 'core');
194 Templates.replaceNode(icon, pixHtml, '');