From 34ce0eddc0e1d9b3bda60418862a16ef8b2abcb5 Mon Sep 17 00:00:00 2001 From: Ferran Recio Date: Fri, 26 Jan 2024 11:12:53 +0100 Subject: [PATCH] MDL-80190 core_courseformat: prevent delegated sections loops MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This patch prevent dropping an activity with delegated sections inside another delegated section. Without this patch the teacher can create a deadlock drag&droping the subsection inside itself. We won't allow subsections inside subsections so any activity with subsection cannot be dropped inside a subsección. --- .../build/local/courseeditor/dndcmitem.min.js | 2 +- .../local/courseeditor/dndcmitem.min.js.map | 2 +- .../local/courseeditor/dndsection.min.js | 2 +- .../local/courseeditor/dndsection.min.js.map | 2 +- .../local/courseeditor/dndsectionitem.min.js | 2 +- .../courseeditor/dndsectionitem.min.js.map | 2 +- .../build/local/courseeditor/exporter.min.js | 2 +- .../local/courseeditor/exporter.min.js.map | 2 +- .../amd/src/local/courseeditor/dndcmitem.js | 13 +++++++++- .../amd/src/local/courseeditor/dndsection.js | 5 +++- .../src/local/courseeditor/dndsectionitem.js | 4 +++ .../amd/src/local/courseeditor/exporter.js | 1 + .../format/classes/output/local/state/cm.php | 3 +++ course/format/classes/sectiondelegate.php | 26 +++++++++++++++++-- course/format/tests/sectiondelegate_test.php | 6 +++++ 15 files changed, 62 insertions(+), 12 deletions(-) diff --git a/course/format/amd/build/local/courseeditor/dndcmitem.min.js b/course/format/amd/build/local/courseeditor/dndcmitem.min.js index 6b3f0d6ea42..b4fbc16191d 100644 --- a/course/format/amd/build/local/courseeditor/dndcmitem.min.js +++ b/course/format/amd/build/local/courseeditor/dndcmitem.min.js @@ -10,6 +10,6 @@ define("core_courseformat/local/courseeditor/dndcmitem",["exports","core/reactiv * @copyright 2021 Ferran Recio * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -class _default extends _reactive.BaseComponent{configDragDrop(cmid){this.id=cmid,this.reactive.isEditing&&this.reactive.supportComponents&&(this.dragdrop=new _reactive.DragDrop(this),this.classes=this.dragdrop.getClasses())}destroy(){void 0!==this.dragdrop&&this.dragdrop.unregister()}setDraggable(value){var _this$dragdrop;null===(_this$dragdrop=this.dragdrop)||void 0===_this$dragdrop||_this$dragdrop.setDraggable(value)}dragStart(dropdata){this.reactive.dispatch("cmDrag",[dropdata.id],!0)}dragEnd(dropdata){this.reactive.dispatch("cmDrag",[dropdata.id],!1)}getDraggableData(){return this.reactive.getExporter().cmDraggableData(this.reactive.state,this.id)}validateDropData(dropdata){return"cm"===(null==dropdata?void 0:dropdata.type)}showDropZone(dropdata){dropdata.nextcmid!=this.id&&dropdata.id!=this.id&&this.element.classList.add(this.classes.DROPUP)}hideDropZone(){this.element.classList.remove(this.classes.DROPUP)}drop(dropdata,event){if(dropdata.id!=this.id&&dropdata.nextcmid!=this.id){const mutation=event.altKey?"cmDuplicate":"cmMove";this.reactive.dispatch(mutation,[dropdata.id],null,this.id)}}}return _exports.default=_default,_exports.default})); +class _default extends _reactive.BaseComponent{configDragDrop(cmid){this.id=cmid,this.reactive.isEditing&&this.reactive.supportComponents&&(this.dragdrop=new _reactive.DragDrop(this),this.classes=this.dragdrop.getClasses())}destroy(){void 0!==this.dragdrop&&this.dragdrop.unregister()}setDraggable(value){var _this$dragdrop;null===(_this$dragdrop=this.dragdrop)||void 0===_this$dragdrop||_this$dragdrop.setDraggable(value)}dragStart(dropdata){this.reactive.dispatch("cmDrag",[dropdata.id],!0)}dragEnd(dropdata){this.reactive.dispatch("cmDrag",[dropdata.id],!1)}getDraggableData(){return this.reactive.getExporter().cmDraggableData(this.reactive.state,this.id)}validateDropData(dropdata){if("cm"!==(null==dropdata?void 0:dropdata.type))return!1;if(!0===(null==dropdata?void 0:dropdata.delegatesection)){const mycminfo=this.reactive.get("cm",this.id),mysection=this.reactive.get("section",mycminfo.sectionid);if(null!==(null==mysection?void 0:mysection.component))return!1}return!0}showDropZone(dropdata){dropdata.nextcmid!=this.id&&dropdata.id!=this.id&&this.element.classList.add(this.classes.DROPUP)}hideDropZone(){this.element.classList.remove(this.classes.DROPUP)}drop(dropdata,event){if(dropdata.id!=this.id&&dropdata.nextcmid!=this.id){const mutation=event.altKey?"cmDuplicate":"cmMove";this.reactive.dispatch(mutation,[dropdata.id],null,this.id)}}}return _exports.default=_default,_exports.default})); //# sourceMappingURL=dndcmitem.min.js.map \ No newline at end of file diff --git a/course/format/amd/build/local/courseeditor/dndcmitem.min.js.map b/course/format/amd/build/local/courseeditor/dndcmitem.min.js.map index 8dfc0b8ecfe..800a8464a6c 100644 --- a/course/format/amd/build/local/courseeditor/dndcmitem.min.js.map +++ b/course/format/amd/build/local/courseeditor/dndcmitem.min.js.map @@ -1 +1 @@ -{"version":3,"file":"dndcmitem.min.js","sources":["../../../src/local/courseeditor/dndcmitem.js"],"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 cm component.\n *\n * This component is used to control specific course modules interactions like drag and drop\n * in both course index and course content.\n *\n * @module core_courseformat/local/courseeditor/dndcmitem\n * @class core_courseformat/local/courseeditor/dndcmitem\n * @copyright 2021 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {BaseComponent, DragDrop} from 'core/reactive';\n\nexport default class extends BaseComponent {\n\n /**\n * Configure the component drag and drop.\n *\n * @param {number} cmid course module id\n */\n configDragDrop(cmid) {\n\n this.id = cmid;\n\n // Drag and drop is only available for components compatible course formats.\n if (this.reactive.isEditing && this.reactive.supportComponents) {\n // Init element drag and drop.\n this.dragdrop = new DragDrop(this);\n // Save dropzone classes.\n this.classes = this.dragdrop.getClasses();\n }\n }\n\n /**\n * Remove all subcomponents dependencies.\n */\n destroy() {\n if (this.dragdrop !== undefined) {\n this.dragdrop.unregister();\n }\n }\n\n /**\n * Enable or disable the draggable property.\n *\n * @param {bool} value the new draggable value\n */\n setDraggable(value) {\n this.dragdrop?.setDraggable(value);\n }\n\n // Drag and drop methods.\n\n /**\n * The element drop start hook.\n *\n * @param {Object} dropdata the dropdata\n */\n dragStart(dropdata) {\n this.reactive.dispatch('cmDrag', [dropdata.id], true);\n }\n\n /**\n * The element drop end hook.\n *\n * @param {Object} dropdata the dropdata\n */\n dragEnd(dropdata) {\n this.reactive.dispatch('cmDrag', [dropdata.id], false);\n }\n\n /**\n * Get the draggable data of this component.\n *\n * @returns {Object} exported course module drop data\n */\n getDraggableData() {\n const exporter = this.reactive.getExporter();\n return exporter.cmDraggableData(this.reactive.state, this.id);\n }\n\n /**\n * Validate if the drop data can be dropped over the component.\n *\n * @param {Object} dropdata the exported drop data.\n * @returns {boolean}\n */\n validateDropData(dropdata) {\n return dropdata?.type === 'cm';\n }\n\n /**\n * Display the component dropzone.\n *\n * @param {Object} dropdata the accepted drop data\n */\n showDropZone(dropdata) {\n // If we are the next cmid of the dragged element we accept the drop because otherwise it\n // will get captured by the section. However, we won't trigger any mutation.\n if (dropdata.nextcmid != this.id && dropdata.id != this.id) {\n this.element.classList.add(this.classes.DROPUP);\n }\n }\n\n /**\n * Hide the component dropzone.\n */\n hideDropZone() {\n this.element.classList.remove(this.classes.DROPUP);\n }\n\n /**\n * Drop event handler.\n *\n * @param {Object} dropdata the accepted drop data\n * @param {Event} event the drop event\n */\n drop(dropdata, event) {\n // Call the move mutation if necessary.\n if (dropdata.id != this.id && dropdata.nextcmid != this.id) {\n const mutation = (event.altKey) ? 'cmDuplicate' : 'cmMove';\n this.reactive.dispatch(mutation, [dropdata.id], null, this.id);\n }\n }\n\n}\n"],"names":["BaseComponent","configDragDrop","cmid","id","this","reactive","isEditing","supportComponents","dragdrop","DragDrop","classes","getClasses","destroy","undefined","unregister","setDraggable","value","dragStart","dropdata","dispatch","dragEnd","getDraggableData","getExporter","cmDraggableData","state","validateDropData","type","showDropZone","nextcmid","element","classList","add","DROPUP","hideDropZone","remove","drop","event","mutation","altKey"],"mappings":";;;;;;;;;;;;uBA6B6BA,wBAOzBC,eAAeC,WAENC,GAAKD,KAGNE,KAAKC,SAASC,WAAaF,KAAKC,SAASE,yBAEpCC,SAAW,IAAIC,mBAASL,WAExBM,QAAUN,KAAKI,SAASG,cAOrCC,eAC0BC,IAAlBT,KAAKI,eACAA,SAASM,aAStBC,aAAaC,sDACJR,mDAAUO,aAAaC,OAUhCC,UAAUC,eACDb,SAASc,SAAS,SAAU,CAACD,SAASf,KAAK,GAQpDiB,QAAQF,eACCb,SAASc,SAAS,SAAU,CAACD,SAASf,KAAK,GAQpDkB,0BACqBjB,KAAKC,SAASiB,cACfC,gBAAgBnB,KAAKC,SAASmB,MAAOpB,KAAKD,IAS9DsB,iBAAiBP,gBACa,QAAnBA,MAAAA,gBAAAA,SAAUQ,MAQrBC,aAAaT,UAGLA,SAASU,UAAYxB,KAAKD,IAAMe,SAASf,IAAMC,KAAKD,SAC/C0B,QAAQC,UAAUC,IAAI3B,KAAKM,QAAQsB,QAOhDC,oBACSJ,QAAQC,UAAUI,OAAO9B,KAAKM,QAAQsB,QAS/CG,KAAKjB,SAAUkB,UAEPlB,SAASf,IAAMC,KAAKD,IAAMe,SAASU,UAAYxB,KAAKD,GAAI,OAClDkC,SAAYD,MAAME,OAAU,cAAgB,cAC7CjC,SAASc,SAASkB,SAAU,CAACnB,SAASf,IAAK,KAAMC,KAAKD"} \ No newline at end of file +{"version":3,"file":"dndcmitem.min.js","sources":["../../../src/local/courseeditor/dndcmitem.js"],"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 cm component.\n *\n * This component is used to control specific course modules interactions like drag and drop\n * in both course index and course content.\n *\n * @module core_courseformat/local/courseeditor/dndcmitem\n * @class core_courseformat/local/courseeditor/dndcmitem\n * @copyright 2021 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {BaseComponent, DragDrop} from 'core/reactive';\n\nexport default class extends BaseComponent {\n\n /**\n * Configure the component drag and drop.\n *\n * @param {number} cmid course module id\n */\n configDragDrop(cmid) {\n\n this.id = cmid;\n\n // Drag and drop is only available for components compatible course formats.\n if (this.reactive.isEditing && this.reactive.supportComponents) {\n // Init element drag and drop.\n this.dragdrop = new DragDrop(this);\n // Save dropzone classes.\n this.classes = this.dragdrop.getClasses();\n }\n }\n\n /**\n * Remove all subcomponents dependencies.\n */\n destroy() {\n if (this.dragdrop !== undefined) {\n this.dragdrop.unregister();\n }\n }\n\n /**\n * Enable or disable the draggable property.\n *\n * @param {bool} value the new draggable value\n */\n setDraggable(value) {\n this.dragdrop?.setDraggable(value);\n }\n\n // Drag and drop methods.\n\n /**\n * The element drop start hook.\n *\n * @param {Object} dropdata the dropdata\n */\n dragStart(dropdata) {\n this.reactive.dispatch('cmDrag', [dropdata.id], true);\n }\n\n /**\n * The element drop end hook.\n *\n * @param {Object} dropdata the dropdata\n */\n dragEnd(dropdata) {\n this.reactive.dispatch('cmDrag', [dropdata.id], false);\n }\n\n /**\n * Get the draggable data of this component.\n *\n * @returns {Object} exported course module drop data\n */\n getDraggableData() {\n const exporter = this.reactive.getExporter();\n return exporter.cmDraggableData(this.reactive.state, this.id);\n }\n\n /**\n * Validate if the drop data can be dropped over the component.\n *\n * @param {Object} dropdata the exported drop data.\n * @returns {boolean}\n */\n validateDropData(dropdata) {\n if (dropdata?.type !== 'cm') {\n return false;\n }\n // Prevent delegated sections loops.\n if (dropdata?.delegatesection === true) {\n const mycminfo = this.reactive.get('cm', this.id);\n const mysection = this.reactive.get('section', mycminfo.sectionid);\n if (mysection?.component !== null) {\n return false;\n }\n }\n return true;\n }\n\n /**\n * Display the component dropzone.\n *\n * @param {Object} dropdata the accepted drop data\n */\n showDropZone(dropdata) {\n // If we are the next cmid of the dragged element we accept the drop because otherwise it\n // will get captured by the section. However, we won't trigger any mutation.\n if (dropdata.nextcmid != this.id && dropdata.id != this.id) {\n this.element.classList.add(this.classes.DROPUP);\n }\n }\n\n /**\n * Hide the component dropzone.\n */\n hideDropZone() {\n this.element.classList.remove(this.classes.DROPUP);\n }\n\n /**\n * Drop event handler.\n *\n * @param {Object} dropdata the accepted drop data\n * @param {Event} event the drop event\n */\n drop(dropdata, event) {\n // Call the move mutation if necessary.\n if (dropdata.id != this.id && dropdata.nextcmid != this.id) {\n const mutation = (event.altKey) ? 'cmDuplicate' : 'cmMove';\n this.reactive.dispatch(mutation, [dropdata.id], null, this.id);\n }\n }\n\n}\n"],"names":["BaseComponent","configDragDrop","cmid","id","this","reactive","isEditing","supportComponents","dragdrop","DragDrop","classes","getClasses","destroy","undefined","unregister","setDraggable","value","dragStart","dropdata","dispatch","dragEnd","getDraggableData","getExporter","cmDraggableData","state","validateDropData","type","delegatesection","mycminfo","get","mysection","sectionid","component","showDropZone","nextcmid","element","classList","add","DROPUP","hideDropZone","remove","drop","event","mutation","altKey"],"mappings":";;;;;;;;;;;;uBA6B6BA,wBAOzBC,eAAeC,WAENC,GAAKD,KAGNE,KAAKC,SAASC,WAAaF,KAAKC,SAASE,yBAEpCC,SAAW,IAAIC,mBAASL,WAExBM,QAAUN,KAAKI,SAASG,cAOrCC,eAC0BC,IAAlBT,KAAKI,eACAA,SAASM,aAStBC,aAAaC,sDACJR,mDAAUO,aAAaC,OAUhCC,UAAUC,eACDb,SAASc,SAAS,SAAU,CAACD,SAASf,KAAK,GAQpDiB,QAAQF,eACCb,SAASc,SAAS,SAAU,CAACD,SAASf,KAAK,GAQpDkB,0BACqBjB,KAAKC,SAASiB,cACfC,gBAAgBnB,KAAKC,SAASmB,MAAOpB,KAAKD,IAS9DsB,iBAAiBP,aACU,QAAnBA,MAAAA,gBAAAA,SAAUQ,aACH,MAGuB,KAA9BR,MAAAA,gBAAAA,SAAUS,iBAA0B,OAC9BC,SAAWxB,KAAKC,SAASwB,IAAI,KAAMzB,KAAKD,IACxC2B,UAAY1B,KAAKC,SAASwB,IAAI,UAAWD,SAASG,cAC3B,QAAzBD,MAAAA,iBAAAA,UAAWE,kBACJ,SAGR,EAQXC,aAAaf,UAGLA,SAASgB,UAAY9B,KAAKD,IAAMe,SAASf,IAAMC,KAAKD,SAC/CgC,QAAQC,UAAUC,IAAIjC,KAAKM,QAAQ4B,QAOhDC,oBACSJ,QAAQC,UAAUI,OAAOpC,KAAKM,QAAQ4B,QAS/CG,KAAKvB,SAAUwB,UAEPxB,SAASf,IAAMC,KAAKD,IAAMe,SAASgB,UAAY9B,KAAKD,GAAI,OAClDwC,SAAYD,MAAME,OAAU,cAAgB,cAC7CvC,SAASc,SAASwB,SAAU,CAACzB,SAASf,IAAK,KAAMC,KAAKD"} \ No newline at end of file diff --git a/course/format/amd/build/local/courseeditor/dndsection.min.js b/course/format/amd/build/local/courseeditor/dndsection.min.js index 94a750ba79e..9ef3fc438a1 100644 --- a/course/format/amd/build/local/courseeditor/dndsection.min.js +++ b/course/format/amd/build/local/courseeditor/dndsection.min.js @@ -9,6 +9,6 @@ define("core_courseformat/local/courseeditor/dndsection",["exports","core/reacti * @class core_courseformat/local/courseeditor/dndsection * @copyright 2021 Ferran Recio * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_templates=(obj=_templates)&&obj.__esModule?obj:{default:obj},(0,_prefetch.prefetchStrings)("core",["addfilehere"]);class _default extends _reactive.BaseComponent{configState(state){this.id=this.element.dataset.id,this.section=state.section.get(this.id),this.course=state.course}configDragDrop(sectionitem){this.reactive.isEditing&&this.reactive.supportComponents&&(this.sectionitem=sectionitem,this.dragdrop=new _reactive.DragDrop(this),this.classes=this.dragdrop.getClasses())}destroy(){void 0!==this.sectionitem&&this.sectionitem.unregister(),void 0!==this.dragdrop&&this.dragdrop.unregister()}getLastCm(){return null}dragStart(dropdata){this.reactive.dispatch("sectionDrag",[dropdata.id],!0)}dragEnd(dropdata){this.reactive.dispatch("sectionDrag",[dropdata.id],!1)}validateDropData(dropdata){return"files"===(null==dropdata?void 0:dropdata.type)||("cm"===(null==dropdata?void 0:dropdata.type)||"section"===(null==dropdata?void 0:dropdata.type)&&((null==dropdata?void 0:dropdata.id)!=this.id&&(null==dropdata?void 0:dropdata.number)!=this.section.number+1))}showDropZone(dropdata){var _this$getLastCm;("files"==dropdata.type&&this.addOverlay({content:(0,_str.getString)("addfilehere","core"),icon:_templates.default.renderPix("t/download","core")}).then((()=>{var _this$dragdrop;null!==(_this$dragdrop=this.dragdrop)&&void 0!==_this$dragdrop&&_this$dragdrop.isDropzoneVisible()||this.removeOverlay()})).catch((error=>{throw error})),"cm"==dropdata.type)&&(null===(_this$getLastCm=this.getLastCm())||void 0===_this$getLastCm||_this$getLastCm.classList.add(this.classes.DROPDOWN));"section"==dropdata.type&&(this.element.classList.remove(this.classes.DROPUP),this.element.classList.add(this.classes.DROPDOWN))}hideDropZone(){var _this$getLastCm2;null===(_this$getLastCm2=this.getLastCm())||void 0===_this$getLastCm2||_this$getLastCm2.classList.remove(this.classes.DROPDOWN),this.element.classList.remove(this.classes.DROPUP),this.element.classList.remove(this.classes.DROPDOWN),this.removeOverlay()}drop(dropdata,event){if("files"!=dropdata.type){if("cm"==dropdata.type){const mutation=event.altKey?"cmDuplicate":"cmMove";this.reactive.dispatch(mutation,[dropdata.id],this.id)}"section"==dropdata.type&&this.reactive.dispatch("sectionMoveAfter",[dropdata.id],this.id)}else this.reactive.uploadFiles(this.section.id,this.section.number,dropdata.files)}}return _exports.default=_default,_exports.default})); + */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_templates=(obj=_templates)&&obj.__esModule?obj:{default:obj},(0,_prefetch.prefetchStrings)("core",["addfilehere"]);class _default extends _reactive.BaseComponent{configState(state){this.id=this.element.dataset.id,this.section=state.section.get(this.id),this.course=state.course}configDragDrop(sectionitem){this.reactive.isEditing&&this.reactive.supportComponents&&(this.sectionitem=sectionitem,this.dragdrop=new _reactive.DragDrop(this),this.classes=this.dragdrop.getClasses())}destroy(){void 0!==this.sectionitem&&this.sectionitem.unregister(),void 0!==this.dragdrop&&this.dragdrop.unregister()}getLastCm(){return null}dragStart(dropdata){this.reactive.dispatch("sectionDrag",[dropdata.id],!0)}dragEnd(dropdata){this.reactive.dispatch("sectionDrag",[dropdata.id],!1)}validateDropData(dropdata){return"files"===(null==dropdata?void 0:dropdata.type)||("cm"===(null==dropdata?void 0:dropdata.type)?null===(_this$section=this.section)||void 0===_this$section||!_this$section.component||!0!==(null==dropdata?void 0:dropdata.delegatesection):"section"===(null==dropdata?void 0:dropdata.type)&&((null==dropdata?void 0:dropdata.id)!=this.id&&(null==dropdata?void 0:dropdata.number)!=this.section.number+1));var _this$section}showDropZone(dropdata){var _this$getLastCm;("files"==dropdata.type&&this.addOverlay({content:(0,_str.getString)("addfilehere","core"),icon:_templates.default.renderPix("t/download","core")}).then((()=>{var _this$dragdrop;null!==(_this$dragdrop=this.dragdrop)&&void 0!==_this$dragdrop&&_this$dragdrop.isDropzoneVisible()||this.removeOverlay()})).catch((error=>{throw error})),"cm"==dropdata.type)&&(null===(_this$getLastCm=this.getLastCm())||void 0===_this$getLastCm||_this$getLastCm.classList.add(this.classes.DROPDOWN));"section"==dropdata.type&&(this.element.classList.remove(this.classes.DROPUP),this.element.classList.add(this.classes.DROPDOWN))}hideDropZone(){var _this$getLastCm2;null===(_this$getLastCm2=this.getLastCm())||void 0===_this$getLastCm2||_this$getLastCm2.classList.remove(this.classes.DROPDOWN),this.element.classList.remove(this.classes.DROPUP),this.element.classList.remove(this.classes.DROPDOWN),this.removeOverlay()}drop(dropdata,event){if("files"!=dropdata.type){if("cm"==dropdata.type){const mutation=event.altKey?"cmDuplicate":"cmMove";this.reactive.dispatch(mutation,[dropdata.id],this.id)}"section"==dropdata.type&&this.reactive.dispatch("sectionMoveAfter",[dropdata.id],this.id)}else this.reactive.uploadFiles(this.section.id,this.section.number,dropdata.files)}}return _exports.default=_default,_exports.default})); //# sourceMappingURL=dndsection.min.js.map \ No newline at end of file diff --git a/course/format/amd/build/local/courseeditor/dndsection.min.js.map b/course/format/amd/build/local/courseeditor/dndsection.min.js.map index 6f1bd799402..74e90ffdb82 100644 --- a/course/format/amd/build/local/courseeditor/dndsection.min.js.map +++ b/course/format/amd/build/local/courseeditor/dndsection.min.js.map @@ -1 +1 @@ -{"version":3,"file":"dndsection.min.js","sources":["../../../src/local/courseeditor/dndsection.js"],"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 section component.\n *\n * This component is used to control specific course section interactions like drag and drop\n * in both course index and course content.\n *\n * @module core_courseformat/local/courseeditor/dndsection\n * @class core_courseformat/local/courseeditor/dndsection\n * @copyright 2021 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {BaseComponent, DragDrop} from 'core/reactive';\nimport {getString} from 'core/str';\nimport {prefetchStrings} from 'core/prefetch';\nimport Templates from 'core/templates';\n\n// Load global strings.\nprefetchStrings('core', ['addfilehere']);\n\nexport default class extends BaseComponent {\n\n /**\n * Save some values form the state.\n *\n * @param {Object} state the current state\n */\n configState(state) {\n this.id = this.element.dataset.id;\n this.section = state.section.get(this.id);\n this.course = state.course;\n }\n\n /**\n * Register state values and the drag and drop subcomponent.\n *\n * @param {BaseComponent} sectionitem section item component\n */\n configDragDrop(sectionitem) {\n // Drag and drop is only available for components compatible course formats.\n if (this.reactive.isEditing && this.reactive.supportComponents) {\n // Init the inner dragable element.\n this.sectionitem = sectionitem;\n // Init the dropzone.\n this.dragdrop = new DragDrop(this);\n // Save dropzone classes.\n this.classes = this.dragdrop.getClasses();\n }\n }\n\n /**\n * Remove all subcomponents dependencies.\n */\n destroy() {\n if (this.sectionitem !== undefined) {\n this.sectionitem.unregister();\n }\n if (this.dragdrop !== undefined) {\n this.dragdrop.unregister();\n }\n }\n\n /**\n * Get the last CM element of that section.\n *\n * @returns {element|null} the las course module element of the section.\n */\n getLastCm() {\n return null;\n }\n\n // Drag and drop methods.\n\n /**\n * The element drop start hook.\n *\n * @param {Object} dropdata the dropdata\n */\n dragStart(dropdata) {\n this.reactive.dispatch('sectionDrag', [dropdata.id], true);\n }\n\n /**\n * The element drop end hook.\n *\n * @param {Object} dropdata the dropdata\n */\n dragEnd(dropdata) {\n this.reactive.dispatch('sectionDrag', [dropdata.id], false);\n }\n\n /**\n * Validate if the drop data can be dropped over the component.\n *\n * @param {Object} dropdata the exported drop data.\n * @returns {boolean}\n */\n validateDropData(dropdata) {\n // We accept files.\n if (dropdata?.type === 'files') {\n return true;\n }\n // We accept any course module.\n if (dropdata?.type === 'cm') {\n return true;\n }\n // We accept any section but yourself and the next one.\n if (dropdata?.type === 'section') {\n return dropdata?.id != this.id && dropdata?.number != this.section.number + 1;\n }\n return false;\n }\n\n /**\n * Display the component dropzone.\n *\n * @param {Object} dropdata the accepted drop data\n */\n showDropZone(dropdata) {\n if (dropdata.type == 'files') {\n this.addOverlay({\n content: getString('addfilehere', 'core'),\n icon: Templates.renderPix('t/download', 'core'),\n }).then(() => {\n // Check if we still need the file dropzone.\n if (!this.dragdrop?.isDropzoneVisible()) {\n this.removeOverlay();\n }\n return;\n }).catch((error) => {\n throw error;\n });\n }\n if (dropdata.type == 'cm') {\n this.getLastCm()?.classList.add(this.classes.DROPDOWN);\n }\n if (dropdata.type == 'section') {\n this.element.classList.remove(this.classes.DROPUP);\n this.element.classList.add(this.classes.DROPDOWN);\n }\n }\n\n /**\n * Hide the component dropzone.\n */\n hideDropZone() {\n this.getLastCm()?.classList.remove(this.classes.DROPDOWN);\n this.element.classList.remove(this.classes.DROPUP);\n this.element.classList.remove(this.classes.DROPDOWN);\n this.removeOverlay();\n }\n\n /**\n * Drop event handler.\n *\n * @param {Object} dropdata the accepted drop data\n * @param {Event} event the drop event\n */\n drop(dropdata, event) {\n // File handling.\n if (dropdata.type == 'files') {\n this.reactive.uploadFiles(\n this.section.id,\n this.section.number,\n dropdata.files\n );\n return;\n }\n // Call the move mutation.\n if (dropdata.type == 'cm') {\n const mutation = (event.altKey) ? 'cmDuplicate' : 'cmMove';\n this.reactive.dispatch(mutation, [dropdata.id], this.id);\n }\n if (dropdata.type == 'section') {\n this.reactive.dispatch('sectionMoveAfter', [dropdata.id], this.id);\n }\n }\n}\n"],"names":["BaseComponent","configState","state","id","this","element","dataset","section","get","course","configDragDrop","sectionitem","reactive","isEditing","supportComponents","dragdrop","DragDrop","classes","getClasses","destroy","undefined","unregister","getLastCm","dragStart","dropdata","dispatch","dragEnd","validateDropData","type","number","showDropZone","addOverlay","content","icon","Templates","renderPix","then","_this$dragdrop","isDropzoneVisible","removeOverlay","catch","error","classList","add","DROPDOWN","remove","DROPUP","hideDropZone","drop","event","mutation","altKey","uploadFiles","files"],"mappings":";;;;;;;;;;;iLAiCgB,OAAQ,CAAC,uCAEIA,wBAOzBC,YAAYC,YACHC,GAAKC,KAAKC,QAAQC,QAAQH,QAC1BI,QAAUL,MAAMK,QAAQC,IAAIJ,KAAKD,SACjCM,OAASP,MAAMO,OAQxBC,eAAeC,aAEPP,KAAKQ,SAASC,WAAaT,KAAKQ,SAASE,yBAEpCH,YAAcA,iBAEdI,SAAW,IAAIC,mBAASZ,WAExBa,QAAUb,KAAKW,SAASG,cAOrCC,eAC6BC,IAArBhB,KAAKO,kBACAA,YAAYU,kBAECD,IAAlBhB,KAAKW,eACAA,SAASM,aAStBC,mBACW,KAUXC,UAAUC,eACDZ,SAASa,SAAS,cAAe,CAACD,SAASrB,KAAK,GAQzDuB,QAAQF,eACCZ,SAASa,SAAS,cAAe,CAACD,SAASrB,KAAK,GASzDwB,iBAAiBH,gBAEU,WAAnBA,MAAAA,gBAAAA,SAAUI,QAIS,QAAnBJ,MAAAA,gBAAAA,SAAUI,OAIS,aAAnBJ,MAAAA,gBAAAA,SAAUI,SACHJ,MAAAA,gBAAAA,SAAUrB,KAAMC,KAAKD,KAAMqB,MAAAA,gBAAAA,SAAUK,SAAUzB,KAAKG,QAAQsB,OAAS,IAUpFC,aAAaN,+BACY,SAAjBA,SAASI,WACJG,WAAW,CACZC,SAAS,kBAAU,cAAe,QAClCC,KAAMC,mBAAUC,UAAU,aAAc,UACzCC,MAAK,+CAEChC,KAAKW,oCAALsB,eAAeC,0BACXC,mBAGVC,OAAOC,cACAA,SAGO,MAAjBjB,SAASI,qCACJN,wDAAaoB,UAAUC,IAAIvC,KAAKa,QAAQ2B,WAE5B,WAAjBpB,SAASI,YACJvB,QAAQqC,UAAUG,OAAOzC,KAAKa,QAAQ6B,aACtCzC,QAAQqC,UAAUC,IAAIvC,KAAKa,QAAQ2B,WAOhDG,kEACSzB,0DAAaoB,UAAUG,OAAOzC,KAAKa,QAAQ2B,eAC3CvC,QAAQqC,UAAUG,OAAOzC,KAAKa,QAAQ6B,aACtCzC,QAAQqC,UAAUG,OAAOzC,KAAKa,QAAQ2B,eACtCL,gBASTS,KAAKxB,SAAUyB,UAEU,SAAjBzB,SAASI,SASQ,MAAjBJ,SAASI,KAAc,OACjBsB,SAAYD,MAAME,OAAU,cAAgB,cAC7CvC,SAASa,SAASyB,SAAU,CAAC1B,SAASrB,IAAKC,KAAKD,IAEpC,WAAjBqB,SAASI,WACJhB,SAASa,SAAS,mBAAoB,CAACD,SAASrB,IAAKC,KAAKD,cAb1DS,SAASwC,YACVhD,KAAKG,QAAQJ,GACbC,KAAKG,QAAQsB,OACbL,SAAS6B"} \ No newline at end of file +{"version":3,"file":"dndsection.min.js","sources":["../../../src/local/courseeditor/dndsection.js"],"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 section component.\n *\n * This component is used to control specific course section interactions like drag and drop\n * in both course index and course content.\n *\n * @module core_courseformat/local/courseeditor/dndsection\n * @class core_courseformat/local/courseeditor/dndsection\n * @copyright 2021 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {BaseComponent, DragDrop} from 'core/reactive';\nimport {getString} from 'core/str';\nimport {prefetchStrings} from 'core/prefetch';\nimport Templates from 'core/templates';\n\n// Load global strings.\nprefetchStrings('core', ['addfilehere']);\n\nexport default class extends BaseComponent {\n\n /**\n * Save some values form the state.\n *\n * @param {Object} state the current state\n */\n configState(state) {\n this.id = this.element.dataset.id;\n this.section = state.section.get(this.id);\n this.course = state.course;\n }\n\n /**\n * Register state values and the drag and drop subcomponent.\n *\n * @param {BaseComponent} sectionitem section item component\n */\n configDragDrop(sectionitem) {\n // Drag and drop is only available for components compatible course formats.\n if (this.reactive.isEditing && this.reactive.supportComponents) {\n // Init the inner dragable element.\n this.sectionitem = sectionitem;\n // Init the dropzone.\n this.dragdrop = new DragDrop(this);\n // Save dropzone classes.\n this.classes = this.dragdrop.getClasses();\n }\n }\n\n /**\n * Remove all subcomponents dependencies.\n */\n destroy() {\n if (this.sectionitem !== undefined) {\n this.sectionitem.unregister();\n }\n if (this.dragdrop !== undefined) {\n this.dragdrop.unregister();\n }\n }\n\n /**\n * Get the last CM element of that section.\n *\n * @returns {element|null} the las course module element of the section.\n */\n getLastCm() {\n return null;\n }\n\n // Drag and drop methods.\n\n /**\n * The element drop start hook.\n *\n * @param {Object} dropdata the dropdata\n */\n dragStart(dropdata) {\n this.reactive.dispatch('sectionDrag', [dropdata.id], true);\n }\n\n /**\n * The element drop end hook.\n *\n * @param {Object} dropdata the dropdata\n */\n dragEnd(dropdata) {\n this.reactive.dispatch('sectionDrag', [dropdata.id], false);\n }\n\n /**\n * Validate if the drop data can be dropped over the component.\n *\n * @param {Object} dropdata the exported drop data.\n * @returns {boolean}\n */\n validateDropData(dropdata) {\n // We accept files.\n if (dropdata?.type === 'files') {\n return true;\n }\n // We accept any course module unless it can form a subsection loop.\n if (dropdata?.type === 'cm') {\n if (this.section?.component && dropdata?.delegatesection === true) {\n return false;\n }\n return true;\n }\n // We accept any section but yourself and the next one.\n if (dropdata?.type === 'section') {\n return dropdata?.id != this.id && dropdata?.number != this.section.number + 1;\n }\n return false;\n }\n\n /**\n * Display the component dropzone.\n *\n * @param {Object} dropdata the accepted drop data\n */\n showDropZone(dropdata) {\n if (dropdata.type == 'files') {\n this.addOverlay({\n content: getString('addfilehere', 'core'),\n icon: Templates.renderPix('t/download', 'core'),\n }).then(() => {\n // Check if we still need the file dropzone.\n if (!this.dragdrop?.isDropzoneVisible()) {\n this.removeOverlay();\n }\n return;\n }).catch((error) => {\n throw error;\n });\n }\n if (dropdata.type == 'cm') {\n this.getLastCm()?.classList.add(this.classes.DROPDOWN);\n }\n if (dropdata.type == 'section') {\n this.element.classList.remove(this.classes.DROPUP);\n this.element.classList.add(this.classes.DROPDOWN);\n }\n }\n\n /**\n * Hide the component dropzone.\n */\n hideDropZone() {\n this.getLastCm()?.classList.remove(this.classes.DROPDOWN);\n this.element.classList.remove(this.classes.DROPUP);\n this.element.classList.remove(this.classes.DROPDOWN);\n this.removeOverlay();\n }\n\n /**\n * Drop event handler.\n *\n * @param {Object} dropdata the accepted drop data\n * @param {Event} event the drop event\n */\n drop(dropdata, event) {\n // File handling.\n if (dropdata.type == 'files') {\n this.reactive.uploadFiles(\n this.section.id,\n this.section.number,\n dropdata.files\n );\n return;\n }\n // Call the move mutation.\n if (dropdata.type == 'cm') {\n const mutation = (event.altKey) ? 'cmDuplicate' : 'cmMove';\n this.reactive.dispatch(mutation, [dropdata.id], this.id);\n }\n if (dropdata.type == 'section') {\n this.reactive.dispatch('sectionMoveAfter', [dropdata.id], this.id);\n }\n }\n}\n"],"names":["BaseComponent","configState","state","id","this","element","dataset","section","get","course","configDragDrop","sectionitem","reactive","isEditing","supportComponents","dragdrop","DragDrop","classes","getClasses","destroy","undefined","unregister","getLastCm","dragStart","dropdata","dispatch","dragEnd","validateDropData","type","component","delegatesection","number","showDropZone","addOverlay","content","icon","Templates","renderPix","then","_this$dragdrop","isDropzoneVisible","removeOverlay","catch","error","classList","add","DROPDOWN","remove","DROPUP","hideDropZone","drop","event","mutation","altKey","uploadFiles","files"],"mappings":";;;;;;;;;;;iLAiCgB,OAAQ,CAAC,uCAEIA,wBAOzBC,YAAYC,YACHC,GAAKC,KAAKC,QAAQC,QAAQH,QAC1BI,QAAUL,MAAMK,QAAQC,IAAIJ,KAAKD,SACjCM,OAASP,MAAMO,OAQxBC,eAAeC,aAEPP,KAAKQ,SAASC,WAAaT,KAAKQ,SAASE,yBAEpCH,YAAcA,iBAEdI,SAAW,IAAIC,mBAASZ,WAExBa,QAAUb,KAAKW,SAASG,cAOrCC,eAC6BC,IAArBhB,KAAKO,kBACAA,YAAYU,kBAECD,IAAlBhB,KAAKW,eACAA,SAASM,aAStBC,mBACW,KAUXC,UAAUC,eACDZ,SAASa,SAAS,cAAe,CAACD,SAASrB,KAAK,GAQzDuB,QAAQF,eACCZ,SAASa,SAAS,cAAe,CAACD,SAASrB,KAAK,GASzDwB,iBAAiBH,gBAEU,WAAnBA,MAAAA,gBAAAA,SAAUI,QAIS,QAAnBJ,MAAAA,gBAAAA,SAAUI,iCACDrB,iDAASsB,YAA2C,KAA9BL,MAAAA,gBAAAA,SAAUM,iBAMtB,aAAnBN,MAAAA,gBAAAA,SAAUI,SACHJ,MAAAA,gBAAAA,SAAUrB,KAAMC,KAAKD,KAAMqB,MAAAA,gBAAAA,SAAUO,SAAU3B,KAAKG,QAAQwB,OAAS,sBAUpFC,aAAaR,+BACY,SAAjBA,SAASI,WACJK,WAAW,CACZC,SAAS,kBAAU,cAAe,QAClCC,KAAMC,mBAAUC,UAAU,aAAc,UACzCC,MAAK,+CAEClC,KAAKW,oCAALwB,eAAeC,0BACXC,mBAGVC,OAAOC,cACAA,SAGO,MAAjBnB,SAASI,qCACJN,wDAAasB,UAAUC,IAAIzC,KAAKa,QAAQ6B,WAE5B,WAAjBtB,SAASI,YACJvB,QAAQuC,UAAUG,OAAO3C,KAAKa,QAAQ+B,aACtC3C,QAAQuC,UAAUC,IAAIzC,KAAKa,QAAQ6B,WAOhDG,kEACS3B,0DAAasB,UAAUG,OAAO3C,KAAKa,QAAQ6B,eAC3CzC,QAAQuC,UAAUG,OAAO3C,KAAKa,QAAQ+B,aACtC3C,QAAQuC,UAAUG,OAAO3C,KAAKa,QAAQ6B,eACtCL,gBASTS,KAAK1B,SAAU2B,UAEU,SAAjB3B,SAASI,SASQ,MAAjBJ,SAASI,KAAc,OACjBwB,SAAYD,MAAME,OAAU,cAAgB,cAC7CzC,SAASa,SAAS2B,SAAU,CAAC5B,SAASrB,IAAKC,KAAKD,IAEpC,WAAjBqB,SAASI,WACJhB,SAASa,SAAS,mBAAoB,CAACD,SAASrB,IAAKC,KAAKD,cAb1DS,SAAS0C,YACVlD,KAAKG,QAAQJ,GACbC,KAAKG,QAAQwB,OACbP,SAAS+B"} \ No newline at end of file diff --git a/course/format/amd/build/local/courseeditor/dndsectionitem.min.js b/course/format/amd/build/local/courseeditor/dndsectionitem.min.js index 64bb51dc12b..9d1f2cae8b8 100644 --- a/course/format/amd/build/local/courseeditor/dndsectionitem.min.js +++ b/course/format/amd/build/local/courseeditor/dndsectionitem.min.js @@ -10,6 +10,6 @@ define("core_courseformat/local/courseeditor/dndsectionitem",["exports","core/re * @copyright 2021 Ferran Recio * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -class _default extends _reactive.BaseComponent{configDragDrop(sectionid,state,fullregion){this.id=sectionid,void 0===this.section&&(this.section=state.section.get(this.id)),void 0===this.course&&(this.course=state.course),this.section.number>0&&(this.getDraggableData=this._getDraggableData),this.fullregion=fullregion,this.reactive.isEditing&&this.reactive.supportComponents&&(this.dragdrop=new _reactive.DragDrop(this),this.classes=this.dragdrop.getClasses())}destroy(){void 0!==this.dragdrop&&this.dragdrop.unregister()}setDraggable(value){var _this$dragdrop;this.getDraggableData&&(null===(_this$dragdrop=this.dragdrop)||void 0===_this$dragdrop||_this$dragdrop.setDraggable(value))}dragStart(dropdata){this.reactive.dispatch("sectionDrag",[dropdata.id],!0)}dragEnd(dropdata){this.reactive.dispatch("sectionDrag",[dropdata.id],!1)}_getDraggableData(){return this.reactive.getExporter().sectionDraggableData(this.reactive.state,this.id)}validateDropData(dropdata){if("cm"===(null==dropdata?void 0:dropdata.type)){var _this$section;const firstcmid=null===(_this$section=this.section)||void 0===_this$section?void 0:_this$section.cmlist[0];return dropdata.id!==firstcmid}return!1}showDropZone(){this.element.classList.add(this.classes.DROPZONE)}hideDropZone(){this.element.classList.remove(this.classes.DROPZONE)}drop(dropdata,event){if("cm"==dropdata.type){var _this$section2;const mutation=event.altKey?"cmDuplicate":"cmMove";this.reactive.dispatch(mutation,[dropdata.id],this.id,null===(_this$section2=this.section)||void 0===_this$section2?void 0:_this$section2.cmlist[0])}}}return _exports.default=_default,_exports.default})); +class _default extends _reactive.BaseComponent{configDragDrop(sectionid,state,fullregion){this.id=sectionid,void 0===this.section&&(this.section=state.section.get(this.id)),void 0===this.course&&(this.course=state.course),this.section.number>0&&(this.getDraggableData=this._getDraggableData),this.fullregion=fullregion,this.reactive.isEditing&&this.reactive.supportComponents&&(this.dragdrop=new _reactive.DragDrop(this),this.classes=this.dragdrop.getClasses())}destroy(){void 0!==this.dragdrop&&this.dragdrop.unregister()}setDraggable(value){var _this$dragdrop;this.getDraggableData&&(null===(_this$dragdrop=this.dragdrop)||void 0===_this$dragdrop||_this$dragdrop.setDraggable(value))}dragStart(dropdata){this.reactive.dispatch("sectionDrag",[dropdata.id],!0)}dragEnd(dropdata){this.reactive.dispatch("sectionDrag",[dropdata.id],!1)}_getDraggableData(){return this.reactive.getExporter().sectionDraggableData(this.reactive.state,this.id)}validateDropData(dropdata){if("cm"===(null==dropdata?void 0:dropdata.type)){var _this$section,_this$section2;if(null!==(_this$section=this.section)&&void 0!==_this$section&&_this$section.component&&!0===(null==dropdata?void 0:dropdata.delegatesection))return!1;const firstcmid=null===(_this$section2=this.section)||void 0===_this$section2?void 0:_this$section2.cmlist[0];return dropdata.id!==firstcmid}return!1}showDropZone(){this.element.classList.add(this.classes.DROPZONE)}hideDropZone(){this.element.classList.remove(this.classes.DROPZONE)}drop(dropdata,event){if("cm"==dropdata.type){var _this$section3;const mutation=event.altKey?"cmDuplicate":"cmMove";this.reactive.dispatch(mutation,[dropdata.id],this.id,null===(_this$section3=this.section)||void 0===_this$section3?void 0:_this$section3.cmlist[0])}}}return _exports.default=_default,_exports.default})); //# sourceMappingURL=dndsectionitem.min.js.map \ No newline at end of file diff --git a/course/format/amd/build/local/courseeditor/dndsectionitem.min.js.map b/course/format/amd/build/local/courseeditor/dndsectionitem.min.js.map index 4ee9d06b318..c00ab6e7be1 100644 --- a/course/format/amd/build/local/courseeditor/dndsectionitem.min.js.map +++ b/course/format/amd/build/local/courseeditor/dndsectionitem.min.js.map @@ -1 +1 @@ -{"version":3,"file":"dndsectionitem.min.js","sources":["../../../src/local/courseeditor/dndsectionitem.js"],"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 section title draggable component.\n *\n * This component is used to control specific course section interactions like drag and drop\n * in both course index and course content.\n *\n * @module core_courseformat/local/courseeditor/dndsectionitem\n * @class core_courseformat/local/courseeditor/dndsectionitem\n * @copyright 2021 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {BaseComponent, DragDrop} from 'core/reactive';\n\nexport default class extends BaseComponent {\n\n /**\n * Initial state ready method.\n *\n * @param {number} sectionid the section id\n * @param {Object} state the initial state\n * @param {Element} fullregion the complete section region to mark as dragged\n */\n configDragDrop(sectionid, state, fullregion) {\n\n this.id = sectionid;\n if (this.section === undefined) {\n this.section = state.section.get(this.id);\n }\n if (this.course === undefined) {\n this.course = state.course;\n }\n\n // Prevent topic zero from being draggable.\n if (this.section.number > 0) {\n this.getDraggableData = this._getDraggableData;\n }\n\n this.fullregion = fullregion;\n\n // Drag and drop is only available for components compatible course formats.\n if (this.reactive.isEditing && this.reactive.supportComponents) {\n // Init the dropzone.\n this.dragdrop = new DragDrop(this);\n // Save dropzone classes.\n this.classes = this.dragdrop.getClasses();\n }\n }\n\n /**\n * Remove all subcomponents dependencies.\n */\n destroy() {\n if (this.dragdrop !== undefined) {\n this.dragdrop.unregister();\n }\n }\n\n /**\n * Enable or disable the draggable property.\n *\n * @param {bool} value the new draggable value\n */\n setDraggable(value) {\n if (this.getDraggableData) {\n this.dragdrop?.setDraggable(value);\n }\n }\n\n // Drag and drop methods.\n\n /**\n * The element drop start hook.\n *\n * @param {Object} dropdata the dropdata\n */\n dragStart(dropdata) {\n this.reactive.dispatch('sectionDrag', [dropdata.id], true);\n }\n\n /**\n * The element end start hook.\n *\n * @param {Object} dropdata the dropdata\n */\n dragEnd(dropdata) {\n this.reactive.dispatch('sectionDrag', [dropdata.id], false);\n }\n\n /**\n * Get the draggable data of this component.\n *\n * @returns {Object} exported course module drop data\n */\n _getDraggableData() {\n const exporter = this.reactive.getExporter();\n return exporter.sectionDraggableData(this.reactive.state, this.id);\n }\n\n /**\n * Validate if the drop data can be dropped over the component.\n *\n * @param {Object} dropdata the exported drop data.\n * @returns {boolean}\n */\n validateDropData(dropdata) {\n // Course module validation.\n if (dropdata?.type === 'cm') {\n // The first section element is already there so we can ignore it.\n const firstcmid = this.section?.cmlist[0];\n return dropdata.id !== firstcmid;\n }\n return false;\n }\n\n /**\n * Display the component dropzone.\n */\n showDropZone() {\n this.element.classList.add(this.classes.DROPZONE);\n }\n\n /**\n * Hide the component dropzone.\n */\n hideDropZone() {\n this.element.classList.remove(this.classes.DROPZONE);\n }\n\n /**\n * Drop event handler.\n *\n * @param {Object} dropdata the accepted drop data\n * @param {Event} event the drop event\n */\n drop(dropdata, event) {\n // Call the move mutation.\n if (dropdata.type == 'cm') {\n const mutation = (event.altKey) ? 'cmDuplicate' : 'cmMove';\n this.reactive.dispatch(mutation, [dropdata.id], this.id, this.section?.cmlist[0]);\n }\n }\n}\n"],"names":["BaseComponent","configDragDrop","sectionid","state","fullregion","id","undefined","this","section","get","course","number","getDraggableData","_getDraggableData","reactive","isEditing","supportComponents","dragdrop","DragDrop","classes","getClasses","destroy","unregister","setDraggable","value","dragStart","dropdata","dispatch","dragEnd","getExporter","sectionDraggableData","validateDropData","type","firstcmid","_this$section","cmlist","showDropZone","element","classList","add","DROPZONE","hideDropZone","remove","drop","event","mutation","altKey","_this$section2"],"mappings":";;;;;;;;;;;;uBA6B6BA,wBASzBC,eAAeC,UAAWC,MAAOC,iBAExBC,GAAKH,eACWI,IAAjBC,KAAKC,eACAA,QAAUL,MAAMK,QAAQC,IAAIF,KAAKF,UAEtBC,IAAhBC,KAAKG,cACAA,OAASP,MAAMO,QAIpBH,KAAKC,QAAQG,OAAS,SACjBC,iBAAmBL,KAAKM,wBAG5BT,WAAaA,WAGdG,KAAKO,SAASC,WAAaR,KAAKO,SAASE,yBAEpCC,SAAW,IAAIC,mBAASX,WAExBY,QAAUZ,KAAKU,SAASG,cAOrCC,eAC0Bf,IAAlBC,KAAKU,eACAA,SAASK,aAStBC,aAAaC,0BACLjB,KAAKK,+CACAK,mDAAUM,aAAaC,QAWpCC,UAAUC,eACDZ,SAASa,SAAS,cAAe,CAACD,SAASrB,KAAK,GAQzDuB,QAAQF,eACCZ,SAASa,SAAS,cAAe,CAACD,SAASrB,KAAK,GAQzDQ,2BACqBN,KAAKO,SAASe,cACfC,qBAAqBvB,KAAKO,SAASX,MAAOI,KAAKF,IASnE0B,iBAAiBL,aAEU,QAAnBA,MAAAA,gBAAAA,SAAUM,MAAe,yBAEnBC,gCAAY1B,KAAKC,wCAAL0B,cAAcC,OAAO,UAChCT,SAASrB,KAAO4B,iBAEpB,EAMXG,oBACSC,QAAQC,UAAUC,IAAIhC,KAAKY,QAAQqB,UAM5CC,oBACSJ,QAAQC,UAAUI,OAAOnC,KAAKY,QAAQqB,UAS/CG,KAAKjB,SAAUkB,UAEU,MAAjBlB,SAASM,KAAc,0BACjBa,SAAYD,MAAME,OAAU,cAAgB,cAC7ChC,SAASa,SAASkB,SAAU,CAACnB,SAASrB,IAAKE,KAAKF,0BAAIE,KAAKC,yCAALuC,eAAcZ,OAAO"} \ No newline at end of file +{"version":3,"file":"dndsectionitem.min.js","sources":["../../../src/local/courseeditor/dndsectionitem.js"],"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 section title draggable component.\n *\n * This component is used to control specific course section interactions like drag and drop\n * in both course index and course content.\n *\n * @module core_courseformat/local/courseeditor/dndsectionitem\n * @class core_courseformat/local/courseeditor/dndsectionitem\n * @copyright 2021 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {BaseComponent, DragDrop} from 'core/reactive';\n\nexport default class extends BaseComponent {\n\n /**\n * Initial state ready method.\n *\n * @param {number} sectionid the section id\n * @param {Object} state the initial state\n * @param {Element} fullregion the complete section region to mark as dragged\n */\n configDragDrop(sectionid, state, fullregion) {\n\n this.id = sectionid;\n if (this.section === undefined) {\n this.section = state.section.get(this.id);\n }\n if (this.course === undefined) {\n this.course = state.course;\n }\n\n // Prevent topic zero from being draggable.\n if (this.section.number > 0) {\n this.getDraggableData = this._getDraggableData;\n }\n\n this.fullregion = fullregion;\n\n // Drag and drop is only available for components compatible course formats.\n if (this.reactive.isEditing && this.reactive.supportComponents) {\n // Init the dropzone.\n this.dragdrop = new DragDrop(this);\n // Save dropzone classes.\n this.classes = this.dragdrop.getClasses();\n }\n }\n\n /**\n * Remove all subcomponents dependencies.\n */\n destroy() {\n if (this.dragdrop !== undefined) {\n this.dragdrop.unregister();\n }\n }\n\n /**\n * Enable or disable the draggable property.\n *\n * @param {bool} value the new draggable value\n */\n setDraggable(value) {\n if (this.getDraggableData) {\n this.dragdrop?.setDraggable(value);\n }\n }\n\n // Drag and drop methods.\n\n /**\n * The element drop start hook.\n *\n * @param {Object} dropdata the dropdata\n */\n dragStart(dropdata) {\n this.reactive.dispatch('sectionDrag', [dropdata.id], true);\n }\n\n /**\n * The element end start hook.\n *\n * @param {Object} dropdata the dropdata\n */\n dragEnd(dropdata) {\n this.reactive.dispatch('sectionDrag', [dropdata.id], false);\n }\n\n /**\n * Get the draggable data of this component.\n *\n * @returns {Object} exported course module drop data\n */\n _getDraggableData() {\n const exporter = this.reactive.getExporter();\n return exporter.sectionDraggableData(this.reactive.state, this.id);\n }\n\n /**\n * Validate if the drop data can be dropped over the component.\n *\n * @param {Object} dropdata the exported drop data.\n * @returns {boolean}\n */\n validateDropData(dropdata) {\n // Course module validation.\n if (dropdata?.type === 'cm') {\n // Prevent content loops with subsections.\n if (this.section?.component && dropdata?.delegatesection === true) {\n return false;\n }\n // The first section element is already there so we can ignore it.\n const firstcmid = this.section?.cmlist[0];\n return dropdata.id !== firstcmid;\n }\n return false;\n }\n\n /**\n * Display the component dropzone.\n */\n showDropZone() {\n this.element.classList.add(this.classes.DROPZONE);\n }\n\n /**\n * Hide the component dropzone.\n */\n hideDropZone() {\n this.element.classList.remove(this.classes.DROPZONE);\n }\n\n /**\n * Drop event handler.\n *\n * @param {Object} dropdata the accepted drop data\n * @param {Event} event the drop event\n */\n drop(dropdata, event) {\n // Call the move mutation.\n if (dropdata.type == 'cm') {\n const mutation = (event.altKey) ? 'cmDuplicate' : 'cmMove';\n this.reactive.dispatch(mutation, [dropdata.id], this.id, this.section?.cmlist[0]);\n }\n }\n}\n"],"names":["BaseComponent","configDragDrop","sectionid","state","fullregion","id","undefined","this","section","get","course","number","getDraggableData","_getDraggableData","reactive","isEditing","supportComponents","dragdrop","DragDrop","classes","getClasses","destroy","unregister","setDraggable","value","dragStart","dropdata","dispatch","dragEnd","getExporter","sectionDraggableData","validateDropData","type","component","delegatesection","firstcmid","_this$section2","cmlist","showDropZone","element","classList","add","DROPZONE","hideDropZone","remove","drop","event","mutation","altKey","_this$section3"],"mappings":";;;;;;;;;;;;uBA6B6BA,wBASzBC,eAAeC,UAAWC,MAAOC,iBAExBC,GAAKH,eACWI,IAAjBC,KAAKC,eACAA,QAAUL,MAAMK,QAAQC,IAAIF,KAAKF,UAEtBC,IAAhBC,KAAKG,cACAA,OAASP,MAAMO,QAIpBH,KAAKC,QAAQG,OAAS,SACjBC,iBAAmBL,KAAKM,wBAG5BT,WAAaA,WAGdG,KAAKO,SAASC,WAAaR,KAAKO,SAASE,yBAEpCC,SAAW,IAAIC,mBAASX,WAExBY,QAAUZ,KAAKU,SAASG,cAOrCC,eAC0Bf,IAAlBC,KAAKU,eACAA,SAASK,aAStBC,aAAaC,0BACLjB,KAAKK,+CACAK,mDAAUM,aAAaC,QAWpCC,UAAUC,eACDZ,SAASa,SAAS,cAAe,CAACD,SAASrB,KAAK,GAQzDuB,QAAQF,eACCZ,SAASa,SAAS,cAAe,CAACD,SAASrB,KAAK,GAQzDQ,2BACqBN,KAAKO,SAASe,cACfC,qBAAqBvB,KAAKO,SAASX,MAAOI,KAAKF,IASnE0B,iBAAiBL,aAEU,QAAnBA,MAAAA,gBAAAA,SAAUM,MAAe,gEAEhBxB,gDAASyB,YAA2C,KAA9BP,MAAAA,gBAAAA,SAAUQ,wBAC9B,QAGLC,iCAAY5B,KAAKC,yCAAL4B,eAAcC,OAAO,UAChCX,SAASrB,KAAO8B,iBAEpB,EAMXG,oBACSC,QAAQC,UAAUC,IAAIlC,KAAKY,QAAQuB,UAM5CC,oBACSJ,QAAQC,UAAUI,OAAOrC,KAAKY,QAAQuB,UAS/CG,KAAKnB,SAAUoB,UAEU,MAAjBpB,SAASM,KAAc,0BACjBe,SAAYD,MAAME,OAAU,cAAgB,cAC7ClC,SAASa,SAASoB,SAAU,CAACrB,SAASrB,IAAKE,KAAKF,0BAAIE,KAAKC,yCAALyC,eAAcZ,OAAO"} \ No newline at end of file diff --git a/course/format/amd/build/local/courseeditor/exporter.min.js b/course/format/amd/build/local/courseeditor/exporter.min.js index 2b5150f55e8..3ed55d17121 100644 --- a/course/format/amd/build/local/courseeditor/exporter.min.js +++ b/course/format/amd/build/local/courseeditor/exporter.min.js @@ -8,6 +8,6 @@ define("core_courseformat/local/courseeditor/exporter",["exports"],(function(_ex * @copyright 2021 Ferran Recio * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -class{constructor(reactive){this.reactive=reactive,this.COMPLETIONS=["incomplete","complete","complete","fail"]}course(state){var _state$course$highlig;const data={sections:[],editmode:this.reactive.isEditing,highlighted:null!==(_state$course$highlig=state.course.highlighted)&&void 0!==_state$course$highlig?_state$course$highlig:""};return this.listedSectionIds(state).forEach((sectionid=>{var _state$section$get;const sectioninfo=null!==(_state$section$get=state.section.get(sectionid))&&void 0!==_state$section$get?_state$section$get:{},section=this.section(state,sectioninfo);data.sections.push(section)})),data.hassections=0!=data.sections.length,data}listedSectionIds(state){var _state$course$section;return(null!==(_state$course$section=state.course.sectionlist)&&void 0!==_state$course$section?_state$course$section:[]).filter((sectionid=>{var _state$section$get2;return null===(null!==(_state$section$get2=state.section.get(sectionid))&&void 0!==_state$section$get2?_state$section$get2:{}).component}))}section(state,sectioninfo){var _state$course$highlig2,_sectioninfo$cmlist;const section={...sectioninfo,highlighted:null!==(_state$course$highlig2=state.course.highlighted)&&void 0!==_state$course$highlig2?_state$course$highlig2:"",cms:[]};return(null!==(_sectioninfo$cmlist=sectioninfo.cmlist)&&void 0!==_sectioninfo$cmlist?_sectioninfo$cmlist:[]).forEach((cmid=>{const cminfo=state.cm.get(cmid),cm=this.cm(state,cminfo);section.cms.push(cm)})),section.hascms=0!=section.cms.length,section}cm(state,cminfo){return{...cminfo,isactive:!1}}cmDraggableData(state,cmid){const cminfo=state.cm.get(cmid);if(!cminfo)return null;let nextcmid;const section=state.section.get(cminfo.sectionid),currentindex=null==section?void 0:section.cmlist.indexOf(cminfo.id);return void 0!==currentindex&&(nextcmid=null==section?void 0:section.cmlist[currentindex+1]),{type:"cm",id:cminfo.id,name:cminfo.name,sectionid:cminfo.sectionid,nextcmid:nextcmid}}sectionDraggableData(state,sectionid){const sectioninfo=state.section.get(sectionid);return sectioninfo?{type:"section",id:sectioninfo.id,name:sectioninfo.name,number:sectioninfo.number}:null}fileDraggableData(state,dataTransfer){var _dataTransfer$files;const files=[];return(null===(_dataTransfer$files=dataTransfer.files)||void 0===_dataTransfer$files?void 0:_dataTransfer$files.length)>0&&dataTransfer.files.forEach((file=>{files.push(file)})),{type:"files",files:files}}cmCompletion(state,cminfo){const data={statename:"",state:"NaN"};if(void 0!==cminfo.completionstate){var _this$COMPLETIONS$cmi;data.state=cminfo.completionstate,data.hasstate=!0;const statename=null!==(_this$COMPLETIONS$cmi=this.COMPLETIONS[cminfo.completionstate])&&void 0!==_this$COMPLETIONS$cmi?_this$COMPLETIONS$cmi:"NaN";data["is".concat(statename)]=!0}return data}allItemsArray(state){var _state$course$section2;const items=[];return(null!==(_state$course$section2=state.course.sectionlist)&&void 0!==_state$course$section2?_state$course$section2:[]).forEach((sectionid=>{var _sectioninfo$cmlist2;const sectioninfo=state.section.get(sectionid);items.push({type:"section",id:sectioninfo.id,url:sectioninfo.sectionurl});(null!==(_sectioninfo$cmlist2=sectioninfo.cmlist)&&void 0!==_sectioninfo$cmlist2?_sectioninfo$cmlist2:[]).forEach((cmid=>{const cminfo=state.cm.get(cmid);items.push({type:"cm",id:cminfo.id,url:cminfo.url})}))})),items}canUseStealth(state,cmIds){return cmIds.some((cmId=>{var _cminfo$allowstealth;const cminfo=state.cm.get(cmId);return null!==(_cminfo$allowstealth=null==cminfo?void 0:cminfo.allowstealth)&&void 0!==_cminfo$allowstealth&&_cminfo$allowstealth}))}},_exports.default})); +class{constructor(reactive){this.reactive=reactive,this.COMPLETIONS=["incomplete","complete","complete","fail"]}course(state){var _state$course$highlig;const data={sections:[],editmode:this.reactive.isEditing,highlighted:null!==(_state$course$highlig=state.course.highlighted)&&void 0!==_state$course$highlig?_state$course$highlig:""};return this.listedSectionIds(state).forEach((sectionid=>{var _state$section$get;const sectioninfo=null!==(_state$section$get=state.section.get(sectionid))&&void 0!==_state$section$get?_state$section$get:{},section=this.section(state,sectioninfo);data.sections.push(section)})),data.hassections=0!=data.sections.length,data}listedSectionIds(state){var _state$course$section;return(null!==(_state$course$section=state.course.sectionlist)&&void 0!==_state$course$section?_state$course$section:[]).filter((sectionid=>{var _state$section$get2;return null===(null!==(_state$section$get2=state.section.get(sectionid))&&void 0!==_state$section$get2?_state$section$get2:{}).component}))}section(state,sectioninfo){var _state$course$highlig2,_sectioninfo$cmlist;const section={...sectioninfo,highlighted:null!==(_state$course$highlig2=state.course.highlighted)&&void 0!==_state$course$highlig2?_state$course$highlig2:"",cms:[]};return(null!==(_sectioninfo$cmlist=sectioninfo.cmlist)&&void 0!==_sectioninfo$cmlist?_sectioninfo$cmlist:[]).forEach((cmid=>{const cminfo=state.cm.get(cmid),cm=this.cm(state,cminfo);section.cms.push(cm)})),section.hascms=0!=section.cms.length,section}cm(state,cminfo){return{...cminfo,isactive:!1}}cmDraggableData(state,cmid){const cminfo=state.cm.get(cmid);if(!cminfo)return null;let nextcmid;const section=state.section.get(cminfo.sectionid),currentindex=null==section?void 0:section.cmlist.indexOf(cminfo.id);return void 0!==currentindex&&(nextcmid=null==section?void 0:section.cmlist[currentindex+1]),{type:"cm",id:cminfo.id,name:cminfo.name,sectionid:cminfo.sectionid,delegatesection:cminfo.delegatesection,nextcmid:nextcmid}}sectionDraggableData(state,sectionid){const sectioninfo=state.section.get(sectionid);return sectioninfo?{type:"section",id:sectioninfo.id,name:sectioninfo.name,number:sectioninfo.number}:null}fileDraggableData(state,dataTransfer){var _dataTransfer$files;const files=[];return(null===(_dataTransfer$files=dataTransfer.files)||void 0===_dataTransfer$files?void 0:_dataTransfer$files.length)>0&&dataTransfer.files.forEach((file=>{files.push(file)})),{type:"files",files:files}}cmCompletion(state,cminfo){const data={statename:"",state:"NaN"};if(void 0!==cminfo.completionstate){var _this$COMPLETIONS$cmi;data.state=cminfo.completionstate,data.hasstate=!0;const statename=null!==(_this$COMPLETIONS$cmi=this.COMPLETIONS[cminfo.completionstate])&&void 0!==_this$COMPLETIONS$cmi?_this$COMPLETIONS$cmi:"NaN";data["is".concat(statename)]=!0}return data}allItemsArray(state){var _state$course$section2;const items=[];return(null!==(_state$course$section2=state.course.sectionlist)&&void 0!==_state$course$section2?_state$course$section2:[]).forEach((sectionid=>{var _sectioninfo$cmlist2;const sectioninfo=state.section.get(sectionid);items.push({type:"section",id:sectioninfo.id,url:sectioninfo.sectionurl});(null!==(_sectioninfo$cmlist2=sectioninfo.cmlist)&&void 0!==_sectioninfo$cmlist2?_sectioninfo$cmlist2:[]).forEach((cmid=>{const cminfo=state.cm.get(cmid);items.push({type:"cm",id:cminfo.id,url:cminfo.url})}))})),items}canUseStealth(state,cmIds){return cmIds.some((cmId=>{var _cminfo$allowstealth;const cminfo=state.cm.get(cmId);return null!==(_cminfo$allowstealth=null==cminfo?void 0:cminfo.allowstealth)&&void 0!==_cminfo$allowstealth&&_cminfo$allowstealth}))}},_exports.default})); //# sourceMappingURL=exporter.min.js.map \ No newline at end of file diff --git a/course/format/amd/build/local/courseeditor/exporter.min.js.map b/course/format/amd/build/local/courseeditor/exporter.min.js.map index 03e2316bd64..56cfcc968ec 100644 --- a/course/format/amd/build/local/courseeditor/exporter.min.js.map +++ b/course/format/amd/build/local/courseeditor/exporter.min.js.map @@ -1 +1 @@ -{"version":3,"file":"exporter.min.js","sources":["../../../src/local/courseeditor/exporter.js"],"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 * Module to export parts of the state and transform them to be used in templates\n * and as draggable data.\n *\n * @module core_courseformat/local/courseeditor/exporter\n * @class core_courseformat/local/courseeditor/exporter\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 /**\n * Class constructor.\n *\n * @param {CourseEditor} reactive the course editor object\n */\n constructor(reactive) {\n this.reactive = reactive;\n\n // Completions states are defined in lib/completionlib.php. There are 4 different completion\n // state values, however, the course index uses the same state for complete and complete_pass.\n // This is the reason why completed appears twice in the array.\n this.COMPLETIONS = ['incomplete', 'complete', 'complete', 'fail'];\n }\n\n /**\n * Generate the course export data from the state.\n *\n * @param {Object} state the current state.\n * @returns {Object}\n */\n course(state) {\n // Collect section information from the state.\n const data = {\n sections: [],\n editmode: this.reactive.isEditing,\n highlighted: state.course.highlighted ?? '',\n };\n const sectionlist = this.listedSectionIds(state);\n sectionlist.forEach(sectionid => {\n const sectioninfo = state.section.get(sectionid) ?? {};\n const section = this.section(state, sectioninfo);\n data.sections.push(section);\n });\n data.hassections = (data.sections.length != 0);\n\n return data;\n }\n\n /**\n * Get the IDs of the sections that are listed as regular sections.\n * @param {Object} state the current state.\n * @returns {Number[]} the list of section ids that are listed.\n */\n listedSectionIds(state) {\n const fullSectionList = state.course.sectionlist ?? [];\n return fullSectionList.filter(sectionid => {\n const sectioninfo = state.section.get(sectionid) ?? {};\n // Delegated sections (controlled by a component) are not listed in course.\n return sectioninfo.component === null;\n });\n }\n\n /**\n * Generate a section export data from the state.\n *\n * @param {Object} state the current state.\n * @param {Object} sectioninfo the section state data.\n * @returns {Object}\n */\n section(state, sectioninfo) {\n const section = {\n ...sectioninfo,\n highlighted: state.course.highlighted ?? '',\n cms: [],\n };\n const cmlist = sectioninfo.cmlist ?? [];\n cmlist.forEach(cmid => {\n const cminfo = state.cm.get(cmid);\n const cm = this.cm(state, cminfo);\n section.cms.push(cm);\n });\n section.hascms = (section.cms.length != 0);\n\n return section;\n }\n\n /**\n * Generate a cm export data from the state.\n *\n * @param {Object} state the current state.\n * @param {Object} cminfo the course module state data.\n * @returns {Object}\n */\n cm(state, cminfo) {\n const cm = {\n ...cminfo,\n isactive: false,\n };\n return cm;\n }\n\n /**\n * Generate a dragable cm data structure.\n *\n * This method is used by any draggable course module element to generate drop data\n * for its reactive/dragdrop instance.\n *\n * @param {*} state the state object\n * @param {*} cmid the cours emodule id\n * @returns {Object|null}\n */\n cmDraggableData(state, cmid) {\n const cminfo = state.cm.get(cmid);\n if (!cminfo) {\n return null;\n }\n\n // Drop an activity over the next activity is the same as doing anything.\n let nextcmid;\n const section = state.section.get(cminfo.sectionid);\n const currentindex = section?.cmlist.indexOf(cminfo.id);\n if (currentindex !== undefined) {\n nextcmid = section?.cmlist[currentindex + 1];\n }\n\n return {\n type: 'cm',\n id: cminfo.id,\n name: cminfo.name,\n sectionid: cminfo.sectionid,\n nextcmid,\n };\n }\n\n /**\n * Generate a dragable cm data structure.\n *\n * This method is used by any draggable section element to generate drop data\n * for its reactive/dragdrop instance.\n *\n * @param {*} state the state object\n * @param {*} sectionid the cours section id\n * @returns {Object|null}\n */\n sectionDraggableData(state, sectionid) {\n const sectioninfo = state.section.get(sectionid);\n if (!sectioninfo) {\n return null;\n }\n return {\n type: 'section',\n id: sectioninfo.id,\n name: sectioninfo.name,\n number: sectioninfo.number,\n };\n }\n\n /**\n * Generate a file draggable structure.\n *\n * This method is used when files are dragged on the browser.\n *\n * @param {*} state the state object\n * @param {*} dataTransfer the current data tranfer data\n * @returns {Object|null}\n */\n fileDraggableData(state, dataTransfer) {\n const files = [];\n // Browsers do not provide the file list until the drop event.\n if (dataTransfer.files?.length > 0) {\n dataTransfer.files.forEach(file => {\n files.push(file);\n });\n }\n return {\n type: 'files',\n files,\n };\n }\n\n /**\n * Generate a completion export data from the cm element.\n *\n * @param {Object} state the current state.\n * @param {Object} cminfo the course module state data.\n * @returns {Object}\n */\n cmCompletion(state, cminfo) {\n const data = {\n statename: '',\n state: 'NaN',\n };\n if (cminfo.completionstate !== undefined) {\n data.state = cminfo.completionstate;\n data.hasstate = true;\n const statename = this.COMPLETIONS[cminfo.completionstate] ?? 'NaN';\n data[`is${statename}`] = true;\n }\n return data;\n }\n\n /**\n * Return a sorted list of all sections and cms items in the state.\n *\n * @param {Object} state the current state.\n * @returns {Array} all sections and cms items in the state.\n */\n allItemsArray(state) {\n const items = [];\n const sectionlist = state.course.sectionlist ?? [];\n // Add sections.\n sectionlist.forEach(sectionid => {\n const sectioninfo = state.section.get(sectionid);\n items.push({type: 'section', id: sectioninfo.id, url: sectioninfo.sectionurl});\n // Add cms.\n const cmlist = sectioninfo.cmlist ?? [];\n cmlist.forEach(cmid => {\n const cminfo = state.cm.get(cmid);\n items.push({type: 'cm', id: cminfo.id, url: cminfo.url});\n });\n });\n return items;\n }\n\n /**\n * Check is some activities of a list can be stealth.\n *\n * @param {Object} state the current state.\n * @param {Number[]} cmIds the module ids to check\n * @returns {Boolean} if any of the activities can be stealth.\n */\n canUseStealth(state, cmIds) {\n return cmIds.some(cmId => {\n const cminfo = state.cm.get(cmId);\n return cminfo?.allowstealth ?? false;\n });\n }\n}\n"],"names":["constructor","reactive","COMPLETIONS","course","state","data","sections","editmode","this","isEditing","highlighted","listedSectionIds","forEach","sectionid","sectioninfo","section","get","push","hassections","length","sectionlist","filter","component","cms","cmlist","cmid","cminfo","cm","hascms","isactive","cmDraggableData","nextcmid","currentindex","indexOf","id","undefined","type","name","sectionDraggableData","number","fileDraggableData","dataTransfer","files","file","cmCompletion","statename","completionstate","hasstate","allItemsArray","items","url","sectionurl","canUseStealth","cmIds","some","cmId","allowstealth"],"mappings":";;;;;;;;;;MA+BIA,YAAYC,eACHA,SAAWA,cAKXC,YAAc,CAAC,aAAc,WAAY,WAAY,QAS9DC,OAAOC,uCAEGC,KAAO,CACTC,SAAU,GACVC,SAAUC,KAAKP,SAASQ,UACxBC,0CAAaN,MAAMD,OAAOO,mEAAe,WAEzBF,KAAKG,iBAAiBP,OAC9BQ,SAAQC,yCACVC,uCAAcV,MAAMW,QAAQC,IAAIH,4DAAc,GAC9CE,QAAUP,KAAKO,QAAQX,MAAOU,aACpCT,KAAKC,SAASW,KAAKF,YAEvBV,KAAKa,YAAuC,GAAxBb,KAAKC,SAASa,OAE3Bd,KAQXM,iBAAiBP,sEACWA,MAAMD,OAAOiB,mEAAe,IAC7BC,QAAOR,2CAGO,oCAFbT,MAAMW,QAAQC,IAAIH,8DAAc,IAEjCS,aAW3BP,QAAQX,MAAOU,kEACLC,QAAU,IACTD,YACHJ,2CAAaN,MAAMD,OAAOO,qEAAe,GACzCa,IAAK,uCAEMT,YAAYU,0DAAU,IAC9BZ,SAAQa,aACLC,OAAStB,MAAMuB,GAAGX,IAAIS,MACtBE,GAAKnB,KAAKmB,GAAGvB,MAAOsB,QAC1BX,QAAQQ,IAAIN,KAAKU,OAErBZ,QAAQa,OAAgC,GAAtBb,QAAQQ,IAAIJ,OAEvBJ,QAUXY,GAAGvB,MAAOsB,cACK,IACJA,OACHG,UAAU,GAelBC,gBAAgB1B,MAAOqB,YACbC,OAAStB,MAAMuB,GAAGX,IAAIS,UACvBC,cACM,SAIPK,eACEhB,QAAUX,MAAMW,QAAQC,IAAIU,OAAOb,WACnCmB,aAAejB,MAAAA,eAAAA,QAASS,OAAOS,QAAQP,OAAOQ,gBAC/BC,IAAjBH,eACAD,SAAWhB,MAAAA,eAAAA,QAASS,OAAOQ,aAAe,IAGvC,CACHI,KAAM,KACNF,GAAIR,OAAOQ,GACXG,KAAMX,OAAOW,KACbxB,UAAWa,OAAOb,UAClBkB,SAAAA,UAcRO,qBAAqBlC,MAAOS,iBAClBC,YAAcV,MAAMW,QAAQC,IAAIH,kBACjCC,YAGE,CACHsB,KAAM,UACNF,GAAIpB,YAAYoB,GAChBG,KAAMvB,YAAYuB,KAClBE,OAAQzB,YAAYyB,QANb,KAmBfC,kBAAkBpC,MAAOqC,4CACfC,MAAQ,sCAEVD,aAAaC,gEAAOvB,QAAS,GAC7BsB,aAAaC,MAAM9B,SAAQ+B,OACvBD,MAAMzB,KAAK0B,SAGZ,CACHP,KAAM,QACNM,MAAAA,OAWRE,aAAaxC,MAAOsB,cACVrB,KAAO,CACTwC,UAAW,GACXzC,MAAO,eAEoB+B,IAA3BT,OAAOoB,gBAA+B,2BACtCzC,KAAKD,MAAQsB,OAAOoB,gBACpBzC,KAAK0C,UAAW,QACVF,wCAAYrC,KAAKN,YAAYwB,OAAOoB,wEAAoB,MAC9DzC,iBAAUwC,aAAe,SAEtBxC,KASX2C,cAAc5C,wCACJ6C,MAAQ,yCACM7C,MAAMD,OAAOiB,qEAAe,IAEpCR,SAAQC,2CACVC,YAAcV,MAAMW,QAAQC,IAAIH,WACtCoC,MAAMhC,KAAK,CAACmB,KAAM,UAAWF,GAAIpB,YAAYoB,GAAIgB,IAAKpC,YAAYqC,2CAEnDrC,YAAYU,4DAAU,IAC9BZ,SAAQa,aACLC,OAAStB,MAAMuB,GAAGX,IAAIS,MAC5BwB,MAAMhC,KAAK,CAACmB,KAAM,KAAMF,GAAIR,OAAOQ,GAAIgB,IAAKxB,OAAOwB,YAGpDD,MAUXG,cAAchD,MAAOiD,cACVA,MAAMC,MAAKC,sCACR7B,OAAStB,MAAMuB,GAAGX,IAAIuC,0CACrB7B,MAAAA,cAAAA,OAAQ8B"} \ No newline at end of file +{"version":3,"file":"exporter.min.js","sources":["../../../src/local/courseeditor/exporter.js"],"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 * Module to export parts of the state and transform them to be used in templates\n * and as draggable data.\n *\n * @module core_courseformat/local/courseeditor/exporter\n * @class core_courseformat/local/courseeditor/exporter\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 /**\n * Class constructor.\n *\n * @param {CourseEditor} reactive the course editor object\n */\n constructor(reactive) {\n this.reactive = reactive;\n\n // Completions states are defined in lib/completionlib.php. There are 4 different completion\n // state values, however, the course index uses the same state for complete and complete_pass.\n // This is the reason why completed appears twice in the array.\n this.COMPLETIONS = ['incomplete', 'complete', 'complete', 'fail'];\n }\n\n /**\n * Generate the course export data from the state.\n *\n * @param {Object} state the current state.\n * @returns {Object}\n */\n course(state) {\n // Collect section information from the state.\n const data = {\n sections: [],\n editmode: this.reactive.isEditing,\n highlighted: state.course.highlighted ?? '',\n };\n const sectionlist = this.listedSectionIds(state);\n sectionlist.forEach(sectionid => {\n const sectioninfo = state.section.get(sectionid) ?? {};\n const section = this.section(state, sectioninfo);\n data.sections.push(section);\n });\n data.hassections = (data.sections.length != 0);\n\n return data;\n }\n\n /**\n * Get the IDs of the sections that are listed as regular sections.\n * @param {Object} state the current state.\n * @returns {Number[]} the list of section ids that are listed.\n */\n listedSectionIds(state) {\n const fullSectionList = state.course.sectionlist ?? [];\n return fullSectionList.filter(sectionid => {\n const sectioninfo = state.section.get(sectionid) ?? {};\n // Delegated sections (controlled by a component) are not listed in course.\n return sectioninfo.component === null;\n });\n }\n\n /**\n * Generate a section export data from the state.\n *\n * @param {Object} state the current state.\n * @param {Object} sectioninfo the section state data.\n * @returns {Object}\n */\n section(state, sectioninfo) {\n const section = {\n ...sectioninfo,\n highlighted: state.course.highlighted ?? '',\n cms: [],\n };\n const cmlist = sectioninfo.cmlist ?? [];\n cmlist.forEach(cmid => {\n const cminfo = state.cm.get(cmid);\n const cm = this.cm(state, cminfo);\n section.cms.push(cm);\n });\n section.hascms = (section.cms.length != 0);\n\n return section;\n }\n\n /**\n * Generate a cm export data from the state.\n *\n * @param {Object} state the current state.\n * @param {Object} cminfo the course module state data.\n * @returns {Object}\n */\n cm(state, cminfo) {\n const cm = {\n ...cminfo,\n isactive: false,\n };\n return cm;\n }\n\n /**\n * Generate a dragable cm data structure.\n *\n * This method is used by any draggable course module element to generate drop data\n * for its reactive/dragdrop instance.\n *\n * @param {*} state the state object\n * @param {*} cmid the cours emodule id\n * @returns {Object|null}\n */\n cmDraggableData(state, cmid) {\n const cminfo = state.cm.get(cmid);\n if (!cminfo) {\n return null;\n }\n\n // Drop an activity over the next activity is the same as doing anything.\n let nextcmid;\n const section = state.section.get(cminfo.sectionid);\n const currentindex = section?.cmlist.indexOf(cminfo.id);\n if (currentindex !== undefined) {\n nextcmid = section?.cmlist[currentindex + 1];\n }\n\n return {\n type: 'cm',\n id: cminfo.id,\n name: cminfo.name,\n sectionid: cminfo.sectionid,\n delegatesection: cminfo.delegatesection,\n nextcmid,\n };\n }\n\n /**\n * Generate a dragable cm data structure.\n *\n * This method is used by any draggable section element to generate drop data\n * for its reactive/dragdrop instance.\n *\n * @param {*} state the state object\n * @param {*} sectionid the cours section id\n * @returns {Object|null}\n */\n sectionDraggableData(state, sectionid) {\n const sectioninfo = state.section.get(sectionid);\n if (!sectioninfo) {\n return null;\n }\n return {\n type: 'section',\n id: sectioninfo.id,\n name: sectioninfo.name,\n number: sectioninfo.number,\n };\n }\n\n /**\n * Generate a file draggable structure.\n *\n * This method is used when files are dragged on the browser.\n *\n * @param {*} state the state object\n * @param {*} dataTransfer the current data tranfer data\n * @returns {Object|null}\n */\n fileDraggableData(state, dataTransfer) {\n const files = [];\n // Browsers do not provide the file list until the drop event.\n if (dataTransfer.files?.length > 0) {\n dataTransfer.files.forEach(file => {\n files.push(file);\n });\n }\n return {\n type: 'files',\n files,\n };\n }\n\n /**\n * Generate a completion export data from the cm element.\n *\n * @param {Object} state the current state.\n * @param {Object} cminfo the course module state data.\n * @returns {Object}\n */\n cmCompletion(state, cminfo) {\n const data = {\n statename: '',\n state: 'NaN',\n };\n if (cminfo.completionstate !== undefined) {\n data.state = cminfo.completionstate;\n data.hasstate = true;\n const statename = this.COMPLETIONS[cminfo.completionstate] ?? 'NaN';\n data[`is${statename}`] = true;\n }\n return data;\n }\n\n /**\n * Return a sorted list of all sections and cms items in the state.\n *\n * @param {Object} state the current state.\n * @returns {Array} all sections and cms items in the state.\n */\n allItemsArray(state) {\n const items = [];\n const sectionlist = state.course.sectionlist ?? [];\n // Add sections.\n sectionlist.forEach(sectionid => {\n const sectioninfo = state.section.get(sectionid);\n items.push({type: 'section', id: sectioninfo.id, url: sectioninfo.sectionurl});\n // Add cms.\n const cmlist = sectioninfo.cmlist ?? [];\n cmlist.forEach(cmid => {\n const cminfo = state.cm.get(cmid);\n items.push({type: 'cm', id: cminfo.id, url: cminfo.url});\n });\n });\n return items;\n }\n\n /**\n * Check is some activities of a list can be stealth.\n *\n * @param {Object} state the current state.\n * @param {Number[]} cmIds the module ids to check\n * @returns {Boolean} if any of the activities can be stealth.\n */\n canUseStealth(state, cmIds) {\n return cmIds.some(cmId => {\n const cminfo = state.cm.get(cmId);\n return cminfo?.allowstealth ?? false;\n });\n }\n}\n"],"names":["constructor","reactive","COMPLETIONS","course","state","data","sections","editmode","this","isEditing","highlighted","listedSectionIds","forEach","sectionid","sectioninfo","section","get","push","hassections","length","sectionlist","filter","component","cms","cmlist","cmid","cminfo","cm","hascms","isactive","cmDraggableData","nextcmid","currentindex","indexOf","id","undefined","type","name","delegatesection","sectionDraggableData","number","fileDraggableData","dataTransfer","files","file","cmCompletion","statename","completionstate","hasstate","allItemsArray","items","url","sectionurl","canUseStealth","cmIds","some","cmId","allowstealth"],"mappings":";;;;;;;;;;MA+BIA,YAAYC,eACHA,SAAWA,cAKXC,YAAc,CAAC,aAAc,WAAY,WAAY,QAS9DC,OAAOC,uCAEGC,KAAO,CACTC,SAAU,GACVC,SAAUC,KAAKP,SAASQ,UACxBC,0CAAaN,MAAMD,OAAOO,mEAAe,WAEzBF,KAAKG,iBAAiBP,OAC9BQ,SAAQC,yCACVC,uCAAcV,MAAMW,QAAQC,IAAIH,4DAAc,GAC9CE,QAAUP,KAAKO,QAAQX,MAAOU,aACpCT,KAAKC,SAASW,KAAKF,YAEvBV,KAAKa,YAAuC,GAAxBb,KAAKC,SAASa,OAE3Bd,KAQXM,iBAAiBP,sEACWA,MAAMD,OAAOiB,mEAAe,IAC7BC,QAAOR,2CAGO,oCAFbT,MAAMW,QAAQC,IAAIH,8DAAc,IAEjCS,aAW3BP,QAAQX,MAAOU,kEACLC,QAAU,IACTD,YACHJ,2CAAaN,MAAMD,OAAOO,qEAAe,GACzCa,IAAK,uCAEMT,YAAYU,0DAAU,IAC9BZ,SAAQa,aACLC,OAAStB,MAAMuB,GAAGX,IAAIS,MACtBE,GAAKnB,KAAKmB,GAAGvB,MAAOsB,QAC1BX,QAAQQ,IAAIN,KAAKU,OAErBZ,QAAQa,OAAgC,GAAtBb,QAAQQ,IAAIJ,OAEvBJ,QAUXY,GAAGvB,MAAOsB,cACK,IACJA,OACHG,UAAU,GAelBC,gBAAgB1B,MAAOqB,YACbC,OAAStB,MAAMuB,GAAGX,IAAIS,UACvBC,cACM,SAIPK,eACEhB,QAAUX,MAAMW,QAAQC,IAAIU,OAAOb,WACnCmB,aAAejB,MAAAA,eAAAA,QAASS,OAAOS,QAAQP,OAAOQ,gBAC/BC,IAAjBH,eACAD,SAAWhB,MAAAA,eAAAA,QAASS,OAAOQ,aAAe,IAGvC,CACHI,KAAM,KACNF,GAAIR,OAAOQ,GACXG,KAAMX,OAAOW,KACbxB,UAAWa,OAAOb,UAClByB,gBAAiBZ,OAAOY,gBACxBP,SAAAA,UAcRQ,qBAAqBnC,MAAOS,iBAClBC,YAAcV,MAAMW,QAAQC,IAAIH,kBACjCC,YAGE,CACHsB,KAAM,UACNF,GAAIpB,YAAYoB,GAChBG,KAAMvB,YAAYuB,KAClBG,OAAQ1B,YAAY0B,QANb,KAmBfC,kBAAkBrC,MAAOsC,4CACfC,MAAQ,sCAEVD,aAAaC,gEAAOxB,QAAS,GAC7BuB,aAAaC,MAAM/B,SAAQgC,OACvBD,MAAM1B,KAAK2B,SAGZ,CACHR,KAAM,QACNO,MAAAA,OAWRE,aAAazC,MAAOsB,cACVrB,KAAO,CACTyC,UAAW,GACX1C,MAAO,eAEoB+B,IAA3BT,OAAOqB,gBAA+B,2BACtC1C,KAAKD,MAAQsB,OAAOqB,gBACpB1C,KAAK2C,UAAW,QACVF,wCAAYtC,KAAKN,YAAYwB,OAAOqB,wEAAoB,MAC9D1C,iBAAUyC,aAAe,SAEtBzC,KASX4C,cAAc7C,wCACJ8C,MAAQ,yCACM9C,MAAMD,OAAOiB,qEAAe,IAEpCR,SAAQC,2CACVC,YAAcV,MAAMW,QAAQC,IAAIH,WACtCqC,MAAMjC,KAAK,CAACmB,KAAM,UAAWF,GAAIpB,YAAYoB,GAAIiB,IAAKrC,YAAYsC,2CAEnDtC,YAAYU,4DAAU,IAC9BZ,SAAQa,aACLC,OAAStB,MAAMuB,GAAGX,IAAIS,MAC5ByB,MAAMjC,KAAK,CAACmB,KAAM,KAAMF,GAAIR,OAAOQ,GAAIiB,IAAKzB,OAAOyB,YAGpDD,MAUXG,cAAcjD,MAAOkD,cACVA,MAAMC,MAAKC,sCACR9B,OAAStB,MAAMuB,GAAGX,IAAIwC,0CACrB9B,MAAAA,cAAAA,OAAQ+B"} \ No newline at end of file diff --git a/course/format/amd/src/local/courseeditor/dndcmitem.js b/course/format/amd/src/local/courseeditor/dndcmitem.js index d11fad0d148..b4b568b3173 100644 --- a/course/format/amd/src/local/courseeditor/dndcmitem.js +++ b/course/format/amd/src/local/courseeditor/dndcmitem.js @@ -102,7 +102,18 @@ export default class extends BaseComponent { * @returns {boolean} */ validateDropData(dropdata) { - return dropdata?.type === 'cm'; + if (dropdata?.type !== 'cm') { + return false; + } + // Prevent delegated sections loops. + if (dropdata?.delegatesection === true) { + const mycminfo = this.reactive.get('cm', this.id); + const mysection = this.reactive.get('section', mycminfo.sectionid); + if (mysection?.component !== null) { + return false; + } + } + return true; } /** diff --git a/course/format/amd/src/local/courseeditor/dndsection.js b/course/format/amd/src/local/courseeditor/dndsection.js index a064f7a5811..8fe79834661 100644 --- a/course/format/amd/src/local/courseeditor/dndsection.js +++ b/course/format/amd/src/local/courseeditor/dndsection.js @@ -115,8 +115,11 @@ export default class extends BaseComponent { if (dropdata?.type === 'files') { return true; } - // We accept any course module. + // We accept any course module unless it can form a subsection loop. if (dropdata?.type === 'cm') { + if (this.section?.component && dropdata?.delegatesection === true) { + return false; + } return true; } // We accept any section but yourself and the next one. diff --git a/course/format/amd/src/local/courseeditor/dndsectionitem.js b/course/format/amd/src/local/courseeditor/dndsectionitem.js index fe04c323b00..b280ec5d68e 100644 --- a/course/format/amd/src/local/courseeditor/dndsectionitem.js +++ b/course/format/amd/src/local/courseeditor/dndsectionitem.js @@ -121,6 +121,10 @@ export default class extends BaseComponent { validateDropData(dropdata) { // Course module validation. if (dropdata?.type === 'cm') { + // Prevent content loops with subsections. + if (this.section?.component && dropdata?.delegatesection === true) { + return false; + } // The first section element is already there so we can ignore it. const firstcmid = this.section?.cmlist[0]; return dropdata.id !== firstcmid; diff --git a/course/format/amd/src/local/courseeditor/exporter.js b/course/format/amd/src/local/courseeditor/exporter.js index 63d9df9ce9d..474ac7e58e8 100644 --- a/course/format/amd/src/local/courseeditor/exporter.js +++ b/course/format/amd/src/local/courseeditor/exporter.js @@ -144,6 +144,7 @@ export default class { id: cminfo.id, name: cminfo.name, sectionid: cminfo.sectionid, + delegatesection: cminfo.delegatesection, nextcmid, }; } diff --git a/course/format/classes/output/local/state/cm.php b/course/format/classes/output/local/state/cm.php index a3acbabc250..2fef3e68709 100644 --- a/course/format/classes/output/local/state/cm.php +++ b/course/format/classes/output/local/state/cm.php @@ -18,6 +18,7 @@ namespace core_courseformat\output\local\state; use core_courseformat\base as course_format; use completion_info; +use core_courseformat\sectiondelegate; use renderer_base; use section_info; use cm_info; @@ -94,6 +95,8 @@ class cm implements renderable { 'groupmode' => $cm->groupmode, 'module' => $cm->modname, 'plugin' => 'mod_' . $cm->modname, + // Activities with delegate section has some restriction to prevent structure loops. + 'delegatesection' => sectiondelegate::has_delegate_class('mod_'.$cm->modname), ]; // Check the user access type to this cm. diff --git a/course/format/classes/sectiondelegate.php b/course/format/classes/sectiondelegate.php index ff5b7ad4983..44faea6328d 100644 --- a/course/format/classes/sectiondelegate.php +++ b/course/format/classes/sectiondelegate.php @@ -49,10 +49,32 @@ abstract class sectiondelegate { if (empty($sectioninfo->component)) { return null; } - $classname = $sectioninfo->component . '\courseformat\sectiondelegate'; - if (!class_exists($classname)) { + $classname = self::get_delegate_class_name($sectioninfo->component); + if ($classname === null) { return null; } return new $classname($sectioninfo); } + + /** + * Return the delgate class name of a plugin, if any. + * @param string $pluginname + * @return string|null the delegate class name or null if not found. + */ + protected static function get_delegate_class_name(string $pluginname): ?string { + $classname = $pluginname . '\courseformat\sectiondelegate'; + if (!class_exists($classname)) { + return null; + } + return $classname; + } + + /** + * Check if a plugin has a delegate class. + * @param string $pluginname + * @return bool + */ + public static function has_delegate_class(string $pluginname): bool { + return self::get_delegate_class_name($pluginname) !== null; + } } diff --git a/course/format/tests/sectiondelegate_test.php b/course/format/tests/sectiondelegate_test.php index ffe4d0069e4..06694296a3a 100644 --- a/course/format/tests/sectiondelegate_test.php +++ b/course/format/tests/sectiondelegate_test.php @@ -72,4 +72,10 @@ class sectiondelegate_test extends \advanced_testcase { $this->assertInstanceOf('\test_component\courseformat\sectiondelegate', sectiondelegate::instance($sectioninfos[2])); $this->assertNull(sectiondelegate::instance($sectioninfos[3])); } + + public function test_has_delegate_class(): void { + $this->assertFalse(sectiondelegate::has_delegate_class('missing_component')); + $this->assertFalse(sectiondelegate::has_delegate_class('mod_label')); + $this->assertTrue(sectiondelegate::has_delegate_class('test_component')); + } }