From e65e054466a203ce4cbc72ae2802d39ce4604936 Mon Sep 17 00:00:00 2001
From: Stefan Topfstedt <Stefan.Topfstedt@ucsf.edu>
Date: Mon, 12 Jun 2023 09:07:38 -0700
Subject: [PATCH] MDL-71212 course: adds controls to the course index drawer.

This adds expand-all/collapse-all sections in the course index drawer
functionality.
---
 .../build/local/courseeditor/mutations.min.js | Bin 10575 -> 10741 bytes
 .../local/courseeditor/mutations.min.js.map   | Bin 33527 -> 34065 bytes
 .../amd/src/local/courseeditor/mutations.js   |  11 +++
 .../build/local/reactive/statemanager.min.js  | Bin 9890 -> 10056 bytes
 .../local/reactive/statemanager.min.js.map    | Bin 37501 -> 38049 bytes
 lib/amd/src/local/reactive/statemanager.js    |  15 +++
 .../build/courseindexdrawercontrols.min.js    | Bin 0 -> 1526 bytes
 .../courseindexdrawercontrols.min.js.map      | Bin 0 -> 3768 bytes
 theme/boost/amd/build/drawers.min.js          | Bin 11708 -> 12205 bytes
 theme/boost/amd/build/drawers.min.js.map      | Bin 31003 -> 31722 bytes
 .../amd/src/courseindexdrawercontrols.js      |  89 ++++++++++++++++++
 theme/boost/amd/src/drawers.js                |  13 ++-
 .../courseindexdrawercontrols.mustache        |  52 ++++++++++
 theme/boost/templates/drawer.mustache         |   3 +
 theme/boost/templates/drawers.mustache        |   3 +
 15 files changed, 183 insertions(+), 3 deletions(-)
 create mode 100644 theme/boost/amd/build/courseindexdrawercontrols.min.js
 create mode 100644 theme/boost/amd/build/courseindexdrawercontrols.min.js.map
 create mode 100644 theme/boost/amd/src/courseindexdrawercontrols.js
 create mode 100644 theme/boost/templates/courseindexdrawercontrols.mustache

diff --git a/course/format/amd/build/local/courseeditor/mutations.min.js b/course/format/amd/build/local/courseeditor/mutations.min.js
index 65d4a18ccf9d08f67f75c3196d58fc67e88dbb69..b382fb267f6f3d6394d0815760b8839962a71a27 100644
GIT binary patch
diff --git a/course/format/amd/build/local/courseeditor/mutations.min.js.map b/course/format/amd/build/local/courseeditor/mutations.min.js.map
index d292396fb3e525875a74123e403f195a6a2ef168..86ad01b279195fe9af9f7402cd93bef02076c240 100644
GIT binary patch
diff --git a/course/format/amd/src/local/courseeditor/mutations.js b/course/format/amd/src/local/courseeditor/mutations.js
index c2dc9df224d..7fbfc0f476c 100644
--- a/course/format/amd/src/local/courseeditor/mutations.js
+++ b/course/format/amd/src/local/courseeditor/mutations.js
@@ -474,6 +474,17 @@ export default class {
         await this._callEditWebservice('section_index_collapsed', course.id, collapsedIds);
     }
 
