MDL-80248 course: Display only content for the General section
[moodle.git] / course / format / amd / src / local / content / section.js
blob30333fdc1b8835d667c535544ac21bf2a5320d7a
1 // This file is part of Moodle - http://moodle.org/
2 //
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.
7 //
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/>.
16 /**
17  * Course section format component.
18  *
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
23  */
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 {
31     /**
32      * Constructor hook.
33      */
34     create() {
35         // Optional component name for debugging.
36         this.name = 'content_section';
37         // Default query selectors.
38         this.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`,
46             ICON: `.icon`,
47         };
48         // Most classes will be loaded later by DndCmItem.
49         this.classes = {
50             LOCKED: 'editinprogress',
51             HASDESCRIPTION: 'description',
52             HIDE: 'd-none',
53             HIDDEN: 'hidden',
54             CURRENT: 'current',
55         };
57         // We need our id to watch specific events.
58         this.id = this.element.dataset.id;
59     }
61     /**
62      * Initial state ready method.
63      *
64      * @param {Object} state the initial state
65      */
66     stateReady(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);
72             if (sectionItem) {
73                 // Init the inner dragable element.
74                 const headerComponent = new Header({
75                     ...this,
76                     element: sectionItem,
77                     fullregion: this.element,
78                 });
79                 this.configDragDrop(headerComponent);
80             }
81         }
82     }
84     /**
85      * Component watchers.
86      *
87      * @returns {Array} of watchers
88      */
89     getWatchers() {
90         return [
91             {watch: `section[${this.id}]:updated`, handler: this._refreshSection},
92         ];
93     }
95     /**
96      * Validate if the drop data can be dropped over the component.
97      *
98      * @param {Object} dropdata the exported drop data.
99      * @returns {boolean}
100      */
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) {
104             return false;
105         }
106         return super.validateDropData(dropdata);
107     }
109     /**
110      * Get the last CM element of that section.
111      *
112      * @returns {element|null}
113      */
114     getLastCm() {
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) {
118             return null;
119         }
120         return cms[cms.length - 1];
121     }
123     /**
124      * Update a content section using the state information.
125      *
126      * @param {object} param
127      * @param {Object} param.element details the update details.
128      */
129     _refreshSection({element}) {
130         // Update classes.
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);
138         if (sectioninfo) {
139             sectioninfo.classList.toggle(this.classes.HASDESCRIPTION, element.hasrestrictions);
140         }
141         // Update section badges and menus.
142         this._updateBadges(element);
143         this._updateActionsMenu(element);
144     }
146     /**
147      * Update a section badges using the state information.
148      *
149      * @param {object} section the section state.
150      */
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);
157     }
159     /**
160      * Update a section action menus.
161      *
162      * @param {object} section the section state.
163      */
164     async _updateActionsMenu(section) {
165         let selector;
166         let newAction;
167         if (section.visible) {
168             selector = this.selectors.SHOWSECTION;
169             newAction = 'sectionHide';
170         } else {
171             selector = this.selectors.HIDESECTION;
172             newAction = 'sectionShow';
173         }
174         // Find the affected action.
175         const affectedAction = this.getElement(selector);
176         if (!affectedAction) {
177             return;
178         }
179         // Change action.
180         affectedAction.dataset.action = newAction;
181         // Change text.
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;
187         }
188         // Change icon.
189         const icon = affectedAction.querySelector(this.selectors.ICON);
190         if (affectedAction.dataset?.swapicon && icon) {
191             const newIcon = affectedAction.dataset.swapicon;
192             if (newIcon) {
193                 const pixHtml = await Templates.renderPix(newIcon, 'core');
194                 Templates.replaceNode(icon, pixHtml, '');
195             }
196         }
197     }