From c64bef51a9fb1b9db9959a4ab3c3a8c791b79a6a Mon Sep 17 00:00:00 2001 From: Ferran Recio Date: Mon, 24 Jan 2022 12:39:58 +0100 Subject: [PATCH 1/2] MDL-73547 core_courseformat: enable course index cache Adding caches to the course editor reactive state. The patch improve the course index loading. --- course/format/amd/build/courseeditor.min.js | 2 +- .../format/amd/build/courseeditor.min.js.map | 2 +- .../local/courseeditor/courseeditor.min.js | 2 +- .../courseeditor/courseeditor.min.js.map | 2 +- .../build/local/courseeditor/mutations.min.js | 2 +- .../local/courseeditor/mutations.min.js.map | 2 +- course/format/amd/src/courseeditor.js | 15 ++++++- .../src/local/courseeditor/courseeditor.js | 29 +++++++++++-- .../amd/src/local/courseeditor/mutations.js | 10 +++-- course/format/classes/base.php | 42 +++++++++++++++++++ .../format/classes/external/update_course.php | 8 +++- .../classes/output/local/state/course.php | 1 + course/format/classes/stateactions.php | 26 ++++++++++++ course/lib.php | 3 ++ lang/en/cache.php | 1 + lib/completionlib.php | 7 ++++ lib/db/caches.php | 6 +++ version.php | 2 +- 18 files changed, 145 insertions(+), 17 deletions(-) diff --git a/course/format/amd/build/courseeditor.min.js b/course/format/amd/build/courseeditor.min.js index 7a1a39514d1..381d05c2506 100644 --- a/course/format/amd/build/courseeditor.min.js +++ b/course/format/amd/build/courseeditor.min.js @@ -1,2 +1,2 @@ -define ("core_courseformat/courseeditor",["exports","core_courseformat/local/courseeditor/mutations","core_courseformat/local/courseeditor/courseeditor","core_course/events"],function(a,b,c,d){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.getCurrentCourseEditor=a.getCourseEditor=a.setViewFormat=void 0;b=e(b);c=e(c);d=e(d);function e(a){return a&&a.__esModule?a:{default:a}}var g=new Map;function f(a,b){if(b===void 0){b=document}b.dispatchEvent(new CustomEvent(d.default.stateChanged,{bubbles:!0,detail:a}))}a.setViewFormat=function setViewFormat(a,b){var c=h(a);c.setViewFormat(b)};var h=function(a){a=parseInt(a);if(!g.has(a)){g.set(a,new c.default({name:"CourseEditor".concat(a),eventName:d.default.stateChanged,eventDispatch:f,mutations:new b.default}));g.get(a).loadCourse(a)}return g.get(a)};a.getCourseEditor=h;a.getCurrentCourseEditor=function getCurrentCourseEditor(){return h(M.cfg.courseId)}}); +define ("core_courseformat/courseeditor",["exports","core_courseformat/local/courseeditor/mutations","core_courseformat/local/courseeditor/courseeditor","core_course/events"],function(a,b,c,d){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.getCurrentCourseEditor=a.getCourseEditor=a.setViewFormat=void 0;b=e(b);c=e(c);d=e(d);function e(a){return a&&a.__esModule?a:{default:a}}var g=new Map,h=new Map;function f(a,b){if(b===void 0){b=document}b.dispatchEvent(new CustomEvent(d.default.stateChanged,{bubbles:!0,detail:a}))}a.setViewFormat=function setViewFormat(a,b){a=parseInt(a);if(!b.editing){h.set(a,b.statekey)}var c=i(a);c.setViewFormat(b)};var i=function(a){a=parseInt(a);if(!g.has(a)){g.set(a,new c.default({name:"CourseEditor".concat(a),eventName:d.default.stateChanged,eventDispatch:f,mutations:new b.default}));g.get(a).loadCourse(a,h.get(a))}return g.get(a)};a.getCourseEditor=i;a.getCurrentCourseEditor=function getCurrentCourseEditor(){return i(M.cfg.courseId)}}); //# sourceMappingURL=courseeditor.min.js.map diff --git a/course/format/amd/build/courseeditor.min.js.map b/course/format/amd/build/courseeditor.min.js.map index b45f9a03e8d..ec21309a373 100644 --- a/course/format/amd/build/courseeditor.min.js.map +++ b/course/format/amd/build/courseeditor.min.js.map @@ -1 +1 @@ -{"version":3,"sources":["../src/courseeditor.js"],"names":["courseEditorMap","Map","dispatchStateChangedEvent","detail","target","document","dispatchEvent","CustomEvent","events","stateChanged","bubbles","setViewFormat","courseId","setup","editor","getCourseEditor","parseInt","has","set","CourseEditor","name","eventName","eventDispatch","mutations","DefaultMutations","get","loadCourse","getCurrentCourseEditor","M","cfg"],"mappings":"iUAuBA,OACA,OACA,O,mDAGA,GAAMA,CAAAA,CAAe,CAAG,GAAIC,CAAAA,GAA5B,CAYA,QAASC,CAAAA,CAAT,CAAmCC,CAAnC,CAA2CC,CAA3C,CAAmD,CAC/C,GAAIA,CAAM,SAAV,CAA0B,CACtBA,CAAM,CAAGC,QACZ,CACDD,CAAM,CAACE,aAAP,CAAqB,GAAIC,CAAAA,WAAJ,CAAgBC,UAAOC,YAAvB,CAAqC,CACtDC,OAAO,GAD+C,CAEtDP,MAAM,CAAEA,CAF8C,CAArC,CAArB,CAIH,C,gBAU4B,QAAhBQ,CAAAA,aAAgB,CAACC,CAAD,CAAWC,CAAX,CAAqB,CAC9C,GAAMC,CAAAA,CAAM,CAAGC,CAAe,CAACH,CAAD,CAA9B,CACAE,CAAM,CAACH,aAAP,CAAqBE,CAArB,CACH,C,CAQM,GAAME,CAAAA,CAAe,CAAG,SAACH,CAAD,CAAc,CACzCA,CAAQ,CAAGI,QAAQ,CAACJ,CAAD,CAAnB,CAEA,GAAI,CAACZ,CAAe,CAACiB,GAAhB,CAAoBL,CAApB,CAAL,CAAoC,CAChCZ,CAAe,CAACkB,GAAhB,CACIN,CADJ,CAEI,GAAIO,UAAJ,CAAiB,CACbC,IAAI,uBAAiBR,CAAjB,CADS,CAEbS,SAAS,CAAEb,UAAOC,YAFL,CAGba,aAAa,CAAEpB,CAHF,CAMbqB,SAAS,CAAE,GAAIC,UANF,CAAjB,CAFJ,EAWAxB,CAAe,CAACyB,GAAhB,CAAoBb,CAApB,EAA8Bc,UAA9B,CAAyCd,CAAzC,CACH,CACD,MAAOZ,CAAAA,CAAe,CAACyB,GAAhB,CAAoBb,CAApB,CACV,CAlBM,C,6CAyB+B,QAAzBe,CAAAA,sBAAyB,SAAMZ,CAAAA,CAAe,CAACa,CAAC,CAACC,GAAF,CAAMjB,QAAP,CAArB,C","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Generic reactive module used in the course editor.\n *\n * @module core_courseformat/courseeditor\n * @copyright 2021 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport DefaultMutations from 'core_courseformat/local/courseeditor/mutations';\nimport CourseEditor from 'core_courseformat/local/courseeditor/courseeditor';\nimport events from 'core_course/events';\n\n// A map with all the course editor instances.\nconst courseEditorMap = new Map();\n\n/**\n * Trigger a state changed event.\n *\n * This function will be moved to core_course/events module\n * when the file is migrated to the new JS events structure proposed in MDL-70990.\n *\n * @method dispatchStateChangedEvent\n * @param {object} detail the full state\n * @param {object} target the custom event target (document if none provided)\n */\nfunction dispatchStateChangedEvent(detail, target) {\n if (target === undefined) {\n target = document;\n }\n target.dispatchEvent(new CustomEvent(events.stateChanged, {\n bubbles: true,\n detail: detail,\n }));\n}\n\n/**\n * Setup the current view settings\n *\n * @param {number} courseId the course id\n * @param {setup} setup format, page and course settings\n * @param {boolean} setup.editing if the page is in edit mode\n * @param {boolean} setup.supportscomponents if the format supports components for content\n */\nexport const setViewFormat = (courseId, setup) => {\n const editor = getCourseEditor(courseId);\n editor.setViewFormat(setup);\n};\n\n/**\n * Get a specific course editor reactive instance.\n *\n * @param {number} courseId the course id\n * @returns {CourseEditor}\n */\nexport const getCourseEditor = (courseId) => {\n courseId = parseInt(courseId);\n\n if (!courseEditorMap.has(courseId)) {\n courseEditorMap.set(\n courseId,\n new CourseEditor({\n name: `CourseEditor${courseId}`,\n eventName: events.stateChanged,\n eventDispatch: dispatchStateChangedEvent,\n // Mutations can be overridden by the format plugin using setMutations\n // but we need the default one at least.\n mutations: new DefaultMutations(),\n })\n );\n courseEditorMap.get(courseId).loadCourse(courseId);\n }\n return courseEditorMap.get(courseId);\n};\n\n/**\n * Get the current course reactive instance.\n *\n * @returns {CourseEditor}\n */\nexport const getCurrentCourseEditor = () => getCourseEditor(M.cfg.courseId);\n"],"file":"courseeditor.min.js"} \ No newline at end of file +{"version":3,"sources":["../src/courseeditor.js"],"names":["courseEditorMap","Map","courseStateKeyMap","dispatchStateChangedEvent","detail","target","document","dispatchEvent","CustomEvent","events","stateChanged","bubbles","setViewFormat","courseId","setup","parseInt","editing","set","statekey","editor","getCourseEditor","has","CourseEditor","name","eventName","eventDispatch","mutations","DefaultMutations","get","loadCourse","getCurrentCourseEditor","M","cfg"],"mappings":"iUAuBA,OACA,OACA,O,sDAGMA,CAAAA,CAAe,CAAG,GAAIC,CAAAA,G,CAGtBC,CAAiB,CAAG,GAAID,CAAAA,G,CAY9B,QAASE,CAAAA,CAAT,CAAmCC,CAAnC,CAA2CC,CAA3C,CAAmD,CAC/C,GAAIA,CAAM,SAAV,CAA0B,CACtBA,CAAM,CAAGC,QACZ,CACDD,CAAM,CAACE,aAAP,CAAqB,GAAIC,CAAAA,WAAJ,CAAgBC,UAAOC,YAAvB,CAAqC,CACtDC,OAAO,GAD+C,CAEtDP,MAAM,CAAEA,CAF8C,CAArC,CAArB,CAIH,C,gBAe4B,QAAhBQ,CAAAA,aAAgB,CAACC,CAAD,CAAWC,CAAX,CAAqB,CAC9CD,CAAQ,CAAGE,QAAQ,CAACF,CAAD,CAAnB,CAEA,GAAI,CAACC,CAAK,CAACE,OAAX,CAAoB,CAChBd,CAAiB,CAACe,GAAlB,CAAsBJ,CAAtB,CAAgCC,CAAK,CAACI,QAAtC,CACH,CACD,GAAMC,CAAAA,CAAM,CAAGC,CAAe,CAACP,CAAD,CAA9B,CACAM,CAAM,CAACP,aAAP,CAAqBE,CAArB,CACH,C,CAQM,GAAMM,CAAAA,CAAe,CAAG,SAACP,CAAD,CAAc,CACzCA,CAAQ,CAAGE,QAAQ,CAACF,CAAD,CAAnB,CAEA,GAAI,CAACb,CAAe,CAACqB,GAAhB,CAAoBR,CAApB,CAAL,CAAoC,CAChCb,CAAe,CAACiB,GAAhB,CACIJ,CADJ,CAEI,GAAIS,UAAJ,CAAiB,CACbC,IAAI,uBAAiBV,CAAjB,CADS,CAEbW,SAAS,CAAEf,UAAOC,YAFL,CAGbe,aAAa,CAAEtB,CAHF,CAMbuB,SAAS,CAAE,GAAIC,UANF,CAAjB,CAFJ,EAWA3B,CAAe,CAAC4B,GAAhB,CAAoBf,CAApB,EAA8BgB,UAA9B,CAAyChB,CAAzC,CAAmDX,CAAiB,CAAC0B,GAAlB,CAAsBf,CAAtB,CAAnD,CACH,CACD,MAAOb,CAAAA,CAAe,CAAC4B,GAAhB,CAAoBf,CAApB,CACV,CAlBM,C,6CAyB+B,QAAzBiB,CAAAA,sBAAyB,SAAMV,CAAAA,CAAe,CAACW,CAAC,CAACC,GAAF,CAAMnB,QAAP,CAArB,C","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Generic reactive module used in the course editor.\n *\n * @module core_courseformat/courseeditor\n * @copyright 2021 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport DefaultMutations from 'core_courseformat/local/courseeditor/mutations';\nimport CourseEditor from 'core_courseformat/local/courseeditor/courseeditor';\nimport events from 'core_course/events';\n\n// A map with all the course editor instances.\nconst courseEditorMap = new Map();\n\n// Map with all the state keys the backend send us to know if the frontend cache is valid or not.\nconst courseStateKeyMap = new Map();\n\n/**\n * Trigger a state changed event.\n *\n * This function will be moved to core_course/events module\n * when the file is migrated to the new JS events structure proposed in MDL-70990.\n *\n * @method dispatchStateChangedEvent\n * @param {object} detail the full state\n * @param {object} target the custom event target (document if none provided)\n */\nfunction dispatchStateChangedEvent(detail, target) {\n if (target === undefined) {\n target = document;\n }\n target.dispatchEvent(new CustomEvent(events.stateChanged, {\n bubbles: true,\n detail: detail,\n }));\n}\n\n/**\n * Setup the current view settings\n *\n * The backend cache state revision is a combination of the course->cacherev, the\n * user course preferences and completion state. The backend updates that number\n * everytime some change in the course affects the user course state.\n *\n * @param {number} courseId the course id\n * @param {setup} setup format, page and course settings\n * @param {boolean} setup.editing if the page is in edit mode\n * @param {boolean} setup.supportscomponents if the format supports components for content\n * @param {boolean} setup.statekey the backend cached state revision\n */\nexport const setViewFormat = (courseId, setup) => {\n courseId = parseInt(courseId);\n // Caches are ignored in edit mode.\n if (!setup.editing) {\n courseStateKeyMap.set(courseId, setup.statekey);\n }\n const editor = getCourseEditor(courseId);\n editor.setViewFormat(setup);\n};\n\n/**\n * Get a specific course editor reactive instance.\n *\n * @param {number} courseId the course id\n * @returns {CourseEditor}\n */\nexport const getCourseEditor = (courseId) => {\n courseId = parseInt(courseId);\n\n if (!courseEditorMap.has(courseId)) {\n courseEditorMap.set(\n courseId,\n new CourseEditor({\n name: `CourseEditor${courseId}`,\n eventName: events.stateChanged,\n eventDispatch: dispatchStateChangedEvent,\n // Mutations can be overridden by the format plugin using setMutations\n // but we need the default one at least.\n mutations: new DefaultMutations(),\n })\n );\n courseEditorMap.get(courseId).loadCourse(courseId, courseStateKeyMap.get(courseId));\n }\n return courseEditorMap.get(courseId);\n};\n\n/**\n * Get the current course reactive instance.\n *\n * @returns {CourseEditor}\n */\nexport const getCurrentCourseEditor = () => getCourseEditor(M.cfg.courseId);\n"],"file":"courseeditor.min.js"} \ No newline at end of file diff --git a/course/format/amd/build/local/courseeditor/courseeditor.min.js b/course/format/amd/build/local/courseeditor/courseeditor.min.js index fb5e774e8f8..b9134526475 100644 --- a/course/format/amd/build/local/courseeditor/courseeditor.min.js +++ b/course/format/amd/build/local/courseeditor/courseeditor.min.js @@ -1,2 +1,2 @@ -define ("core_courseformat/local/courseeditor/courseeditor",["exports","core/reactive","core/notification","core_courseformat/local/courseeditor/exporter","core/log","core/ajax","core/sessionstorage"],function(a,b,c,d,e,f,g){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;c=j(c);d=j(d);e=j(e);f=j(f);g=i(g);function h(){if("function"!=typeof WeakMap)return null;var a=new WeakMap;h=function(){return a};return a}function i(a){if(a&&a.__esModule){return a}if(null===a||"object"!==k(a)&&"function"!=typeof a){return{default:a}}var b=h();if(b&&b.has(a)){return b.get(a)}var c={},d=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var e in a){if(Object.prototype.hasOwnProperty.call(a,e)){var f=d?Object.getOwnPropertyDescriptor(a,e):null;if(f&&(f.get||f.set)){Object.defineProperty(c,e,f)}else{c[e]=a[e]}}}c.default=a;if(b){b.set(a,c)}return c}function j(a){return a&&a.__esModule?a:{default:a}}function k(a){"@babel/helpers - typeof";if("function"==typeof Symbol&&"symbol"==typeof Symbol.iterator){k=function(a){return typeof a}}else{k=function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?"symbol":typeof a}}return k(a)}function l(a,b){var c=Object.keys(a);if(Object.getOwnPropertySymbols){var d=Object.getOwnPropertySymbols(a);if(b)d=d.filter(function(b){return Object.getOwnPropertyDescriptor(a,b).enumerable});c.push.apply(c,d)}return c}function m(a){for(var b=1,c;b.\n\nimport {Reactive} from 'core/reactive';\nimport notification from 'core/notification';\nimport Exporter from 'core_courseformat/local/courseeditor/exporter';\nimport log from 'core/log';\nimport ajax from 'core/ajax';\nimport * as Storage from 'core/sessionstorage';\n\n/**\n * Main course editor module.\n *\n * All formats can register new components on this object to create new reactive\n * UI components that watch the current course state.\n *\n * @module core_courseformat/local/courseeditor/courseeditor\n * @class core_courseformat/local/courseeditor/courseeditor\n * @copyright 2021 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nexport default class extends Reactive {\n\n /**\n * The current state cache key\n *\n * The state cache is considered dirty if the state changes from the last page or\n * if the page has editing mode on.\n *\n * @attribute stateKey\n * @type number|null\n * @default 1\n * @package\n */\n stateKey = 1;\n\n /**\n * The current page section return\n * @attribute sectionReturn\n * @type number\n * @default 0\n */\n sectionReturn = 0;\n\n /**\n * Set up the course editor when the page is ready.\n *\n * The course can only be loaded once per instance. Otherwise an error is thrown.\n *\n * @param {number} courseId course id\n */\n async loadCourse(courseId) {\n\n if (this.courseId) {\n throw new Error(`Cannot load ${courseId}, course already loaded with id ${this.courseId}`);\n }\n\n // Default view format setup.\n this._editing = false;\n this._supportscomponents = false;\n\n this.courseId = courseId;\n\n let stateData;\n\n try {\n stateData = await this.getServerCourseState();\n } catch (error) {\n log.error(\"EXCEPTION RAISED WHILE INIT COURSE EDITOR\");\n log.error(error);\n return;\n }\n\n this.setInitialState(stateData);\n\n // In editing mode, the session cache is considered dirty always.\n if (this.isEditing) {\n this.stateKey = null;\n } else {\n // Check if the last state is the same as the cached one.\n const newState = JSON.stringify(stateData);\n const previousState = Storage.get(`course/${courseId}/staticState`);\n if (previousState !== newState) {\n Storage.set(`course/${courseId}/staticState`, newState);\n Storage.set(`course/${courseId}/stateKey`, Date.now());\n }\n this.stateKey = Storage.get(`course/${courseId}/stateKey`);\n }\n }\n\n /**\n * Setup the current view settings\n *\n * @param {Object} setup format, page and course settings\n * @param {boolean} setup.editing if the page is in edit mode\n * @param {boolean} setup.supportscomponents if the format supports components for content\n */\n setViewFormat(setup) {\n this._editing = setup.editing ?? false;\n this._supportscomponents = setup.supportscomponents ?? false;\n }\n\n /**\n * Load the current course state from the server.\n *\n * @returns {Object} the current course state\n */\n async getServerCourseState() {\n const courseState = await ajax.call([{\n methodname: 'core_courseformat_get_state',\n args: {\n courseid: this.courseId,\n }\n }])[0];\n\n const stateData = JSON.parse(courseState);\n\n return {\n course: {},\n section: [],\n cm: [],\n ...stateData,\n };\n }\n\n /**\n * Return the current edit mode.\n *\n * Components should use this method to check if edit mode is active.\n *\n * @return {boolean} if edit is enabled\n */\n get isEditing() {\n return this._editing ?? false;\n }\n\n /**\n * Return a data exporter to transform state part into mustache contexts.\n *\n * @return {Exporter} the exporter class\n */\n getExporter() {\n return new Exporter(this);\n }\n\n /**\n * Return if the current course support components to refresh the content.\n *\n * @returns {boolean} if the current content support components\n */\n get supportComponents() {\n return this._supportscomponents ?? false;\n }\n\n /**\n * Get a value from the course editor static storage if any.\n *\n * The course editor static storage uses the sessionStorage to store values from the\n * components. This is used to prevent unnecesary template loadings on every page. However,\n * the storage does not work if no sessionStorage can be used (in debug mode for example),\n * if the page is in editing mode or if the initial state change from the last page.\n *\n * @param {string} key the key to get\n * @return {boolean|string} the storage value or false if cannot be loaded\n */\n getStorageValue(key) {\n if (this.isEditing || !this.stateKey) {\n return false;\n }\n const dataJson = Storage.get(`course/${this.courseId}/${key}`);\n if (!dataJson) {\n return false;\n }\n // Check the stateKey.\n try {\n const data = JSON.parse(dataJson);\n if (data?.stateKey !== this.stateKey) {\n return false;\n }\n return data.value;\n } catch (error) {\n return false;\n }\n }\n\n /**\n * Stores a value into the course editor static storage if available\n *\n * @param {String} key the key to store\n * @param {*} value the value to store (must be compatible with JSON,stringify)\n * @returns {boolean} true if the value is stored\n */\n setStorageValue(key, value) {\n // Values cannot be stored on edit mode.\n if (this.isEditing) {\n return false;\n }\n const data = {\n stateKey: this.stateKey,\n value,\n };\n return Storage.set(`course/${this.courseId}/${key}`, JSON.stringify(data));\n }\n\n /**\n * Dispatch a change in the state.\n *\n * Usually reactive modules throw an error directly to the components when something\n * goes wrong. However, course editor can directly display a notification.\n *\n * @method dispatch\n * @param {mixed} args any number of params the mutation needs.\n */\n async dispatch(...args) {\n try {\n await super.dispatch(...args);\n } catch (error) {\n // Display error modal.\n notification.exception(error);\n // Force unlock all elements.\n super.dispatch('unlockAll');\n }\n }\n}\n"],"file":"courseeditor.min.js"} \ No newline at end of file +{"version":3,"sources":["../../../src/local/courseeditor/courseeditor.js"],"names":["courseId","serverStateKey","Error","Date","now","_editing","_supportscomponents","storeStateKey","Storage","get","isEditing","stateData","JSON","parse","getServerCourseState","log","error","setInitialState","stateKey","newState","stringify","previousState","set","course","statekey","setup","editing","supportscomponents","ajax","call","methodname","args","courseid","courseState","section","cm","Exporter","key","dataJson","data","value","notification","exception","Reactive"],"mappings":"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","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\nimport {Reactive} from 'core/reactive';\nimport notification from 'core/notification';\nimport Exporter from 'core_courseformat/local/courseeditor/exporter';\nimport log from 'core/log';\nimport ajax from 'core/ajax';\nimport * as Storage from 'core/sessionstorage';\n\n/**\n * Main course editor module.\n *\n * All formats can register new components on this object to create new reactive\n * UI components that watch the current course state.\n *\n * @module core_courseformat/local/courseeditor/courseeditor\n * @class core_courseformat/local/courseeditor/courseeditor\n * @copyright 2021 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nexport default class extends Reactive {\n\n /**\n * The current state cache key\n *\n * The state cache is considered dirty if the state changes from the last page or\n * if the page has editing mode on.\n *\n * @attribute stateKey\n * @type number|null\n * @default 1\n * @package\n */\n stateKey = 1;\n\n /**\n * The current page section return\n * @attribute sectionReturn\n * @type number\n * @default 0\n */\n sectionReturn = 0;\n\n /**\n * Set up the course editor when the page is ready.\n *\n * The course can only be loaded once per instance. Otherwise an error is thrown.\n *\n * The backend can inform the module of the current state key. This key changes every time some\n * update in the course affect the current user state. Some examples are:\n * - The course content has been edited\n * - The user marks some activity as completed\n * - The user collapses or uncollapses a section (it is stored as a user preference)\n *\n * @param {number} courseId course id\n * @param {string} serverStateKey the current backend course cache reference\n */\n async loadCourse(courseId, serverStateKey) {\n\n if (this.courseId) {\n throw new Error(`Cannot load ${courseId}, course already loaded with id ${this.courseId}`);\n }\n\n if (!serverStateKey) {\n // The server state key is not provided, we use a invalid statekey to force reloading.\n serverStateKey = `invalidStateKey_${Date.now()}`;\n }\n\n // Default view format setup.\n this._editing = false;\n this._supportscomponents = false;\n\n this.courseId = courseId;\n\n let stateData;\n\n const storeStateKey = Storage.get(`course/${courseId}/stateKey`);\n try {\n // Check if the backend state key is the same we have in our session storage.\n if (!this.isEditing && serverStateKey == storeStateKey) {\n stateData = JSON.parse(Storage.get(`course/${courseId}/staticState`));\n }\n if (!stateData) {\n stateData = await this.getServerCourseState();\n }\n\n } catch (error) {\n log.error(\"EXCEPTION RAISED WHILE INIT COURSE EDITOR\");\n log.error(error);\n return;\n }\n\n this.setInitialState(stateData);\n\n // In editing mode, the session cache is considered dirty always.\n if (this.isEditing) {\n this.stateKey = null;\n } else {\n // Check if the last state is the same as the cached one.\n const newState = JSON.stringify(stateData);\n const previousState = Storage.get(`course/${courseId}/staticState`);\n if (previousState !== newState || storeStateKey !== serverStateKey) {\n Storage.set(`course/${courseId}/staticState`, newState);\n Storage.set(`course/${courseId}/stateKey`, stateData?.course?.statekey ?? serverStateKey);\n }\n this.stateKey = Storage.get(`course/${courseId}/stateKey`);\n }\n }\n\n /**\n * Setup the current view settings\n *\n * @param {Object} setup format, page and course settings\n * @param {boolean} setup.editing if the page is in edit mode\n * @param {boolean} setup.supportscomponents if the format supports components for content\n * @param {string} setup.cacherev the backend cached state revision\n */\n setViewFormat(setup) {\n this._editing = setup.editing ?? false;\n this._supportscomponents = setup.supportscomponents ?? false;\n }\n\n /**\n * Load the current course state from the server.\n *\n * @returns {Object} the current course state\n */\n async getServerCourseState() {\n const courseState = await ajax.call([{\n methodname: 'core_courseformat_get_state',\n args: {\n courseid: this.courseId,\n }\n }])[0];\n\n const stateData = JSON.parse(courseState);\n\n return {\n course: {},\n section: [],\n cm: [],\n ...stateData,\n };\n }\n\n /**\n * Return the current edit mode.\n *\n * Components should use this method to check if edit mode is active.\n *\n * @return {boolean} if edit is enabled\n */\n get isEditing() {\n return this._editing ?? false;\n }\n\n /**\n * Return a data exporter to transform state part into mustache contexts.\n *\n * @return {Exporter} the exporter class\n */\n getExporter() {\n return new Exporter(this);\n }\n\n /**\n * Return if the current course support components to refresh the content.\n *\n * @returns {boolean} if the current content support components\n */\n get supportComponents() {\n return this._supportscomponents ?? false;\n }\n\n /**\n * Get a value from the course editor static storage if any.\n *\n * The course editor static storage uses the sessionStorage to store values from the\n * components. This is used to prevent unnecesary template loadings on every page. However,\n * the storage does not work if no sessionStorage can be used (in debug mode for example),\n * if the page is in editing mode or if the initial state change from the last page.\n *\n * @param {string} key the key to get\n * @return {boolean|string} the storage value or false if cannot be loaded\n */\n getStorageValue(key) {\n if (this.isEditing || !this.stateKey) {\n return false;\n }\n const dataJson = Storage.get(`course/${this.courseId}/${key}`);\n if (!dataJson) {\n return false;\n }\n // Check the stateKey.\n try {\n const data = JSON.parse(dataJson);\n if (data?.stateKey !== this.stateKey) {\n return false;\n }\n return data.value;\n } catch (error) {\n return false;\n }\n }\n\n /**\n * Stores a value into the course editor static storage if available\n *\n * @param {String} key the key to store\n * @param {*} value the value to store (must be compatible with JSON,stringify)\n * @returns {boolean} true if the value is stored\n */\n setStorageValue(key, value) {\n // Values cannot be stored on edit mode.\n if (this.isEditing) {\n return false;\n }\n const data = {\n stateKey: this.stateKey,\n value,\n };\n return Storage.set(`course/${this.courseId}/${key}`, JSON.stringify(data));\n }\n\n /**\n * Dispatch a change in the state.\n *\n * Usually reactive modules throw an error directly to the components when something\n * goes wrong. However, course editor can directly display a notification.\n *\n * @method dispatch\n * @param {mixed} args any number of params the mutation needs.\n */\n async dispatch(...args) {\n try {\n await super.dispatch(...args);\n } catch (error) {\n // Display error modal.\n notification.exception(error);\n // Force unlock all elements.\n super.dispatch('unlockAll');\n }\n }\n}\n"],"file":"courseeditor.min.js"} \ No newline at end of file diff --git a/course/format/amd/build/local/courseeditor/mutations.min.js b/course/format/amd/build/local/courseeditor/mutations.min.js index ddb2fb47a1c..bdbe2ce7427 100644 --- a/course/format/amd/build/local/courseeditor/mutations.min.js +++ b/course/format/amd/build/local/courseeditor/mutations.min.js @@ -1,2 +1,2 @@ -define ("core_courseformat/local/courseeditor/mutations",["exports","core/ajax"],function(a,b){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;b=function(a){return a&&a.__esModule?a:{default:a}}(b);function c(a,b,c,d,e,f,g){try{var h=a[f](g),i=h.value}catch(a){c(a);return}if(h.done){b(i)}else{Promise.resolve(i).then(d,e)}}function d(a){return function(){var b=this,d=arguments;return new Promise(function(e,f){var i=a.apply(b,d);function g(a){c(i,e,f,g,h,"next",a)}function h(a){c(i,e,f,g,h,"throw",a)}g(void 0)})}}function e(a,b){if(!(a instanceof b)){throw new TypeError("Cannot call a class as a function")}}function f(a,b){for(var c=0,d;ca.length)b=a.length;for(var c=0,d=Array(b);c.\n\nimport ajax from 'core/ajax';\n\n/**\n * Default mutation manager\n *\n * @module core_courseformat/local/courseeditor/mutations\n * @class core_courseformat/local/courseeditor/mutations\n * @copyright 2021 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nexport default class {\n\n // All course editor mutations for Moodle 4.0 will be located in this file.\n\n /**\n * Private method to call core_courseformat_update_course webservice.\n *\n * @method _callEditWebservice\n * @param {string} action\n * @param {number} courseId\n * @param {array} ids\n * @param {number} targetSectionId optional target section id (for moving actions)\n * @param {number} targetCmId optional target cm id (for moving actions)\n */\n async _callEditWebservice(action, courseId, ids, targetSectionId, targetCmId) {\n const args = {\n action,\n courseid: courseId,\n ids,\n };\n if (targetSectionId) {\n args.targetsectionid = targetSectionId;\n }\n if (targetCmId) {\n args.targetcmid = targetCmId;\n }\n let ajaxresult = await ajax.call([{\n methodname: 'core_courseformat_update_course',\n args,\n }])[0];\n return JSON.parse(ajaxresult);\n }\n\n\n /**\n * Mutation module initialize.\n *\n * The reactive instance will execute this method when addMutations or setMutation is invoked.\n *\n * @param {StateManager} stateManager the state manager\n */\n init(stateManager) {\n // Add a method to prepare the fields when some update is comming from the server.\n stateManager.addUpdateTypes({\n prepareFields: this._prepareFields,\n });\n }\n\n /**\n * Add default values to state elements.\n *\n * This method is called every time a webservice returns a update state message.\n *\n * @param {Object} stateManager the state manager\n * @param {String} updateName the state element to update\n * @param {Object} fields the new data\n * @returns {Object} final fields data\n */\n _prepareFields(stateManager, updateName, fields) {\n // Any update should unlock the element.\n fields.locked = false;\n return fields;\n }\n\n /**\n * Move course modules to specific course location.\n *\n * Note that one of targetSectionId or targetCmId should be provided in order to identify the\n * new location:\n * - targetCmId: the activities will be located avobe the target cm. The targetSectionId\n * value will be ignored in this case.\n * - targetSectionId: the activities will be appended to the section. In this case\n * targetSectionId should not be present.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} cmids the list of cm ids to move\n * @param {number} targetSectionId the target section id\n * @param {number} targetCmId the target course module id\n */\n async cmMove(stateManager, cmids, targetSectionId, targetCmId) {\n if (!targetSectionId && !targetCmId) {\n throw new Error(`Mutation cmMove requires targetSectionId or targetCmId`);\n }\n const course = stateManager.get('course');\n this.cmLock(stateManager, cmids, true);\n const updates = await this._callEditWebservice('cm_move', course.id, cmids, targetSectionId, targetCmId);\n stateManager.processUpdates(updates);\n this.cmLock(stateManager, cmids, false);\n }\n\n /**\n * Move course modules to specific course location.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} sectionIds the list of section ids to move\n * @param {number} targetSectionId the target section id\n */\n async sectionMove(stateManager, sectionIds, targetSectionId) {\n if (!targetSectionId) {\n throw new Error(`Mutation sectionMove requires targetSectionId`);\n }\n const course = stateManager.get('course');\n this.sectionLock(stateManager, sectionIds, true);\n const updates = await this._callEditWebservice('section_move', course.id, sectionIds, targetSectionId);\n stateManager.processUpdates(updates);\n this.sectionLock(stateManager, sectionIds, false);\n }\n\n /**\n * Add a new section to a specific course location.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {number} targetSectionId optional the target section id\n */\n async addSection(stateManager, targetSectionId) {\n if (!targetSectionId) {\n targetSectionId = 0;\n }\n const course = stateManager.get('course');\n const updates = await this._callEditWebservice('section_add', course.id, [], targetSectionId);\n stateManager.processUpdates(updates);\n }\n\n /**\n * Delete sections.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} sectionIds the list of course modules ids\n */\n async sectionDelete(stateManager, sectionIds) {\n const course = stateManager.get('course');\n const updates = await this._callEditWebservice('section_delete', course.id, sectionIds);\n stateManager.processUpdates(updates);\n }\n\n /**\n * Mark or unmark course modules as dragging.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} cmIds the list of course modules ids\n * @param {bool} dragValue the new dragging value\n */\n cmDrag(stateManager, cmIds, dragValue) {\n this.setPageItem(stateManager);\n this._setElementsValue(stateManager, 'cm', cmIds, 'dragging', dragValue);\n }\n\n /**\n * Mark or unmark course sections as dragging.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} sectionIds the list of section ids\n * @param {bool} dragValue the new dragging value\n */\n sectionDrag(stateManager, sectionIds, dragValue) {\n this.setPageItem(stateManager);\n this._setElementsValue(stateManager, 'section', sectionIds, 'dragging', dragValue);\n }\n\n /**\n * Mark or unmark course modules as complete.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} cmIds the list of course modules ids\n * @param {bool} complete the new completion value\n */\n cmCompletion(stateManager, cmIds, complete) {\n const newValue = (complete) ? 1 : 0;\n this._setElementsValue(stateManager, 'cm', cmIds, 'completionstate', newValue);\n }\n\n /**\n * Lock or unlock course modules.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} cmIds the list of course modules ids\n * @param {bool} lockValue the new locked value\n */\n cmLock(stateManager, cmIds, lockValue) {\n this._setElementsValue(stateManager, 'cm', cmIds, 'locked', lockValue);\n }\n\n /**\n * Lock or unlock course sections.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} sectionIds the list of section ids\n * @param {bool} lockValue the new locked value\n */\n sectionLock(stateManager, sectionIds, lockValue) {\n this._setElementsValue(stateManager, 'section', sectionIds, 'locked', lockValue);\n }\n\n _setElementsValue(stateManager, name, ids, fieldName, newValue) {\n stateManager.setReadOnly(false);\n ids.forEach((id) => {\n const element = stateManager.get(name, id);\n if (element) {\n element[fieldName] = newValue;\n }\n });\n stateManager.setReadOnly(true);\n }\n\n /**\n * Set the page current item.\n *\n * Only one element of the course state can be the page item at a time.\n *\n * There are several actions that can alter the page current item. For example, when the user is in an activity\n * page, the page item is always the activity one. However, in a course page, when the user scrolls to an element,\n * this element get the page item.\n *\n * If the page item is static means that it is not meant to change. This is important because\n * static page items has some special logic. For example, if a cm is the static page item\n * and it is inside a collapsed section, the course index will expand the section to make it visible.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {String|undefined} type the element type (section or cm). Undefined will remove the current page item.\n * @param {Number|undefined} id the element id\n * @param {boolean|undefined} isStatic if the page item is static\n */\n setPageItem(stateManager, type, id, isStatic) {\n let newPageItem;\n if (type !== undefined) {\n newPageItem = stateManager.get(type, id);\n if (!newPageItem) {\n return;\n }\n }\n stateManager.setReadOnly(false);\n // Remove the current page item.\n const course = stateManager.get('course');\n course.pageItem = null;\n // Save the new page item.\n if (newPageItem) {\n course.pageItem = {\n id,\n type,\n sectionId: (type == 'section') ? newPageItem.id : newPageItem.sectionid,\n isStatic,\n };\n }\n stateManager.setReadOnly(true);\n }\n\n /**\n * Unlock all course elements.\n *\n * @param {StateManager} stateManager the current state manager\n */\n unlockAll(stateManager) {\n const state = stateManager.state;\n stateManager.setReadOnly(false);\n state.section.forEach((section) => {\n section.locked = false;\n });\n state.cm.forEach((cm) => {\n cm.locked = false;\n });\n stateManager.setReadOnly(true);\n }\n\n /*\n * Get updated user preferences and state data related to some section ids.\n *\n * @param {StateManager} stateManager the current state\n * @param {array} sectionIds the list of section ids to update\n * @param {Object} preferences the new preferences values\n */\n async sectionPreferences(stateManager, sectionIds, preferences) {\n stateManager.setReadOnly(false);\n // Check if we need to update preferences.\n let updatePreferences = false;\n sectionIds.forEach(sectionId => {\n const section = stateManager.get('section', sectionId);\n if (section === undefined) {\n return;\n }\n let newValue = preferences.contentcollapsed ?? section.contentcollapsed;\n if (section.contentcollapsed != newValue) {\n section.contentcollapsed = newValue;\n updatePreferences = true;\n }\n newValue = preferences.indexcollapsed ?? section.indexcollapsed;\n if (section.indexcollapsed != newValue) {\n section.indexcollapsed = newValue;\n updatePreferences = true;\n }\n });\n stateManager.setReadOnly(true);\n\n if (updatePreferences) {\n // Build the preference structures.\n const course = stateManager.get('course');\n const state = stateManager.state;\n const prefKey = `coursesectionspreferences_${course.id}`;\n const preferences = {\n contentcollapsed: [],\n indexcollapsed: [],\n };\n state.section.forEach(section => {\n if (section.contentcollapsed) {\n preferences.contentcollapsed.push(section.id);\n }\n if (section.indexcollapsed) {\n preferences.indexcollapsed.push(section.id);\n }\n });\n const jsonString = JSON.stringify(preferences);\n M.util.set_user_preference(prefKey, jsonString);\n }\n }\n\n /**\n * Get updated state data related to some cm ids.\n *\n * @method cmState\n * @param {StateManager} stateManager the current state\n * @param {array} cmids the list of cm ids to update\n */\n async cmState(stateManager, cmids) {\n this.cmLock(stateManager, cmids, true);\n const course = stateManager.get('course');\n const updates = await this._callEditWebservice('cm_state', course.id, cmids);\n stateManager.processUpdates(updates);\n this.cmLock(stateManager, cmids, false);\n }\n\n /**\n * Get updated state data related to some section ids.\n *\n * @method sectionState\n * @param {StateManager} stateManager the current state\n * @param {array} sectionIds the list of section ids to update\n */\n async sectionState(stateManager, sectionIds) {\n this.sectionLock(stateManager, sectionIds, true);\n const course = stateManager.get('course');\n const updates = await this._callEditWebservice('section_state', course.id, sectionIds);\n stateManager.processUpdates(updates);\n this.sectionLock(stateManager, sectionIds, false);\n }\n\n /**\n * Get the full updated state data of the course.\n *\n * @param {StateManager} stateManager the current state\n */\n async courseState(stateManager) {\n const course = stateManager.get('course');\n const updates = await this._callEditWebservice('course_state', course.id);\n stateManager.processUpdates(updates);\n }\n\n}\n"],"file":"mutations.min.js"} \ No newline at end of file +{"version":3,"sources":["../../../src/local/courseeditor/mutations.js"],"names":["action","courseId","ids","targetSectionId","targetCmId","args","courseid","targetsectionid","targetcmid","ajax","call","methodname","ajaxresult","JSON","parse","stateManager","addUpdateTypes","prepareFields","_prepareFields","updateName","fields","locked","cmids","Error","course","get","cmLock","_callEditWebservice","id","updates","processUpdates","sectionIds","sectionLock","cmIds","dragValue","setPageItem","_setElementsValue","complete","newValue","lockValue","name","fieldName","setReadOnly","forEach","element","type","isStatic","newPageItem","pageItem","sectionId","sectionid","state","section","cm","preferences","affectedSections","Set","contentcollapsed","add","indexcollapsed","size","prefKey","push","jsonString","stringify","M","util","set_user_preference"],"mappings":"8KAeA,uD,q/CAwB8BA,C,CAAQC,C,CAAUC,C,CAAKC,C,CAAiBC,C,2FACxDC,C,CAAO,CACTL,MAAM,CAANA,CADS,CAETM,QAAQ,CAAEL,CAFD,CAGTC,GAAG,CAAHA,CAHS,C,CAKb,GAAIC,CAAJ,CAAqB,CACjBE,CAAI,CAACE,eAAL,CAAuBJ,CAC1B,CACD,GAAIC,CAAJ,CAAgB,CACZC,CAAI,CAACG,UAAL,CAAkBJ,CACrB,C,eACsBK,WAAKC,IAAL,CAAU,CAAC,CAC9BC,UAAU,CAAE,iCADkB,CAE9BN,IAAI,CAAJA,CAF8B,CAAD,CAAV,EAGnB,CAHmB,C,QAAnBO,C,iCAIGC,IAAI,CAACC,KAAL,CAAWF,CAAX,C,uJAWNG,C,CAAc,CAEfA,CAAY,CAACC,cAAb,CAA4B,CACxBC,aAAa,CAAE,KAAKC,cADI,CAA5B,CAGH,C,sDAYcH,C,CAAcI,C,CAAYC,C,CAAQ,CAE7CA,CAAM,CAACC,MAAP,IACA,MAAOD,CAAAA,CACV,C,4EAiBYL,C,CAAcO,C,CAAOnB,C,CAAiBC,C,gGAC3C,CAACD,CAAD,EAAoB,CAACC,C,uBACf,IAAImB,CAAAA,KAAJ,0D,QAEJC,C,CAAST,CAAY,CAACU,GAAb,CAAiB,QAAjB,C,CACf,KAAKC,MAAL,CAAYX,CAAZ,CAA0BO,CAA1B,K,eACsB,MAAKK,mBAAL,CAAyB,SAAzB,CAAoCH,CAAM,CAACI,EAA3C,CAA+CN,CAA/C,CAAsDnB,CAAtD,CAAuEC,CAAvE,C,QAAhByB,C,QACNd,CAAY,CAACe,cAAb,CAA4BD,CAA5B,EACA,KAAKH,MAAL,CAAYX,CAAZ,CAA0BO,CAA1B,K,4LAUcP,C,CAAcgB,C,CAAY5B,C,8FACnCA,C,sBACK,IAAIoB,CAAAA,KAAJ,iD,QAEJC,C,CAAST,CAAY,CAACU,GAAb,CAAiB,QAAjB,C,CACf,KAAKO,WAAL,CAAiBjB,CAAjB,CAA+BgB,CAA/B,K,eACsB,MAAKJ,mBAAL,CAAyB,cAAzB,CAAyCH,CAAM,CAACI,EAAhD,CAAoDG,CAApD,CAAgE5B,CAAhE,C,QAAhB0B,C,QACNd,CAAY,CAACe,cAAb,CAA4BD,CAA5B,EACA,KAAKG,WAAL,CAAiBjB,CAAjB,CAA+BgB,CAA/B,K,gMASahB,C,CAAcZ,C,2FAC3B,GAAI,CAACA,CAAL,CAAsB,CAClBA,CAAe,CAAG,CACrB,CACKqB,C,CAAST,CAAY,CAACU,GAAb,CAAiB,QAAjB,C,gBACO,MAAKE,mBAAL,CAAyB,aAAzB,CAAwCH,CAAM,CAACI,EAA/C,CAAmD,EAAnD,CAAuDzB,CAAvD,C,QAAhB0B,C,QACNd,CAAY,CAACe,cAAb,CAA4BD,CAA5B,E,kMASgBd,C,CAAcgB,C,2FACxBP,C,CAAST,CAAY,CAACU,GAAb,CAAiB,QAAjB,C,gBACO,MAAKE,mBAAL,CAAyB,gBAAzB,CAA2CH,CAAM,CAACI,EAAlD,CAAsDG,CAAtD,C,QAAhBF,C,QACNd,CAAY,CAACe,cAAb,CAA4BD,CAA5B,E,wJAUGd,C,CAAckB,C,CAAOC,C,CAAW,CACnC,KAAKC,WAAL,CAAiBpB,CAAjB,EACA,KAAKqB,iBAAL,CAAuBrB,CAAvB,CAAqC,IAArC,CAA2CkB,CAA3C,CAAkD,UAAlD,CAA8DC,CAA9D,CACH,C,gDASWnB,C,CAAcgB,C,CAAYG,C,CAAW,CAC7C,KAAKC,WAAL,CAAiBpB,CAAjB,EACA,KAAKqB,iBAAL,CAAuBrB,CAAvB,CAAqC,SAArC,CAAgDgB,CAAhD,CAA4D,UAA5D,CAAwEG,CAAxE,CACH,C,kDASYnB,C,CAAckB,C,CAAOI,C,CAAU,CACxC,GAAMC,CAAAA,CAAQ,CAAID,CAAD,CAAa,CAAb,CAAiB,CAAlC,CACA,KAAKD,iBAAL,CAAuBrB,CAAvB,CAAqC,IAArC,CAA2CkB,CAA3C,CAAkD,iBAAlD,CAAqEK,CAArE,CACH,C,sCASMvB,C,CAAckB,C,CAAOM,C,CAAW,CACnC,KAAKH,iBAAL,CAAuBrB,CAAvB,CAAqC,IAArC,CAA2CkB,CAA3C,CAAkD,QAAlD,CAA4DM,CAA5D,CACH,C,gDASWxB,C,CAAcgB,C,CAAYQ,C,CAAW,CAC7C,KAAKH,iBAAL,CAAuBrB,CAAvB,CAAqC,SAArC,CAAgDgB,CAAhD,CAA4D,QAA5D,CAAsEQ,CAAtE,CACH,C,4DAEiBxB,C,CAAcyB,C,CAAMtC,C,CAAKuC,C,CAAWH,C,CAAU,CAC5DvB,CAAY,CAAC2B,WAAb,KACAxC,CAAG,CAACyC,OAAJ,CAAY,SAACf,CAAD,CAAQ,CAChB,GAAMgB,CAAAA,CAAO,CAAG7B,CAAY,CAACU,GAAb,CAAiBe,CAAjB,CAAuBZ,CAAvB,CAAhB,CACA,GAAIgB,CAAJ,CAAa,CACTA,CAAO,CAACH,CAAD,CAAP,CAAqBH,CACxB,CACJ,CALD,EAMAvB,CAAY,CAAC2B,WAAb,IACH,C,gDAoBW3B,C,CAAc8B,C,CAAMjB,C,CAAIkB,C,CAAU,CAC1C,GAAIC,CAAAA,CAAJ,CACA,GAAIF,CAAI,SAAR,CAAwB,CACpBE,CAAW,CAAGhC,CAAY,CAACU,GAAb,CAAiBoB,CAAjB,CAAuBjB,CAAvB,CAAd,CACA,GAAI,CAACmB,CAAL,CAAkB,CACd,MACH,CACJ,CACDhC,CAAY,CAAC2B,WAAb,KAEA,GAAMlB,CAAAA,CAAM,CAAGT,CAAY,CAACU,GAAb,CAAiB,QAAjB,CAAf,CACAD,CAAM,CAACwB,QAAP,CAAkB,IAAlB,CAEA,GAAID,CAAJ,CAAiB,CACbvB,CAAM,CAACwB,QAAP,CAAkB,CACdpB,EAAE,CAAFA,CADc,CAEdiB,IAAI,CAAJA,CAFc,CAGdI,SAAS,CAAW,SAAR,EAAAJ,CAAD,CAAsBE,CAAW,CAACnB,EAAlC,CAAuCmB,CAAW,CAACG,SAHhD,CAIdJ,QAAQ,CAARA,CAJc,CAMrB,CACD/B,CAAY,CAAC2B,WAAb,IACH,C,4CAOS3B,C,CAAc,CACpB,GAAMoC,CAAAA,CAAK,CAAGpC,CAAY,CAACoC,KAA3B,CACApC,CAAY,CAAC2B,WAAb,KACAS,CAAK,CAACC,OAAN,CAAcT,OAAd,CAAsB,SAACS,CAAD,CAAa,CAC/BA,CAAO,CAAC/B,MAAR,GACH,CAFD,EAGA8B,CAAK,CAACE,EAAN,CAASV,OAAT,CAAiB,SAACU,CAAD,CAAQ,CACrBA,CAAE,CAAChC,MAAH,GACH,CAFD,EAGAN,CAAY,CAAC2B,WAAb,IACH,C,wFASwB3B,C,CAAcgB,C,CAAYuB,C,mGAC/CvC,CAAY,CAAC2B,WAAb,KACMa,C,CAAmB,GAAIC,CAAAA,G,CAE7BzB,CAAU,CAACY,OAAX,CAAmB,SAAAM,CAAS,CAAI,SACtBG,CAAO,CAAGrC,CAAY,CAACU,GAAb,CAAiB,SAAjB,CAA4BwB,CAA5B,CADY,CAE5B,GAAIG,CAAO,SAAX,CAA2B,CACvB,MACH,CACD,GAAId,CAAAA,CAAQ,WAAGgB,CAAW,CAACG,gBAAf,gBAAmCL,CAAO,CAACK,gBAAvD,CACA,GAAIL,CAAO,CAACK,gBAAR,EAA4BnB,CAAhC,CAA0C,CACtCc,CAAO,CAACK,gBAAR,CAA2BnB,CAA3B,CACAiB,CAAgB,CAACG,GAAjB,CAAqBN,CAAO,CAACxB,EAA7B,CACH,CACDU,CAAQ,WAAGgB,CAAW,CAACK,cAAf,gBAAiCP,CAAO,CAACO,cAAjD,CACA,GAAIP,CAAO,CAACO,cAAR,EAA0BrB,CAA9B,CAAwC,CACpCc,CAAO,CAACO,cAAR,CAAyBrB,CAAzB,CACAiB,CAAgB,CAACG,GAAjB,CAAqBN,CAAO,CAACxB,EAA7B,CACH,CACJ,CAfD,EAgBAb,CAAY,CAAC2B,WAAb,K,KAE4B,CAAxB,CAAAa,CAAgB,CAACK,I,mBAEXpC,C,CAAST,CAAY,CAACU,GAAb,CAAiB,QAAjB,C,CACT0B,C,CAAQpC,CAAY,CAACoC,K,CACrBU,C,qCAAuCrC,CAAM,CAACI,E,EAC9C0B,C,CAAc,CAChBG,gBAAgB,CAAE,EADF,CAEhBE,cAAc,CAAE,EAFA,C,CAIpBR,CAAK,CAACC,OAAN,CAAcT,OAAd,CAAsB,SAAAS,CAAO,CAAI,CAC7B,GAAIA,CAAO,CAACK,gBAAZ,CAA8B,CAC1BH,CAAW,CAACG,gBAAZ,CAA6BK,IAA7B,CAAkCV,CAAO,CAACxB,EAA1C,CACH,CACD,GAAIwB,CAAO,CAACO,cAAZ,CAA4B,CACxBL,CAAW,CAACK,cAAZ,CAA2BG,IAA3B,CAAgCV,CAAO,CAACxB,EAAxC,CACH,CACJ,CAPD,EAQMmC,C,CAAalD,IAAI,CAACmD,SAAL,CAAeV,CAAf,C,CACnBW,CAAC,CAACC,IAAF,CAAOC,mBAAP,CAA2BN,CAA3B,CAAoCE,CAApC,E,gBAEM,MAAKpC,mBAAL,CAAyB,2BAAzB,CAAsDH,CAAM,CAACI,EAA7D,GAAqE2B,CAArE,E,sMAWAxC,C,CAAcO,C,2FACxB,KAAKI,MAAL,CAAYX,CAAZ,CAA0BO,CAA1B,KACME,C,CAAST,CAAY,CAACU,GAAb,CAAiB,QAAjB,C,gBACO,MAAKE,mBAAL,CAAyB,UAAzB,CAAqCH,CAAM,CAACI,EAA5C,CAAgDN,CAAhD,C,QAAhBO,C,QACNd,CAAY,CAACe,cAAb,CAA4BD,CAA5B,EACA,KAAKH,MAAL,CAAYX,CAAZ,CAA0BO,CAA1B,K,8LAUeP,C,CAAcgB,C,2FAC7B,KAAKC,WAAL,CAAiBjB,CAAjB,CAA+BgB,CAA/B,KACMP,C,CAAST,CAAY,CAACU,GAAb,CAAiB,QAAjB,C,gBACO,MAAKE,mBAAL,CAAyB,eAAzB,CAA0CH,CAAM,CAACI,EAAjD,CAAqDG,CAArD,C,QAAhBF,C,QACNd,CAAY,CAACe,cAAb,CAA4BD,CAA5B,EACA,KAAKG,WAAL,CAAiBjB,CAAjB,CAA+BgB,CAA/B,K,kMAQchB,C,2FACRS,C,CAAST,CAAY,CAACU,GAAb,CAAiB,QAAjB,C,gBACO,MAAKE,mBAAL,CAAyB,cAAzB,CAAyCH,CAAM,CAACI,EAAhD,C,QAAhBC,C,QACNd,CAAY,CAACe,cAAb,CAA4BD,CAA5B,E","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\nimport ajax from 'core/ajax';\n\n/**\n * Default mutation manager\n *\n * @module core_courseformat/local/courseeditor/mutations\n * @class core_courseformat/local/courseeditor/mutations\n * @copyright 2021 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nexport default class {\n\n // All course editor mutations for Moodle 4.0 will be located in this file.\n\n /**\n * Private method to call core_courseformat_update_course webservice.\n *\n * @method _callEditWebservice\n * @param {string} action\n * @param {number} courseId\n * @param {array} ids\n * @param {number} targetSectionId optional target section id (for moving actions)\n * @param {number} targetCmId optional target cm id (for moving actions)\n */\n async _callEditWebservice(action, courseId, ids, targetSectionId, targetCmId) {\n const args = {\n action,\n courseid: courseId,\n ids,\n };\n if (targetSectionId) {\n args.targetsectionid = targetSectionId;\n }\n if (targetCmId) {\n args.targetcmid = targetCmId;\n }\n let ajaxresult = await ajax.call([{\n methodname: 'core_courseformat_update_course',\n args,\n }])[0];\n return JSON.parse(ajaxresult);\n }\n\n\n /**\n * Mutation module initialize.\n *\n * The reactive instance will execute this method when addMutations or setMutation is invoked.\n *\n * @param {StateManager} stateManager the state manager\n */\n init(stateManager) {\n // Add a method to prepare the fields when some update is comming from the server.\n stateManager.addUpdateTypes({\n prepareFields: this._prepareFields,\n });\n }\n\n /**\n * Add default values to state elements.\n *\n * This method is called every time a webservice returns a update state message.\n *\n * @param {Object} stateManager the state manager\n * @param {String} updateName the state element to update\n * @param {Object} fields the new data\n * @returns {Object} final fields data\n */\n _prepareFields(stateManager, updateName, fields) {\n // Any update should unlock the element.\n fields.locked = false;\n return fields;\n }\n\n /**\n * Move course modules to specific course location.\n *\n * Note that one of targetSectionId or targetCmId should be provided in order to identify the\n * new location:\n * - targetCmId: the activities will be located avobe the target cm. The targetSectionId\n * value will be ignored in this case.\n * - targetSectionId: the activities will be appended to the section. In this case\n * targetSectionId should not be present.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} cmids the list of cm ids to move\n * @param {number} targetSectionId the target section id\n * @param {number} targetCmId the target course module id\n */\n async cmMove(stateManager, cmids, targetSectionId, targetCmId) {\n if (!targetSectionId && !targetCmId) {\n throw new Error(`Mutation cmMove requires targetSectionId or targetCmId`);\n }\n const course = stateManager.get('course');\n this.cmLock(stateManager, cmids, true);\n const updates = await this._callEditWebservice('cm_move', course.id, cmids, targetSectionId, targetCmId);\n stateManager.processUpdates(updates);\n this.cmLock(stateManager, cmids, false);\n }\n\n /**\n * Move course modules to specific course location.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} sectionIds the list of section ids to move\n * @param {number} targetSectionId the target section id\n */\n async sectionMove(stateManager, sectionIds, targetSectionId) {\n if (!targetSectionId) {\n throw new Error(`Mutation sectionMove requires targetSectionId`);\n }\n const course = stateManager.get('course');\n this.sectionLock(stateManager, sectionIds, true);\n const updates = await this._callEditWebservice('section_move', course.id, sectionIds, targetSectionId);\n stateManager.processUpdates(updates);\n this.sectionLock(stateManager, sectionIds, false);\n }\n\n /**\n * Add a new section to a specific course location.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {number} targetSectionId optional the target section id\n */\n async addSection(stateManager, targetSectionId) {\n if (!targetSectionId) {\n targetSectionId = 0;\n }\n const course = stateManager.get('course');\n const updates = await this._callEditWebservice('section_add', course.id, [], targetSectionId);\n stateManager.processUpdates(updates);\n }\n\n /**\n * Delete sections.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} sectionIds the list of course modules ids\n */\n async sectionDelete(stateManager, sectionIds) {\n const course = stateManager.get('course');\n const updates = await this._callEditWebservice('section_delete', course.id, sectionIds);\n stateManager.processUpdates(updates);\n }\n\n /**\n * Mark or unmark course modules as dragging.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} cmIds the list of course modules ids\n * @param {bool} dragValue the new dragging value\n */\n cmDrag(stateManager, cmIds, dragValue) {\n this.setPageItem(stateManager);\n this._setElementsValue(stateManager, 'cm', cmIds, 'dragging', dragValue);\n }\n\n /**\n * Mark or unmark course sections as dragging.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} sectionIds the list of section ids\n * @param {bool} dragValue the new dragging value\n */\n sectionDrag(stateManager, sectionIds, dragValue) {\n this.setPageItem(stateManager);\n this._setElementsValue(stateManager, 'section', sectionIds, 'dragging', dragValue);\n }\n\n /**\n * Mark or unmark course modules as complete.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} cmIds the list of course modules ids\n * @param {bool} complete the new completion value\n */\n cmCompletion(stateManager, cmIds, complete) {\n const newValue = (complete) ? 1 : 0;\n this._setElementsValue(stateManager, 'cm', cmIds, 'completionstate', newValue);\n }\n\n /**\n * Lock or unlock course modules.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} cmIds the list of course modules ids\n * @param {bool} lockValue the new locked value\n */\n cmLock(stateManager, cmIds, lockValue) {\n this._setElementsValue(stateManager, 'cm', cmIds, 'locked', lockValue);\n }\n\n /**\n * Lock or unlock course sections.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} sectionIds the list of section ids\n * @param {bool} lockValue the new locked value\n */\n sectionLock(stateManager, sectionIds, lockValue) {\n this._setElementsValue(stateManager, 'section', sectionIds, 'locked', lockValue);\n }\n\n _setElementsValue(stateManager, name, ids, fieldName, newValue) {\n stateManager.setReadOnly(false);\n ids.forEach((id) => {\n const element = stateManager.get(name, id);\n if (element) {\n element[fieldName] = newValue;\n }\n });\n stateManager.setReadOnly(true);\n }\n\n /**\n * Set the page current item.\n *\n * Only one element of the course state can be the page item at a time.\n *\n * There are several actions that can alter the page current item. For example, when the user is in an activity\n * page, the page item is always the activity one. However, in a course page, when the user scrolls to an element,\n * this element get the page item.\n *\n * If the page item is static means that it is not meant to change. This is important because\n * static page items has some special logic. For example, if a cm is the static page item\n * and it is inside a collapsed section, the course index will expand the section to make it visible.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {String|undefined} type the element type (section or cm). Undefined will remove the current page item.\n * @param {Number|undefined} id the element id\n * @param {boolean|undefined} isStatic if the page item is static\n */\n setPageItem(stateManager, type, id, isStatic) {\n let newPageItem;\n if (type !== undefined) {\n newPageItem = stateManager.get(type, id);\n if (!newPageItem) {\n return;\n }\n }\n stateManager.setReadOnly(false);\n // Remove the current page item.\n const course = stateManager.get('course');\n course.pageItem = null;\n // Save the new page item.\n if (newPageItem) {\n course.pageItem = {\n id,\n type,\n sectionId: (type == 'section') ? newPageItem.id : newPageItem.sectionid,\n isStatic,\n };\n }\n stateManager.setReadOnly(true);\n }\n\n /**\n * Unlock all course elements.\n *\n * @param {StateManager} stateManager the current state manager\n */\n unlockAll(stateManager) {\n const state = stateManager.state;\n stateManager.setReadOnly(false);\n state.section.forEach((section) => {\n section.locked = false;\n });\n state.cm.forEach((cm) => {\n cm.locked = false;\n });\n stateManager.setReadOnly(true);\n }\n\n /*\n * Get updated user preferences and state data related to some section ids.\n *\n * @param {StateManager} stateManager the current state\n * @param {array} sectionIds the list of section ids to update\n * @param {Object} preferences the new preferences values\n */\n async sectionPreferences(stateManager, sectionIds, preferences) {\n stateManager.setReadOnly(false);\n const affectedSections = new Set();\n // Check if we need to update preferences.\n sectionIds.forEach(sectionId => {\n const section = stateManager.get('section', sectionId);\n if (section === undefined) {\n return;\n }\n let newValue = preferences.contentcollapsed ?? section.contentcollapsed;\n if (section.contentcollapsed != newValue) {\n section.contentcollapsed = newValue;\n affectedSections.add(section.id);\n }\n newValue = preferences.indexcollapsed ?? section.indexcollapsed;\n if (section.indexcollapsed != newValue) {\n section.indexcollapsed = newValue;\n affectedSections.add(section.id);\n }\n });\n stateManager.setReadOnly(true);\n\n if (affectedSections.size > 0) {\n // Build the preference structures.\n const course = stateManager.get('course');\n const state = stateManager.state;\n const prefKey = `coursesectionspreferences_${course.id}`;\n const preferences = {\n contentcollapsed: [],\n indexcollapsed: [],\n };\n state.section.forEach(section => {\n if (section.contentcollapsed) {\n preferences.contentcollapsed.push(section.id);\n }\n if (section.indexcollapsed) {\n preferences.indexcollapsed.push(section.id);\n }\n });\n const jsonString = JSON.stringify(preferences);\n M.util.set_user_preference(prefKey, jsonString);\n // Inform the backend of the change.\n await this._callEditWebservice('topic_preferences_updated', course.id, [...affectedSections]);\n }\n }\n\n /**\n * Get updated state data related to some cm ids.\n *\n * @method cmState\n * @param {StateManager} stateManager the current state\n * @param {array} cmids the list of cm ids to update\n */\n async cmState(stateManager, cmids) {\n this.cmLock(stateManager, cmids, true);\n const course = stateManager.get('course');\n const updates = await this._callEditWebservice('cm_state', course.id, cmids);\n stateManager.processUpdates(updates);\n this.cmLock(stateManager, cmids, false);\n }\n\n /**\n * Get updated state data related to some section ids.\n *\n * @method sectionState\n * @param {StateManager} stateManager the current state\n * @param {array} sectionIds the list of section ids to update\n */\n async sectionState(stateManager, sectionIds) {\n this.sectionLock(stateManager, sectionIds, true);\n const course = stateManager.get('course');\n const updates = await this._callEditWebservice('section_state', course.id, sectionIds);\n stateManager.processUpdates(updates);\n this.sectionLock(stateManager, sectionIds, false);\n }\n\n /**\n * Get the full updated state data of the course.\n *\n * @param {StateManager} stateManager the current state\n */\n async courseState(stateManager) {\n const course = stateManager.get('course');\n const updates = await this._callEditWebservice('course_state', course.id);\n stateManager.processUpdates(updates);\n }\n\n}\n"],"file":"mutations.min.js"} \ No newline at end of file diff --git a/course/format/amd/src/courseeditor.js b/course/format/amd/src/courseeditor.js index fb92f2e987c..23b08ce7bdc 100644 --- a/course/format/amd/src/courseeditor.js +++ b/course/format/amd/src/courseeditor.js @@ -28,6 +28,9 @@ import events from 'core_course/events'; // A map with all the course editor instances. const courseEditorMap = new Map(); +// Map with all the state keys the backend send us to know if the frontend cache is valid or not. +const courseStateKeyMap = new Map(); + /** * Trigger a state changed event. * @@ -51,12 +54,22 @@ function dispatchStateChangedEvent(detail, target) { /** * Setup the current view settings * + * The backend cache state revision is a combination of the course->cacherev, the + * user course preferences and completion state. The backend updates that number + * everytime some change in the course affects the user course state. + * * @param {number} courseId the course id * @param {setup} setup format, page and course settings * @param {boolean} setup.editing if the page is in edit mode * @param {boolean} setup.supportscomponents if the format supports components for content + * @param {boolean} setup.statekey the backend cached state revision */ export const setViewFormat = (courseId, setup) => { + courseId = parseInt(courseId); + // Caches are ignored in edit mode. + if (!setup.editing) { + courseStateKeyMap.set(courseId, setup.statekey); + } const editor = getCourseEditor(courseId); editor.setViewFormat(setup); }; @@ -82,7 +95,7 @@ export const getCourseEditor = (courseId) => { mutations: new DefaultMutations(), }) ); - courseEditorMap.get(courseId).loadCourse(courseId); + courseEditorMap.get(courseId).loadCourse(courseId, courseStateKeyMap.get(courseId)); } return courseEditorMap.get(courseId); }; diff --git a/course/format/amd/src/local/courseeditor/courseeditor.js b/course/format/amd/src/local/courseeditor/courseeditor.js index 0db1eb2aa90..778f3c8285c 100644 --- a/course/format/amd/src/local/courseeditor/courseeditor.js +++ b/course/format/amd/src/local/courseeditor/courseeditor.js @@ -59,14 +59,26 @@ export default class extends Reactive { * * The course can only be loaded once per instance. Otherwise an error is thrown. * + * The backend can inform the module of the current state key. This key changes every time some + * update in the course affect the current user state. Some examples are: + * - The course content has been edited + * - The user marks some activity as completed + * - The user collapses or uncollapses a section (it is stored as a user preference) + * * @param {number} courseId course id + * @param {string} serverStateKey the current backend course cache reference */ - async loadCourse(courseId) { + async loadCourse(courseId, serverStateKey) { if (this.courseId) { throw new Error(`Cannot load ${courseId}, course already loaded with id ${this.courseId}`); } + if (!serverStateKey) { + // The server state key is not provided, we use a invalid statekey to force reloading. + serverStateKey = `invalidStateKey_${Date.now()}`; + } + // Default view format setup. this._editing = false; this._supportscomponents = false; @@ -75,8 +87,16 @@ export default class extends Reactive { let stateData; + const storeStateKey = Storage.get(`course/${courseId}/stateKey`); try { - stateData = await this.getServerCourseState(); + // Check if the backend state key is the same we have in our session storage. + if (!this.isEditing && serverStateKey == storeStateKey) { + stateData = JSON.parse(Storage.get(`course/${courseId}/staticState`)); + } + if (!stateData) { + stateData = await this.getServerCourseState(); + } + } catch (error) { log.error("EXCEPTION RAISED WHILE INIT COURSE EDITOR"); log.error(error); @@ -92,9 +112,9 @@ export default class extends Reactive { // Check if the last state is the same as the cached one. const newState = JSON.stringify(stateData); const previousState = Storage.get(`course/${courseId}/staticState`); - if (previousState !== newState) { + if (previousState !== newState || storeStateKey !== serverStateKey) { Storage.set(`course/${courseId}/staticState`, newState); - Storage.set(`course/${courseId}/stateKey`, Date.now()); + Storage.set(`course/${courseId}/stateKey`, stateData?.course?.statekey ?? serverStateKey); } this.stateKey = Storage.get(`course/${courseId}/stateKey`); } @@ -106,6 +126,7 @@ export default class extends Reactive { * @param {Object} setup format, page and course settings * @param {boolean} setup.editing if the page is in edit mode * @param {boolean} setup.supportscomponents if the format supports components for content + * @param {string} setup.cacherev the backend cached state revision */ setViewFormat(setup) { this._editing = setup.editing ?? false; diff --git a/course/format/amd/src/local/courseeditor/mutations.js b/course/format/amd/src/local/courseeditor/mutations.js index 7f791c79689..7cca89bfda2 100644 --- a/course/format/amd/src/local/courseeditor/mutations.js +++ b/course/format/amd/src/local/courseeditor/mutations.js @@ -295,8 +295,8 @@ export default class { */ async sectionPreferences(stateManager, sectionIds, preferences) { stateManager.setReadOnly(false); + const affectedSections = new Set(); // Check if we need to update preferences. - let updatePreferences = false; sectionIds.forEach(sectionId => { const section = stateManager.get('section', sectionId); if (section === undefined) { @@ -305,17 +305,17 @@ export default class { let newValue = preferences.contentcollapsed ?? section.contentcollapsed; if (section.contentcollapsed != newValue) { section.contentcollapsed = newValue; - updatePreferences = true; + affectedSections.add(section.id); } newValue = preferences.indexcollapsed ?? section.indexcollapsed; if (section.indexcollapsed != newValue) { section.indexcollapsed = newValue; - updatePreferences = true; + affectedSections.add(section.id); } }); stateManager.setReadOnly(true); - if (updatePreferences) { + if (affectedSections.size > 0) { // Build the preference structures. const course = stateManager.get('course'); const state = stateManager.state; @@ -334,6 +334,8 @@ export default class { }); const jsonString = JSON.stringify(preferences); M.util.set_user_preference(prefKey, jsonString); + // Inform the backend of the change. + await this._callEditWebservice('topic_preferences_updated', course.id, [...affectedSections]); } } diff --git a/course/format/classes/base.php b/course/format/classes/base.php index 079cc4acf2c..0e87fb1db25 100644 --- a/course/format/classes/base.php +++ b/course/format/classes/base.php @@ -225,6 +225,48 @@ abstract class base { } } + /** + * Reset the current user course format cache. + * + * The course format cache resets every time the course cache resets but + * also when the user changes their course format preference, complete + * an activity... + * + * @param stdClass $course the course object + * @return string the new statekey + */ + public static function session_cache_reset(stdClass $course): string { + $statecache = cache::make('core', 'courseeditorstate'); + $newkey = $course->cacherev . '_' . time(); + $statecache->set($course->id, $newkey); + return $newkey; + } + + /** + * Return the current user course format cache key. + * + * The course format session cache can be used to cache the + * user course representation. The statekey will be reset when the + * the course state changes. For example when the course is edited, + * the user completes an activity or simply some course preference + * like collapsing a section happens. + * + * @param stdClass $course the course object + * @return string the current statekey + */ + public static function session_cache(stdClass $course): string { + $statecache = cache::make('core', 'courseeditorstate'); + $statekey = $statecache->get($course->id); + // Validate the statekey code. + if (preg_match('/^[0-9]+_[0-9]+$/', $statekey)) { + list($cacherev) = explode('_', $statekey); + if ($cacherev == $course->cacherev) { + return $statekey; + } + } + return self::session_cache_reset($course); + } + /** * Returns the format name used by this course * diff --git a/course/format/classes/external/update_course.php b/course/format/classes/external/update_course.php index ddc123b3b80..d14cee3ee4f 100644 --- a/course/format/classes/external/update_course.php +++ b/course/format/classes/external/update_course.php @@ -28,6 +28,7 @@ use external_multiple_structure; use moodle_exception; use coding_exception; use context_course; +use core_courseformat\base as course_format; /** * External secrvie to update the course from the course editor components. @@ -135,8 +136,13 @@ class update_course extends external_api { throw new moodle_exception("Invalid course state action $action in ".get_class($actions)); } + $course = $courseformat->get_course(); + // Execute the action. - $actions->$action($updates, $courseformat->get_course(), $ids, $targetsectionid, $targetcmid); + $actions->$action($updates, $course, $ids, $targetsectionid, $targetcmid); + + // Any state action mark the state cache as dirty. + course_format::session_cache_reset($course); return json_encode($updates); } diff --git a/course/format/classes/output/local/state/course.php b/course/format/classes/output/local/state/course.php index e6f33e50401..f8316c75e5f 100644 --- a/course/format/classes/output/local/state/course.php +++ b/course/format/classes/output/local/state/course.php @@ -65,6 +65,7 @@ class course implements renderable { 'highlighted' => $format->get_section_highlighted_name(), 'maxsections' => $format->get_max_sections(), 'baseurl' => $url->out(), + 'statekey' => course_format::session_cache($course), ]; $sections = $modinfo->get_section_info_all(); diff --git a/course/format/classes/stateactions.php b/course/format/classes/stateactions.php index 8c6995cce68..42df0f13399 100644 --- a/course/format/classes/stateactions.php +++ b/course/format/classes/stateactions.php @@ -16,6 +16,7 @@ namespace core_courseformat; +use core_courseformat\base as course_format; use core_courseformat\stateupdates; use cm_info; use section_info; @@ -24,6 +25,7 @@ use course_modinfo; use moodle_exception; use context_module; use context_course; +use cache; /** * Contains the core course state actions. @@ -286,6 +288,30 @@ class stateactions { return $sections; } + /** + * Some of the topic preferences has been updated. + * + * Section preferences can be handled by many user actions and even some of them can affect + * only the frontend part. Format plugins can override this method to add extra logic to the + * section preferences. + * + * @param stateupdates $updates the affected course elements track + * @param stdClass $course the course object + * @param int[] $ids the collapsed section ids + * @param int $targetsectionid not used + * @param int $targetcmid not used + */ + public function topic_preferences_updated( + stateupdates $updates, + stdClass $course, + array $ids = [], + ?int $targetsectionid = null, + ?int $targetcmid = null + ): void { + // Format plugins may override this method to provide extra functionalities to + // section preferences. + } + /** * Add the update messages of the updated version of any cm and section related to the cm ids. * diff --git a/course/lib.php b/course/lib.php index c3b83638aa8..374da482b4d 100644 --- a/course/lib.php +++ b/course/lib.php @@ -3311,10 +3311,13 @@ function include_course_editor(course_format $format) { return; } + $statekey = course_format::session_cache($course); + // Edition mode and some format specs must be passed to the init method. $setup = (object)[ 'editing' => $format->show_editor(), 'supportscomponents' => $format->supports_components(), + 'statekey' => $statekey, ]; // All the new editor elements will be loaded after the course is presented and // the initial course state will be generated using core_course_get_state webservice. diff --git a/lang/en/cache.php b/lang/en/cache.php index aa8f87c358f..45bfda216b9 100644 --- a/lang/en/cache.php +++ b/lang/en/cache.php @@ -52,6 +52,7 @@ $string['cachedef_coursecattree'] = 'Course categories tree'; $string['cachedef_coursecompletion'] = 'Course completion status'; $string['cachedef_coursecontacts'] = 'List of course contacts'; $string['cachedef_coursemodinfo'] = 'Accumulated information about modules and sections for each course'; +$string['cachedef_courseeditorstate'] = 'Session course state cache keys to detect course changes in the frontend'; $string['cachedef_course_image'] = 'Course images'; $string['cachedef_course_user_dates'] = 'The user dates for courses set to relative dates mode'; $string['cachedef_completion'] = 'Activity completion status'; diff --git a/lib/completionlib.php b/lib/completionlib.php index 94a9271327b..2b910d33ed2 100644 --- a/lib/completionlib.php +++ b/lib/completionlib.php @@ -27,6 +27,7 @@ */ use core_completion\activity_custom_completion; +use core_courseformat\base as course_format; defined('MOODLE_INTERNAL') || die(); @@ -635,6 +636,12 @@ class completion_info { return; } + // The activity completion alters the course state cache for this particular user. + $course = get_course($cm->course); + if ($course) { + course_format::session_cache_reset($course); + } + // For auto tracking, if the status is overridden to 'COMPLETION_COMPLETE', then disallow further changes, // unless processing another override. // Basically, we want those activities which have been overridden to COMPLETE to hold state, and those which have been diff --git a/lib/db/caches.php b/lib/db/caches.php index e64739d5502..55cb49ef222 100644 --- a/lib/db/caches.php +++ b/lib/db/caches.php @@ -211,6 +211,12 @@ $definitions = array( 'simplekeys' => true, 'ttl' => 3600, ), + // Course reactive state cache. + 'courseeditorstate' => [ + 'mode' => cache_store::MODE_SESSION, + 'simplekeys' => true, + 'simpledata' => true, + ], // Used to store data for repositories to avoid repetitive DB queries within one request. 'repositories' => array( 'mode' => cache_store::MODE_REQUEST, diff --git a/version.php b/version.php index a4a1865abce..96714159a6d 100644 --- a/version.php +++ b/version.php @@ -29,7 +29,7 @@ defined('MOODLE_INTERNAL') || die(); -$version = 2022021800.00; // YYYYMMDD = weekly release date of this DEV branch. +$version = 2022021800.01; // YYYYMMDD = weekly release date of this DEV branch. // RR = release increments - 00 in DEV branches. // .XX = incremental changes. $release = '4.0dev+ (Build: 20220218)'; // Human-friendly version name From 3651b85fbe7672347235b37f0308a7ecabd26d4d Mon Sep 17 00:00:00 2001 From: Ferran Recio Date: Mon, 21 Feb 2022 11:28:17 +0100 Subject: [PATCH 2/2] MDL-73547 core_courseformat: add collapsed section update actions Now content and course index sections have special mutations to store the collapsed preferences. This way the backend implementation is independent of the frontend one and can use caches or other kind of optimizations of necessary. --- course/format/amd/build/local/content.min.js | 2 +- .../format/amd/build/local/content.min.js.map | 2 +- .../build/local/courseeditor/mutations.min.js | 2 +- .../local/courseeditor/mutations.min.js.map | 2 +- .../local/courseindex/courseindex.min.js | 2 +- .../local/courseindex/courseindex.min.js.map | 2 +- course/format/amd/src/local/content.js | 12 +-- .../amd/src/local/courseeditor/mutations.js | 84 +++++++++++-------- .../amd/src/local/courseindex/courseindex.js | 6 +- course/format/classes/base.php | 54 +++++++++--- course/format/classes/stateactions.php | 38 +++++++-- course/format/tests/base_test.php | 31 ++++++- course/renderer.php | 1 - 13 files changed, 161 insertions(+), 77 deletions(-) diff --git a/course/format/amd/build/local/content.min.js b/course/format/amd/build/local/content.min.js index 25195d224b2..f7e9dbfe1a7 100644 --- a/course/format/amd/build/local/content.min.js +++ b/course/format/amd/build/local/content.min.js @@ -1,2 +1,2 @@ -define ("core_courseformat/local/content",["exports","core/reactive","core_courseformat/courseeditor","core/inplace_editable","core_courseformat/local/content/section","core_courseformat/local/content/section/cmitem","core_course/actions","core_courseformat/local/content/actions","core_course/events"],function(a,b,c,d,e,f,g,h,i){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;d=l(d);e=l(e);f=l(f);g=l(g);h=l(h);i=k(i);function j(){if("function"!=typeof WeakMap)return null;var a=new WeakMap;j=function(){return a};return a}function k(a){if(a&&a.__esModule){return a}if(null===a||"object"!==m(a)&&"function"!=typeof a){return{default:a}}var b=j();if(b&&b.has(a)){return b.get(a)}var c={},d=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var e in a){if(Object.prototype.hasOwnProperty.call(a,e)){var f=d?Object.getOwnPropertyDescriptor(a,e):null;if(f&&(f.get||f.set)){Object.defineProperty(c,e,f)}else{c[e]=a[e]}}}c.default=a;if(b){b.set(a,c)}return c}function l(a){return a&&a.__esModule?a:{default:a}}function m(a){"@babel/helpers - typeof";if("function"==typeof Symbol&&"symbol"==typeof Symbol.iterator){m=function(a){return typeof a}}else{m=function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?"symbol":typeof a}}return m(a)}function n(a,b,c,d,e,f,g){try{var h=a[f](g),i=h.value}catch(a){c(a);return}if(h.done){b(i)}else{Promise.resolve(i).then(d,e)}}function o(a){return function(){var b=this,c=arguments;return new Promise(function(d,e){var h=a.apply(b,c);function f(a){n(h,d,e,f,g,"next",a)}function g(a){n(h,d,e,f,g,"throw",a)}f(void 0)})}}function p(a,b){var c=Object.keys(a);if(Object.getOwnPropertySymbols){var d=Object.getOwnPropertySymbols(a);if(b)d=d.filter(function(b){return Object.getOwnPropertyDescriptor(a,b).enumerable});c.push.apply(c,d)}return c}function q(a){for(var b=1,c;b=f.offsetTop}d=c;return b>=f.offsetTop});if(d){this.reactive.dispatch("setPageItem",d.type,d.id)}}},{key:"_refreshSectionNumber",value:function _refreshSectionNumber(a){var b=a.element,c=this.getElement(this.selectors.SECTION,b.id);if(!c){return}c.id="section-".concat(b.number);c.dataset.sectionid=b.number;c.dataset.number=b.number;var e=d.default.getInplaceEditable(c.querySelector(this.selectors.SECTION_ITEM));if(e){var f=e.getValue(),g=e.getItemId();if(""===e.getValue()){if(g==b.id&&(f!=b.rawtitle||""==b.rawtitle)){e.setValue(b.rawtitle)}}}}},{key:"_refreshSectionCmlist",value:function _refreshSectionCmlist(a){var b,c=a.element,d=null!==(b=c.cmlist)&&void 0!==b?b:[],e=this.getElement(this.selectors.SECTION,c.id),f=null===e||void 0===e?void 0:e.querySelector(this.selectors.SECTION_CMLIST),g=this._createCmItem.bind(this);if(f){this._fixOrder(f,d,this.selectors.CM,this.dettachedCms,g)}}},{key:"_refreshCourseSectionlist",value:function _refreshCourseSectionlist(a){var b,c=a.element;if(0!=this.reactive.sectionReturn){return}var d=null!==(b=c.sectionlist)&&void 0!==b?b:[],e=this.getElement(this.selectors.COURSE_SECTIONLIST),f=this._createSectionItem.bind(this);if(e){this._fixOrder(e,d,this.selectors.SECTION,this.dettachedSections,f)}}},{key:"_indexContents",value:function _indexContents(){this._scanIndex(this.selectors.SECTION,this.sections,function(a){return new e.default(a)});this._scanIndex(this.selectors.CM,this.cms,function(a){return new f.default(a)})}},{key:"_scanIndex",value:function _scanIndex(a,b,c){var d=this,e=this.getElements("".concat(a,":not([data-indexed])"));e.forEach(function(a){var e;if(!(null===a||void 0===a?void 0:null===(e=a.dataset)||void 0===e?void 0:e.id)){return}if(b[a.dataset.id]!==void 0){b[a.dataset.id].unregister()}b[a.dataset.id]=c(q({},d,{element:a}));a.dataset.indexed=!0})}},{key:"_reloadCm",value:function _reloadCm(a){var b=this,c=a.element,d=this.getElement(this.selectors.CM,c.id);if(d){var e=g.default.refreshModule(d,c.id);e.then(function(){b._indexContents()}).catch()}}},{key:"_reloadSection",value:function _reloadSection(a){var b=this,c=a.element,d=this.getElement(this.selectors.SECTION,c.id);if(d){var e=g.default.refreshSection(d,c.id);e.then(function(){b._indexContents()}).catch()}}},{key:"_createCmItem",value:function _createCmItem(a,b){var c=document.createElement(this.selectors.ACTIVITYTAG);c.dataset.for="cmitem";c.dataset.id=b;c.id="module-".concat(b);c.classList.add(this.classes.ACTIVITY);a.append(c);this._reloadCm({element:this.reactive.get("cm",b)});return c}},{key:"_createSectionItem",value:function _createSectionItem(a,b){var c=this.reactive.get("section",b),d=document.createElement(this.selectors.SECTIONTAG);d.dataset.for="section";d.dataset.id=b;d.dataset.number=c.number;d.id="section-".concat(b);d.classList.add(this.classes.SECTION);a.append(d);this._reloadSection({element:c});return d}},{key:"_fixOrder",value:function(){var a=o(regeneratorRuntime.mark(function a(b,c,d,e,f){var g=this,h,i,j,k,l;return regeneratorRuntime.wrap(function(a){while(1){switch(a.prev=a.next){case 0:if(!(b===void 0)){a.next=2;break}return a.abrupt("return");case 2:if(c.length){a.next=6;break}b.classList.add("hidden");b.innerHTML="";return a.abrupt("return");case 6:b.classList.remove("hidden");c.forEach(function(a,c){var h,i,j=null!==(h=null!==(i=g.getElement(d,a))&&void 0!==i?i:e[a])&&void 0!==h?h:f(b,a);if(j===void 0){return}var k=b.children[c];if(k===void 0){b.append(j);return}if(k!==j){b.insertBefore(j,k)}});while(b.children.length>c.length){j=b.lastChild;if(null===j||void 0===j?void 0:null===(i=j.classList)||void 0===i?void 0:i.contains("dndupload-preview")){h=j}else{e[null!==(k=null===j||void 0===j?void 0:null===(l=j.dataset)||void 0===l?void 0:l.id)&&void 0!==k?k:0]=j}b.removeChild(j)}if(h){b.append(h)}case 10:case"end":return a.stop();}}},a)}));return function _fixOrder(){return a.apply(this,arguments)}}()}],[{key:"init",value:function init(a,d,e){return new b({element:document.getElementById(a),reactive:(0,c.getCurrentCourseEditor)(),selectors:d,sectionReturn:e})}}]);return b}(b.BaseComponent);a.default=C;return a.default}); +define ("core_courseformat/local/content",["exports","core/reactive","core_courseformat/courseeditor","core/inplace_editable","core_courseformat/local/content/section","core_courseformat/local/content/section/cmitem","core_course/actions","core_courseformat/local/content/actions","core_course/events"],function(a,b,c,d,e,f,g,h,i){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;d=l(d);e=l(e);f=l(f);g=l(g);h=l(h);i=k(i);function j(){if("function"!=typeof WeakMap)return null;var a=new WeakMap;j=function(){return a};return a}function k(a){if(a&&a.__esModule){return a}if(null===a||"object"!==m(a)&&"function"!=typeof a){return{default:a}}var b=j();if(b&&b.has(a)){return b.get(a)}var c={},d=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var e in a){if(Object.prototype.hasOwnProperty.call(a,e)){var f=d?Object.getOwnPropertyDescriptor(a,e):null;if(f&&(f.get||f.set)){Object.defineProperty(c,e,f)}else{c[e]=a[e]}}}c.default=a;if(b){b.set(a,c)}return c}function l(a){return a&&a.__esModule?a:{default:a}}function m(a){"@babel/helpers - typeof";if("function"==typeof Symbol&&"symbol"==typeof Symbol.iterator){m=function(a){return typeof a}}else{m=function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?"symbol":typeof a}}return m(a)}function n(a,b,c,d,e,f,g){try{var h=a[f](g),i=h.value}catch(a){c(a);return}if(h.done){b(i)}else{Promise.resolve(i).then(d,e)}}function o(a){return function(){var b=this,c=arguments;return new Promise(function(d,e){var h=a.apply(b,c);function f(a){n(h,d,e,f,g,"next",a)}function g(a){n(h,d,e,f,g,"throw",a)}f(void 0)})}}function p(a,b){var c=Object.keys(a);if(Object.getOwnPropertySymbols){var d=Object.getOwnPropertySymbols(a);if(b)d=d.filter(function(b){return Object.getOwnPropertyDescriptor(a,b).enumerable});c.push.apply(c,d)}return c}function q(a){for(var b=1,c;b=f.offsetTop}d=c;return b>=f.offsetTop});if(d){this.reactive.dispatch("setPageItem",d.type,d.id)}}},{key:"_refreshSectionNumber",value:function _refreshSectionNumber(a){var b=a.element,c=this.getElement(this.selectors.SECTION,b.id);if(!c){return}c.id="section-".concat(b.number);c.dataset.sectionid=b.number;c.dataset.number=b.number;var e=d.default.getInplaceEditable(c.querySelector(this.selectors.SECTION_ITEM));if(e){var f=e.getValue(),g=e.getItemId();if(""===e.getValue()){if(g==b.id&&(f!=b.rawtitle||""==b.rawtitle)){e.setValue(b.rawtitle)}}}}},{key:"_refreshSectionCmlist",value:function _refreshSectionCmlist(a){var b,c=a.element,d=null!==(b=c.cmlist)&&void 0!==b?b:[],e=this.getElement(this.selectors.SECTION,c.id),f=null===e||void 0===e?void 0:e.querySelector(this.selectors.SECTION_CMLIST),g=this._createCmItem.bind(this);if(f){this._fixOrder(f,d,this.selectors.CM,this.dettachedCms,g)}}},{key:"_refreshCourseSectionlist",value:function _refreshCourseSectionlist(a){var b,c=a.element;if(0!=this.reactive.sectionReturn){return}var d=null!==(b=c.sectionlist)&&void 0!==b?b:[],e=this.getElement(this.selectors.COURSE_SECTIONLIST),f=this._createSectionItem.bind(this);if(e){this._fixOrder(e,d,this.selectors.SECTION,this.dettachedSections,f)}}},{key:"_indexContents",value:function _indexContents(){this._scanIndex(this.selectors.SECTION,this.sections,function(a){return new e.default(a)});this._scanIndex(this.selectors.CM,this.cms,function(a){return new f.default(a)})}},{key:"_scanIndex",value:function _scanIndex(a,b,c){var d=this,e=this.getElements("".concat(a,":not([data-indexed])"));e.forEach(function(a){var e;if(!(null===a||void 0===a?void 0:null===(e=a.dataset)||void 0===e?void 0:e.id)){return}if(b[a.dataset.id]!==void 0){b[a.dataset.id].unregister()}b[a.dataset.id]=c(q({},d,{element:a}));a.dataset.indexed=!0})}},{key:"_reloadCm",value:function _reloadCm(a){var b=this,c=a.element,d=this.getElement(this.selectors.CM,c.id);if(d){var e=g.default.refreshModule(d,c.id);e.then(function(){b._indexContents()}).catch()}}},{key:"_reloadSection",value:function _reloadSection(a){var b=this,c=a.element,d=this.getElement(this.selectors.SECTION,c.id);if(d){var e=g.default.refreshSection(d,c.id);e.then(function(){b._indexContents()}).catch()}}},{key:"_createCmItem",value:function _createCmItem(a,b){var c=document.createElement(this.selectors.ACTIVITYTAG);c.dataset.for="cmitem";c.dataset.id=b;c.id="module-".concat(b);c.classList.add(this.classes.ACTIVITY);a.append(c);this._reloadCm({element:this.reactive.get("cm",b)});return c}},{key:"_createSectionItem",value:function _createSectionItem(a,b){var c=this.reactive.get("section",b),d=document.createElement(this.selectors.SECTIONTAG);d.dataset.for="section";d.dataset.id=b;d.dataset.number=c.number;d.id="section-".concat(b);d.classList.add(this.classes.SECTION);a.append(d);this._reloadSection({element:c});return d}},{key:"_fixOrder",value:function(){var a=o(regeneratorRuntime.mark(function a(b,c,d,e,f){var g=this,h,i,j,k,l;return regeneratorRuntime.wrap(function(a){while(1){switch(a.prev=a.next){case 0:if(!(b===void 0)){a.next=2;break}return a.abrupt("return");case 2:if(c.length){a.next=6;break}b.classList.add("hidden");b.innerHTML="";return a.abrupt("return");case 6:b.classList.remove("hidden");c.forEach(function(a,c){var h,i,j=null!==(h=null!==(i=g.getElement(d,a))&&void 0!==i?i:e[a])&&void 0!==h?h:f(b,a);if(j===void 0){return}var k=b.children[c];if(k===void 0){b.append(j);return}if(k!==j){b.insertBefore(j,k)}});while(b.children.length>c.length){j=b.lastChild;if(null===j||void 0===j?void 0:null===(i=j.classList)||void 0===i?void 0:i.contains("dndupload-preview")){h=j}else{e[null!==(k=null===j||void 0===j?void 0:null===(l=j.dataset)||void 0===l?void 0:l.id)&&void 0!==k?k:0]=j}b.removeChild(j)}if(h){b.append(h)}case 10:case"end":return a.stop();}}},a)}));return function _fixOrder(){return a.apply(this,arguments)}}()}],[{key:"init",value:function init(a,d,e){return new b({element:document.getElementById(a),reactive:(0,c.getCurrentCourseEditor)(),selectors:d,sectionReturn:e})}}]);return b}(b.BaseComponent);a.default=C;return a.default}); //# sourceMappingURL=content.min.js.map diff --git a/course/format/amd/build/local/content.min.js.map b/course/format/amd/build/local/content.min.js.map index 782d1da57ad..1a635edca64 100644 --- a/course/format/amd/build/local/content.min.js.map +++ b/course/format/amd/build/local/content.min.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../src/local/content.js"],"names":["Component","descriptor","name","selectors","SECTION","SECTION_ITEM","SECTION_CMLIST","COURSE_SECTIONLIST","CM","PAGE","TOGGLER","COLLAPSE","TOGGLEALL","ACTIVITYTAG","SECTIONTAG","classes","COLLAPSED","ACTIVITY","STATEDREADY","dettachedCms","dettachedSections","sections","cms","sectionReturn","state","_indexContents","addEventListener","element","_sectionTogglers","toogleAll","getElement","_allSectionToggler","_refreshAllSectionsToggler","reactive","supportComponents","isEditing","DispatchActions","classList","add","CourseEvents","manualCompletionToggled","_completionHandler","document","querySelector","_scrollHandler","event","sectionlink","target","closest","isChevron","section","toggler","isCollapsed","contains","sectionId","getAttribute","dispatch","contentcollapsed","preventDefault","isAllCollapsed","course","get","sectionlist","watch","handler","_reloadCm","_refreshSectionNumber","_refreshSectionCollapsed","_startProcessing","_refreshCourseSectionlist","_refreshSectionCmlist","id","Error","click","allcollapsed","allexpanded","forEach","remove","detail","cmid","completed","pageOffset","scrollTop","items","getExporter","allItemsArray","pageItem","every","item","index","type","url","offsetTop","number","dataset","sectionid","inplace","inplaceeditable","getInplaceEditable","currentvalue","getValue","currentitemid","getItemId","rawtitle","setValue","cmlist","listparent","createCm","_createCmItem","bind","_fixOrder","createSection","_createSectionItem","_scanIndex","Section","CmItem","selector","creationhandler","getElements","unregister","indexed","cmitem","promise","courseActions","refreshModule","then","catch","sectionitem","refreshSection","container","newItem","createElement","for","append","_reloadSection","neworder","dettachedelements","createMethod","length","innerHTML","itemid","currentitem","children","insertBefore","lastchild","lastChild","dndFakeActivity","removeChild","getElementById","BaseComponent"],"mappings":"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","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Course index main component.\n *\n * @module core_courseformat/local/content\n * @class core_courseformat/local/content\n * @copyright 2020 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {BaseComponent} from 'core/reactive';\nimport {getCurrentCourseEditor} from 'core_courseformat/courseeditor';\nimport inplaceeditable from 'core/inplace_editable';\nimport Section from 'core_courseformat/local/content/section';\nimport CmItem from 'core_courseformat/local/content/section/cmitem';\n// Course actions is needed for actions that are not migrated to components.\nimport courseActions from 'core_course/actions';\nimport DispatchActions from 'core_courseformat/local/content/actions';\nimport * as CourseEvents from 'core_course/events';\n\nexport default class Component extends BaseComponent {\n\n /**\n * Constructor hook.\n *\n * @param {Object} descriptor the component descriptor\n */\n create(descriptor) {\n // Optional component name for debugging.\n this.name = 'course_format';\n // Default query selectors.\n this.selectors = {\n SECTION: `[data-for='section']`,\n SECTION_ITEM: `[data-for='section_title']`,\n SECTION_CMLIST: `[data-for='cmlist']`,\n COURSE_SECTIONLIST: `[data-for='course_sectionlist']`,\n CM: `[data-for='cmitem']`,\n PAGE: `#page`,\n TOGGLER: `[data-action=\"togglecoursecontentsection\"]`,\n COLLAPSE: `[data-toggle=\"collapse\"]`,\n TOGGLEALL: `[data-toggle=\"toggleall\"]`,\n // Formats can override the activity tag but a default one is needed to create new elements.\n ACTIVITYTAG: 'li',\n SECTIONTAG: 'li',\n };\n // Default classes to toggle on refresh.\n this.classes = {\n COLLAPSED: `collapsed`,\n // Course content classes.\n ACTIVITY: `activity`,\n STATEDREADY: `stateready`,\n SECTION: `section`,\n };\n // Array to save dettached elements during element resorting.\n this.dettachedCms = {};\n this.dettachedSections = {};\n // Index of sections and cms components.\n this.sections = {};\n this.cms = {};\n // The page section return.\n this.sectionReturn = descriptor.sectionReturn ?? 0;\n }\n\n /**\n * Static method to create a component instance form the mustahce template.\n *\n * @param {string} target the DOM main element or its ID\n * @param {object} selectors optional css selector overrides\n * @param {number} sectionReturn the content section return\n * @return {Component}\n */\n static init(target, selectors, sectionReturn) {\n return new Component({\n element: document.getElementById(target),\n reactive: getCurrentCourseEditor(),\n selectors,\n sectionReturn,\n });\n }\n\n /**\n * Initial state ready method.\n *\n * @param {Object} state the state data\n */\n stateReady(state) {\n this._indexContents();\n // Activate section togglers.\n this.addEventListener(this.element, 'click', this._sectionTogglers);\n\n // Collapse/Expand all sections button.\n const toogleAll = this.getElement(this.selectors.TOGGLEALL);\n if (toogleAll) {\n this.addEventListener(toogleAll, 'click', this._allSectionToggler);\n this._refreshAllSectionsToggler(state);\n }\n\n if (this.reactive.supportComponents) {\n // Actions are only available in edit mode.\n if (this.reactive.isEditing) {\n new DispatchActions(this);\n }\n\n // Mark content as state ready.\n this.element.classList.add(this.classes.STATEDREADY);\n }\n\n // Capture completion events.\n this.addEventListener(\n this.element,\n CourseEvents.manualCompletionToggled,\n this._completionHandler\n );\n\n // Capture page scroll to update page item.\n this.addEventListener(\n document.querySelector(this.selectors.PAGE),\n \"scroll\",\n this._scrollHandler\n );\n }\n\n /**\n * Setup sections toggler.\n *\n * Toggler click is delegated to the main course content element because new sections can\n * appear at any moment and this way we prevent accidental double bindings.\n *\n * @param {Event} event the triggered event\n */\n _sectionTogglers(event) {\n const sectionlink = event.target.closest(this.selectors.TOGGLER);\n const isChevron = event.target.closest(this.selectors.COLLAPSE);\n\n if (sectionlink || isChevron) {\n\n const section = event.target.closest(this.selectors.SECTION);\n const toggler = section.querySelector(this.selectors.COLLAPSE);\n const isCollapsed = toggler?.classList.contains(this.classes.COLLAPSED) ?? false;\n\n if (isChevron || isCollapsed) {\n // Update the state.\n const sectionId = section.getAttribute('data-id');\n this.reactive.dispatch(\n 'sectionPreferences',\n [sectionId],\n {\n contentcollapsed: !isCollapsed,\n },\n );\n }\n }\n }\n\n /**\n * Handle the collapse/expand all sections button.\n *\n * Toggler click is delegated to the main course content element because new sections can\n * appear at any moment and this way we prevent accidental double bindings.\n *\n * @param {Event} event the triggered event\n */\n _allSectionToggler(event) {\n event.preventDefault();\n\n const target = event.target.closest(this.selectors.TOGGLEALL);\n const isAllCollapsed = target.classList.contains(this.classes.COLLAPSED);\n\n const course = this.reactive.get('course');\n this.reactive.dispatch(\n 'sectionPreferences',\n course.sectionlist ?? [],\n {\n contentcollapsed: !isAllCollapsed,\n }\n );\n }\n\n /**\n * Return the component watchers.\n *\n * @returns {Array} of watchers\n */\n getWatchers() {\n // Section return is a global page variable but most formats define it just before start printing\n // the course content. This is the reason why we define this page setting here.\n this.reactive.sectionReturn = this.sectionReturn;\n\n // Check if the course format is compatible with reactive components.\n if (!this.reactive.supportComponents) {\n return [];\n }\n return [\n // State changes that require to reload some course modules.\n {watch: `cm.visible:updated`, handler: this._reloadCm},\n // Update section number and title.\n {watch: `section.number:updated`, handler: this._refreshSectionNumber},\n // Collapse and expand sections.\n {watch: `section.contentcollapsed:updated`, handler: this._refreshSectionCollapsed},\n // Sections and cm sorting.\n {watch: `transaction:start`, handler: this._startProcessing},\n {watch: `course.sectionlist:updated`, handler: this._refreshCourseSectionlist},\n {watch: `section.cmlist:updated`, handler: this._refreshSectionCmlist},\n // Reindex sections and cms.\n {watch: `state:updated`, handler: this._indexContents},\n // State changes thaty require to reload course modules.\n {watch: `cm.visible:updated`, handler: this._reloadCm},\n {watch: `cm.sectionid:updated`, handler: this._reloadCm},\n ];\n }\n\n /**\n * Update section collapsed.\n *\n * @param {object} args\n * @param {Object} args.state The state data\n * @param {Object} args.element The element to update\n */\n _refreshSectionCollapsed({state, element}) {\n const target = this.getElement(this.selectors.SECTION, element.id);\n if (!target) {\n throw new Error(`Unknown section with ID ${element.id}`);\n }\n // Check if it is already done.\n const toggler = target.querySelector(this.selectors.COLLAPSE);\n const isCollapsed = toggler?.classList.contains(this.classes.COLLAPSED) ?? false;\n\n if (element.contentcollapsed !== isCollapsed) {\n toggler.click();\n }\n\n this._refreshAllSectionsToggler(state);\n }\n\n /**\n * Refresh the collapse/expand all sections element.\n *\n * @param {Object} state The state data\n */\n _refreshAllSectionsToggler(state) {\n const target = this.getElement(this.selectors.TOGGLEALL);\n if (!target) {\n return;\n }\n // Check if we have all sections collapsed/expanded.\n let allcollapsed = true;\n let allexpanded = true;\n state.section.forEach(\n section => {\n allcollapsed = allcollapsed && section.contentcollapsed;\n allexpanded = allexpanded && !section.contentcollapsed;\n }\n );\n if (allcollapsed) {\n target.classList.add(this.classes.COLLAPSED);\n }\n if (allexpanded) {\n target.classList.remove(this.classes.COLLAPSED);\n }\n }\n\n /**\n * Setup the component to start a transaction.\n *\n * Some of the course actions replaces the current DOM element with a new one before updating the\n * course state. This means the component cannot preload any index properly until the transaction starts.\n *\n */\n _startProcessing() {\n // During a section or cm sorting, some elements could be dettached from the DOM and we\n // need to store somewhare in case they are needed later.\n this.dettachedCms = {};\n this.dettachedSections = {};\n }\n\n /**\n * Activity manual completion listener.\n *\n * @param {Event} event the custom ecent\n */\n _completionHandler({detail}) {\n if (detail === undefined) {\n return;\n }\n this.reactive.dispatch('cmCompletion', [detail.cmid], detail.completed);\n }\n\n /**\n * Check the current page scroll and update the active element if necessary.\n */\n _scrollHandler() {\n const pageOffset = document.querySelector(this.selectors.PAGE).scrollTop;\n const items = this.reactive.getExporter().allItemsArray(this.reactive.state);\n // Check what is the active element now.\n let pageItem = null;\n items.every(item => {\n const index = (item.type === 'section') ? this.sections : this.cms;\n if (index[item.id] === undefined) {\n return true;\n }\n\n const element = index[item.id].element;\n // Activities without url can only be page items in edit mode.\n if (item.type === 'cm' && !item.url && !this.reactive.isEditing) {\n return pageOffset >= element.offsetTop;\n }\n pageItem = item;\n return pageOffset >= element.offsetTop;\n });\n if (pageItem) {\n this.reactive.dispatch('setPageItem', pageItem.type, pageItem.id);\n }\n }\n\n /**\n * Update a course section when the section number changes.\n *\n * The courseActions module used for most course section tools still depends on css classes and\n * section numbers (not id). To prevent inconsistencies when a section is moved, we need to refresh\n * the\n *\n * Course formats can override the section title rendering so the frontend depends heavily on backend\n * rendering. Luckily in edit mode we can trigger a title update using the inplace_editable module.\n *\n * @param {Object} param\n * @param {Object} param.element details the update details.\n */\n _refreshSectionNumber({element}) {\n // Find the element.\n const target = this.getElement(this.selectors.SECTION, element.id);\n if (!target) {\n // Job done. Nothing to refresh.\n return;\n }\n // Update section numbers in all data, css and YUI attributes.\n target.id = `section-${element.number}`;\n // YUI uses section number as section id in data-sectionid, in principle if a format use components\n // don't need this sectionid attribute anymore, but we keep the compatibility in case some plugin\n // use it for legacy purposes.\n target.dataset.sectionid = element.number;\n // The data-number is the attribute used by components to store the section number.\n target.dataset.number = element.number;\n\n // Update title and title inplace editable, if any.\n const inplace = inplaceeditable.getInplaceEditable(target.querySelector(this.selectors.SECTION_ITEM));\n if (inplace) {\n // The course content HTML can be modified at any moment, so the function need to do some checkings\n // to make sure the inplace editable still represents the same itemid.\n const currentvalue = inplace.getValue();\n const currentitemid = inplace.getItemId();\n // Unnamed sections must be recalculated.\n if (inplace.getValue() === '') {\n // The value to send can be an empty value if it is a default name.\n if (currentitemid == element.id && (currentvalue != element.rawtitle || element.rawtitle == '')) {\n inplace.setValue(element.rawtitle);\n }\n }\n }\n }\n\n /**\n * Refresh a section cm list.\n *\n * @param {Object} param\n * @param {Object} param.element details the update details.\n */\n _refreshSectionCmlist({element}) {\n const cmlist = element.cmlist ?? [];\n const section = this.getElement(this.selectors.SECTION, element.id);\n const listparent = section?.querySelector(this.selectors.SECTION_CMLIST);\n // A method to create a fake element to be replaced when the item is ready.\n const createCm = this._createCmItem.bind(this);\n if (listparent) {\n this._fixOrder(listparent, cmlist, this.selectors.CM, this.dettachedCms, createCm);\n }\n }\n\n /**\n * Refresh the section list.\n *\n * @param {Object} param\n * @param {Object} param.element details the update details.\n */\n _refreshCourseSectionlist({element}) {\n // If we have a section return means we only show a single section so no need to fix order.\n if (this.reactive.sectionReturn != 0) {\n return;\n }\n const sectionlist = element.sectionlist ?? [];\n const listparent = this.getElement(this.selectors.COURSE_SECTIONLIST);\n // For now section cannot be created at a frontend level.\n const createSection = this._createSectionItem.bind(this);\n if (listparent) {\n this._fixOrder(listparent, sectionlist, this.selectors.SECTION, this.dettachedSections, createSection);\n }\n }\n\n /**\n * Regenerate content indexes.\n *\n * This method is used when a legacy action refresh some content element.\n */\n _indexContents() {\n // Find unindexed sections.\n this._scanIndex(\n this.selectors.SECTION,\n this.sections,\n (item) => {\n return new Section(item);\n }\n );\n\n // Find unindexed cms.\n this._scanIndex(\n this.selectors.CM,\n this.cms,\n (item) => {\n return new CmItem(item);\n }\n );\n }\n\n /**\n * Reindex a content (section or cm) of the course content.\n *\n * This method is used internally by _indexContents.\n *\n * @param {string} selector the DOM selector to scan\n * @param {*} index the index attribute to update\n * @param {*} creationhandler method to create a new indexed element\n */\n _scanIndex(selector, index, creationhandler) {\n const items = this.getElements(`${selector}:not([data-indexed])`);\n items.forEach((item) => {\n if (!item?.dataset?.id) {\n return;\n }\n // Delete previous item component.\n if (index[item.dataset.id] !== undefined) {\n index[item.dataset.id].unregister();\n }\n // Create the new component.\n index[item.dataset.id] = creationhandler({\n ...this,\n element: item,\n });\n // Mark as indexed.\n item.dataset.indexed = true;\n });\n }\n\n /**\n * Reload a course module contents.\n *\n * Most course module HTML is still strongly backend dependant.\n * Some changes require to get a new version of the module.\n *\n * @param {object} param0 the watcher details\n * @param {object} param0.element the state object\n */\n _reloadCm({element}) {\n const cmitem = this.getElement(this.selectors.CM, element.id);\n if (cmitem) {\n const promise = courseActions.refreshModule(cmitem, element.id);\n promise.then(() => {\n this._indexContents();\n return;\n }).catch();\n }\n }\n\n /**\n * Reload a course section contents.\n *\n * Section HTML is still strongly backend dependant.\n * Some changes require to get a new version of the section.\n *\n * @param {details} param0 the watcher details\n * @param {object} param0.element the state object\n */\n _reloadSection({element}) {\n const sectionitem = this.getElement(this.selectors.SECTION, element.id);\n if (sectionitem) {\n const promise = courseActions.refreshSection(sectionitem, element.id);\n promise.then(() => {\n this._indexContents();\n return;\n }).catch();\n }\n }\n\n /**\n * Create a new course module item in a section.\n *\n * Thos method will append a fake item in the container and trigger an ajax request to\n * replace the fake element by the real content.\n *\n * @param {Element} container the container element (section)\n * @param {Number} cmid the course-module ID\n * @returns {Element} the created element\n */\n _createCmItem(container, cmid) {\n const newItem = document.createElement(this.selectors.ACTIVITYTAG);\n newItem.dataset.for = 'cmitem';\n newItem.dataset.id = cmid;\n // The legacy actions.js requires a specific ID and class to refresh the CM.\n newItem.id = `module-${cmid}`;\n newItem.classList.add(this.classes.ACTIVITY);\n container.append(newItem);\n this._reloadCm({\n element: this.reactive.get('cm', cmid),\n });\n return newItem;\n }\n\n /**\n * Create a new section item.\n *\n * This method will append a fake item in the container and trigger an ajax request to\n * replace the fake element by the real content.\n *\n * @param {Element} container the container element (section)\n * @param {Number} sectionid the course-module ID\n * @returns {Element} the created element\n */\n _createSectionItem(container, sectionid) {\n const section = this.reactive.get('section', sectionid);\n const newItem = document.createElement(this.selectors.SECTIONTAG);\n newItem.dataset.for = 'section';\n newItem.dataset.id = sectionid;\n newItem.dataset.number = section.number;\n // The legacy actions.js requires a specific ID and class to refresh the section.\n newItem.id = `section-${sectionid}`;\n newItem.classList.add(this.classes.SECTION);\n container.append(newItem);\n this._reloadSection({\n element: section,\n });\n return newItem;\n }\n\n /**\n * Fix/reorder the section or cms order.\n *\n * @param {Element} container the HTML element to reorder.\n * @param {Array} neworder an array with the ids order\n * @param {string} selector the element selector\n * @param {Object} dettachedelements a list of dettached elements\n * @param {function} createMethod method to create missing elements\n */\n async _fixOrder(container, neworder, selector, dettachedelements, createMethod) {\n if (container === undefined) {\n return;\n }\n\n // Empty lists should not be visible.\n if (!neworder.length) {\n container.classList.add('hidden');\n container.innerHTML = '';\n return;\n }\n\n // Grant the list is visible (in case it was empty).\n container.classList.remove('hidden');\n\n // Move the elements in order at the beginning of the list.\n neworder.forEach((itemid, index) => {\n let item = this.getElement(selector, itemid) ?? dettachedelements[itemid] ?? createMethod(container, itemid);\n if (item === undefined) {\n // Missing elements cannot be sorted.\n return;\n }\n // Get the current elemnt at that position.\n const currentitem = container.children[index];\n if (currentitem === undefined) {\n container.append(item);\n return;\n }\n if (currentitem !== item) {\n container.insertBefore(item, currentitem);\n }\n });\n\n // Dndupload add a fake element we need to keep.\n let dndFakeActivity;\n\n // Remove the remaining elements.\n while (container.children.length > neworder.length) {\n const lastchild = container.lastChild;\n if (lastchild?.classList?.contains('dndupload-preview')) {\n dndFakeActivity = lastchild;\n } else {\n dettachedelements[lastchild?.dataset?.id ?? 0] = lastchild;\n }\n container.removeChild(lastchild);\n }\n // Restore dndupload fake element.\n if (dndFakeActivity) {\n container.append(dndFakeActivity);\n }\n }\n}\n"],"file":"content.min.js"} \ No newline at end of file +{"version":3,"sources":["../../src/local/content.js"],"names":["Component","descriptor","name","selectors","SECTION","SECTION_ITEM","SECTION_CMLIST","COURSE_SECTIONLIST","CM","PAGE","TOGGLER","COLLAPSE","TOGGLEALL","ACTIVITYTAG","SECTIONTAG","classes","COLLAPSED","ACTIVITY","STATEDREADY","dettachedCms","dettachedSections","sections","cms","sectionReturn","state","_indexContents","addEventListener","element","_sectionTogglers","toogleAll","getElement","_allSectionToggler","_refreshAllSectionsToggler","reactive","supportComponents","isEditing","DispatchActions","classList","add","CourseEvents","manualCompletionToggled","_completionHandler","document","querySelector","_scrollHandler","event","sectionlink","target","closest","isChevron","section","toggler","isCollapsed","contains","sectionId","getAttribute","dispatch","preventDefault","isAllCollapsed","course","get","sectionlist","watch","handler","_reloadCm","_refreshSectionNumber","_refreshSectionCollapsed","_startProcessing","_refreshCourseSectionlist","_refreshSectionCmlist","id","Error","contentcollapsed","click","allcollapsed","allexpanded","forEach","remove","detail","cmid","completed","pageOffset","scrollTop","items","getExporter","allItemsArray","pageItem","every","item","index","type","url","offsetTop","number","dataset","sectionid","inplace","inplaceeditable","getInplaceEditable","currentvalue","getValue","currentitemid","getItemId","rawtitle","setValue","cmlist","listparent","createCm","_createCmItem","bind","_fixOrder","createSection","_createSectionItem","_scanIndex","Section","CmItem","selector","creationhandler","getElements","unregister","indexed","cmitem","promise","courseActions","refreshModule","then","catch","sectionitem","refreshSection","container","newItem","createElement","for","append","_reloadSection","neworder","dettachedelements","createMethod","length","innerHTML","itemid","currentitem","children","insertBefore","lastchild","lastChild","dndFakeActivity","removeChild","getElementById","BaseComponent"],"mappings":"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","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Course index main component.\n *\n * @module core_courseformat/local/content\n * @class core_courseformat/local/content\n * @copyright 2020 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {BaseComponent} from 'core/reactive';\nimport {getCurrentCourseEditor} from 'core_courseformat/courseeditor';\nimport inplaceeditable from 'core/inplace_editable';\nimport Section from 'core_courseformat/local/content/section';\nimport CmItem from 'core_courseformat/local/content/section/cmitem';\n// Course actions is needed for actions that are not migrated to components.\nimport courseActions from 'core_course/actions';\nimport DispatchActions from 'core_courseformat/local/content/actions';\nimport * as CourseEvents from 'core_course/events';\n\nexport default class Component extends BaseComponent {\n\n /**\n * Constructor hook.\n *\n * @param {Object} descriptor the component descriptor\n */\n create(descriptor) {\n // Optional component name for debugging.\n this.name = 'course_format';\n // Default query selectors.\n this.selectors = {\n SECTION: `[data-for='section']`,\n SECTION_ITEM: `[data-for='section_title']`,\n SECTION_CMLIST: `[data-for='cmlist']`,\n COURSE_SECTIONLIST: `[data-for='course_sectionlist']`,\n CM: `[data-for='cmitem']`,\n PAGE: `#page`,\n TOGGLER: `[data-action=\"togglecoursecontentsection\"]`,\n COLLAPSE: `[data-toggle=\"collapse\"]`,\n TOGGLEALL: `[data-toggle=\"toggleall\"]`,\n // Formats can override the activity tag but a default one is needed to create new elements.\n ACTIVITYTAG: 'li',\n SECTIONTAG: 'li',\n };\n // Default classes to toggle on refresh.\n this.classes = {\n COLLAPSED: `collapsed`,\n // Course content classes.\n ACTIVITY: `activity`,\n STATEDREADY: `stateready`,\n SECTION: `section`,\n };\n // Array to save dettached elements during element resorting.\n this.dettachedCms = {};\n this.dettachedSections = {};\n // Index of sections and cms components.\n this.sections = {};\n this.cms = {};\n // The page section return.\n this.sectionReturn = descriptor.sectionReturn ?? 0;\n }\n\n /**\n * Static method to create a component instance form the mustahce template.\n *\n * @param {string} target the DOM main element or its ID\n * @param {object} selectors optional css selector overrides\n * @param {number} sectionReturn the content section return\n * @return {Component}\n */\n static init(target, selectors, sectionReturn) {\n return new Component({\n element: document.getElementById(target),\n reactive: getCurrentCourseEditor(),\n selectors,\n sectionReturn,\n });\n }\n\n /**\n * Initial state ready method.\n *\n * @param {Object} state the state data\n */\n stateReady(state) {\n this._indexContents();\n // Activate section togglers.\n this.addEventListener(this.element, 'click', this._sectionTogglers);\n\n // Collapse/Expand all sections button.\n const toogleAll = this.getElement(this.selectors.TOGGLEALL);\n if (toogleAll) {\n this.addEventListener(toogleAll, 'click', this._allSectionToggler);\n this._refreshAllSectionsToggler(state);\n }\n\n if (this.reactive.supportComponents) {\n // Actions are only available in edit mode.\n if (this.reactive.isEditing) {\n new DispatchActions(this);\n }\n\n // Mark content as state ready.\n this.element.classList.add(this.classes.STATEDREADY);\n }\n\n // Capture completion events.\n this.addEventListener(\n this.element,\n CourseEvents.manualCompletionToggled,\n this._completionHandler\n );\n\n // Capture page scroll to update page item.\n this.addEventListener(\n document.querySelector(this.selectors.PAGE),\n \"scroll\",\n this._scrollHandler\n );\n }\n\n /**\n * Setup sections toggler.\n *\n * Toggler click is delegated to the main course content element because new sections can\n * appear at any moment and this way we prevent accidental double bindings.\n *\n * @param {Event} event the triggered event\n */\n _sectionTogglers(event) {\n const sectionlink = event.target.closest(this.selectors.TOGGLER);\n const isChevron = event.target.closest(this.selectors.COLLAPSE);\n\n if (sectionlink || isChevron) {\n\n const section = event.target.closest(this.selectors.SECTION);\n const toggler = section.querySelector(this.selectors.COLLAPSE);\n const isCollapsed = toggler?.classList.contains(this.classes.COLLAPSED) ?? false;\n\n if (isChevron || isCollapsed) {\n // Update the state.\n const sectionId = section.getAttribute('data-id');\n this.reactive.dispatch(\n 'sectionContentCollapsed',\n [sectionId],\n !isCollapsed\n );\n }\n }\n }\n\n /**\n * Handle the collapse/expand all sections button.\n *\n * Toggler click is delegated to the main course content element because new sections can\n * appear at any moment and this way we prevent accidental double bindings.\n *\n * @param {Event} event the triggered event\n */\n _allSectionToggler(event) {\n event.preventDefault();\n\n const target = event.target.closest(this.selectors.TOGGLEALL);\n const isAllCollapsed = target.classList.contains(this.classes.COLLAPSED);\n\n const course = this.reactive.get('course');\n this.reactive.dispatch(\n 'sectionContentCollapsed',\n course.sectionlist ?? [],\n !isAllCollapsed\n );\n }\n\n /**\n * Return the component watchers.\n *\n * @returns {Array} of watchers\n */\n getWatchers() {\n // Section return is a global page variable but most formats define it just before start printing\n // the course content. This is the reason why we define this page setting here.\n this.reactive.sectionReturn = this.sectionReturn;\n\n // Check if the course format is compatible with reactive components.\n if (!this.reactive.supportComponents) {\n return [];\n }\n return [\n // State changes that require to reload some course modules.\n {watch: `cm.visible:updated`, handler: this._reloadCm},\n // Update section number and title.\n {watch: `section.number:updated`, handler: this._refreshSectionNumber},\n // Collapse and expand sections.\n {watch: `section.contentcollapsed:updated`, handler: this._refreshSectionCollapsed},\n // Sections and cm sorting.\n {watch: `transaction:start`, handler: this._startProcessing},\n {watch: `course.sectionlist:updated`, handler: this._refreshCourseSectionlist},\n {watch: `section.cmlist:updated`, handler: this._refreshSectionCmlist},\n // Reindex sections and cms.\n {watch: `state:updated`, handler: this._indexContents},\n // State changes thaty require to reload course modules.\n {watch: `cm.visible:updated`, handler: this._reloadCm},\n {watch: `cm.sectionid:updated`, handler: this._reloadCm},\n ];\n }\n\n /**\n * Update section collapsed.\n *\n * @param {object} args\n * @param {Object} args.state The state data\n * @param {Object} args.element The element to update\n */\n _refreshSectionCollapsed({state, element}) {\n const target = this.getElement(this.selectors.SECTION, element.id);\n if (!target) {\n throw new Error(`Unknown section with ID ${element.id}`);\n }\n // Check if it is already done.\n const toggler = target.querySelector(this.selectors.COLLAPSE);\n const isCollapsed = toggler?.classList.contains(this.classes.COLLAPSED) ?? false;\n\n if (element.contentcollapsed !== isCollapsed) {\n toggler.click();\n }\n\n this._refreshAllSectionsToggler(state);\n }\n\n /**\n * Refresh the collapse/expand all sections element.\n *\n * @param {Object} state The state data\n */\n _refreshAllSectionsToggler(state) {\n const target = this.getElement(this.selectors.TOGGLEALL);\n if (!target) {\n return;\n }\n // Check if we have all sections collapsed/expanded.\n let allcollapsed = true;\n let allexpanded = true;\n state.section.forEach(\n section => {\n allcollapsed = allcollapsed && section.contentcollapsed;\n allexpanded = allexpanded && !section.contentcollapsed;\n }\n );\n if (allcollapsed) {\n target.classList.add(this.classes.COLLAPSED);\n }\n if (allexpanded) {\n target.classList.remove(this.classes.COLLAPSED);\n }\n }\n\n /**\n * Setup the component to start a transaction.\n *\n * Some of the course actions replaces the current DOM element with a new one before updating the\n * course state. This means the component cannot preload any index properly until the transaction starts.\n *\n */\n _startProcessing() {\n // During a section or cm sorting, some elements could be dettached from the DOM and we\n // need to store somewhare in case they are needed later.\n this.dettachedCms = {};\n this.dettachedSections = {};\n }\n\n /**\n * Activity manual completion listener.\n *\n * @param {Event} event the custom ecent\n */\n _completionHandler({detail}) {\n if (detail === undefined) {\n return;\n }\n this.reactive.dispatch('cmCompletion', [detail.cmid], detail.completed);\n }\n\n /**\n * Check the current page scroll and update the active element if necessary.\n */\n _scrollHandler() {\n const pageOffset = document.querySelector(this.selectors.PAGE).scrollTop;\n const items = this.reactive.getExporter().allItemsArray(this.reactive.state);\n // Check what is the active element now.\n let pageItem = null;\n items.every(item => {\n const index = (item.type === 'section') ? this.sections : this.cms;\n if (index[item.id] === undefined) {\n return true;\n }\n\n const element = index[item.id].element;\n // Activities without url can only be page items in edit mode.\n if (item.type === 'cm' && !item.url && !this.reactive.isEditing) {\n return pageOffset >= element.offsetTop;\n }\n pageItem = item;\n return pageOffset >= element.offsetTop;\n });\n if (pageItem) {\n this.reactive.dispatch('setPageItem', pageItem.type, pageItem.id);\n }\n }\n\n /**\n * Update a course section when the section number changes.\n *\n * The courseActions module used for most course section tools still depends on css classes and\n * section numbers (not id). To prevent inconsistencies when a section is moved, we need to refresh\n * the\n *\n * Course formats can override the section title rendering so the frontend depends heavily on backend\n * rendering. Luckily in edit mode we can trigger a title update using the inplace_editable module.\n *\n * @param {Object} param\n * @param {Object} param.element details the update details.\n */\n _refreshSectionNumber({element}) {\n // Find the element.\n const target = this.getElement(this.selectors.SECTION, element.id);\n if (!target) {\n // Job done. Nothing to refresh.\n return;\n }\n // Update section numbers in all data, css and YUI attributes.\n target.id = `section-${element.number}`;\n // YUI uses section number as section id in data-sectionid, in principle if a format use components\n // don't need this sectionid attribute anymore, but we keep the compatibility in case some plugin\n // use it for legacy purposes.\n target.dataset.sectionid = element.number;\n // The data-number is the attribute used by components to store the section number.\n target.dataset.number = element.number;\n\n // Update title and title inplace editable, if any.\n const inplace = inplaceeditable.getInplaceEditable(target.querySelector(this.selectors.SECTION_ITEM));\n if (inplace) {\n // The course content HTML can be modified at any moment, so the function need to do some checkings\n // to make sure the inplace editable still represents the same itemid.\n const currentvalue = inplace.getValue();\n const currentitemid = inplace.getItemId();\n // Unnamed sections must be recalculated.\n if (inplace.getValue() === '') {\n // The value to send can be an empty value if it is a default name.\n if (currentitemid == element.id && (currentvalue != element.rawtitle || element.rawtitle == '')) {\n inplace.setValue(element.rawtitle);\n }\n }\n }\n }\n\n /**\n * Refresh a section cm list.\n *\n * @param {Object} param\n * @param {Object} param.element details the update details.\n */\n _refreshSectionCmlist({element}) {\n const cmlist = element.cmlist ?? [];\n const section = this.getElement(this.selectors.SECTION, element.id);\n const listparent = section?.querySelector(this.selectors.SECTION_CMLIST);\n // A method to create a fake element to be replaced when the item is ready.\n const createCm = this._createCmItem.bind(this);\n if (listparent) {\n this._fixOrder(listparent, cmlist, this.selectors.CM, this.dettachedCms, createCm);\n }\n }\n\n /**\n * Refresh the section list.\n *\n * @param {Object} param\n * @param {Object} param.element details the update details.\n */\n _refreshCourseSectionlist({element}) {\n // If we have a section return means we only show a single section so no need to fix order.\n if (this.reactive.sectionReturn != 0) {\n return;\n }\n const sectionlist = element.sectionlist ?? [];\n const listparent = this.getElement(this.selectors.COURSE_SECTIONLIST);\n // For now section cannot be created at a frontend level.\n const createSection = this._createSectionItem.bind(this);\n if (listparent) {\n this._fixOrder(listparent, sectionlist, this.selectors.SECTION, this.dettachedSections, createSection);\n }\n }\n\n /**\n * Regenerate content indexes.\n *\n * This method is used when a legacy action refresh some content element.\n */\n _indexContents() {\n // Find unindexed sections.\n this._scanIndex(\n this.selectors.SECTION,\n this.sections,\n (item) => {\n return new Section(item);\n }\n );\n\n // Find unindexed cms.\n this._scanIndex(\n this.selectors.CM,\n this.cms,\n (item) => {\n return new CmItem(item);\n }\n );\n }\n\n /**\n * Reindex a content (section or cm) of the course content.\n *\n * This method is used internally by _indexContents.\n *\n * @param {string} selector the DOM selector to scan\n * @param {*} index the index attribute to update\n * @param {*} creationhandler method to create a new indexed element\n */\n _scanIndex(selector, index, creationhandler) {\n const items = this.getElements(`${selector}:not([data-indexed])`);\n items.forEach((item) => {\n if (!item?.dataset?.id) {\n return;\n }\n // Delete previous item component.\n if (index[item.dataset.id] !== undefined) {\n index[item.dataset.id].unregister();\n }\n // Create the new component.\n index[item.dataset.id] = creationhandler({\n ...this,\n element: item,\n });\n // Mark as indexed.\n item.dataset.indexed = true;\n });\n }\n\n /**\n * Reload a course module contents.\n *\n * Most course module HTML is still strongly backend dependant.\n * Some changes require to get a new version of the module.\n *\n * @param {object} param0 the watcher details\n * @param {object} param0.element the state object\n */\n _reloadCm({element}) {\n const cmitem = this.getElement(this.selectors.CM, element.id);\n if (cmitem) {\n const promise = courseActions.refreshModule(cmitem, element.id);\n promise.then(() => {\n this._indexContents();\n return;\n }).catch();\n }\n }\n\n /**\n * Reload a course section contents.\n *\n * Section HTML is still strongly backend dependant.\n * Some changes require to get a new version of the section.\n *\n * @param {details} param0 the watcher details\n * @param {object} param0.element the state object\n */\n _reloadSection({element}) {\n const sectionitem = this.getElement(this.selectors.SECTION, element.id);\n if (sectionitem) {\n const promise = courseActions.refreshSection(sectionitem, element.id);\n promise.then(() => {\n this._indexContents();\n return;\n }).catch();\n }\n }\n\n /**\n * Create a new course module item in a section.\n *\n * Thos method will append a fake item in the container and trigger an ajax request to\n * replace the fake element by the real content.\n *\n * @param {Element} container the container element (section)\n * @param {Number} cmid the course-module ID\n * @returns {Element} the created element\n */\n _createCmItem(container, cmid) {\n const newItem = document.createElement(this.selectors.ACTIVITYTAG);\n newItem.dataset.for = 'cmitem';\n newItem.dataset.id = cmid;\n // The legacy actions.js requires a specific ID and class to refresh the CM.\n newItem.id = `module-${cmid}`;\n newItem.classList.add(this.classes.ACTIVITY);\n container.append(newItem);\n this._reloadCm({\n element: this.reactive.get('cm', cmid),\n });\n return newItem;\n }\n\n /**\n * Create a new section item.\n *\n * This method will append a fake item in the container and trigger an ajax request to\n * replace the fake element by the real content.\n *\n * @param {Element} container the container element (section)\n * @param {Number} sectionid the course-module ID\n * @returns {Element} the created element\n */\n _createSectionItem(container, sectionid) {\n const section = this.reactive.get('section', sectionid);\n const newItem = document.createElement(this.selectors.SECTIONTAG);\n newItem.dataset.for = 'section';\n newItem.dataset.id = sectionid;\n newItem.dataset.number = section.number;\n // The legacy actions.js requires a specific ID and class to refresh the section.\n newItem.id = `section-${sectionid}`;\n newItem.classList.add(this.classes.SECTION);\n container.append(newItem);\n this._reloadSection({\n element: section,\n });\n return newItem;\n }\n\n /**\n * Fix/reorder the section or cms order.\n *\n * @param {Element} container the HTML element to reorder.\n * @param {Array} neworder an array with the ids order\n * @param {string} selector the element selector\n * @param {Object} dettachedelements a list of dettached elements\n * @param {function} createMethod method to create missing elements\n */\n async _fixOrder(container, neworder, selector, dettachedelements, createMethod) {\n if (container === undefined) {\n return;\n }\n\n // Empty lists should not be visible.\n if (!neworder.length) {\n container.classList.add('hidden');\n container.innerHTML = '';\n return;\n }\n\n // Grant the list is visible (in case it was empty).\n container.classList.remove('hidden');\n\n // Move the elements in order at the beginning of the list.\n neworder.forEach((itemid, index) => {\n let item = this.getElement(selector, itemid) ?? dettachedelements[itemid] ?? createMethod(container, itemid);\n if (item === undefined) {\n // Missing elements cannot be sorted.\n return;\n }\n // Get the current elemnt at that position.\n const currentitem = container.children[index];\n if (currentitem === undefined) {\n container.append(item);\n return;\n }\n if (currentitem !== item) {\n container.insertBefore(item, currentitem);\n }\n });\n\n // Dndupload add a fake element we need to keep.\n let dndFakeActivity;\n\n // Remove the remaining elements.\n while (container.children.length > neworder.length) {\n const lastchild = container.lastChild;\n if (lastchild?.classList?.contains('dndupload-preview')) {\n dndFakeActivity = lastchild;\n } else {\n dettachedelements[lastchild?.dataset?.id ?? 0] = lastchild;\n }\n container.removeChild(lastchild);\n }\n // Restore dndupload fake element.\n if (dndFakeActivity) {\n container.append(dndFakeActivity);\n }\n }\n}\n"],"file":"content.min.js"} \ No newline at end of file diff --git a/course/format/amd/build/local/courseeditor/mutations.min.js b/course/format/amd/build/local/courseeditor/mutations.min.js index bdbe2ce7427..a53dd7d2e24 100644 --- a/course/format/amd/build/local/courseeditor/mutations.min.js +++ b/course/format/amd/build/local/courseeditor/mutations.min.js @@ -1,2 +1,2 @@ -define ("core_courseformat/local/courseeditor/mutations",["exports","core/ajax"],function(a,b){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;b=function(a){return a&&a.__esModule?a:{default:a}}(b);function c(a){return g(a)||f(a)||e(a)||d()}function d(){throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function e(a,b){if(!a)return;if("string"==typeof a)return h(a,b);var c=Object.prototype.toString.call(a).slice(8,-1);if("Object"===c&&a.constructor)c=a.constructor.name;if("Map"===c||"Set"===c)return Array.from(c);if("Arguments"===c||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(c))return h(a,b)}function f(a){if("undefined"!=typeof Symbol&&Symbol.iterator in Object(a))return Array.from(a)}function g(a){if(Array.isArray(a))return h(a)}function h(a,b){if(null==b||b>a.length)b=a.length;for(var c=0,d=Array(b);c.\n\nimport ajax from 'core/ajax';\n\n/**\n * Default mutation manager\n *\n * @module core_courseformat/local/courseeditor/mutations\n * @class core_courseformat/local/courseeditor/mutations\n * @copyright 2021 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nexport default class {\n\n // All course editor mutations for Moodle 4.0 will be located in this file.\n\n /**\n * Private method to call core_courseformat_update_course webservice.\n *\n * @method _callEditWebservice\n * @param {string} action\n * @param {number} courseId\n * @param {array} ids\n * @param {number} targetSectionId optional target section id (for moving actions)\n * @param {number} targetCmId optional target cm id (for moving actions)\n */\n async _callEditWebservice(action, courseId, ids, targetSectionId, targetCmId) {\n const args = {\n action,\n courseid: courseId,\n ids,\n };\n if (targetSectionId) {\n args.targetsectionid = targetSectionId;\n }\n if (targetCmId) {\n args.targetcmid = targetCmId;\n }\n let ajaxresult = await ajax.call([{\n methodname: 'core_courseformat_update_course',\n args,\n }])[0];\n return JSON.parse(ajaxresult);\n }\n\n\n /**\n * Mutation module initialize.\n *\n * The reactive instance will execute this method when addMutations or setMutation is invoked.\n *\n * @param {StateManager} stateManager the state manager\n */\n init(stateManager) {\n // Add a method to prepare the fields when some update is comming from the server.\n stateManager.addUpdateTypes({\n prepareFields: this._prepareFields,\n });\n }\n\n /**\n * Add default values to state elements.\n *\n * This method is called every time a webservice returns a update state message.\n *\n * @param {Object} stateManager the state manager\n * @param {String} updateName the state element to update\n * @param {Object} fields the new data\n * @returns {Object} final fields data\n */\n _prepareFields(stateManager, updateName, fields) {\n // Any update should unlock the element.\n fields.locked = false;\n return fields;\n }\n\n /**\n * Move course modules to specific course location.\n *\n * Note that one of targetSectionId or targetCmId should be provided in order to identify the\n * new location:\n * - targetCmId: the activities will be located avobe the target cm. The targetSectionId\n * value will be ignored in this case.\n * - targetSectionId: the activities will be appended to the section. In this case\n * targetSectionId should not be present.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} cmids the list of cm ids to move\n * @param {number} targetSectionId the target section id\n * @param {number} targetCmId the target course module id\n */\n async cmMove(stateManager, cmids, targetSectionId, targetCmId) {\n if (!targetSectionId && !targetCmId) {\n throw new Error(`Mutation cmMove requires targetSectionId or targetCmId`);\n }\n const course = stateManager.get('course');\n this.cmLock(stateManager, cmids, true);\n const updates = await this._callEditWebservice('cm_move', course.id, cmids, targetSectionId, targetCmId);\n stateManager.processUpdates(updates);\n this.cmLock(stateManager, cmids, false);\n }\n\n /**\n * Move course modules to specific course location.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} sectionIds the list of section ids to move\n * @param {number} targetSectionId the target section id\n */\n async sectionMove(stateManager, sectionIds, targetSectionId) {\n if (!targetSectionId) {\n throw new Error(`Mutation sectionMove requires targetSectionId`);\n }\n const course = stateManager.get('course');\n this.sectionLock(stateManager, sectionIds, true);\n const updates = await this._callEditWebservice('section_move', course.id, sectionIds, targetSectionId);\n stateManager.processUpdates(updates);\n this.sectionLock(stateManager, sectionIds, false);\n }\n\n /**\n * Add a new section to a specific course location.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {number} targetSectionId optional the target section id\n */\n async addSection(stateManager, targetSectionId) {\n if (!targetSectionId) {\n targetSectionId = 0;\n }\n const course = stateManager.get('course');\n const updates = await this._callEditWebservice('section_add', course.id, [], targetSectionId);\n stateManager.processUpdates(updates);\n }\n\n /**\n * Delete sections.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} sectionIds the list of course modules ids\n */\n async sectionDelete(stateManager, sectionIds) {\n const course = stateManager.get('course');\n const updates = await this._callEditWebservice('section_delete', course.id, sectionIds);\n stateManager.processUpdates(updates);\n }\n\n /**\n * Mark or unmark course modules as dragging.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} cmIds the list of course modules ids\n * @param {bool} dragValue the new dragging value\n */\n cmDrag(stateManager, cmIds, dragValue) {\n this.setPageItem(stateManager);\n this._setElementsValue(stateManager, 'cm', cmIds, 'dragging', dragValue);\n }\n\n /**\n * Mark or unmark course sections as dragging.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} sectionIds the list of section ids\n * @param {bool} dragValue the new dragging value\n */\n sectionDrag(stateManager, sectionIds, dragValue) {\n this.setPageItem(stateManager);\n this._setElementsValue(stateManager, 'section', sectionIds, 'dragging', dragValue);\n }\n\n /**\n * Mark or unmark course modules as complete.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} cmIds the list of course modules ids\n * @param {bool} complete the new completion value\n */\n cmCompletion(stateManager, cmIds, complete) {\n const newValue = (complete) ? 1 : 0;\n this._setElementsValue(stateManager, 'cm', cmIds, 'completionstate', newValue);\n }\n\n /**\n * Lock or unlock course modules.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} cmIds the list of course modules ids\n * @param {bool} lockValue the new locked value\n */\n cmLock(stateManager, cmIds, lockValue) {\n this._setElementsValue(stateManager, 'cm', cmIds, 'locked', lockValue);\n }\n\n /**\n * Lock or unlock course sections.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} sectionIds the list of section ids\n * @param {bool} lockValue the new locked value\n */\n sectionLock(stateManager, sectionIds, lockValue) {\n this._setElementsValue(stateManager, 'section', sectionIds, 'locked', lockValue);\n }\n\n _setElementsValue(stateManager, name, ids, fieldName, newValue) {\n stateManager.setReadOnly(false);\n ids.forEach((id) => {\n const element = stateManager.get(name, id);\n if (element) {\n element[fieldName] = newValue;\n }\n });\n stateManager.setReadOnly(true);\n }\n\n /**\n * Set the page current item.\n *\n * Only one element of the course state can be the page item at a time.\n *\n * There are several actions that can alter the page current item. For example, when the user is in an activity\n * page, the page item is always the activity one. However, in a course page, when the user scrolls to an element,\n * this element get the page item.\n *\n * If the page item is static means that it is not meant to change. This is important because\n * static page items has some special logic. For example, if a cm is the static page item\n * and it is inside a collapsed section, the course index will expand the section to make it visible.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {String|undefined} type the element type (section or cm). Undefined will remove the current page item.\n * @param {Number|undefined} id the element id\n * @param {boolean|undefined} isStatic if the page item is static\n */\n setPageItem(stateManager, type, id, isStatic) {\n let newPageItem;\n if (type !== undefined) {\n newPageItem = stateManager.get(type, id);\n if (!newPageItem) {\n return;\n }\n }\n stateManager.setReadOnly(false);\n // Remove the current page item.\n const course = stateManager.get('course');\n course.pageItem = null;\n // Save the new page item.\n if (newPageItem) {\n course.pageItem = {\n id,\n type,\n sectionId: (type == 'section') ? newPageItem.id : newPageItem.sectionid,\n isStatic,\n };\n }\n stateManager.setReadOnly(true);\n }\n\n /**\n * Unlock all course elements.\n *\n * @param {StateManager} stateManager the current state manager\n */\n unlockAll(stateManager) {\n const state = stateManager.state;\n stateManager.setReadOnly(false);\n state.section.forEach((section) => {\n section.locked = false;\n });\n state.cm.forEach((cm) => {\n cm.locked = false;\n });\n stateManager.setReadOnly(true);\n }\n\n /*\n * Get updated user preferences and state data related to some section ids.\n *\n * @param {StateManager} stateManager the current state\n * @param {array} sectionIds the list of section ids to update\n * @param {Object} preferences the new preferences values\n */\n async sectionPreferences(stateManager, sectionIds, preferences) {\n stateManager.setReadOnly(false);\n const affectedSections = new Set();\n // Check if we need to update preferences.\n sectionIds.forEach(sectionId => {\n const section = stateManager.get('section', sectionId);\n if (section === undefined) {\n return;\n }\n let newValue = preferences.contentcollapsed ?? section.contentcollapsed;\n if (section.contentcollapsed != newValue) {\n section.contentcollapsed = newValue;\n affectedSections.add(section.id);\n }\n newValue = preferences.indexcollapsed ?? section.indexcollapsed;\n if (section.indexcollapsed != newValue) {\n section.indexcollapsed = newValue;\n affectedSections.add(section.id);\n }\n });\n stateManager.setReadOnly(true);\n\n if (affectedSections.size > 0) {\n // Build the preference structures.\n const course = stateManager.get('course');\n const state = stateManager.state;\n const prefKey = `coursesectionspreferences_${course.id}`;\n const preferences = {\n contentcollapsed: [],\n indexcollapsed: [],\n };\n state.section.forEach(section => {\n if (section.contentcollapsed) {\n preferences.contentcollapsed.push(section.id);\n }\n if (section.indexcollapsed) {\n preferences.indexcollapsed.push(section.id);\n }\n });\n const jsonString = JSON.stringify(preferences);\n M.util.set_user_preference(prefKey, jsonString);\n // Inform the backend of the change.\n await this._callEditWebservice('topic_preferences_updated', course.id, [...affectedSections]);\n }\n }\n\n /**\n * Get updated state data related to some cm ids.\n *\n * @method cmState\n * @param {StateManager} stateManager the current state\n * @param {array} cmids the list of cm ids to update\n */\n async cmState(stateManager, cmids) {\n this.cmLock(stateManager, cmids, true);\n const course = stateManager.get('course');\n const updates = await this._callEditWebservice('cm_state', course.id, cmids);\n stateManager.processUpdates(updates);\n this.cmLock(stateManager, cmids, false);\n }\n\n /**\n * Get updated state data related to some section ids.\n *\n * @method sectionState\n * @param {StateManager} stateManager the current state\n * @param {array} sectionIds the list of section ids to update\n */\n async sectionState(stateManager, sectionIds) {\n this.sectionLock(stateManager, sectionIds, true);\n const course = stateManager.get('course');\n const updates = await this._callEditWebservice('section_state', course.id, sectionIds);\n stateManager.processUpdates(updates);\n this.sectionLock(stateManager, sectionIds, false);\n }\n\n /**\n * Get the full updated state data of the course.\n *\n * @param {StateManager} stateManager the current state\n */\n async courseState(stateManager) {\n const course = stateManager.get('course');\n const updates = await this._callEditWebservice('course_state', course.id);\n stateManager.processUpdates(updates);\n }\n\n}\n"],"file":"mutations.min.js"} \ No newline at end of file +{"version":3,"sources":["../../../src/local/courseeditor/mutations.js"],"names":["action","courseId","ids","targetSectionId","targetCmId","args","courseid","targetsectionid","targetcmid","ajax","call","methodname","ajaxresult","JSON","parse","stateManager","addUpdateTypes","prepareFields","_prepareFields","updateName","fields","locked","cmids","Error","course","get","cmLock","_callEditWebservice","id","updates","processUpdates","sectionIds","sectionLock","cmIds","dragValue","setPageItem","_setElementsValue","complete","newValue","lockValue","name","fieldName","setReadOnly","forEach","element","type","isStatic","newPageItem","pageItem","sectionId","sectionid","state","section","cm","collapsed","collapsedIds","_updateStateSectionPreference","preferenceName","preferenceValue","affectedSections","Set","add","size","collapsedSectionIds","push"],"mappings":"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","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\nimport ajax from 'core/ajax';\n\n/**\n * Default mutation manager\n *\n * @module core_courseformat/local/courseeditor/mutations\n * @class core_courseformat/local/courseeditor/mutations\n * @copyright 2021 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nexport default class {\n\n // All course editor mutations for Moodle 4.0 will be located in this file.\n\n /**\n * Private method to call core_courseformat_update_course webservice.\n *\n * @method _callEditWebservice\n * @param {string} action\n * @param {number} courseId\n * @param {array} ids\n * @param {number} targetSectionId optional target section id (for moving actions)\n * @param {number} targetCmId optional target cm id (for moving actions)\n */\n async _callEditWebservice(action, courseId, ids, targetSectionId, targetCmId) {\n const args = {\n action,\n courseid: courseId,\n ids,\n };\n if (targetSectionId) {\n args.targetsectionid = targetSectionId;\n }\n if (targetCmId) {\n args.targetcmid = targetCmId;\n }\n let ajaxresult = await ajax.call([{\n methodname: 'core_courseformat_update_course',\n args,\n }])[0];\n return JSON.parse(ajaxresult);\n }\n\n\n /**\n * Mutation module initialize.\n *\n * The reactive instance will execute this method when addMutations or setMutation is invoked.\n *\n * @param {StateManager} stateManager the state manager\n */\n init(stateManager) {\n // Add a method to prepare the fields when some update is comming from the server.\n stateManager.addUpdateTypes({\n prepareFields: this._prepareFields,\n });\n }\n\n /**\n * Add default values to state elements.\n *\n * This method is called every time a webservice returns a update state message.\n *\n * @param {Object} stateManager the state manager\n * @param {String} updateName the state element to update\n * @param {Object} fields the new data\n * @returns {Object} final fields data\n */\n _prepareFields(stateManager, updateName, fields) {\n // Any update should unlock the element.\n fields.locked = false;\n return fields;\n }\n\n /**\n * Move course modules to specific course location.\n *\n * Note that one of targetSectionId or targetCmId should be provided in order to identify the\n * new location:\n * - targetCmId: the activities will be located avobe the target cm. The targetSectionId\n * value will be ignored in this case.\n * - targetSectionId: the activities will be appended to the section. In this case\n * targetSectionId should not be present.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} cmids the list of cm ids to move\n * @param {number} targetSectionId the target section id\n * @param {number} targetCmId the target course module id\n */\n async cmMove(stateManager, cmids, targetSectionId, targetCmId) {\n if (!targetSectionId && !targetCmId) {\n throw new Error(`Mutation cmMove requires targetSectionId or targetCmId`);\n }\n const course = stateManager.get('course');\n this.cmLock(stateManager, cmids, true);\n const updates = await this._callEditWebservice('cm_move', course.id, cmids, targetSectionId, targetCmId);\n stateManager.processUpdates(updates);\n this.cmLock(stateManager, cmids, false);\n }\n\n /**\n * Move course modules to specific course location.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} sectionIds the list of section ids to move\n * @param {number} targetSectionId the target section id\n */\n async sectionMove(stateManager, sectionIds, targetSectionId) {\n if (!targetSectionId) {\n throw new Error(`Mutation sectionMove requires targetSectionId`);\n }\n const course = stateManager.get('course');\n this.sectionLock(stateManager, sectionIds, true);\n const updates = await this._callEditWebservice('section_move', course.id, sectionIds, targetSectionId);\n stateManager.processUpdates(updates);\n this.sectionLock(stateManager, sectionIds, false);\n }\n\n /**\n * Add a new section to a specific course location.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {number} targetSectionId optional the target section id\n */\n async addSection(stateManager, targetSectionId) {\n if (!targetSectionId) {\n targetSectionId = 0;\n }\n const course = stateManager.get('course');\n const updates = await this._callEditWebservice('section_add', course.id, [], targetSectionId);\n stateManager.processUpdates(updates);\n }\n\n /**\n * Delete sections.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} sectionIds the list of course modules ids\n */\n async sectionDelete(stateManager, sectionIds) {\n const course = stateManager.get('course');\n const updates = await this._callEditWebservice('section_delete', course.id, sectionIds);\n stateManager.processUpdates(updates);\n }\n\n /**\n * Mark or unmark course modules as dragging.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} cmIds the list of course modules ids\n * @param {bool} dragValue the new dragging value\n */\n cmDrag(stateManager, cmIds, dragValue) {\n this.setPageItem(stateManager);\n this._setElementsValue(stateManager, 'cm', cmIds, 'dragging', dragValue);\n }\n\n /**\n * Mark or unmark course sections as dragging.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} sectionIds the list of section ids\n * @param {bool} dragValue the new dragging value\n */\n sectionDrag(stateManager, sectionIds, dragValue) {\n this.setPageItem(stateManager);\n this._setElementsValue(stateManager, 'section', sectionIds, 'dragging', dragValue);\n }\n\n /**\n * Mark or unmark course modules as complete.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} cmIds the list of course modules ids\n * @param {bool} complete the new completion value\n */\n cmCompletion(stateManager, cmIds, complete) {\n const newValue = (complete) ? 1 : 0;\n this._setElementsValue(stateManager, 'cm', cmIds, 'completionstate', newValue);\n }\n\n /**\n * Lock or unlock course modules.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} cmIds the list of course modules ids\n * @param {bool} lockValue the new locked value\n */\n cmLock(stateManager, cmIds, lockValue) {\n this._setElementsValue(stateManager, 'cm', cmIds, 'locked', lockValue);\n }\n\n /**\n * Lock or unlock course sections.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} sectionIds the list of section ids\n * @param {bool} lockValue the new locked value\n */\n sectionLock(stateManager, sectionIds, lockValue) {\n this._setElementsValue(stateManager, 'section', sectionIds, 'locked', lockValue);\n }\n\n _setElementsValue(stateManager, name, ids, fieldName, newValue) {\n stateManager.setReadOnly(false);\n ids.forEach((id) => {\n const element = stateManager.get(name, id);\n if (element) {\n element[fieldName] = newValue;\n }\n });\n stateManager.setReadOnly(true);\n }\n\n /**\n * Set the page current item.\n *\n * Only one element of the course state can be the page item at a time.\n *\n * There are several actions that can alter the page current item. For example, when the user is in an activity\n * page, the page item is always the activity one. However, in a course page, when the user scrolls to an element,\n * this element get the page item.\n *\n * If the page item is static means that it is not meant to change. This is important because\n * static page items has some special logic. For example, if a cm is the static page item\n * and it is inside a collapsed section, the course index will expand the section to make it visible.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {String|undefined} type the element type (section or cm). Undefined will remove the current page item.\n * @param {Number|undefined} id the element id\n * @param {boolean|undefined} isStatic if the page item is static\n */\n setPageItem(stateManager, type, id, isStatic) {\n let newPageItem;\n if (type !== undefined) {\n newPageItem = stateManager.get(type, id);\n if (!newPageItem) {\n return;\n }\n }\n stateManager.setReadOnly(false);\n // Remove the current page item.\n const course = stateManager.get('course');\n course.pageItem = null;\n // Save the new page item.\n if (newPageItem) {\n course.pageItem = {\n id,\n type,\n sectionId: (type == 'section') ? newPageItem.id : newPageItem.sectionid,\n isStatic,\n };\n }\n stateManager.setReadOnly(true);\n }\n\n /**\n * Unlock all course elements.\n *\n * @param {StateManager} stateManager the current state manager\n */\n unlockAll(stateManager) {\n const state = stateManager.state;\n stateManager.setReadOnly(false);\n state.section.forEach((section) => {\n section.locked = false;\n });\n state.cm.forEach((cm) => {\n cm.locked = false;\n });\n stateManager.setReadOnly(true);\n }\n\n /**\n * Update the course index collapsed attribute of some sections.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} sectionIds the affected section ids\n * @param {boolean} collapsed the new collapsed value\n */\n async sectionIndexCollapsed(stateManager, sectionIds, collapsed) {\n const collapsedIds = this._updateStateSectionPreference(stateManager, 'indexcollapsed', sectionIds, collapsed);\n const course = stateManager.get('course');\n await this._callEditWebservice('section_index_collapsed', course.id, collapsedIds);\n }\n\n /**\n * Update the course content collapsed attribute of some sections.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} sectionIds the affected section ids\n * @param {boolean} collapsed the new collapsed value\n */\n async sectionContentCollapsed(stateManager, sectionIds, collapsed) {\n const collapsedIds = this._updateStateSectionPreference(stateManager, 'contentcollapsed', sectionIds, collapsed);\n const course = stateManager.get('course');\n await this._callEditWebservice('section_content_collapsed', course.id, collapsedIds);\n }\n\n /**\n * Private batch update for a section preference attribute.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {string} preferenceName the preference name\n * @param {array} sectionIds the affected section ids\n * @param {boolean} preferenceValue the new preferenceValue value\n * @return {array} the list of all sections with that preference set to true\n */\n _updateStateSectionPreference(stateManager, preferenceName, sectionIds, preferenceValue) {\n stateManager.setReadOnly(false);\n const affectedSections = new Set();\n // Check if we need to update preferences.\n sectionIds.forEach(sectionId => {\n const section = stateManager.get('section', sectionId);\n if (section === undefined) {\n return;\n }\n const newValue = preferenceValue ?? section[preferenceName];\n if (section[preferenceName] != newValue) {\n section[preferenceName] = newValue;\n affectedSections.add(section.id);\n }\n });\n stateManager.setReadOnly(true);\n if (affectedSections.size == 0) {\n return [];\n }\n // Get all collapsed section ids.\n const collapsedSectionIds = [];\n const state = stateManager.state;\n state.section.forEach(section => {\n if (section[preferenceName]) {\n collapsedSectionIds.push(section.id);\n }\n });\n return collapsedSectionIds;\n }\n\n /**\n * Get updated state data related to some cm ids.\n *\n * @method cmState\n * @param {StateManager} stateManager the current state\n * @param {array} cmids the list of cm ids to update\n */\n async cmState(stateManager, cmids) {\n this.cmLock(stateManager, cmids, true);\n const course = stateManager.get('course');\n const updates = await this._callEditWebservice('cm_state', course.id, cmids);\n stateManager.processUpdates(updates);\n this.cmLock(stateManager, cmids, false);\n }\n\n /**\n * Get updated state data related to some section ids.\n *\n * @method sectionState\n * @param {StateManager} stateManager the current state\n * @param {array} sectionIds the list of section ids to update\n */\n async sectionState(stateManager, sectionIds) {\n this.sectionLock(stateManager, sectionIds, true);\n const course = stateManager.get('course');\n const updates = await this._callEditWebservice('section_state', course.id, sectionIds);\n stateManager.processUpdates(updates);\n this.sectionLock(stateManager, sectionIds, false);\n }\n\n /**\n * Get the full updated state data of the course.\n *\n * @param {StateManager} stateManager the current state\n */\n async courseState(stateManager) {\n const course = stateManager.get('course');\n const updates = await this._callEditWebservice('course_state', course.id);\n stateManager.processUpdates(updates);\n }\n\n}\n"],"file":"mutations.min.js"} \ No newline at end of file diff --git a/course/format/amd/build/local/courseindex/courseindex.min.js b/course/format/amd/build/local/courseindex/courseindex.min.js index 86aad9e2d37..b10144926b1 100644 --- a/course/format/amd/build/local/courseindex/courseindex.min.js +++ b/course/format/amd/build/local/courseindex/courseindex.min.js @@ -1,2 +1,2 @@ -define ("core_courseformat/local/courseindex/courseindex",["exports","core/reactive","core_courseformat/courseeditor","jquery","core_courseformat/local/courseeditor/contenttree"],function(a,b,c,d,e){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;d=f(d);e=f(e);function f(a){return a&&a.__esModule?a:{default:a}}function g(a){"@babel/helpers - typeof";if("function"==typeof Symbol&&"symbol"==typeof Symbol.iterator){g=function(a){return typeof a}}else{g=function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?"symbol":typeof a}}return g(a)}function h(a,b,c,d,e,f,g){try{var h=a[f](g),i=h.value}catch(a){c(a);return}if(h.done){b(i)}else{Promise.resolve(i).then(d,e)}}function i(a){return function(){var b=this,c=arguments;return new Promise(function(d,e){var i=a.apply(b,c);function f(a){h(i,d,e,f,g,"next",a)}function g(a){h(i,d,e,f,g,"throw",a)}f(void 0)})}}function j(a,b){if(!(a instanceof b)){throw new TypeError("Cannot call a class as a function")}}function k(a,b){for(var c=0,d;cb.length){a.removeChild(a.lastChild)}}},{key:"_deleteCm",value:function _deleteCm(a){var b=a.element;delete this.cms[b.id]}},{key:"_deleteSection",value:function _deleteSection(a){var b=a.element;delete this.sections[b.id]}}],[{key:"init",value:function init(a,d){return new b({element:document.getElementById(a),reactive:(0,c.getCurrentCourseEditor)(),selectors:d})}}]);return b}(b.BaseComponent);a.default=t;return a.default}); +define ("core_courseformat/local/courseindex/courseindex",["exports","core/reactive","core_courseformat/courseeditor","jquery","core_courseformat/local/courseeditor/contenttree"],function(a,b,c,d,e){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;d=f(d);e=f(e);function f(a){return a&&a.__esModule?a:{default:a}}function g(a){"@babel/helpers - typeof";if("function"==typeof Symbol&&"symbol"==typeof Symbol.iterator){g=function(a){return typeof a}}else{g=function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?"symbol":typeof a}}return g(a)}function h(a,b,c,d,e,f,g){try{var h=a[f](g),i=h.value}catch(a){c(a);return}if(h.done){b(i)}else{Promise.resolve(i).then(d,e)}}function i(a){return function(){var b=this,c=arguments;return new Promise(function(d,e){var i=a.apply(b,c);function f(a){h(i,d,e,f,g,"next",a)}function g(a){h(i,d,e,f,g,"throw",a)}f(void 0)})}}function j(a,b){if(!(a instanceof b)){throw new TypeError("Cannot call a class as a function")}}function k(a,b){for(var c=0,d;cb.length){a.removeChild(a.lastChild)}}},{key:"_deleteCm",value:function _deleteCm(a){var b=a.element;delete this.cms[b.id]}},{key:"_deleteSection",value:function _deleteSection(a){var b=a.element;delete this.sections[b.id]}}],[{key:"init",value:function init(a,d){return new b({element:document.getElementById(a),reactive:(0,c.getCurrentCourseEditor)(),selectors:d})}}]);return b}(b.BaseComponent);a.default=t;return a.default}); //# sourceMappingURL=courseindex.min.js.map diff --git a/course/format/amd/build/local/courseindex/courseindex.min.js.map b/course/format/amd/build/local/courseindex/courseindex.min.js.map index 309f48ee5fc..5ac2805ae77 100644 --- a/course/format/amd/build/local/courseindex/courseindex.min.js.map +++ b/course/format/amd/build/local/courseindex/courseindex.min.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../src/local/courseindex/courseindex.js"],"names":["Component","name","selectors","SECTION","SECTION_CMLIST","CM","TOGGLER","COLLAPSE","DRAWER","classes","SECTIONHIDDEN","CMHIDDEN","SECTIONCURRENT","COLLAPSED","SHOW","sections","cms","state","addEventListener","element","_sectionTogglers","getElements","forEach","section","dataset","id","cm","_refreshPageItem","course","contentTree","ContentTree","reactive","isEditing","watch","handler","_refreshSectionCollapsed","_createCm","_deleteCm","_createSection","_deleteSection","_refreshCourseSectionlist","_refreshSectionCmlist","event","sectionlink","target","closest","isChevron","toggler","querySelector","isCollapsed","classList","contains","sectionId","getAttribute","dispatch","indexcollapsed","getElement","Error","_expandSectionNode","forceValue","collapsibleId","replace","collapsible","document","getElementById","togglerValue","collapse","pageItem","isStatic","type","get","setTimeout","scrollIntoView","block","fakeelement","createElement","add","innerHTML","sectionid","exporter","getExporter","data","renderComponent","newcomponent","newelement","parentNode","replaceChild","cmlist","listparent","_fixOrder","sectionlist","container","neworder","allitems","length","remove","itemid","index","item","currentitem","children","append","insertBefore","removeChild","lastChild","BaseComponent"],"mappings":"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","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Course index main component.\n *\n * @module core_courseformat/local/courseindex/courseindex\n * @class core_courseformat/local/courseindex/courseindex\n * @copyright 2021 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {BaseComponent} from 'core/reactive';\nimport {getCurrentCourseEditor} from 'core_courseformat/courseeditor';\nimport jQuery from 'jquery';\nimport ContentTree from 'core_courseformat/local/courseeditor/contenttree';\n\nexport default class Component extends BaseComponent {\n\n /**\n * Constructor hook.\n */\n create() {\n // Optional component name for debugging.\n this.name = 'courseindex';\n // Default query selectors.\n this.selectors = {\n SECTION: `[data-for='section']`,\n SECTION_CMLIST: `[data-for='cmlist']`,\n CM: `[data-for='cm']`,\n TOGGLER: `[data-action=\"togglecourseindexsection\"]`,\n COLLAPSE: `[data-toggle=\"collapse\"]`,\n DRAWER: `.drawer`,\n };\n // Default classes to toggle on refresh.\n this.classes = {\n SECTIONHIDDEN: 'dimmed',\n CMHIDDEN: 'dimmed',\n SECTIONCURRENT: 'current',\n COLLAPSED: `collapsed`,\n SHOW: `show`,\n };\n // Arrays to keep cms and sections elements.\n this.sections = {};\n this.cms = {};\n }\n\n /**\n * Static method to create a component instance form the mustache template.\n *\n * @param {element|string} target the DOM main element or its ID\n * @param {object} selectors optional css selector overrides\n * @return {Component}\n */\n static init(target, selectors) {\n return new Component({\n element: document.getElementById(target),\n reactive: getCurrentCourseEditor(),\n selectors,\n });\n }\n\n /**\n * Initial state ready method.\n *\n * @param {Object} state the state data\n */\n stateReady(state) {\n // Activate section togglers.\n this.addEventListener(this.element, 'click', this._sectionTogglers);\n\n // Get cms and sections elements.\n const sections = this.getElements(this.selectors.SECTION);\n sections.forEach((section) => {\n this.sections[section.dataset.id] = section;\n });\n const cms = this.getElements(this.selectors.CM);\n cms.forEach((cm) => {\n this.cms[cm.dataset.id] = cm;\n });\n\n // Set the page item if any.\n this._refreshPageItem({element: state.course, state});\n\n // Configure Aria Tree.\n this.contentTree = new ContentTree(this.element, this.selectors, this.reactive.isEditing);\n }\n\n getWatchers() {\n return [\n {watch: `section.indexcollapsed:updated`, handler: this._refreshSectionCollapsed},\n {watch: `cm:created`, handler: this._createCm},\n {watch: `cm:deleted`, handler: this._deleteCm},\n {watch: `section:created`, handler: this._createSection},\n {watch: `section:deleted`, handler: this._deleteSection},\n {watch: `course.pageItem:created`, handler: this._refreshPageItem},\n {watch: `course.pageItem:updated`, handler: this._refreshPageItem},\n // Sections and cm sorting.\n {watch: `course.sectionlist:updated`, handler: this._refreshCourseSectionlist},\n {watch: `section.cmlist:updated`, handler: this._refreshSectionCmlist},\n ];\n }\n\n /**\n * Setup sections toggler.\n *\n * Toggler click is delegated to the main course index element because new sections can\n * appear at any moment and this way we prevent accidental double bindings.\n *\n * @param {Event} event the triggered event\n */\n _sectionTogglers(event) {\n const sectionlink = event.target.closest(this.selectors.TOGGLER);\n const isChevron = event.target.closest(this.selectors.COLLAPSE);\n\n if (sectionlink || isChevron) {\n\n const section = event.target.closest(this.selectors.SECTION);\n const toggler = section.querySelector(this.selectors.COLLAPSE);\n const isCollapsed = toggler?.classList.contains(this.classes.COLLAPSED) ?? false;\n\n if (isChevron || isCollapsed) {\n // Update the state.\n const sectionId = section.getAttribute('data-id');\n this.reactive.dispatch(\n 'sectionPreferences',\n [sectionId],\n {\n indexcollapsed: !isCollapsed,\n },\n );\n }\n }\n }\n\n /**\n * Update section collapsed.\n *\n * @param {object} args\n * @param {object} args.element The leement to be expanded\n */\n _refreshSectionCollapsed({element}) {\n const target = this.getElement(this.selectors.SECTION, element.id);\n if (!target) {\n throw new Error(`Unkown section with ID ${element.id}`);\n }\n // Check if it is already done.\n const toggler = target.querySelector(this.selectors.COLLAPSE);\n const isCollapsed = toggler?.classList.contains(this.classes.COLLAPSED) ?? false;\n\n if (element.indexcollapsed !== isCollapsed) {\n this._expandSectionNode(element);\n }\n }\n\n /**\n * Expand a section node.\n *\n * By default the method will use element.indexcollapsed to decide if the\n * section is opened or closed. However, using forceValue it is possible\n * to open or close a section independant from the indexcollapsed attribute.\n *\n * @param {Object} element the course module state element\n * @param {boolean} forceValue optional forced expanded value\n */\n _expandSectionNode(element, forceValue) {\n const target = this.getElement(this.selectors.SECTION, element.id);\n const toggler = target.querySelector(this.selectors.COLLAPSE);\n let collapsibleId = toggler.dataset.target ?? toggler.getAttribute(\"href\");\n if (!collapsibleId) {\n return;\n }\n collapsibleId = collapsibleId.replace('#', '');\n const collapsible = document.getElementById(collapsibleId);\n if (!collapsible) {\n return;\n }\n\n if (forceValue === undefined) {\n forceValue = (element.indexcollapsed) ? false : true;\n }\n\n // Course index is based on Bootstrap 4 collapsibles. To collapse them we need jQuery to\n // interact with collapsibles methods. Hopefully, this will change in Bootstrap 5 because\n // it does not require jQuery anymore (when MDL-79179 is integrated).\n const togglerValue = (forceValue) ? 'show' : 'hide';\n jQuery(collapsible).collapse(togglerValue);\n }\n\n /**\n * Handle a page item update.\n *\n * @param {Object} details the update details\n * @param {Object} details.state the state data.\n * @param {Object} details.element the course state data.\n */\n _refreshPageItem({element, state}) {\n if (!element?.pageItem?.isStatic || element.pageItem.type != 'cm') {\n return;\n }\n // Check if we need to uncollapse the section and scroll to the element.\n const section = state.section.get(element.pageItem.sectionId);\n if (section.indexcollapsed) {\n this._expandSectionNode(section, true);\n setTimeout(\n () => this.cms[element.pageItem.id]?.scrollIntoView({block: \"nearest\"}),\n 250\n );\n }\n }\n\n /**\n * Create a newcm instance.\n *\n * @param {object} param\n * @param {Object} param.state\n * @param {Object} param.element\n */\n async _createCm({state, element}) {\n // Create a fake node while the component is loading.\n const fakeelement = document.createElement('li');\n fakeelement.classList.add('bg-pulse-grey', 'w-100');\n fakeelement.innerHTML = ' ';\n this.cms[element.id] = fakeelement;\n // Place the fake node on the correct position.\n this._refreshSectionCmlist({\n state,\n element: state.section.get(element.sectionid),\n });\n // Collect render data.\n const exporter = this.reactive.getExporter();\n const data = exporter.cm(state, element);\n // Create the new content.\n const newcomponent = await this.renderComponent(fakeelement, 'core_courseformat/local/courseindex/cm', data);\n // Replace the fake node with the real content.\n const newelement = newcomponent.getElement();\n this.cms[element.id] = newelement;\n fakeelement.parentNode.replaceChild(newelement, fakeelement);\n }\n\n /**\n * Create a new section instance.\n *\n * @param {Object} details the update details.\n * @param {Object} details.state the state data.\n * @param {Object} details.element the element data.\n */\n async _createSection({state, element}) {\n // Create a fake node while the component is loading.\n const fakeelement = document.createElement('div');\n fakeelement.classList.add('bg-pulse-grey', 'w-100');\n fakeelement.innerHTML = ' ';\n this.sections[element.id] = fakeelement;\n // Place the fake node on the correct position.\n this._refreshCourseSectionlist({\n state,\n element: state.course,\n });\n // Collect render data.\n const exporter = this.reactive.getExporter();\n const data = exporter.section(state, element);\n // Create the new content.\n const newcomponent = await this.renderComponent(fakeelement, 'core_courseformat/local/courseindex/section', data);\n // Replace the fake node with the real content.\n const newelement = newcomponent.getElement();\n this.sections[element.id] = newelement;\n fakeelement.parentNode.replaceChild(newelement, fakeelement);\n }\n\n /**\n * Refresh a section cm list.\n *\n * @param {object} param\n * @param {Object} param.element\n */\n _refreshSectionCmlist({element}) {\n const cmlist = element.cmlist ?? [];\n const listparent = this.getElement(this.selectors.SECTION_CMLIST, element.id);\n this._fixOrder(listparent, cmlist, this.cms);\n }\n\n /**\n * Refresh the section list.\n *\n * @param {object} param\n * @param {Object} param.element\n */\n _refreshCourseSectionlist({element}) {\n const sectionlist = element.sectionlist ?? [];\n this._fixOrder(this.element, sectionlist, this.sections);\n }\n\n /**\n * Fix/reorder the section or cms order.\n *\n * @param {Element} container the HTML element to reorder.\n * @param {Array} neworder an array with the ids order\n * @param {Array} allitems the list of html elements that can be placed in the container\n */\n _fixOrder(container, neworder, allitems) {\n\n // Empty lists should not be visible.\n if (!neworder.length) {\n container.classList.add('hidden');\n container.innerHTML = '';\n return;\n }\n\n // Grant the list is visible (in case it was empty).\n container.classList.remove('hidden');\n\n // Move the elements in order at the beginning of the list.\n neworder.forEach((itemid, index) => {\n const item = allitems[itemid];\n // Get the current element at that position.\n const currentitem = container.children[index];\n if (currentitem === undefined) {\n container.append(item);\n return;\n }\n if (currentitem !== item) {\n container.insertBefore(item, currentitem);\n }\n });\n // Remove the remaining elements.\n while (container.children.length > neworder.length) {\n container.removeChild(container.lastChild);\n }\n }\n\n /**\n * Remove a cm from the list.\n *\n * The actual DOM element removal is delegated to the cm component.\n *\n * @param {object} param\n * @param {Object} param.element\n */\n _deleteCm({element}) {\n delete this.cms[element.id];\n }\n\n /**\n * Remove a section from the list.\n *\n * The actual DOM element removal is delegated to the section component.\n *\n * @param {Object} details the update details.\n * @param {Object} details.element the element data.\n */\n _deleteSection({element}) {\n delete this.sections[element.id];\n }\n}\n"],"file":"courseindex.min.js"} \ No newline at end of file +{"version":3,"sources":["../../../src/local/courseindex/courseindex.js"],"names":["Component","name","selectors","SECTION","SECTION_CMLIST","CM","TOGGLER","COLLAPSE","DRAWER","classes","SECTIONHIDDEN","CMHIDDEN","SECTIONCURRENT","COLLAPSED","SHOW","sections","cms","state","addEventListener","element","_sectionTogglers","getElements","forEach","section","dataset","id","cm","_refreshPageItem","course","contentTree","ContentTree","reactive","isEditing","watch","handler","_refreshSectionCollapsed","_createCm","_deleteCm","_createSection","_deleteSection","_refreshCourseSectionlist","_refreshSectionCmlist","event","sectionlink","target","closest","isChevron","toggler","querySelector","isCollapsed","classList","contains","sectionId","getAttribute","dispatch","getElement","Error","indexcollapsed","_expandSectionNode","forceValue","collapsibleId","replace","collapsible","document","getElementById","togglerValue","collapse","pageItem","isStatic","type","get","setTimeout","scrollIntoView","block","fakeelement","createElement","add","innerHTML","sectionid","exporter","getExporter","data","renderComponent","newcomponent","newelement","parentNode","replaceChild","cmlist","listparent","_fixOrder","sectionlist","container","neworder","allitems","length","remove","itemid","index","item","currentitem","children","append","insertBefore","removeChild","lastChild","BaseComponent"],"mappings":"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","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Course index main component.\n *\n * @module core_courseformat/local/courseindex/courseindex\n * @class core_courseformat/local/courseindex/courseindex\n * @copyright 2021 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {BaseComponent} from 'core/reactive';\nimport {getCurrentCourseEditor} from 'core_courseformat/courseeditor';\nimport jQuery from 'jquery';\nimport ContentTree from 'core_courseformat/local/courseeditor/contenttree';\n\nexport default class Component extends BaseComponent {\n\n /**\n * Constructor hook.\n */\n create() {\n // Optional component name for debugging.\n this.name = 'courseindex';\n // Default query selectors.\n this.selectors = {\n SECTION: `[data-for='section']`,\n SECTION_CMLIST: `[data-for='cmlist']`,\n CM: `[data-for='cm']`,\n TOGGLER: `[data-action=\"togglecourseindexsection\"]`,\n COLLAPSE: `[data-toggle=\"collapse\"]`,\n DRAWER: `.drawer`,\n };\n // Default classes to toggle on refresh.\n this.classes = {\n SECTIONHIDDEN: 'dimmed',\n CMHIDDEN: 'dimmed',\n SECTIONCURRENT: 'current',\n COLLAPSED: `collapsed`,\n SHOW: `show`,\n };\n // Arrays to keep cms and sections elements.\n this.sections = {};\n this.cms = {};\n }\n\n /**\n * Static method to create a component instance form the mustache template.\n *\n * @param {element|string} target the DOM main element or its ID\n * @param {object} selectors optional css selector overrides\n * @return {Component}\n */\n static init(target, selectors) {\n return new Component({\n element: document.getElementById(target),\n reactive: getCurrentCourseEditor(),\n selectors,\n });\n }\n\n /**\n * Initial state ready method.\n *\n * @param {Object} state the state data\n */\n stateReady(state) {\n // Activate section togglers.\n this.addEventListener(this.element, 'click', this._sectionTogglers);\n\n // Get cms and sections elements.\n const sections = this.getElements(this.selectors.SECTION);\n sections.forEach((section) => {\n this.sections[section.dataset.id] = section;\n });\n const cms = this.getElements(this.selectors.CM);\n cms.forEach((cm) => {\n this.cms[cm.dataset.id] = cm;\n });\n\n // Set the page item if any.\n this._refreshPageItem({element: state.course, state});\n\n // Configure Aria Tree.\n this.contentTree = new ContentTree(this.element, this.selectors, this.reactive.isEditing);\n }\n\n getWatchers() {\n return [\n {watch: `section.indexcollapsed:updated`, handler: this._refreshSectionCollapsed},\n {watch: `cm:created`, handler: this._createCm},\n {watch: `cm:deleted`, handler: this._deleteCm},\n {watch: `section:created`, handler: this._createSection},\n {watch: `section:deleted`, handler: this._deleteSection},\n {watch: `course.pageItem:created`, handler: this._refreshPageItem},\n {watch: `course.pageItem:updated`, handler: this._refreshPageItem},\n // Sections and cm sorting.\n {watch: `course.sectionlist:updated`, handler: this._refreshCourseSectionlist},\n {watch: `section.cmlist:updated`, handler: this._refreshSectionCmlist},\n ];\n }\n\n /**\n * Setup sections toggler.\n *\n * Toggler click is delegated to the main course index element because new sections can\n * appear at any moment and this way we prevent accidental double bindings.\n *\n * @param {Event} event the triggered event\n */\n _sectionTogglers(event) {\n const sectionlink = event.target.closest(this.selectors.TOGGLER);\n const isChevron = event.target.closest(this.selectors.COLLAPSE);\n\n if (sectionlink || isChevron) {\n\n const section = event.target.closest(this.selectors.SECTION);\n const toggler = section.querySelector(this.selectors.COLLAPSE);\n const isCollapsed = toggler?.classList.contains(this.classes.COLLAPSED) ?? false;\n\n if (isChevron || isCollapsed) {\n // Update the state.\n const sectionId = section.getAttribute('data-id');\n this.reactive.dispatch(\n 'sectionIndexCollapsed',\n [sectionId],\n !isCollapsed\n );\n }\n }\n }\n\n /**\n * Update section collapsed.\n *\n * @param {object} args\n * @param {object} args.element The leement to be expanded\n */\n _refreshSectionCollapsed({element}) {\n const target = this.getElement(this.selectors.SECTION, element.id);\n if (!target) {\n throw new Error(`Unkown section with ID ${element.id}`);\n }\n // Check if it is already done.\n const toggler = target.querySelector(this.selectors.COLLAPSE);\n const isCollapsed = toggler?.classList.contains(this.classes.COLLAPSED) ?? false;\n\n if (element.indexcollapsed !== isCollapsed) {\n this._expandSectionNode(element);\n }\n }\n\n /**\n * Expand a section node.\n *\n * By default the method will use element.indexcollapsed to decide if the\n * section is opened or closed. However, using forceValue it is possible\n * to open or close a section independant from the indexcollapsed attribute.\n *\n * @param {Object} element the course module state element\n * @param {boolean} forceValue optional forced expanded value\n */\n _expandSectionNode(element, forceValue) {\n const target = this.getElement(this.selectors.SECTION, element.id);\n const toggler = target.querySelector(this.selectors.COLLAPSE);\n let collapsibleId = toggler.dataset.target ?? toggler.getAttribute(\"href\");\n if (!collapsibleId) {\n return;\n }\n collapsibleId = collapsibleId.replace('#', '');\n const collapsible = document.getElementById(collapsibleId);\n if (!collapsible) {\n return;\n }\n\n if (forceValue === undefined) {\n forceValue = (element.indexcollapsed) ? false : true;\n }\n\n // Course index is based on Bootstrap 4 collapsibles. To collapse them we need jQuery to\n // interact with collapsibles methods. Hopefully, this will change in Bootstrap 5 because\n // it does not require jQuery anymore (when MDL-79179 is integrated).\n const togglerValue = (forceValue) ? 'show' : 'hide';\n jQuery(collapsible).collapse(togglerValue);\n }\n\n /**\n * Handle a page item update.\n *\n * @param {Object} details the update details\n * @param {Object} details.state the state data.\n * @param {Object} details.element the course state data.\n */\n _refreshPageItem({element, state}) {\n if (!element?.pageItem?.isStatic || element.pageItem.type != 'cm') {\n return;\n }\n // Check if we need to uncollapse the section and scroll to the element.\n const section = state.section.get(element.pageItem.sectionId);\n if (section.indexcollapsed) {\n this._expandSectionNode(section, true);\n setTimeout(\n () => this.cms[element.pageItem.id]?.scrollIntoView({block: \"nearest\"}),\n 250\n );\n }\n }\n\n /**\n * Create a newcm instance.\n *\n * @param {object} param\n * @param {Object} param.state\n * @param {Object} param.element\n */\n async _createCm({state, element}) {\n // Create a fake node while the component is loading.\n const fakeelement = document.createElement('li');\n fakeelement.classList.add('bg-pulse-grey', 'w-100');\n fakeelement.innerHTML = ' ';\n this.cms[element.id] = fakeelement;\n // Place the fake node on the correct position.\n this._refreshSectionCmlist({\n state,\n element: state.section.get(element.sectionid),\n });\n // Collect render data.\n const exporter = this.reactive.getExporter();\n const data = exporter.cm(state, element);\n // Create the new content.\n const newcomponent = await this.renderComponent(fakeelement, 'core_courseformat/local/courseindex/cm', data);\n // Replace the fake node with the real content.\n const newelement = newcomponent.getElement();\n this.cms[element.id] = newelement;\n fakeelement.parentNode.replaceChild(newelement, fakeelement);\n }\n\n /**\n * Create a new section instance.\n *\n * @param {Object} details the update details.\n * @param {Object} details.state the state data.\n * @param {Object} details.element the element data.\n */\n async _createSection({state, element}) {\n // Create a fake node while the component is loading.\n const fakeelement = document.createElement('div');\n fakeelement.classList.add('bg-pulse-grey', 'w-100');\n fakeelement.innerHTML = ' ';\n this.sections[element.id] = fakeelement;\n // Place the fake node on the correct position.\n this._refreshCourseSectionlist({\n state,\n element: state.course,\n });\n // Collect render data.\n const exporter = this.reactive.getExporter();\n const data = exporter.section(state, element);\n // Create the new content.\n const newcomponent = await this.renderComponent(fakeelement, 'core_courseformat/local/courseindex/section', data);\n // Replace the fake node with the real content.\n const newelement = newcomponent.getElement();\n this.sections[element.id] = newelement;\n fakeelement.parentNode.replaceChild(newelement, fakeelement);\n }\n\n /**\n * Refresh a section cm list.\n *\n * @param {object} param\n * @param {Object} param.element\n */\n _refreshSectionCmlist({element}) {\n const cmlist = element.cmlist ?? [];\n const listparent = this.getElement(this.selectors.SECTION_CMLIST, element.id);\n this._fixOrder(listparent, cmlist, this.cms);\n }\n\n /**\n * Refresh the section list.\n *\n * @param {object} param\n * @param {Object} param.element\n */\n _refreshCourseSectionlist({element}) {\n const sectionlist = element.sectionlist ?? [];\n this._fixOrder(this.element, sectionlist, this.sections);\n }\n\n /**\n * Fix/reorder the section or cms order.\n *\n * @param {Element} container the HTML element to reorder.\n * @param {Array} neworder an array with the ids order\n * @param {Array} allitems the list of html elements that can be placed in the container\n */\n _fixOrder(container, neworder, allitems) {\n\n // Empty lists should not be visible.\n if (!neworder.length) {\n container.classList.add('hidden');\n container.innerHTML = '';\n return;\n }\n\n // Grant the list is visible (in case it was empty).\n container.classList.remove('hidden');\n\n // Move the elements in order at the beginning of the list.\n neworder.forEach((itemid, index) => {\n const item = allitems[itemid];\n // Get the current element at that position.\n const currentitem = container.children[index];\n if (currentitem === undefined) {\n container.append(item);\n return;\n }\n if (currentitem !== item) {\n container.insertBefore(item, currentitem);\n }\n });\n // Remove the remaining elements.\n while (container.children.length > neworder.length) {\n container.removeChild(container.lastChild);\n }\n }\n\n /**\n * Remove a cm from the list.\n *\n * The actual DOM element removal is delegated to the cm component.\n *\n * @param {object} param\n * @param {Object} param.element\n */\n _deleteCm({element}) {\n delete this.cms[element.id];\n }\n\n /**\n * Remove a section from the list.\n *\n * The actual DOM element removal is delegated to the section component.\n *\n * @param {Object} details the update details.\n * @param {Object} details.element the element data.\n */\n _deleteSection({element}) {\n delete this.sections[element.id];\n }\n}\n"],"file":"courseindex.min.js"} \ No newline at end of file diff --git a/course/format/amd/src/local/content.js b/course/format/amd/src/local/content.js index 223f9bed63b..10c6d5bce13 100644 --- a/course/format/amd/src/local/content.js +++ b/course/format/amd/src/local/content.js @@ -156,11 +156,9 @@ export default class Component extends BaseComponent { // Update the state. const sectionId = section.getAttribute('data-id'); this.reactive.dispatch( - 'sectionPreferences', + 'sectionContentCollapsed', [sectionId], - { - contentcollapsed: !isCollapsed, - }, + !isCollapsed ); } } @@ -182,11 +180,9 @@ export default class Component extends BaseComponent { const course = this.reactive.get('course'); this.reactive.dispatch( - 'sectionPreferences', + 'sectionContentCollapsed', course.sectionlist ?? [], - { - contentcollapsed: !isAllCollapsed, - } + !isAllCollapsed ); } diff --git a/course/format/amd/src/local/courseeditor/mutations.js b/course/format/amd/src/local/courseeditor/mutations.js index 7cca89bfda2..2c12b0a9002 100644 --- a/course/format/amd/src/local/courseeditor/mutations.js +++ b/course/format/amd/src/local/courseeditor/mutations.js @@ -286,14 +286,42 @@ export default class { stateManager.setReadOnly(true); } - /* - * Get updated user preferences and state data related to some section ids. + /** + * Update the course index collapsed attribute of some sections. * - * @param {StateManager} stateManager the current state - * @param {array} sectionIds the list of section ids to update - * @param {Object} preferences the new preferences values + * @param {StateManager} stateManager the current state manager + * @param {array} sectionIds the affected section ids + * @param {boolean} collapsed the new collapsed value */ - async sectionPreferences(stateManager, sectionIds, preferences) { + async sectionIndexCollapsed(stateManager, sectionIds, collapsed) { + const collapsedIds = this._updateStateSectionPreference(stateManager, 'indexcollapsed', sectionIds, collapsed); + const course = stateManager.get('course'); + await this._callEditWebservice('section_index_collapsed', course.id, collapsedIds); + } + + /** + * Update the course content collapsed attribute of some sections. + * + * @param {StateManager} stateManager the current state manager + * @param {array} sectionIds the affected section ids + * @param {boolean} collapsed the new collapsed value + */ + async sectionContentCollapsed(stateManager, sectionIds, collapsed) { + const collapsedIds = this._updateStateSectionPreference(stateManager, 'contentcollapsed', sectionIds, collapsed); + const course = stateManager.get('course'); + await this._callEditWebservice('section_content_collapsed', course.id, collapsedIds); + } + + /** + * Private batch update for a section preference attribute. + * + * @param {StateManager} stateManager the current state manager + * @param {string} preferenceName the preference name + * @param {array} sectionIds the affected section ids + * @param {boolean} preferenceValue the new preferenceValue value + * @return {array} the list of all sections with that preference set to true + */ + _updateStateSectionPreference(stateManager, preferenceName, sectionIds, preferenceValue) { stateManager.setReadOnly(false); const affectedSections = new Set(); // Check if we need to update preferences. @@ -302,41 +330,25 @@ export default class { if (section === undefined) { return; } - let newValue = preferences.contentcollapsed ?? section.contentcollapsed; - if (section.contentcollapsed != newValue) { - section.contentcollapsed = newValue; - affectedSections.add(section.id); - } - newValue = preferences.indexcollapsed ?? section.indexcollapsed; - if (section.indexcollapsed != newValue) { - section.indexcollapsed = newValue; + const newValue = preferenceValue ?? section[preferenceName]; + if (section[preferenceName] != newValue) { + section[preferenceName] = newValue; affectedSections.add(section.id); } }); stateManager.setReadOnly(true); - - if (affectedSections.size > 0) { - // Build the preference structures. - const course = stateManager.get('course'); - const state = stateManager.state; - const prefKey = `coursesectionspreferences_${course.id}`; - const preferences = { - contentcollapsed: [], - indexcollapsed: [], - }; - state.section.forEach(section => { - if (section.contentcollapsed) { - preferences.contentcollapsed.push(section.id); - } - if (section.indexcollapsed) { - preferences.indexcollapsed.push(section.id); - } - }); - const jsonString = JSON.stringify(preferences); - M.util.set_user_preference(prefKey, jsonString); - // Inform the backend of the change. - await this._callEditWebservice('topic_preferences_updated', course.id, [...affectedSections]); + if (affectedSections.size == 0) { + return []; } + // Get all collapsed section ids. + const collapsedSectionIds = []; + const state = stateManager.state; + state.section.forEach(section => { + if (section[preferenceName]) { + collapsedSectionIds.push(section.id); + } + }); + return collapsedSectionIds; } /** diff --git a/course/format/amd/src/local/courseindex/courseindex.js b/course/format/amd/src/local/courseindex/courseindex.js index 9cc270c0e65..0556e30defb 100644 --- a/course/format/amd/src/local/courseindex/courseindex.js +++ b/course/format/amd/src/local/courseindex/courseindex.js @@ -135,11 +135,9 @@ export default class Component extends BaseComponent { // Update the state. const sectionId = section.getAttribute('data-id'); this.reactive.dispatch( - 'sectionPreferences', + 'sectionIndexCollapsed', [sectionId], - { - indexcollapsed: !isCollapsed, - }, + !isCollapsed ); } } diff --git a/course/format/classes/base.php b/course/format/classes/base.php index 0e87fb1db25..25963ca097c 100644 --- a/course/format/classes/base.php +++ b/course/format/classes/base.php @@ -550,6 +550,8 @@ abstract class base { /** * Return the format section preferences. + * + * @return array of preferences indexed by sectionid */ public function get_sections_preferences(): array { global $USER; @@ -564,17 +566,7 @@ abstract class base { return $coursesections; } - // Calculate collapsed preferences. - try { - $sectionpreferences = (array) json_decode( - get_user_preferences('coursesectionspreferences_' . $course->id, null, $USER->id) - ); - if (empty($sectionpreferences)) { - $sectionpreferences = []; - } - } catch (\Throwable $e) { - $sectionpreferences = []; - } + $sectionpreferences = $this->get_sections_preferences_by_preference(); foreach ($sectionpreferences as $preference => $sectionids) { if (!empty($sectionids) && is_array($sectionids)) { @@ -588,10 +580,48 @@ abstract class base { } $coursesectionscache->set($course->id, $result); - return $result; } + /** + * Return the format section preferences. + * + * @return array of preferences indexed by preference name + */ + public function get_sections_preferences_by_preference(): array { + global $USER; + $course = $this->get_course(); + try { + $sectionpreferences = (array) json_decode( + get_user_preferences('coursesectionspreferences_' . $course->id, null, $USER->id) + ); + if (empty($sectionpreferences)) { + $sectionpreferences = []; + } + } catch (\Throwable $e) { + $sectionpreferences = []; + } + return $sectionpreferences; + } + + /** + * Return the format section preferences. + * + * @param string $preferencename preference name + * @param int[] $sectionids affected section ids + * + */ + public function set_sections_preference(string $preferencename, array $sectionids) { + global $USER; + $course = $this->get_course(); + $sectionpreferences = $this->get_sections_preferences_by_preference(); + $sectionpreferences[$preferencename] = $sectionids; + set_user_preference('coursesectionspreferences_' . $course->id, json_encode($sectionpreferences), $USER->id); + // Invalidate section preferences cache. + $coursesectionscache = cache::make('core', 'coursesectionspreferences'); + $coursesectionscache->delete($course->id); + } + /** * Returns the information about the ajax support in the given source format * diff --git a/course/format/classes/stateactions.php b/course/format/classes/stateactions.php index 42df0f13399..935c6e6690b 100644 --- a/course/format/classes/stateactions.php +++ b/course/format/classes/stateactions.php @@ -289,11 +289,7 @@ class stateactions { } /** - * Some of the topic preferences has been updated. - * - * Section preferences can be handled by many user actions and even some of them can affect - * only the frontend part. Format plugins can override this method to add extra logic to the - * section preferences. + * Update the course content section collapsed value. * * @param stateupdates $updates the affected course elements track * @param stdClass $course the course object @@ -301,15 +297,41 @@ class stateactions { * @param int $targetsectionid not used * @param int $targetcmid not used */ - public function topic_preferences_updated( + public function section_content_collapsed( stateupdates $updates, stdClass $course, array $ids = [], ?int $targetsectionid = null, ?int $targetcmid = null ): void { - // Format plugins may override this method to provide extra functionalities to - // section preferences. + if (!empty($ids)) { + $this->validate_sections($course, $ids, __FUNCTION__); + } + $format = course_get_format($course->id); + $format->set_sections_preference('contentcollapsed', $ids); + } + + /** + * Update the course index section collapsed value. + * + * @param stateupdates $updates the affected course elements track + * @param stdClass $course the course object + * @param int[] $ids the collapsed section ids + * @param int $targetsectionid not used + * @param int $targetcmid not used + */ + public function section_index_collapsed( + stateupdates $updates, + stdClass $course, + array $ids = [], + ?int $targetsectionid = null, + ?int $targetcmid = null + ): void { + if (!empty($ids)) { + $this->validate_sections($course, $ids, __FUNCTION__); + } + $format = course_get_format($course->id); + $format->set_sections_preference('indexcollapsed', $ids); } /** diff --git a/course/format/tests/base_test.php b/course/format/tests/base_test.php index 4d6f9b8bb47..0a6eb84a511 100644 --- a/course/format/tests/base_test.php +++ b/course/format/tests/base_test.php @@ -261,9 +261,7 @@ class base_test extends advanced_testcase { * @covers ::get_sections_preferences */ public function test_get_sections_preferences() { - $this->resetAfterTest(); - $generator = $this->getDataGenerator(); $course = $generator->create_course(); $user = $generator->create_and_enrol($course, 'student'); @@ -289,7 +287,36 @@ class base_test extends advanced_testcase { (object)['pref1' => true], $preferences[2] ); + } + /** + * Test for the default delete format data behaviour. + * + * @covers ::set_sections_preference + */ + public function test_set_sections_preference() { + $this->resetAfterTest(); + $generator = $this->getDataGenerator(); + $course = $generator->create_course(); + $user = $generator->create_and_enrol($course, 'student'); + + $format = course_get_format($course); + $this->setUser($user); + + // Load data from user 1. + $format->set_sections_preference('pref1', [1, 2]); + $format->set_sections_preference('pref2', [1]); + $format->set_sections_preference('pref3', []); + + $preferences = $format->get_sections_preferences(); + $this->assertEquals( + (object)['pref1' => true, 'pref2' => true], + $preferences[1] + ); + $this->assertEquals( + (object)['pref1' => true], + $preferences[2] + ); } /** diff --git a/course/renderer.php b/course/renderer.php index 124e245023b..2c97561c3d9 100644 --- a/course/renderer.php +++ b/course/renderer.php @@ -66,7 +66,6 @@ class core_course_renderer extends plugin_renderer_base { public function __construct(moodle_page $page, $target) { $this->strings = new stdClass; $courseid = $page->course->id; - user_preference_allow_ajax_update('coursesectionspreferences_' . $courseid, PARAM_RAW); parent::__construct($page, $target); }