+    /**
+     * Update the course index collapsed attribute of all sections.
+     *
+     * @param {StateManager} stateManager the current state manager
+     * @param {boolean} collapsed the new collapsed value
+     */
+    async allSectionsIndexCollapsed(stateManager, collapsed) {
+        const sectionIds = stateManager.getIds('section');
+        this.sectionIndexCollapsed(stateManager, sectionIds, collapsed);
+    }
+
     /**
      * Update the course content collapsed attribute of some sections.
      *
diff --git a/lib/amd/build/local/reactive/statemanager.min.js b/lib/amd/build/local/reactive/statemanager.min.js
index 015e34f9c3530b5921dcb7445430e552df121300..91877fba6a9b5ed80df608957c70966908d67500 100644
GIT binary patch
diff --git a/lib/amd/build/local/reactive/statemanager.min.js.map b/lib/amd/build/local/reactive/statemanager.min.js.map
index 9310b33768b34cd2c5f33eebc606c77a38e38a33..9d0d2db271e598541aba17e68bfa1cbf40443dfb 100644
GIT binary patch
diff --git a/lib/amd/src/local/reactive/statemanager.js b/lib/amd/src/local/reactive/statemanager.js
index a8bcbae2962..99266b44afa 100644
--- a/lib/amd/src/local/reactive/statemanager.js
+++ b/lib/amd/src/local/reactive/statemanager.js
@@ -467,6 +467,21 @@ export default class StateManager {
         return current;
     }
 
+    /**
+     * Get all element ids from the given state.
+     *
+     * @param {String} name the state object name
+     * @return {Array} the element ids.
+     */
+    getIds(name) {
+        const state = this.state;
+        const current = state[name];
+        if (!(current instanceof StateMap)) {
+            throw Error(`${name} is not an instance of StateMap`);
+        }
+        return [...state[name].keys()];
+    }
+
     /**
      * Register a state modification and generate the necessary events.
      *
diff --git a/theme/boost/amd/build/courseindexdrawercontrols.min.js b/theme/boost/amd/build/courseindexdrawercontrols.min.js
new file mode 100644
index 0000000000000000000000000000000000000000..9f6c0a32cc4f6bc10c3a7a01a028d14bb4adbb84
GIT binary patch
diff --git a/theme/boost/amd/build/courseindexdrawercontrols.min.js.map b/theme/boost/amd/build/courseindexdrawercontrols.min.js.map
new file mode 100644
index 0000000000000000000000000000000000000000..379a0e476628133fc98ae813e227aa4af6177b54
GIT binary patch
diff --git a/theme/boost/amd/build/drawers.min.js b/theme/boost/amd/build/drawers.min.js
index ca7ff49f194697772253c9ef3cee7f877a20a8d5..46d5f7a73966ca84aa26e8fcbf5c64ce0922a120 100644
GIT binary patch
diff --git a/theme/boost/amd/build/drawers.min.js.map b/theme/boost/amd/build/drawers.min.js.map
index 6ae1a3a931a788b5c365d9c0def826e66b859bd0..93be37ffd88aed289b86eb468331579629f4d087 100644
GIT binary patch
diff --git a/theme/boost/amd/src/courseindexdrawercontrols.js b/theme/boost/amd/src/courseindexdrawercontrols.js
new file mode 100644
index 00000000000..464c99e5691
--- /dev/null
+++ b/theme/boost/amd/src/courseindexdrawercontrols.js
@@ -0,0 +1,89 @@
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Controls for the course index drawer, such as expand-all/collapse-all sections.
+ *
+ * @module     theme_boost/courseindexdrawercontrols
+ * @copyright  2023 Stefan Topfstedt
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+import {BaseComponent} from 'core/reactive';
+import {getCurrentCourseEditor} from 'core_courseformat/courseeditor';
+
+export default class Component extends BaseComponent {
+
+  create() {
+    this.name = 'courseindexdrawercontrols';
+    this.selectors = {
+      COLLAPSEALL: `[data-action="collapseallcourseindexsections"]`,
+      EXPANDALL: `[data-action="expandallcourseindexsections"]`,
+    };
+  }
+
+  /**
+   * @param {element|string} target the DOM main element or its ID
+   * @param {object} selectors optional css selector overrides
+   * @return {Component}
+   */
+  static init(target, selectors) {
+    return new Component({
+      element: document.getElementById(target),
+      reactive: getCurrentCourseEditor(),
+      selectors,
+    });
+  }
+
+  /**
+   * Initial state ready method.
+   */
+  stateReady() {
+    // Attach the on-click event handlers to the expand-all and collapse-all buttons, if present.
+    const expandAllBtn = this.getElement(this.selectors.EXPANDALL);
+    if (expandAllBtn) {
+      this.addEventListener(expandAllBtn, 'click', this._expandAllSections);
+
+    }
+    const collapseAllBtn = this.getElement(this.selectors.COLLAPSEALL);
+    if (collapseAllBtn) {
+      this.addEventListener(collapseAllBtn, 'click', this._collapseAllSections);
+    }
+  }
+
+  /**
+   * On-click event handler for the collapse-all button.
+   * @private
+   */
+  _collapseAllSections() {
+    this._toggleAllSections(true);
+  }
+
+  /**
+   * On-click event handler for the expand-all button.
+   * @private
+   */
+  _expandAllSections() {
+    this._toggleAllSections(false);
+  }
+
+  /**
+   * Collapses or expands all sections in the course index.
+   * @param {boolean} expandOrCollapse set to TRUE to collapse all, and FALSE to expand all.
+   * @private
+   */
+  _toggleAllSections(expandOrCollapse) {
+    this.reactive.dispatch('allSectionsIndexCollapsed', expandOrCollapse);
+  }
+}
diff --git a/theme/boost/amd/src/drawers.js b/theme/boost/amd/src/drawers.js
index a27a9bf95b7..17b35d672e2 100644
--- a/theme/boost/amd/src/drawers.js
+++ b/theme/boost/amd/src/drawers.js
@@ -41,6 +41,7 @@ const SELECTORS = {
     DRAWERS: '[data-region="fixed-drawer"]',
     CONTAINER: '#page.drawers',
     DRAWERCONTENT: '.drawercontent',
+    HEADERCONTENT: '.drawerheadercontent',
 };
 
 const CLASSES = {
@@ -429,8 +430,10 @@ export default class Drawers {
             return;
         }
 
-        // Hide close button while the drawer is showing to prevent glitchy effects.
+        // Hide close button and header content while the drawer is showing to prevent glitchy effects.
         this.drawerNode.querySelector(SELECTORS.CLOSEBTN)?.classList.toggle('hidden', true);
+        this.drawerNode.querySelector(SELECTORS.HEADERCONTENT)?.classList.toggle('hidden', true);
+
 
         // Remove open tooltip if still visible.
         let openButton = getDrawerOpenButton(this.drawerNode.id);
@@ -464,13 +467,15 @@ export default class Drawers {
             .catch();
         }
 
-        // Show close button once the drawer is fully opened.
+        // Show close button and header content once the drawer is fully opened.
         const closeButton = this.drawerNode.querySelector(SELECTORS.CLOSEBTN);
+        const headerContent = this.drawerNode.querySelector(SELECTORS.HEADERCONTENT);
         if (focusOnCloseButton && closeButton) {
             disableButtonTooltip(closeButton, true);
         }
         setTimeout(() => {
             closeButton.classList.toggle('hidden', false);
+            headerContent.classList.toggle('hidden', false);
             if (focusOnCloseButton) {
                 closeButton.focus();
             }
@@ -496,9 +501,11 @@ export default class Drawers {
             return;
         }
 
-        // Hide close button while the drawer is hiding to prevent glitchy effects.
+        // Hide close button and header content while the drawer is hiding to prevent glitchy effects.
         const closeButton = this.drawerNode.querySelector(SELECTORS.CLOSEBTN);
         closeButton?.classList.toggle('hidden', true);
+        const headerContent = this.drawerNode.querySelector(SELECTORS.HEADERCONTENT);
+        headerContent?.classList.toggle('hidden', true);
         // Remove the close button tooltip if visible.
         if (closeButton.hasAttribute('data-original-title')) {
             // The jQuery is still used in Boostrap 4. It can we removed when MDL-71979 is integrated.
diff --git a/theme/boost/templates/courseindexdrawercontrols.mustache b/theme/boost/templates/courseindexdrawercontrols.mustache
new file mode 100644
index 00000000000..1b30f53b46c
--- /dev/null
+++ b/theme/boost/templates/courseindexdrawercontrols.mustache
@@ -0,0 +1,52 @@
+{{!
+    This file is part of Moodle - http://moodle.org/
+
+    Moodle is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Moodle is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+}}
+{{!
+    @template theme_boost/courseindexdrawercontrols
+
+    This template will render "expand-all" and "collapse-all" buttons for the course index drawer.
+}}
+<div id="courseindexdrawercontrols" class="dropdown">
+    <button class="btn btn-link"
+            type="button"
+            data-toggle="dropdown"
+            aria-haspopup="true"
+            aria-expanded="false">
+        {{#str}}courseindex, courseformat{{/str}}
+        <i class="icon fa fa-ellipsis-v fa-fw m-0"></i>
+    </button>
+    <div class="dropdown-menu dropdown-menu-right">
+        <a class="dropdown-item"
+           href="#"
+           data-action="expandallcourseindexsections"
+        >
+            <i class="icon fa fa-chevron-down" aria-hidden="true"></i>
+            {{#str}}expandall{{/str}}
+        </a>
+        <a class="dropdown-item"
+           href="#"
+           data-action="collapseallcourseindexsections"
+        >
+            <i class="icon fa fa-chevron-right" aria-hidden="true"></i>
+            {{#str}}collapseall{{/str}}
+        </a>
+    </div>
+</div>
+{{#js}}
+    require(['theme_boost/courseindexdrawercontrols'], function(component) {
+    component.init('courseindexdrawercontrols');
+    });
+{{/js}}
diff --git a/theme/boost/templates/drawer.mustache b/theme/boost/templates/drawer.mustache
index 5f944f3097d..24c75d779c6 100644
--- a/theme/boost/templates/drawer.mustache
+++ b/theme/boost/templates/drawer.mustache
@@ -47,6 +47,9 @@
         >
             {{#pix}} e/cancel, core {{/pix}}
         </button>
+        <div class="drawerheadercontent hidden">
+            {{$drawerheadercontent}}{{/drawerheadercontent}}
+        </div>
     </div>
     <div class="drawercontent drag-container" data-usertour="scroller">
         {{$drawercontent}}{{/drawercontent}}
diff --git a/theme/boost/templates/drawers.mustache b/theme/boost/templates/drawers.mustache
index 3f1c7b185be..6c79bca70f4 100644
--- a/theme/boost/templates/drawers.mustache
+++ b/theme/boost/templates/drawers.mustache
@@ -61,6 +61,9 @@
     {{#courseindex}}
         {{< theme_boost/drawer }}
             {{$id}}theme_boost-drawers-courseindex{{/id}}
+            {{$drawerheadercontent}}
+                {{> theme_boost/courseindexdrawercontrols}}
+            {{/drawerheadercontent}}
             {{$drawerclasses}}drawer drawer-left {{#courseindexopen}}show{{/courseindexopen}}{{/drawerclasses}}
             {{$drawercontent}}
                 {{{courseindex}}}
-- 
2.25.1

