MDL-71228 course: course index drag and drop

This commit is contained in:
Ferran Recio 2021-06-21 15:47:09 +02:00
parent 206023c15f
commit 7f750dc01c
48 changed files with 1451 additions and 27 deletions

View File

@ -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,gBAS4B,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 <http://www.gnu.org/licenses/>.\n\n/**\n * Generic reactive module used in the course editor.\n *\n * @module core_courseformat/courseeditor\n * @copyright 2021 Ferran Recio <ferran@moodle.com>\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 * @property {boolean} setup.editing if the page is in edit mode\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"}
{"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 <http://www.gnu.org/licenses/>.\n\n/**\n * Generic reactive module used in the course editor.\n *\n * @module core_courseformat/courseeditor\n * @copyright 2021 Ferran Recio <ferran@moodle.com>\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 * @property {boolean} setup.editing if the page is in edit mode\n * @property {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"}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,2 @@
define ("core_courseformat/local/courseeditor/dndcmitem",["exports","core/reactive"],function(a,b){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;function c(a){"@babel/helpers - typeof";if("function"==typeof Symbol&&"symbol"==typeof Symbol.iterator){c=function(a){return typeof a}}else{c=function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?"symbol":typeof a}}return c(a)}function d(a,b){if(!(a instanceof b)){throw new TypeError("Cannot call a class as a function")}}function e(a,b){for(var c=0,d;c<b.length;c++){d=b[c];d.enumerable=d.enumerable||!1;d.configurable=!0;if("value"in d)d.writable=!0;Object.defineProperty(a,d.key,d)}}function f(a,b,c){if(b)e(a.prototype,b);if(c)e(a,c);return a}function g(a,b){if("function"!=typeof b&&null!==b){throw new TypeError("Super expression must either be null or a function")}a.prototype=Object.create(b&&b.prototype,{constructor:{value:a,writable:!0,configurable:!0}});if(b)h(a,b)}function h(a,b){h=Object.setPrototypeOf||function(a,b){a.__proto__=b;return a};return h(a,b)}function i(a){return function(){var b=m(a),c;if(l()){var d=m(this).constructor;c=Reflect.construct(b,arguments,d)}else{c=b.apply(this,arguments)}return j(this,c)}}function j(a,b){if(b&&("object"===c(b)||"function"==typeof b)){return b}return k(a)}function k(a){if(void 0===a){throw new ReferenceError("this hasn't been initialised - super() hasn't been called")}return a}function l(){if("undefined"==typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if("function"==typeof Proxy)return!0;try{Date.prototype.toString.call(Reflect.construct(Date,[],function(){}));return!0}catch(a){return!1}}function m(a){m=Object.setPrototypeOf?Object.getPrototypeOf:function(a){return a.__proto__||Object.getPrototypeOf(a)};return m(a)}var n=function(a){g(c,a);var e=i(c);function c(){d(this,c);return e.apply(this,arguments)}f(c,[{key:"configDragDrop",value:function configDragDrop(a){this.id=a;if(this.reactive.isEditing&&this.reactive.supportComponents){this.dragdrop=new b.DragDrop(this);this.classes=this.dragdrop.getClasses()}}},{key:"destroy",value:function destroy(){if(this.dragdrop!==void 0){this.dragdrop.unregister()}}},{key:"getDraggableData",value:function getDraggableData(){var a=this.reactive.getExporter();return a.cmDraggableData(this.reactive.state,this.id)}},{key:"validateDropData",value:function validateDropData(a){return"cm"===(null===a||void 0===a?void 0:a.type)}},{key:"showDropZone",value:function showDropZone(a){if(a.nextcmid!=this.id&&a.id!=this.id){this.element.classList.add(this.classes.DROPUP)}}},{key:"hideDropZone",value:function hideDropZone(){this.element.classList.remove(this.classes.DROPUP)}},{key:"drop",value:function drop(a){if(a.id!=this.id&&a.nextcmid!=this.id){this.reactive.dispatch("cmMove",[a.id],null,this.id)}}}]);return c}(b.BaseComponent);a.default=n;return a.default});
//# sourceMappingURL=dndcmitem.min.js.map

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,2 @@
define ("core_courseformat/local/courseeditor/dndsection",["exports","core/reactive"],function(a,b){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;function c(a){"@babel/helpers - typeof";if("function"==typeof Symbol&&"symbol"==typeof Symbol.iterator){c=function(a){return typeof a}}else{c=function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?"symbol":typeof a}}return c(a)}function d(a,b){if(!(a instanceof b)){throw new TypeError("Cannot call a class as a function")}}function e(a,b){for(var c=0,d;c<b.length;c++){d=b[c];d.enumerable=d.enumerable||!1;d.configurable=!0;if("value"in d)d.writable=!0;Object.defineProperty(a,d.key,d)}}function f(a,b,c){if(b)e(a.prototype,b);if(c)e(a,c);return a}function g(a,b){if("function"!=typeof b&&null!==b){throw new TypeError("Super expression must either be null or a function")}a.prototype=Object.create(b&&b.prototype,{constructor:{value:a,writable:!0,configurable:!0}});if(b)h(a,b)}function h(a,b){h=Object.setPrototypeOf||function(a,b){a.__proto__=b;return a};return h(a,b)}function i(a){return function(){var b=m(a),c;if(l()){var d=m(this).constructor;c=Reflect.construct(b,arguments,d)}else{c=b.apply(this,arguments)}return j(this,c)}}function j(a,b){if(b&&("object"===c(b)||"function"==typeof b)){return b}return k(a)}function k(a){if(void 0===a){throw new ReferenceError("this hasn't been initialised - super() hasn't been called")}return a}function l(){if("undefined"==typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if("function"==typeof Proxy)return!0;try{Date.prototype.toString.call(Reflect.construct(Date,[],function(){}));return!0}catch(a){return!1}}function m(a){m=Object.setPrototypeOf?Object.getPrototypeOf:function(a){return a.__proto__||Object.getPrototypeOf(a)};return m(a)}var n=function(a){g(c,a);var e=i(c);function c(){d(this,c);return e.apply(this,arguments)}f(c,[{key:"configState",value:function configState(a){this.id=this.element.dataset.id;this.section=a.section.get(this.id);this.course=a.course}},{key:"configDragDrop",value:function configDragDrop(a){if(this.reactive.isEditing&&this.reactive.supportComponents){this.sectionitem=a;this.dragdrop=new b.DragDrop(this);this.classes=this.dragdrop.getClasses()}}},{key:"destroy",value:function destroy(){if(this.sectionitem!==void 0){this.sectionitem.unregister()}if(this.dragdrop!==void 0){this.dragdrop.unregister()}}},{key:"getLastCm",value:function getLastCm(){return null}},{key:"validateDropData",value:function validateDropData(a){if("cm"===(null===a||void 0===a?void 0:a.type)){return!0}if("section"===(null===a||void 0===a?void 0:a.type)){var b=this.course.sectionlist[0];return(null===a||void 0===a?void 0:a.id)!=this.id&&(null===a||void 0===a?void 0:a.id)!=b&&this.id!=b}return!1}},{key:"showDropZone",value:function showDropZone(a){if("cm"==a.type){var b;null===(b=this.getLastCm())||void 0===b?void 0:b.classList.add(this.classes.DROPDOWN)}if("section"==a.type){if(this.section.number>a.number){this.element.classList.remove(this.classes.DROPUP);this.element.classList.add(this.classes.DROPDOWN)}else{this.element.classList.add(this.classes.DROPUP);this.element.classList.remove(this.classes.DROPDOWN)}}}},{key:"hideDropZone",value:function hideDropZone(){var a;null===(a=this.getLastCm())||void 0===a?void 0:a.classList.remove(this.classes.DROPDOWN);this.element.classList.remove(this.classes.DROPUP);this.element.classList.remove(this.classes.DROPDOWN)}},{key:"drop",value:function drop(a){if("cm"==a.type){this.reactive.dispatch("cmMove",[a.id],this.id)}if("section"==a.type){this.reactive.dispatch("sectionMove",[a.id],this.id)}}}]);return c}(b.BaseComponent);a.default=n;return a.default});
//# sourceMappingURL=dndsection.min.js.map

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,2 @@
define ("core_courseformat/local/courseeditor/dndsectionitem",["exports","core/reactive"],function(a,b){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;function c(a){"@babel/helpers - typeof";if("function"==typeof Symbol&&"symbol"==typeof Symbol.iterator){c=function(a){return typeof a}}else{c=function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?"symbol":typeof a}}return c(a)}function d(a,b){if(!(a instanceof b)){throw new TypeError("Cannot call a class as a function")}}function e(a,b){for(var c=0,d;c<b.length;c++){d=b[c];d.enumerable=d.enumerable||!1;d.configurable=!0;if("value"in d)d.writable=!0;Object.defineProperty(a,d.key,d)}}function f(a,b,c){if(b)e(a.prototype,b);if(c)e(a,c);return a}function g(a,b){if("function"!=typeof b&&null!==b){throw new TypeError("Super expression must either be null or a function")}a.prototype=Object.create(b&&b.prototype,{constructor:{value:a,writable:!0,configurable:!0}});if(b)h(a,b)}function h(a,b){h=Object.setPrototypeOf||function(a,b){a.__proto__=b;return a};return h(a,b)}function i(a){return function(){var b=m(a),c;if(l()){var d=m(this).constructor;c=Reflect.construct(b,arguments,d)}else{c=b.apply(this,arguments)}return j(this,c)}}function j(a,b){if(b&&("object"===c(b)||"function"==typeof b)){return b}return k(a)}function k(a){if(void 0===a){throw new ReferenceError("this hasn't been initialised - super() hasn't been called")}return a}function l(){if("undefined"==typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if("function"==typeof Proxy)return!0;try{Date.prototype.toString.call(Reflect.construct(Date,[],function(){}));return!0}catch(a){return!1}}function m(a){m=Object.setPrototypeOf?Object.getPrototypeOf:function(a){return a.__proto__||Object.getPrototypeOf(a)};return m(a)}var n=function(a){g(c,a);var e=i(c);function c(){d(this,c);return e.apply(this,arguments)}f(c,[{key:"configDragDrop",value:function configDragDrop(a,c,d){this.id=a;if(this.section===void 0){this.section=c.section.get(this.id)}if(this.course===void 0){this.course=c.course}if(0<this.section.number){this.getDraggableData=this._getDraggableData}this.fullregion=d;if(this.reactive.isEditing&&this.reactive.supportComponents){this.dragdrop=new b.DragDrop(this);this.classes=this.dragdrop.getClasses()}}},{key:"destroy",value:function destroy(){if(this.dragdrop!==void 0){this.dragdrop.unregister()}}},{key:"_getDraggableData",value:function _getDraggableData(){var a=this.reactive.getExporter();return a.sectionDraggableData(this.reactive.state,this.id)}},{key:"validateDropData",value:function validateDropData(a){if("cm"===(null===a||void 0===a?void 0:a.type)){var b,c=null===(b=this.section)||void 0===b?void 0:b.cmlist[0];return a.id!==c}return!1}},{key:"showDropZone",value:function showDropZone(){this.element.classList.add(this.classes.DROPZONE)}},{key:"hideDropZone",value:function hideDropZone(){this.element.classList.remove(this.classes.DROPZONE)}},{key:"drop",value:function drop(a){if("cm"==a.type){var b;this.reactive.dispatch("cmMove",[a.id],this.id,null===(b=this.section)||void 0===b?void 0:b.cmlist[0])}}}]);return c}(b.BaseComponent);a.default=n;return a.default});
//# sourceMappingURL=dndsectionitem.min.js.map

File diff suppressed because one or more lines are too long

View File

@ -1,2 +1,2 @@
define ("core_courseformat/local/courseeditor/exporter",["exports"],function(a){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;function b(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 c(a){for(var c=1,e;c<arguments.length;c++){e=null!=arguments[c]?arguments[c]:{};if(c%2){b(Object(e),!0).forEach(function(b){d(a,b,e[b])})}else if(Object.getOwnPropertyDescriptors){Object.defineProperties(a,Object.getOwnPropertyDescriptors(e))}else{b(Object(e)).forEach(function(b){Object.defineProperty(a,b,Object.getOwnPropertyDescriptor(e,b))})}}return a}function d(a,b,c){if(b in a){Object.defineProperty(a,b,{value:c,enumerable:!0,configurable:!0,writable:!0})}else{a[b]=c}return a}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;c<b.length;c++){d=b[c];d.enumerable=d.enumerable||!1;d.configurable=!0;if("value"in d)d.writable=!0;Object.defineProperty(a,d.key,d)}}function g(a,b,c){if(b)f(a.prototype,b);if(c)f(a,c);return a}var h=function(){function a(b){e(this,a);this.reactive=b}g(a,[{key:"course",value:function course(a){var b,c=this,d={sections:[],editmode:this.reactive.isEditing},e=null!==(b=a.course.sectionlist)&&void 0!==b?b:[];e.forEach(function(b){var e,f=null!==(e=a.section.get(b))&&void 0!==e?e:{},g=c.section(a,f);d.sections.push(g)});d.hassections=0!=d.sections.length;return d}},{key:"section",value:function(a,b){var d,e=this,f=c({},b,{cms:[],isactive:!1}),g=null!==(d=b.cmlist)&&void 0!==d?d:[];g.forEach(function(b){var c=a.cm.get(b),d=e.cm(a,c);f.cms.push(d)});f.hascms=0!=f.cms.length;return f}},{key:"cm",value:function(a,b){var d=c({},b,{isactive:!1});return d}}]);return a}();a.default=h;return a.default});
define ("core_courseformat/local/courseeditor/exporter",["exports"],function(a){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;function b(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 c(a){for(var c=1,e;c<arguments.length;c++){e=null!=arguments[c]?arguments[c]:{};if(c%2){b(Object(e),!0).forEach(function(b){d(a,b,e[b])})}else if(Object.getOwnPropertyDescriptors){Object.defineProperties(a,Object.getOwnPropertyDescriptors(e))}else{b(Object(e)).forEach(function(b){Object.defineProperty(a,b,Object.getOwnPropertyDescriptor(e,b))})}}return a}function d(a,b,c){if(b in a){Object.defineProperty(a,b,{value:c,enumerable:!0,configurable:!0,writable:!0})}else{a[b]=c}return a}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;c<b.length;c++){d=b[c];d.enumerable=d.enumerable||!1;d.configurable=!0;if("value"in d)d.writable=!0;Object.defineProperty(a,d.key,d)}}function g(a,b,c){if(b)f(a.prototype,b);if(c)f(a,c);return a}var h=function(){function a(b){e(this,a);this.reactive=b}g(a,[{key:"course",value:function course(a){var b,c=this,d={sections:[],editmode:this.reactive.isEditing},e=null!==(b=a.course.sectionlist)&&void 0!==b?b:[];e.forEach(function(b){var e,f=null!==(e=a.section.get(b))&&void 0!==e?e:{},g=c.section(a,f);d.sections.push(g)});d.hassections=0!=d.sections.length;return d}},{key:"section",value:function(a,b){var d,e=this,f=c({},b,{cms:[],isactive:!1}),g=null!==(d=b.cmlist)&&void 0!==d?d:[];g.forEach(function(b){var c=a.cm.get(b),d=e.cm(a,c);f.cms.push(d)});f.hascms=0!=f.cms.length;return f}},{key:"cm",value:function(a,b){var d=c({},b,{isactive:!1});return d}},{key:"cmDraggableData",value:function cmDraggableData(a,b){var c=a.cm.get(b);if(!c){return null}var d,e=a.section.get(c.sectionid),f=null===e||void 0===e?void 0:e.cmlist.indexOf(c.id);if(f!==void 0){d=null===e||void 0===e?void 0:e.cmlist[f+1]}return{type:"cm",id:c.id,name:c.name,nextcmid:d}}},{key:"sectionDraggableData",value:function sectionDraggableData(a,b){var c=a.section.get(b);if(!c){return null}return{type:"section",id:c.id,name:c.name,number:c.number}}}]);return a}();a.default=h;return a.default});
//# sourceMappingURL=exporter.min.js.map

File diff suppressed because one or more lines are too long

View File

@ -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;c<b.length;c++){d=b[c];d.enumerable=d.enumerable||!1;d.configurable=!0;if("value"in d)d.writable=!0;Object.defineProperty(a,d.key,d)}}function g(a,b,c){if(b)f(a.prototype,b);if(c)f(a,c);return a}var h=function(){function a(){e(this,a)}g(a,[{key:"_callEditWebservice",value:function(){var a=d(regeneratorRuntime.mark(function a(c,d,e){var f;return regeneratorRuntime.wrap(function(a){while(1){switch(a.prev=a.next){case 0:a.next=2;return b.default.call([{methodname:"core_courseformat_update_course",args:{action:c,courseid:d,ids:e}}])[0];case 2:f=a.sent;return a.abrupt("return",JSON.parse(f));case 4:case"end":return a.stop();}}},a)}));return function _callEditWebservice(){return a.apply(this,arguments)}}()},{key:"cmState",value:function(){var a=d(regeneratorRuntime.mark(function a(b,c){var d,e;return regeneratorRuntime.wrap(function(a){while(1){switch(a.prev=a.next){case 0:d=b.get("course");a.next=3;return this._callEditWebservice("cm_state",d.id,c);case 3:e=a.sent;b.processUpdates(e);case 5:case"end":return a.stop();}}},a,this)}));return function cmState(){return a.apply(this,arguments)}}()},{key:"sectionState",value:function(){var a=d(regeneratorRuntime.mark(function a(b,c){var d,e;return regeneratorRuntime.wrap(function(a){while(1){switch(a.prev=a.next){case 0:d=b.get("course");a.next=3;return this._callEditWebservice("section_state",d.id,c);case 3:e=a.sent;b.processUpdates(e);case 5:case"end":return a.stop();}}},a,this)}));return function sectionState(){return a.apply(this,arguments)}}()},{key:"courseState",value:function(){var a=d(regeneratorRuntime.mark(function a(b){var c,d;return regeneratorRuntime.wrap(function(a){while(1){switch(a.prev=a.next){case 0:c=b.get("course");a.next=3;return this._callEditWebservice("course_state",c.id);case 3:d=a.sent;b.processUpdates(d);case 5:case"end":return a.stop();}}},a,this)}));return function courseState(){return a.apply(this,arguments)}}()}]);return a}();a.default=h;return a.default});
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;c<b.length;c++){d=b[c];d.enumerable=d.enumerable||!1;d.configurable=!0;if("value"in d)d.writable=!0;Object.defineProperty(a,d.key,d)}}function g(a,b,c){if(b)f(a.prototype,b);if(c)f(a,c);return a}var h=function(){function a(){e(this,a)}g(a,[{key:"_callEditWebservice",value:function(){var a=d(regeneratorRuntime.mark(function a(c,d,e,f,g){var h,i;return regeneratorRuntime.wrap(function(a){while(1){switch(a.prev=a.next){case 0:h={action:c,courseid:d,ids:e};if(f){h.targetsectionid=f}if(g){h.targetcmid=g}a.next=5;return b.default.call([{methodname:"core_courseformat_update_course",args:h}])[0];case 5:i=a.sent;return a.abrupt("return",JSON.parse(i));case 7:case"end":return a.stop();}}},a)}));return function _callEditWebservice(){return a.apply(this,arguments)}}()},{key:"cmMove",value:function(){var a=d(regeneratorRuntime.mark(function a(b,c,d,e){var f,g;return regeneratorRuntime.wrap(function(a){while(1){switch(a.prev=a.next){case 0:if(!(!d&&!e)){a.next=2;break}throw new Error("Mutation cmMove requires targetSectionId or targetCmId");case 2:f=b.get("course");a.next=5;return this._callEditWebservice("cm_move",f.id,c,d,e);case 5:g=a.sent;b.processUpdates(g);case 7:case"end":return a.stop();}}},a,this)}));return function cmMove(){return a.apply(this,arguments)}}()},{key:"sectionMove",value:function(){var a=d(regeneratorRuntime.mark(function a(b,c,d){var e,f;return regeneratorRuntime.wrap(function(a){while(1){switch(a.prev=a.next){case 0:if(d){a.next=2;break}throw new Error("Mutation sectionMove requires targetSectionId");case 2:e=b.get("course");a.next=5;return this._callEditWebservice("section_move",e.id,c,d);case 5:f=a.sent;b.processUpdates(f);case 7:case"end":return a.stop();}}},a,this)}));return function sectionMove(){return a.apply(this,arguments)}}()},{key:"cmState",value:function(){var a=d(regeneratorRuntime.mark(function a(b,c){var d,e;return regeneratorRuntime.wrap(function(a){while(1){switch(a.prev=a.next){case 0:d=b.get("course");a.next=3;return this._callEditWebservice("cm_state",d.id,c);case 3:e=a.sent;b.processUpdates(e);case 5:case"end":return a.stop();}}},a,this)}));return function cmState(){return a.apply(this,arguments)}}()},{key:"sectionState",value:function(){var a=d(regeneratorRuntime.mark(function a(b,c){var d,e;return regeneratorRuntime.wrap(function(a){while(1){switch(a.prev=a.next){case 0:d=b.get("course");a.next=3;return this._callEditWebservice("section_state",d.id,c);case 3:e=a.sent;b.processUpdates(e);case 5:case"end":return a.stop();}}},a,this)}));return function sectionState(){return a.apply(this,arguments)}}()},{key:"courseState",value:function(){var a=d(regeneratorRuntime.mark(function a(b){var c,d;return regeneratorRuntime.wrap(function(a){while(1){switch(a.prev=a.next){case 0:c=b.get("course");a.next=3;return this._callEditWebservice("course_state",c.id);case 3:d=a.sent;b.processUpdates(d);case 5:case"end":return a.stop();}}},a,this)}));return function courseState(){return a.apply(this,arguments)}}()}]);return a}();a.default=h;return a.default});
//# sourceMappingURL=mutations.min.js.map

File diff suppressed because one or more lines are too long

View File

@ -1,2 +1,2 @@
define ("core_courseformat/local/courseindex/cm",["exports","core/reactive"],function(a,b){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;function c(a){"@babel/helpers - typeof";if("function"==typeof Symbol&&"symbol"==typeof Symbol.iterator){c=function(a){return typeof a}}else{c=function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?"symbol":typeof a}}return c(a)}function d(a,b){if(!(a instanceof b)){throw new TypeError("Cannot call a class as a function")}}function e(a,b){for(var c=0,d;c<b.length;c++){d=b[c];d.enumerable=d.enumerable||!1;d.configurable=!0;if("value"in d)d.writable=!0;Object.defineProperty(a,d.key,d)}}function f(a,b,c){if(b)e(a.prototype,b);if(c)e(a,c);return a}function g(a,b){if("function"!=typeof b&&null!==b){throw new TypeError("Super expression must either be null or a function")}a.prototype=Object.create(b&&b.prototype,{constructor:{value:a,writable:!0,configurable:!0}});if(b)h(a,b)}function h(a,b){h=Object.setPrototypeOf||function(a,b){a.__proto__=b;return a};return h(a,b)}function i(a){return function(){var b=m(a),c;if(l()){var d=m(this).constructor;c=Reflect.construct(b,arguments,d)}else{c=b.apply(this,arguments)}return j(this,c)}}function j(a,b){if(b&&("object"===c(b)||"function"==typeof b)){return b}return k(a)}function k(a){if(void 0===a){throw new ReferenceError("this hasn't been initialised - super() hasn't been called")}return a}function l(){if("undefined"==typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if("function"==typeof Proxy)return!0;try{Date.prototype.toString.call(Reflect.construct(Date,[],function(){}));return!0}catch(a){return!1}}function m(a){m=Object.setPrototypeOf?Object.getPrototypeOf:function(a){return a.__proto__||Object.getPrototypeOf(a)};return m(a)}var n=function(a){g(b,a);var c=i(b);function b(){d(this,b);return c.apply(this,arguments)}f(b,[{key:"create",value:function create(){this.name="courseindex_cm";this.selectors={};this.id=this.element.dataset.id}},{key:"stateReady",value:function stateReady(){}},{key:"getWatchers",value:function getWatchers(){return[{watch:"cm[".concat(this.id,"]:deleted"),handler:this.remove}]}}],[{key:"init",value:function init(a,c){return new b({element:document.getElementById(a),selectors:c})}}]);return b}(b.BaseComponent);a.default=n;return a.default});
define ("core_courseformat/local/courseindex/cm",["exports","core_courseformat/local/courseeditor/dndcmitem"],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){"@babel/helpers - typeof";if("function"==typeof Symbol&&"symbol"==typeof Symbol.iterator){c=function(a){return typeof a}}else{c=function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?"symbol":typeof a}}return c(a)}function d(a,b){if(!(a instanceof b)){throw new TypeError("Cannot call a class as a function")}}function e(a,b){for(var c=0,d;c<b.length;c++){d=b[c];d.enumerable=d.enumerable||!1;d.configurable=!0;if("value"in d)d.writable=!0;Object.defineProperty(a,d.key,d)}}function f(a,b,c){if(b)e(a.prototype,b);if(c)e(a,c);return a}function g(a,b){if("function"!=typeof b&&null!==b){throw new TypeError("Super expression must either be null or a function")}a.prototype=Object.create(b&&b.prototype,{constructor:{value:a,writable:!0,configurable:!0}});if(b)h(a,b)}function h(a,b){h=Object.setPrototypeOf||function(a,b){a.__proto__=b;return a};return h(a,b)}function i(a){return function(){var b=m(a),c;if(l()){var d=m(this).constructor;c=Reflect.construct(b,arguments,d)}else{c=b.apply(this,arguments)}return j(this,c)}}function j(a,b){if(b&&("object"===c(b)||"function"==typeof b)){return b}return k(a)}function k(a){if(void 0===a){throw new ReferenceError("this hasn't been initialised - super() hasn't been called")}return a}function l(){if("undefined"==typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if("function"==typeof Proxy)return!0;try{Date.prototype.toString.call(Reflect.construct(Date,[],function(){}));return!0}catch(a){return!1}}function m(a){m=Object.setPrototypeOf?Object.getPrototypeOf:function(a){return a.__proto__||Object.getPrototypeOf(a)};return m(a)}var n=function(a){g(b,a);var c=i(b);function b(){d(this,b);return c.apply(this,arguments)}f(b,[{key:"create",value:function create(){this.name="courseindex_cm";this.id=this.element.dataset.id}},{key:"stateReady",value:function stateReady(){this.configDragDrop(this.id)}},{key:"getWatchers",value:function getWatchers(){return[{watch:"cm[".concat(this.id,"]:deleted"),handler:this.remove}]}}],[{key:"init",value:function init(a,c){return new b({element:document.getElementById(a),selectors:c})}}]);return b}(b.default);a.default=n;return a.default});
//# sourceMappingURL=cm.min.js.map

View File

@ -1 +1 @@
{"version":3,"sources":["../../../src/local/courseindex/cm.js"],"names":["Component","name","selectors","id","element","dataset","watch","handler","remove","target","document","getElementById","BaseComponent"],"mappings":"yyDA4BqBA,CAAAA,C,+HAKR,CAEL,KAAKC,IAAL,CAAY,gBAAZ,CAEA,KAAKC,SAAL,CAAiB,EAAjB,CAGA,KAAKC,EAAL,CAAU,KAAKC,OAAL,CAAaC,OAAb,CAAqBF,EAClC,C,+CAmBY,CAEZ,C,iDAEa,CACV,MAAO,CACH,CAACG,KAAK,cAAQ,KAAKH,EAAb,aAAN,CAAkCI,OAAO,CAAE,KAAKC,MAAhD,CADG,CAGV,C,oCAlBWC,C,CAAQP,C,CAAW,CAC3B,MAAO,IAAIF,CAAAA,CAAJ,CAAc,CACjBI,OAAO,CAAEM,QAAQ,CAACC,cAAT,CAAwBF,CAAxB,CADQ,CAEjBP,SAAS,CAATA,CAFiB,CAAd,CAIV,C,cA3BkCU,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 <http://www.gnu.org/licenses/>.\n\n/**\n * Course index cm component.\n *\n * This component is used to control specific course modules interactions like drag and drop.\n *\n * @module core_courseformat/local/courseindex/cm\n * @class core_courseformat/local/courseindex/cm\n * @copyright 2021 Ferran Recio <ferran@moodle.com>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {BaseComponent} from 'core/reactive';\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_cm';\n // Default query selectors.\n this.selectors = {\n };\n // We need our id to watch specific events.\n this.id = this.element.dataset.id;\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 selectors,\n });\n }\n\n /**\n * Initial state ready method.\n */\n stateReady() {\n // Activate drag and drop soon.\n }\n\n getWatchers() {\n return [\n {watch: `cm[${this.id}]:deleted`, handler: this.remove},\n ];\n }\n\n}\n"],"file":"cm.min.js"}
{"version":3,"sources":["../../../src/local/courseindex/cm.js"],"names":["Component","name","id","element","dataset","configDragDrop","watch","handler","remove","target","selectors","document","getElementById","DndCmItem"],"mappings":"2MA0BA,uD,+nDAEqBA,CAAAA,C,+HAKR,CAEL,KAAKC,IAAL,CAAY,gBAAZ,CAEA,KAAKC,EAAL,CAAU,KAAKC,OAAL,CAAaC,OAAb,CAAqBF,EAClC,C,+CAmBY,CACT,KAAKG,cAAL,CAAoB,KAAKH,EAAzB,CACH,C,iDAOa,CACV,MAAO,CACH,CAACI,KAAK,cAAQ,KAAKJ,EAAb,aAAN,CAAkCK,OAAO,CAAE,KAAKC,MAAhD,CADG,CAGV,C,oCAvBWC,C,CAAQC,C,CAAW,CAC3B,MAAO,IAAIV,CAAAA,CAAJ,CAAc,CACjBG,OAAO,CAAEQ,QAAQ,CAACC,cAAT,CAAwBH,CAAxB,CADQ,CAEjBC,SAAS,CAATA,CAFiB,CAAd,CAIV,C,cAxBkCG,S","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 <http://www.gnu.org/licenses/>.\n\n/**\n * Course index cm component.\n *\n * This component is used to control specific course modules interactions like drag and drop.\n *\n * @module core_courseformat/local/courseindex/cm\n * @class core_courseformat/local/courseindex/cm\n * @copyright 2021 Ferran Recio <ferran@moodle.com>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport DndCmItem from 'core_courseformat/local/courseeditor/dndcmitem';\n\nexport default class Component extends DndCmItem {\n\n /**\n * Constructor hook.\n */\n create() {\n // Optional component name for debugging.\n this.name = 'courseindex_cm';\n // We need our id to watch specific events.\n this.id = this.element.dataset.id;\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 selectors,\n });\n }\n\n /**\n * Initial state ready method.\n */\n stateReady() {\n this.configDragDrop(this.id);\n }\n\n /**\n * Component watchers.\n *\n * @returns {Array} of watchers\n */\n getWatchers() {\n return [\n {watch: `cm[${this.id}]:deleted`, handler: this.remove},\n ];\n }\n\n}\n"],"file":"cm.min.js"}

View File

@ -0,0 +1,2 @@
define ("core_courseformat/local/courseindex/section",["exports","core_courseformat/local/courseindex/sectiontitle","core_courseformat/local/courseeditor/dndsection"],function(a,b,c){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;b=d(b);c=d(c);function d(a){return a&&a.__esModule?a:{default:a}}function e(a){"@babel/helpers - typeof";if("function"==typeof Symbol&&"symbol"==typeof Symbol.iterator){e=function(a){return typeof a}}else{e=function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?"symbol":typeof a}}return e(a)}function f(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 g(a){for(var b=1,c;b<arguments.length;b++){c=null!=arguments[b]?arguments[b]:{};if(b%2){f(Object(c),!0).forEach(function(b){h(a,b,c[b])})}else if(Object.getOwnPropertyDescriptors){Object.defineProperties(a,Object.getOwnPropertyDescriptors(c))}else{f(Object(c)).forEach(function(b){Object.defineProperty(a,b,Object.getOwnPropertyDescriptor(c,b))})}}return a}function h(a,b,c){if(b in a){Object.defineProperty(a,b,{value:c,enumerable:!0,configurable:!0,writable:!0})}else{a[b]=c}return a}function i(a,b){if(!(a instanceof b)){throw new TypeError("Cannot call a class as a function")}}function j(a,b){for(var c=0,d;c<b.length;c++){d=b[c];d.enumerable=d.enumerable||!1;d.configurable=!0;if("value"in d)d.writable=!0;Object.defineProperty(a,d.key,d)}}function k(a,b,c){if(b)j(a.prototype,b);if(c)j(a,c);return a}function l(a,b){if("function"!=typeof b&&null!==b){throw new TypeError("Super expression must either be null or a function")}a.prototype=Object.create(b&&b.prototype,{constructor:{value:a,writable:!0,configurable:!0}});if(b)m(a,b)}function m(a,b){m=Object.setPrototypeOf||function(a,b){a.__proto__=b;return a};return m(a,b)}function n(a){return function(){var b=r(a),c;if(q()){var d=r(this).constructor;c=Reflect.construct(b,arguments,d)}else{c=b.apply(this,arguments)}return o(this,c)}}function o(a,b){if(b&&("object"===e(b)||"function"==typeof b)){return b}return p(a)}function p(a){if(void 0===a){throw new ReferenceError("this hasn't been initialised - super() hasn't been called")}return a}function q(){if("undefined"==typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if("function"==typeof Proxy)return!0;try{Date.prototype.toString.call(Reflect.construct(Date,[],function(){}));return!0}catch(a){return!1}}function r(a){r=Object.setPrototypeOf?Object.getPrototypeOf:function(a){return a.__proto__||Object.getPrototypeOf(a)};return r(a)}var s=function(a){l(c,a);var d=n(c);function c(){i(this,c);return d.apply(this,arguments)}k(c,[{key:"create",value:function create(){this.name="courseindex_section";this.selectors={SECTION_ITEM:"[data-for='section_item']",CM_LAST:"[data-for=\"cm\"]:last-child"}}},{key:"stateReady",value:function stateReady(a){this.configState(a);if(this.reactive.isEditing&&this.reactive.supportComponents){var c=new b.default(g({},this,{element:this.getElement(this.selectors.SECTION_ITEM),fullregion:this.element}));this.configDragDrop(c)}}},{key:"getLastCm",value:function getLastCm(){return this.getElement(this.selectors.CM_LAST)}}],[{key:"init",value:function init(a,b){return new c({element:document.getElementById(a),selectors:b})}}]);return c}(c.default);a.default=s;return a.default});
//# sourceMappingURL=section.min.js.map

View File

@ -0,0 +1 @@
{"version":3,"sources":["../../../src/local/courseindex/section.js"],"names":["Component","name","selectors","SECTION_ITEM","CM_LAST","state","configState","reactive","isEditing","supportComponents","titleitem","SectionTitle","element","getElement","fullregion","configDragDrop","target","document","getElementById","DndSection"],"mappings":"sQA0BA,OACA,O,63EAEqBA,CAAAA,C,+HAKR,CAEL,KAAKC,IAAL,CAAY,qBAAZ,CAEA,KAAKC,SAAL,CAAiB,CACbC,YAAY,4BADC,CAEbC,OAAO,+BAFM,CAIpB,C,8CAqBUC,C,CAAO,CACd,KAAKC,WAAL,CAAiBD,CAAjB,EAEA,GAAI,KAAKE,QAAL,CAAcC,SAAd,EAA2B,KAAKD,QAAL,CAAcE,iBAA7C,CAAgE,CAE5D,GAAMC,CAAAA,CAAS,CAAG,GAAIC,UAAJ,MACX,IADW,EAEdC,OAAO,CAAE,KAAKC,UAAL,CAAgB,KAAKX,SAAL,CAAeC,YAA/B,CAFK,CAGdW,UAAU,CAAE,KAAKF,OAHH,GAAlB,CAKA,KAAKG,cAAL,CAAoBL,CAApB,CACH,CACJ,C,6CAOW,CACR,MAAO,MAAKG,UAAL,CAAgB,KAAKX,SAAL,CAAeE,OAA/B,CACV,C,oCAjCWY,C,CAAQd,C,CAAW,CAC3B,MAAO,IAAIF,CAAAA,CAAJ,CAAc,CACjBY,OAAO,CAAEK,QAAQ,CAACC,cAAT,CAAwBF,CAAxB,CADQ,CAEjBd,SAAS,CAATA,CAFiB,CAAd,CAIV,C,cA3BkCiB,S","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 <http://www.gnu.org/licenses/>.\n\n/**\n * Course index section component.\n *\n * This component is used to control specific course section interactions like drag and drop.\n *\n * @module core_courseformat/local/courseindex/section\n * @class core_courseformat/local/courseindex/section\n * @copyright 2021 Ferran Recio <ferran@moodle.com>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport SectionTitle from 'core_courseformat/local/courseindex/sectiontitle';\nimport DndSection from 'core_courseformat/local/courseeditor/dndsection';\n\nexport default class Component extends DndSection {\n\n /**\n * Constructor hook.\n */\n create() {\n // Optional component name for debugging.\n this.name = 'courseindex_section';\n // Default query selectors.\n this.selectors = {\n SECTION_ITEM: `[data-for='section_item']`,\n CM_LAST: `[data-for=\"cm\"]:last-child`,\n };\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 * @return {Component}\n */\n static init(target, selectors) {\n return new Component({\n element: document.getElementById(target),\n selectors,\n });\n }\n\n /**\n * Initial state ready method.\n *\n * @param {Object} state the initial state\n */\n stateReady(state) {\n this.configState(state);\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 passing the full section as affected region.\n const titleitem = new SectionTitle({\n ...this,\n element: this.getElement(this.selectors.SECTION_ITEM),\n fullregion: this.element,\n });\n this.configDragDrop(titleitem);\n }\n }\n\n /**\n * Get the last CM element of that section.\n *\n * @returns {element|null}\n */\n getLastCm() {\n return this.getElement(this.selectors.CM_LAST);\n }\n}\n"],"file":"section.min.js"}

View File

@ -0,0 +1,2 @@
define ("core_courseformat/local/courseindex/sectiontitle",["exports","core_courseformat/local/courseeditor/dndsectionitem"],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){"@babel/helpers - typeof";if("function"==typeof Symbol&&"symbol"==typeof Symbol.iterator){c=function(a){return typeof a}}else{c=function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?"symbol":typeof a}}return c(a)}function d(a,b){if(!(a instanceof b)){throw new TypeError("Cannot call a class as a function")}}function e(a,b){for(var c=0,d;c<b.length;c++){d=b[c];d.enumerable=d.enumerable||!1;d.configurable=!0;if("value"in d)d.writable=!0;Object.defineProperty(a,d.key,d)}}function f(a,b,c){if(b)e(a.prototype,b);if(c)e(a,c);return a}function g(a,b){if("function"!=typeof b&&null!==b){throw new TypeError("Super expression must either be null or a function")}a.prototype=Object.create(b&&b.prototype,{constructor:{value:a,writable:!0,configurable:!0}});if(b)h(a,b)}function h(a,b){h=Object.setPrototypeOf||function(a,b){a.__proto__=b;return a};return h(a,b)}function i(a){return function(){var b=m(a),c;if(l()){var d=m(this).constructor;c=Reflect.construct(b,arguments,d)}else{c=b.apply(this,arguments)}return j(this,c)}}function j(a,b){if(b&&("object"===c(b)||"function"==typeof b)){return b}return k(a)}function k(a){if(void 0===a){throw new ReferenceError("this hasn't been initialised - super() hasn't been called")}return a}function l(){if("undefined"==typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if("function"==typeof Proxy)return!0;try{Date.prototype.toString.call(Reflect.construct(Date,[],function(){}));return!0}catch(a){return!1}}function m(a){m=Object.setPrototypeOf?Object.getPrototypeOf:function(a){return a.__proto__||Object.getPrototypeOf(a)};return m(a)}var n=function(a){g(b,a);var c=i(b);function b(){d(this,b);return c.apply(this,arguments)}f(b,[{key:"create",value:function create(a){this.name="courseindex_sectiontitle";this.id=a.id;this.section=a.section;this.course=a.course;this.fullregion=a.fullregion;if(0<this.section.number){this.getDraggableData=this._getDraggableData}}},{key:"stateReady",value:function stateReady(a){this.configDragDrop(this.id,a,this.fullregion)}}],[{key:"init",value:function init(a,c){return new b({element:document.getElementById(a),selectors:c})}}]);return b}(b.default);a.default=n;return a.default});
//# sourceMappingURL=sectiontitle.min.js.map

View File

@ -0,0 +1 @@
{"version":3,"sources":["../../../src/local/courseindex/sectiontitle.js"],"names":["Component","descriptor","name","id","section","course","fullregion","number","getDraggableData","_getDraggableData","state","configDragDrop","target","selectors","element","document","getElementById","DndSectionItem"],"mappings":"0NA0BA,uD,+nDAEqBA,CAAAA,C,8HAOVC,C,CAAY,CAEf,KAAKC,IAAL,CAAY,0BAAZ,CAEA,KAAKC,EAAL,CAAUF,CAAU,CAACE,EAArB,CACA,KAAKC,OAAL,CAAeH,CAAU,CAACG,OAA1B,CACA,KAAKC,MAAL,CAAcJ,CAAU,CAACI,MAAzB,CACA,KAAKC,UAAL,CAAkBL,CAAU,CAACK,UAA7B,CAGA,GAA0B,CAAtB,MAAKF,OAAL,CAAaG,MAAjB,CAA6B,CACzB,KAAKC,gBAAL,CAAwB,KAAKC,iBAChC,CACJ,C,8CAqBUC,C,CAAO,CACd,KAAKC,cAAL,CAAoB,KAAKR,EAAzB,CAA6BO,CAA7B,CAAoC,KAAKJ,UAAzC,CACH,C,oCAdWM,C,CAAQC,C,CAAW,CAC3B,MAAO,IAAIb,CAAAA,CAAJ,CAAc,CACjBc,OAAO,CAAEC,QAAQ,CAACC,cAAT,CAAwBJ,CAAxB,CADQ,CAEjBC,SAAS,CAATA,CAFiB,CAAd,CAIV,C,cAlCkCI,S","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 <http://www.gnu.org/licenses/>.\n\n/**\n * Course index section title component.\n *\n * This component is used to control specific course section interactions like drag and drop.\n *\n * @module core_courseformat/local/courseindex/sectiontitle\n * @class core_courseformat/local/courseindex/sectiontitle\n * @copyright 2021 Ferran Recio <ferran@moodle.com>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport DndSectionItem from 'core_courseformat/local/courseeditor/dndsectionitem';\n\nexport default class Component extends DndSectionItem {\n\n /**\n * Constructor hook.\n *\n * @param {Object} descriptor\n */\n create(descriptor) {\n // Optional component name for debugging.\n this.name = 'courseindex_sectiontitle';\n\n this.id = descriptor.id;\n this.section = descriptor.section;\n this.course = descriptor.course;\n this.fullregion = descriptor.fullregion;\n\n // Prevent topic zero from being draggable.\n if (this.section.number > 0) {\n this.getDraggableData = this._getDraggableData;\n }\n }\n\n /**\n * Static method to create a component instance form the mustahce 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 selectors,\n });\n }\n\n /**\n * Initial state ready method.\n *\n * @param {Object} state the initial state\n */\n stateReady(state) {\n this.configDragDrop(this.id, state, this.fullregion);\n }\n}\n"],"file":"sectiontitle.min.js"}

View File

@ -54,6 +54,7 @@ function dispatchStateChangedEvent(detail, target) {
* @param {number} courseId the course id
* @param {setup} setup format, page and course settings
* @property {boolean} setup.editing if the page is in edit mode
* @property {boolean} setup.supportscomponents if the format supports components for content
*/
export const setViewFormat = (courseId, setup) => {
const editor = getCourseEditor(courseId);

View File

@ -47,6 +47,7 @@ export default class extends Reactive {
// Default view format setup.
this._editing = false;
this._supportscomponents = false;
this.courseId = courseId;
@ -68,9 +69,11 @@ export default class extends Reactive {
*
* @param {Object} setup format, page and course settings
* @property {boolean} setup.editing if the page is in edit mode
* @property {boolean} setup.supportscomponents if the format supports components for content
*/
setViewFormat(setup) {
this._editing = setup.editing ?? false;
this._supportscomponents = setup.supportscomponents ?? false;
}
/**
@ -116,6 +119,15 @@ export default class extends Reactive {
return new Exporter(this);
}
/**
* Return if the current course support components to refresh the content.
*
* @returns {boolean} if the current content support components
*/
get supportComponents() {
return this._supportscomponents ?? false;
}
/**
* Dispatch a change in the state.
*

View File

@ -0,0 +1,113 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Course index cm component.
*
* This component is used to control specific course modules interactions like drag and drop
* in both course index and course content.
*
* @module core_courseformat/local/courseeditor/dndcmitem
* @class core_courseformat/local/courseeditor/dndcmitem
* @copyright 2021 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import {BaseComponent, DragDrop} from 'core/reactive';
export default class extends BaseComponent {
/**
* Configure the component drag and drop.
*
* @param {number} cmid course module id
*/
configDragDrop(cmid) {
this.id = cmid;
// Drag and drop is only available for components compatible course formats.
if (this.reactive.isEditing && this.reactive.supportComponents) {
// Init element drag and drop.
this.dragdrop = new DragDrop(this);
// Save dropzone classes.
this.classes = this.dragdrop.getClasses();
}
}
/**
* Remove all subcomponents dependencies.
*/
destroy() {
if (this.dragdrop !== undefined) {
this.dragdrop.unregister();
}
}
// Drag and drop methods.
/**
* Get the draggable data of this component.
*
* @returns {Object} exported course module drop data
*/
getDraggableData() {
const exporter = this.reactive.getExporter();
return exporter.cmDraggableData(this.reactive.state, this.id);
}
/**
* Validate if the drop data can be dropped over the component.
*
* @param {Object} dropdata the exported drop data.
* @returns {boolean}
*/
validateDropData(dropdata) {
return dropdata?.type === 'cm';
}
/**
* Display the component dropzone.
*
* @param {Object} dropdata the accepted drop data
*/
showDropZone(dropdata) {
// If we are the next cmid of the dragged element we accept the drop because otherwise it
// will get captured by the section. However, we won't trigger any mutation.
if (dropdata.nextcmid != this.id && dropdata.id != this.id) {
this.element.classList.add(this.classes.DROPUP);
}
}
/**
* Hide the component dropzone.
*/
hideDropZone() {
this.element.classList.remove(this.classes.DROPUP);
}
/**
* Drop event handler.
*
* @param {Object} dropdata the accepted drop data
*/
drop(dropdata) {
// Call the move mutation if necessary.
if (dropdata.id != this.id && dropdata.nextcmid != this.id) {
this.reactive.dispatch('cmMove', [dropdata.id], null, this.id);
}
}
}

View File

@ -0,0 +1,146 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Course index section component.
*
* This component is used to control specific course section interactions like drag and drop
* in both course index and course content.
*
* @module core_courseformat/local/courseeditor/dndsection
* @class core_courseformat/local/courseeditor/dndsection
* @copyright 2021 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import {BaseComponent, DragDrop} from 'core/reactive';
export default class extends BaseComponent {
/**
* Save some values form the state.
*
* @param {Object} state the current state
*/
configState(state) {
this.id = this.element.dataset.id;
this.section = state.section.get(this.id);
this.course = state.course;
}
/**
* Register state values and the drag and drop subcomponent.
*
* @param {BaseComponent} sectionitem section item component
*/
configDragDrop(sectionitem) {
// Drag and drop is only available for components compatible course formats.
if (this.reactive.isEditing && this.reactive.supportComponents) {
// Init the inner dragable element.
this.sectionitem = sectionitem;
// Init the dropzone.
this.dragdrop = new DragDrop(this);
// Save dropzone classes.
this.classes = this.dragdrop.getClasses();
}
}
/**
* Remove all subcomponents dependencies.
*/
destroy() {
if (this.sectionitem !== undefined) {
this.sectionitem.unregister();
}
if (this.dragdrop !== undefined) {
this.dragdrop.unregister();
}
}
/**
* Get the last CM element of that section.
*
* @returns {element|null} the las course module element of the section.
*/
getLastCm() {
return null;
}
// Drag and drop methods.
/**
* Validate if the drop data can be dropped over the component.
*
* @param {Object} dropdata the exported drop data.
* @returns {boolean}
*/
validateDropData(dropdata) {
// We accept any course module.
if (dropdata?.type === 'cm') {
return true;
}
// We accept any section bu the section 0 or ourself
if (dropdata?.type === 'section') {
const sectionzeroid = this.course.sectionlist[0];
return dropdata?.id != this.id && dropdata?.id != sectionzeroid && this.id != sectionzeroid;
}
return false;
}
/**
* Display the component dropzone.
*
* @param {Object} dropdata the accepted drop data
*/
showDropZone(dropdata) {
if (dropdata.type == 'cm') {
this.getLastCm()?.classList.add(this.classes.DROPDOWN);
}
if (dropdata.type == 'section') {
// The relative move of section depends on the section number.
if (this.section.number > dropdata.number) {
this.element.classList.remove(this.classes.DROPUP);
this.element.classList.add(this.classes.DROPDOWN);
} else {
this.element.classList.add(this.classes.DROPUP);
this.element.classList.remove(this.classes.DROPDOWN);
}
}
}
/**
* Hide the component dropzone.
*/
hideDropZone() {
this.getLastCm()?.classList.remove(this.classes.DROPDOWN);
this.element.classList.remove(this.classes.DROPUP);
this.element.classList.remove(this.classes.DROPDOWN);
}
/**
* Drop event handler.
*
* @param {Object} dropdata the accepted drop data
*/
drop(dropdata) {
// Call the move mutation.
if (dropdata.type == 'cm') {
this.reactive.dispatch('cmMove', [dropdata.id], this.id);
}
if (dropdata.type == 'section') {
this.reactive.dispatch('sectionMove', [dropdata.id], this.id);
}
}
}

View File

@ -0,0 +1,129 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Course index section title draggable component.
*
* This component is used to control specific course section interactions like drag and drop
* in both course index and course content.
*
* @module core_courseformat/local/courseeditor/dndsectionitem
* @class core_courseformat/local/courseeditor/dndsectionitem
* @copyright 2021 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import {BaseComponent, DragDrop} from 'core/reactive';
export default class extends BaseComponent {
/**
* Initial state ready method.
*
* @param {number} sectionid the section id
* @param {Object} state the initial state
* @param {Element} fullregion the complete section region to mark as dragged
*/
configDragDrop(sectionid, state, fullregion) {
this.id = sectionid;
if (this.section === undefined) {
this.section = state.section.get(this.id);
}
if (this.course === undefined) {
this.course = state.course;
}
// Prevent topic zero from being draggable.
if (this.section.number > 0) {
this.getDraggableData = this._getDraggableData;
}
this.fullregion = fullregion;
// Drag and drop is only available for components compatible course formats.
if (this.reactive.isEditing && this.reactive.supportComponents) {
// Init the dropzone.
this.dragdrop = new DragDrop(this);
// Save dropzone classes.
this.classes = this.dragdrop.getClasses();
}
}
/**
* Remove all subcomponents dependencies.
*/
destroy() {
if (this.dragdrop !== undefined) {
this.dragdrop.unregister();
}
}
// Drag and drop methods.
/**
* Get the draggable data of this component.
*
* @returns {Object} exported course module drop data
*/
_getDraggableData() {
const exporter = this.reactive.getExporter();
return exporter.sectionDraggableData(this.reactive.state, this.id);
}
/**
* Validate if the drop data can be dropped over the component.
*
* @param {Object} dropdata the exported drop data.
* @returns {boolean}
*/
validateDropData(dropdata) {
// Course module validation.
if (dropdata?.type === 'cm') {
// The first section element is already there so we can ignore it.
const firstcmid = this.section?.cmlist[0];
return dropdata.id !== firstcmid;
}
return false;
}
/**
* Display the component dropzone.
*
* @param {Object} dropdata the accepted drop data
*/
showDropZone() {
this.element.classList.add(this.classes.DROPZONE);
}
/**
* Hide the component dropzone.
*/
hideDropZone() {
this.element.classList.remove(this.classes.DROPZONE);
}
/**
* Drop event handler.
*
* @param {Object} dropdata the accepted drop data
*/
drop(dropdata) {
// Call the move mutation.
if (dropdata.type == 'cm') {
this.reactive.dispatch('cmMove', [dropdata.id], this.id, this.section?.cmlist[0]);
}
}
}

View File

@ -94,4 +94,59 @@ export default class {
};
return cm;
}
/**
* Generate a dragable cm data structure.
*
* This method is used by any draggable course module element to generate drop data
* for its reactive/dragdrop instance.
*
* @param {*} state the state object
* @param {*} cmid the cours emodule id
* @returns {Object|null}
*/
cmDraggableData(state, cmid) {
const cminfo = state.cm.get(cmid);
if (!cminfo) {
return null;
}
// Drop an activity over the next activity is the same as doing anything.
let nextcmid;
const section = state.section.get(cminfo.sectionid);
const currentindex = section?.cmlist.indexOf(cminfo.id);
if (currentindex !== undefined) {
nextcmid = section?.cmlist[currentindex + 1];
}
return {
type: 'cm',
id: cminfo.id,
name: cminfo.name,
nextcmid,
};
}
/**
* Generate a dragable cm data structure.
*
* This method is used by any draggable section element to generate drop data
* for its reactive/dragdrop instance.
*
* @param {*} state the state object
* @param {*} sectionid the cours section id
* @returns {Object|null}
*/
sectionDraggableData(state, sectionid) {
const sectioninfo = state.section.get(sectionid);
if (!sectioninfo) {
return null;
}
return {
type: 'section',
id: sectioninfo.id,
name: sectioninfo.name,
number: sectioninfo.number,
};
}
}

View File

@ -34,19 +34,68 @@ export default class {
* @param {string} action
* @param {number} courseId
* @param {array} ids
* @param {number} targetSectionId optional target section id (for moving actions)
* @param {number} targetCmId optional target cm id (for moving actions)
*/
async _callEditWebservice(action, courseId, ids) {
async _callEditWebservice(action, courseId, ids, targetSectionId, targetCmId) {
const args = {
action,
courseid: courseId,
ids,
};
if (targetSectionId) {
args.targetsectionid = targetSectionId;
}
if (targetCmId) {
args.targetcmid = targetCmId;
}
let ajaxresult = await ajax.call([{
methodname: 'core_courseformat_update_course',
args: {
action,
courseid: courseId,
ids,
}
args,
}])[0];
return JSON.parse(ajaxresult);
}
/**
* Move course modules to specific course location.
*
* Note that one of targetSectionId or targetCmId should be provided in order to identify the
* new location:
* - targetCmId: the activities will be located avobe the target cm. The targetSectionId
* value will be ignored in this case.
* - targetSectionId: the activities will be appended to the section. In this case
* targetSectionId should not be present.
*
* @param {StateManager} stateManager the current state manager
* @param {array} cmids the list of cm ids to move
* @param {number} targetSectionId the target section id
* @param {number} targetCmId the target course module id
*/
async cmMove(stateManager, cmids, targetSectionId, targetCmId) {
if (!targetSectionId && !targetCmId) {
throw new Error(`Mutation cmMove requires targetSectionId or targetCmId`);
}
const course = stateManager.get('course');
const updates = await this._callEditWebservice('cm_move', course.id, cmids, targetSectionId, targetCmId);
stateManager.processUpdates(updates);
}
/**
* Move course modules to specific course location.
*
* @param {StateManager} stateManager the current state manager
* @param {array} sectionIds the list of section ids to move
* @param {number} targetSectionId the target section id
*/
async sectionMove(stateManager, sectionIds, targetSectionId) {
if (!targetSectionId) {
throw new Error(`Mutation sectionMove requires targetSectionId`);
}
const course = stateManager.get('course');
const updates = await this._callEditWebservice('section_move', course.id, sectionIds, targetSectionId);
stateManager.processUpdates(updates);
}
/**
* Get updated state data related to some cm ids.
*

View File

@ -24,9 +24,9 @@
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import {BaseComponent} from 'core/reactive';
import DndCmItem from 'core_courseformat/local/courseeditor/dndcmitem';
export default class Component extends BaseComponent {
export default class Component extends DndCmItem {
/**
* Constructor hook.
@ -34,9 +34,6 @@ export default class Component extends BaseComponent {
create() {
// Optional component name for debugging.
this.name = 'courseindex_cm';
// Default query selectors.
this.selectors = {
};
// We need our id to watch specific events.
this.id = this.element.dataset.id;
}
@ -59,9 +56,14 @@ export default class Component extends BaseComponent {
* Initial state ready method.
*/
stateReady() {
// Activate drag and drop soon.
this.configDragDrop(this.id);
}
/**
* Component watchers.
*
* @returns {Array} of watchers
*/
getWatchers() {
return [
{watch: `cm[${this.id}]:deleted`, handler: this.remove},

View File

@ -0,0 +1,86 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Course index section component.
*
* This component is used to control specific course section interactions like drag and drop.
*
* @module core_courseformat/local/courseindex/section
* @class core_courseformat/local/courseindex/section
* @copyright 2021 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import SectionTitle from 'core_courseformat/local/courseindex/sectiontitle';
import DndSection from 'core_courseformat/local/courseeditor/dndsection';
export default class Component extends DndSection {
/**
* Constructor hook.
*/
create() {
// Optional component name for debugging.
this.name = 'courseindex_section';
// Default query selectors.
this.selectors = {
SECTION_ITEM: `[data-for='section_item']`,
CM_LAST: `[data-for="cm"]:last-child`,
};
}
/**
* Static method to create a component instance form the mustahce template.
*
* @param {string} target the DOM main element or its ID
* @param {object} selectors optional css selector overrides
* @return {Component}
*/
static init(target, selectors) {
return new Component({
element: document.getElementById(target),
selectors,
});
}
/**
* Initial state ready method.
*
* @param {Object} state the initial state
*/
stateReady(state) {
this.configState(state);
// Drag and drop is only available for components compatible course formats.
if (this.reactive.isEditing && this.reactive.supportComponents) {
// Init the inner dragable element passing the full section as affected region.
const titleitem = new SectionTitle({
...this,
element: this.getElement(this.selectors.SECTION_ITEM),
fullregion: this.element,
});
this.configDragDrop(titleitem);
}
}
/**
* Get the last CM element of that section.
*
* @returns {element|null}
*/
getLastCm() {
return this.getElement(this.selectors.CM_LAST);
}
}

View File

@ -0,0 +1,73 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Course index section title component.
*
* This component is used to control specific course section interactions like drag and drop.
*
* @module core_courseformat/local/courseindex/sectiontitle
* @class core_courseformat/local/courseindex/sectiontitle
* @copyright 2021 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import DndSectionItem from 'core_courseformat/local/courseeditor/dndsectionitem';
export default class Component extends DndSectionItem {
/**
* Constructor hook.
*
* @param {Object} descriptor
*/
create(descriptor) {
// Optional component name for debugging.
this.name = 'courseindex_sectiontitle';
this.id = descriptor.id;
this.section = descriptor.section;
this.course = descriptor.course;
this.fullregion = descriptor.fullregion;
// Prevent topic zero from being draggable.
if (this.section.number > 0) {
this.getDraggableData = this._getDraggableData;
}
}
/**
* Static method to create a component instance form the mustahce template.
*
* @param {element|string} target the DOM main element or its ID
* @param {object} selectors optional css selector overrides
* @return {Component}
*/
static init(target, selectors) {
return new Component({
element: document.getElementById(target),
selectors,
});
}
/**
* Initial state ready method.
*
* @param {Object} state the initial state
*/
stateReady(state) {
this.configDragDrop(this.id, state, this.fullregion);
}
}

View File

@ -494,6 +494,21 @@ abstract class base {
return $ajaxsupport;
}
/**
* Returns true if this course format is compatible with content components.
*
* Using components means the content elements can watch the frontend course state and
* react to the changes. Formats with component compatibility can have more interactions
* without refreshing the page, like having drag and drop from the course index to reorder
* sections and activities.
*
* @return bool if the format is compatible with components.
*/
public function supports_components() {
return false;
}
/**
* Custom action after section has been moved in AJAX mode
*

View File

@ -64,6 +64,7 @@ class section implements renderable {
'section' => $section->section,
'number' => $section->section,
'title' => $format->get_section_name($section),
'rawtitle' => $section->name,
'cmlist' => [],
'visible' => !empty($section->visible),
'sectionurl' => course_get_url($course, $section->section)->out(),

View File

@ -17,9 +17,13 @@
namespace core_courseformat;
use core_courseformat\stateupdates;
use cm_info;
use section_info;
use stdClass;
use course_modinfo;
use moodle_exception;
use context_module;
use context_course;
/**
* Contains the core course state actions.
@ -36,6 +40,159 @@ use moodle_exception;
*/
class stateactions {
/**
* Move course modules to another location in the same course.
*
* @param stateupdates $updates the affected course elements track
* @param stdClass $course the course object
* @param int[] $ids the list of affected course module ids
* @param int $targetsectionid optional target section id
* @param int $targetcmid optional target cm id
*/
public function cm_move(
stateupdates $updates,
stdClass $course,
array $ids,
?int $targetsectionid = null,
?int $targetcmid = null
): void {
// Validate target elements.
if (!$targetsectionid && !$targetcmid) {
throw new moodle_exception("Action cm_move requires targetsectionid or targetcmid");
}
$this->validate_cms($course, $ids, __FUNCTION__);
// Check capabilities on every activity context.
foreach ($ids as $cmid) {
$modcontext = context_module::instance($cmid);
require_capability('moodle/course:manageactivities', $modcontext);
}
$modinfo = get_fast_modinfo($course);
// Target cm has more priority than target section.
if (!empty($targetcmid)) {
$this->validate_cms($course, [$targetcmid], __FUNCTION__);
$targetcm = $modinfo->get_cm($targetcmid);
$targetsection = $modinfo->get_section_info_by_id($targetcm->section, MUST_EXIST);
} else {
$this->validate_sections($course, [$targetsectionid], __FUNCTION__);
$targetcm = null;
$targetsection = $modinfo->get_section_info_by_id($targetsectionid, MUST_EXIST);
}
// The origin sections must be updated as well.
$originalsections = [];
$cms = $this->get_cm_info($modinfo, $ids);
foreach ($cms as $cm) {
$currentsection = $modinfo->get_section_info_by_id($cm->section, MUST_EXIST);
moveto_module($cm, $targetsection, $targetcm);
$updates->add_cm_put($cm->id);
if ($currentsection->id != $targetsection->id) {
$originalsections[$currentsection->id] = true;
}
// If some of the original sections are also target sections, we don't need to update them.
if (array_key_exists($targetsection->id, $originalsections)) {
unset($originalsections[$targetsection->id]);
}
}
// Use section_state to return the full affected section and activities updated state.
$this->cm_state($updates, $course, $ids, $targetsectionid, $targetcmid);
foreach (array_keys($originalsections) as $sectionid) {
$updates->add_section_put($sectionid);
}
}
/**
* Move course sections to another location in the same course.
*
* @param stateupdates $updates the affected course elements track
* @param stdClass $course the course object
* @param int[] $ids the list of affected course module ids
* @param int $targetsectionid optional target section id
* @param int $targetcmid optional target cm id
*/
public function section_move(
stateupdates $updates,
stdClass $course,
array $ids,
?int $targetsectionid = null,
?int $targetcmid = null
): void {
// Validate target elements.
if (!$targetsectionid) {
throw new moodle_exception("Action cm_move requires targetsectionid");
}
$this->validate_sections($course, $ids, __FUNCTION__);
$coursecontext = context_course::instance($course->id);
require_capability('moodle/course:movesections', $coursecontext);
$modinfo = get_fast_modinfo($course);
// Target section.
$this->validate_sections($course, [$targetsectionid], __FUNCTION__);
$targetsection = $modinfo->get_section_info_by_id($targetsectionid, MUST_EXIST);
$affectedsections = [$targetsection->section => true];
$sections = $this->get_section_info($modinfo, $ids);
foreach ($sections as $section) {
$affectedsections[$section->section] = true;
move_section_to($course, $section->section, $targetsection->section);
}
// Use section_state to return the section and activities updated state.
$this->section_state($updates, $course, $ids, $targetsectionid);
// All course sections can be renamed because of the resort.
$allsections = $modinfo->get_section_info_all();
foreach ($allsections as $section) {
// Ignore the affected sections because they are already in the updates.
if (isset($affectedsections[$section->section])) {
continue;
}
$updates->add_section_put($section->id);
}
// The section order is at a course level.
$updates->add_course_put();
}
/**
* Extract several cm_info from the course_modinfo.
*
* @param course_modinfo $modinfo the course modinfo.
* @param int[] $ids the course modules $ids
* @return cm_info[] the extracted cm_info objects
*/
protected function get_cm_info (course_modinfo $modinfo, array $ids): array {
$cms = [];
foreach ($ids as $cmid) {
$cms[$cmid] = $modinfo->get_cm($cmid);
}
return $cms;
}
/**
* Extract several section_info from the course_modinfo.
*
* @param course_modinfo $modinfo the course modinfo.
* @param int[] $ids the course modules $ids
* @return section_info[] the extracted section_info objects
*/
protected function get_section_info(course_modinfo $modinfo, array $ids): array {
$sections = [];
foreach ($ids as $sectionid) {
$sections[$sectionid] = $modinfo->get_section_info_by_id($sectionid);
}
return $sections;
}
/**
* Add the update messages of the updated version of any cm and section related to the cm ids.
*

View File

@ -56,6 +56,7 @@
{{{name}}}
</span>
{{/url}}
<span class="dragicon ml-auto">{{#pix}}i/dragdrop{{/pix}}</span>
</li>
{{#js}}
require(['core_courseformat/local/courseindex/cm'], function(component) {

View File

@ -89,6 +89,7 @@
>
{{{title}}}
</a>
<span class="dragicon ml-auto">{{#pix}}i/dragdrop{{/pix}}</span>
</div>
<div id="courseindexcollapse{{number}}"
class="courseindex-item-content collapse {{#isactive}}show{{/isactive}}"
@ -100,3 +101,8 @@
</ul>
</div>
</div>
{{#js}}
require(['core_courseformat/local/courseindex/section'], function(component) {
component.init('{{uniqid}}-course-index-section-{{id}}');
});
{{/js}}

View File

@ -159,6 +159,10 @@ class format_topics extends core_courseformat\base {
return $ajaxsupport;
}
public function supports_components() {
return true;
}
/**
* Loads all of the course sections into the navigation.
*

View File

@ -3,7 +3,8 @@ This files describes API changes for course formats
Overview of this plugin type at http://docs.moodle.org/dev/Course_formats
=== 4.0 ===
* New core_courseformat\uses_course_index() to define whether the course format uses course index or not.
* New core_courseformat\base::uses_course_index() to define whether the course format uses course index or not.
* New core_courseformat\base::supports_components() to specify if the format is compatible with reactive components.
=== 3.10 ===
* Added the missing callback supports_ajax() to format_social.

View File

@ -160,6 +160,10 @@ class format_weeks extends core_courseformat\base {
return $ajaxsupport;
}
public function supports_components() {
return true;
}
/**
* Loads all of the course sections into the navigation
*

View File

@ -3308,6 +3308,7 @@ function include_course_editor(course_format $format) {
// Edition mode and some format specs must be passed to the init method.
$setup = (object)[
'editing' => $format->show_editor(),
'supportscomponents' => $format->supports_components(),
];
// 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.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,2 +1,2 @@
define ("core/reactive",["exports","core/local/reactive/basecomponent","core/local/reactive/reactive"],function(a,b,c){"use strict";Object.defineProperty(a,"__esModule",{value:!0});Object.defineProperty(a,"BaseComponent",{enumerable:!0,get:function get(){return b.default}});Object.defineProperty(a,"Reactive",{enumerable:!0,get:function get(){return c.default}});b=d(b);c=d(c);function d(a){return a&&a.__esModule?a:{default:a}}});
define ("core/reactive",["exports","core/local/reactive/basecomponent","core/local/reactive/reactive","core/local/reactive/dragdrop"],function(a,b,c,d){"use strict";Object.defineProperty(a,"__esModule",{value:!0});Object.defineProperty(a,"BaseComponent",{enumerable:!0,get:function get(){return b.default}});Object.defineProperty(a,"Reactive",{enumerable:!0,get:function get(){return c.default}});Object.defineProperty(a,"DragDrop",{enumerable:!0,get:function get(){return d.default}});b=e(b);c=e(c);d=e(d);function e(a){return a&&a.__esModule?a:{default:a}}});
//# sourceMappingURL=reactive.min.js.map

View File

@ -1 +1 @@
{"version":3,"sources":["../src/reactive.js"],"names":[],"mappings":"4WAuBA,OACA,O","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 <http://www.gnu.org/licenses/>.\n\n/**\n * Generic reactive module used in the course editor.\n *\n * @module core/reactive\n * @copyright 2021 Ferran Recio <ferran@moodle.com>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport BaseComponent from 'core/local/reactive/basecomponent';\nimport Reactive from 'core/local/reactive/reactive';\n\nexport {Reactive, BaseComponent};\n"],"file":"reactive.min.js"}
{"version":3,"sources":["../src/reactive.js"],"names":[],"mappings":"seAuBA,OACA,OACA,O","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 <http://www.gnu.org/licenses/>.\n\n/**\n * Generic reactive module used in the course editor.\n *\n * @module core/reactive\n * @copyright 2021 Ferran Recio <ferran@moodle.com>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport BaseComponent from 'core/local/reactive/basecomponent';\nimport Reactive from 'core/local/reactive/reactive';\nimport DragDrop from 'core/local/reactive/dragdrop';\n\nexport {Reactive, BaseComponent, DragDrop};\n"],"file":"reactive.min.js"}

465
lib/amd/src/local/reactive/dragdrop.js vendored Normal file
View File

@ -0,0 +1,465 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Drag and drop helper component.
*
* This component is used to delegate drag and drop handling.
*
* To delegate the logic to this particular element the component should create a new instance
* passing "this" as param. The component will use all the necessary callbacks and add all the
* necessary listeners to the component element.
*
* Component attributes used by dragdrop module:
* - element: the draggable or dropzone element.
* - (optional) classes: object with alternative CSS classes
* - (optional) fullregion: page element affeted by the elementy dragging. Use this attribute if
* the draggable element affects a bigger region (for example a draggable
* title).
* - (optional) autoconfigDraggable: by default, the component will be draggable if it has a
* getDraggableData method. If this value is false draggable
* property must be defined using setDraggable method.
* - (optional) relativeDrag: by default the drag image is located at point (0,0) relative to the
* mouse position to prevent the mouse from covering it. If this attribute
* is true the drag image will be located at the click offset.
*
* Methods the parent component should have for making it draggable:
*
* - getDraggableData(): Object|data
* Return the data that will be passed to any valid dropzone while it is dragged.
* If the component has this method, the dragdrop module will enable the dragging,
* this is the only required method for dragging.
* If at the dragging moment this method returns a false|null|undefined, the dragging
* actions won't be captured.
*
* - (optional) dragStart(Object dropdata, Event event): void
* - (optional) dragEnd(Object dropdata, Event event): void
* Callbacks dragdrop will call when the element is dragged and getDraggableData
* return some data.
*
* Methods the parent component should have for enabling it as a dropzone:
*
* - validateDropData(Object dropdata): boolean
* If that method exists, the dragdrop module will automathically configure the element as dropzone.
* This method will return true if the dropdata is accepted. In case it returns false, no drag and
* drop event will be listened for this specific dragged dropdata.
*
* - (Optional) showDropZone(Object dropdata, Event event): void
* - (Optional) hideDropZone(Object dropdata, Event event): void
* Methods called when a valid dragged data pass over the element.
*
* - (Optional) drop(Object dropdata, Event event): void
* Called when a valid dragged element is dropped over the element.
*
* Note that none of this methods will be called if validateDropData
* returns a false value.
*
* This module will also add or remove several CSS classes from both dragged elements and dropzones.
* See the "this.classes" in the create method for more details. In case the parent component wants
* to use the same classes, it can use the getClasses method. On the other hand, if the parent
* component has an alternative "classes" attribute, this will override the default drag and drop
* classes.
*
* @module core/local/reactive/dragdrop
* @class core/local/reactive/dragdrop
* @copyright 2021 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import BaseComponent from 'core/local/reactive/basecomponent';
// Map with the dragged element generate by an specific reactive applications.
// Potentially, any component can generate a draggable element to interact with other
// page elements. However, the dragged data is specific and could only interact with
// components of the same reactive instance.
let activeDropData = new Map();
// Drag & Drop API provides the final drop point and incremental movements but we can
// provide also starting points and displacements. Absolute displacements simplifies
// moving components with aboslute position around the page.
let dragStartPoint = {};
export default class extends BaseComponent {
/**
* Constructor hook.
*
* @param {BaseComponent} parent the parent component.
*/
create(parent) {
// Optional component name for debugging.
this.name = `${parent.name ?? 'unkown'}_dragdrop`;
// Default drag and drop classes.
this.classes = Object.assign(
{
// This class indicate a dragging action is active at a page level.
BODYDRAGGING: 'dragging',
// Added when draggable and drop are ready.
DRAGGABLEREADY: 'draggable',
DROPREADY: 'dropready',
// When a valid drag element is over the element.
DRAGOVER: 'dragover',
// When a the component is dragged.
DRAGGING: 'dragging',
// Dropzones classes names.
DROPUP: 'drop-up',
DROPDOWN: 'drop-down',
DROPZONE: 'drop-zone',
// Drag icon class.
DRAGICON: 'dragicon',
},
parent?.classes ?? {}
);
// Add the affected region if any.
this.fullregion = parent.fullregion;
// Keep parent to execute drap and drop handlers.
this.parent = parent;
// Check if parent handle draggable manually.
this.autoconfigDraggable = this.parent.draggable ?? true;
// Drag image relative position.
this.relativeDrag = this.parent.relativeDrag ?? false;
// Sub HTML elements will trigger extra dragEnter and dragOver all the time.
// To prevent that from affecting dropzones, we need to count the enters and leaves.
this.entercount = 0;
// Stores if the droparea is shown or not.
this.dropzonevisible = false;
}
/**
* Return the component drag and drop CSS classes.
*
* @returns {Object} the dragdrop css classes
*/
getClasses() {
return this.classes;
}
/**
* Initial state ready method.
*
* This method will add all the necessary event listeners to the component depending on the
* parent methods.
* - Add drop events to the element if the parent component has validateDropData method.
* - Configure the elements draggable if the parent component has getDraggableData method.
*/
stateReady() {
// Add drop events to the element if the parent component has dropable types.
if (typeof this.parent.validateDropData === 'function') {
this.element.classList.add(this.classes.DROPREADY);
this.addEventListener(this.element, 'dragenter', this._dragEnter);
this.addEventListener(this.element, 'dragleave', this._dragLeave);
this.addEventListener(this.element, 'dragover', this._dragOver);
this.addEventListener(this.element, 'drop', this._drop);
}
// Configure the elements draggable if the parent component has dragable data.
if (this.autoconfigDraggable && typeof this.parent.getDraggableData === 'function') {
this.setDraggable(true);
}
}
/**
* Enable or disable the draggable property.
*
* @param {bool} value the new draggable value
*/
setDraggable(value) {
if (typeof this.parent.getDraggableData !== 'function') {
throw new Error(`Draggable components must have a getDraggableData method`);
}
this.element.setAttribute('draggable', value);
if (value) {
this.addEventListener(this.element, 'dragstart', this._dragStart);
this.addEventListener(this.element, 'dragend', this._dragEnd);
this.element.classList.add(this.classes.DRAGGABLEREADY);
} else {
this.removeEventListener(this.element, 'dragstart', this._dragStart);
this.removeEventListener(this.element, 'dragend', this._dragEnd);
this.element.classList.remove(this.classes.DRAGGABLEREADY);
}
}
/**
* Drag start event handler.
*
* This method will generate the current dropable data. This data is the one used to determine
* if a droparea accepts the dropping or not.
*
* @param {Event} event the event.
*/
_dragStart(event) {
const dropdata = this.parent.getDraggableData();
if (!dropdata) {
return;
}
// Save the starting point.
dragStartPoint = {
pageX: event.pageX,
pageY: event.pageY,
};
// If the drag event is accepted we prevent any other draggable element from interfiering.
event.stopPropagation();
// Save the drop data of the current reactive intance.
activeDropData.set(this.reactive, dropdata);
// Add some CSS classes to indicate the state.
document.body.classList.add(this.classes.BODYDRAGGING);
this.element.classList.add(this.classes.DRAGGING);
this.fullregion?.classList.add(this.classes.DRAGGING);
// Force the drag image. This makes the UX more consistent in case the
// user dragged an internal element like a link or some other element.
let dragImage = this.element;
if (this.parent.setDragImage !== undefined) {
const customImage = this.parent.setDragImage(dropdata, event);
if (customImage) {
dragImage = customImage;
}
}
// Define the image position relative to the mouse.
const position = {x: 0, y: 0};
if (this.relativeDrag) {
position.x = event.offsetX;
position.y = event.offsetY;
}
event.dataTransfer.setDragImage(dragImage, position.x, position.y);
this._callParentMethod('dragStart', dropdata, event);
}
/**
* Drag end event handler.
*
* @param {Event} event the event.
*/
_dragEnd(event) {
const dropdata = activeDropData.get(this.reactive);
if (!dropdata) {
return;
}
// Remove the current dropdata.
activeDropData.delete(this.reactive);
// Remove the dragging classes.
document.body.classList.remove(this.classes.BODYDRAGGING);
this.element.classList.remove(this.classes.DRAGGING);
this.fullregion?.classList.remove(this.classes.DRAGGING);
// We add the total movement to the event in case the component
// wants to move its absolute position.
this._addEventTotalMovement(event);
this._callParentMethod('dragEnd', dropdata, event);
}
/**
* Drag enter event handler.
*
* The JS drag&drop API triggers several dragenter events on the same element because it bubbles the
* child events as well. To prevent this form affecting the dropzones display, this methods use
* "entercount" to determine if it's one extra child event or a valid one.
*
* @param {Event} event the event.
*/
_dragEnter(event) {
const dropdata = this._processEvent(event);
if (dropdata) {
this.entercount++;
this.element.classList.add(this.classes.DRAGOVER);
if (this.entercount == 1 && !this.dropzonevisible) {
this.dropzonevisible = true;
this.element.classList.add(this.classes.DRAGOVER);
this._callParentMethod('showDropZone', dropdata, event);
}
}
}
/**
* Drag over event handler.
*
* We only use dragover event when a draggable action starts inside a valid dropzone. In those cases
* the API won't trigger any dragEnter because the dragged alement was already there. We use the
* dropzonevisible to determine if the component needs to display the dropzones or not.
*
* @param {Event} event the event.
*/
_dragOver(event) {
const dropdata = this._processEvent(event);
if (dropdata && !this.dropzonevisible) {
this.dropzonevisible = true;
this.element.classList.add(this.classes.DRAGOVER);
this._callParentMethod('showDropZone', dropdata, event);
}
}
/**
* Drag over leave handler.
*
* The JS drag&drop API triggers several dragleave events on the same element because it bubbles the
* child events as well. To prevent this form affecting the dropzones display, this methods use
* "entercount" to determine if it's one extra child event or a valid one.
*
* @param {Event} event the event.
*/
_dragLeave(event) {
const dropdata = this._processEvent(event);
if (dropdata) {
this.entercount--;
if (this.entercount == 0 && this.dropzonevisible) {
this.dropzonevisible = false;
this.element.classList.remove(this.classes.DRAGOVER);
this._callParentMethod('hideDropZone', dropdata, event);
}
}
}
/**
* Drop event handler.
*
* This method will call both hideDropZones and drop methods on the parent component.
*
* @param {Event} event the event.
*/
_drop(event) {
const dropdata = this._processEvent(event);
if (dropdata) {
this.entercount = 0;
if (this.dropzonevisible) {
this.dropzonevisible = false;
this._callParentMethod('hideDropZone', dropdata, event);
}
this.element.classList.remove(this.classes.DRAGOVER);
this._callParentMethod('drop', dropdata, event);
// An accepted drop resets the initial position.
// Save the starting point.
dragStartPoint = {};
}
}
/**
* Process a drag and drop event and delegate logic to the parent component.
*
* @param {Event} event the drag and drop event
* @return {Object|false} the dropdata or null if the event should not be processed
*/
_processEvent(event) {
const dropdata = this._getDropData(event);
if (!dropdata) {
return null;
}
if (this.parent.validateDropData(dropdata)) {
// All accepted drag&drop event must prevent bubbling and defaults, otherwise
// parent dragdrop instances could capture it by mistake.
event.preventDefault();
event.stopPropagation();
this._addEventTotalMovement(event);
return dropdata;
}
return null;
}
/**
* Add the total amout of movement to a mouse event.
*
* @param {MouseEvent} event
*/
_addEventTotalMovement(event) {
if (dragStartPoint.pageX === undefined || event.pageX === undefined) {
return;
}
event.fixedMovementX = event.pageX - dragStartPoint.pageX;
event.fixedMovementY = event.pageY - dragStartPoint.pageY;
event.initialPageX = dragStartPoint.pageX;
event.initialPageY = dragStartPoint.pageY;
// The element possible new top.
const current = this.element.getBoundingClientRect();
// Add the new position fixed position.
event.newFixedTop = current.top + event.fixedMovementY;
event.newFixedLeft = current.left + event.fixedMovementX;
// The affected region possible new top.
if (this.fullregion !== undefined) {
const current = this.fullregion.getBoundingClientRect();
event.newRegionFixedxTop = current.top + event.fixedMovementY;
event.newRegionFixedxLeft = current.left + event.fixedMovementX;
}
}
/**
* Convenient method for calling parent component functions if present.
*
* @param {string} methodname the name of the method
* @param {Object} dropdata the current drop data object
* @param {Event} event the original event
*/
_callParentMethod(methodname, dropdata, event) {
if (typeof this.parent[methodname] === 'function') {
this.parent[methodname](dropdata, event);
}
}
/**
* Get the current dropdata for a specific event.
*
* The browser can generate drag&drop events related to several user interactions:
* - Drag a page elements: this case is registered in the activeDropData map
* - Drag some HTML selections: ignored for now
* - Drag a file over the browser: file drag may appear in the future but for now they are ignored.
*
* @param {Event} event the original event.
* @returns {Object|undefined} with the dragged data (or undefined if none)
*/
_getDropData(event) {
if (this._containsFiles(event)) {
return undefined;
}
return activeDropData.get(this.reactive);
}
/**
* Check if the dragged event contains files.
*
* Files dragging does not generate drop data because they came from outsite the page and the component
* must check it before validating the event.
*
* @param {Event} event the original event.
* @returns {boolean} if the drag dataTransfers contains files.
*/
_containsFiles(event) {
if (event.dataTransfer.types) {
for (var i = 0; i < event.dataTransfer.types.length; i++) {
if (event.dataTransfer.types[i] == "Files") {
return true;
}
}
}
return false;
}
}

View File

@ -23,5 +23,6 @@
import BaseComponent from 'core/local/reactive/basecomponent';
import Reactive from 'core/local/reactive/reactive';
import DragDrop from 'core/local/reactive/dragdrop';
export {Reactive, BaseComponent};
export {Reactive, BaseComponent, DragDrop};

View File

@ -5,6 +5,8 @@
// want white default colour.
$bg-inverse-link-color: #fff !default;
$dropzone-border: $gray-900 !default;
$font-size-xs: ($font-size-base * .75) !default;
#region-main {
@ -2784,3 +2786,32 @@ $scrollbar-track: lighten($primary, 40%);
border-right: $border-width solid $white;
}
}
// Generic dropzones and dragging styles.
body.dragging {
.drop-zone {
border: 2px dashed $dropzone-border;
}
.drop-up {
border-top: 2px solid $dropzone-border;
}
.drop-down {
border-bottom: 2px solid $dropzone-border;
}
.dragging {
opacity: .6;
}
}
.dragicon {
visibility: hidden;
}
.draggable:hover .dragicon {
visibility: visible;
cursor: move;
}

View File

@ -56,8 +56,15 @@ $courseindex-item-current: $primary !default;
}
}
.courseindex-section.current {
border-left: solid 3px $courseindex-item-current;
.courseindex-section {
&.current {
border-left: solid 3px $courseindex-item-current;
}
&.dropready .courseindex-item-content {
/* Extra dropzone space */
padding-bottom: 1em;
}
}
.d-flex-noedit {

View File

@ -11944,6 +11944,25 @@ input[disabled] {
background-color: #a8d2f8;
border-right: 1px solid #fff; }
body.dragging .drop-zone {
border: 2px dashed #212529; }
body.dragging .drop-up {
border-top: 2px solid #212529; }
body.dragging .drop-down {
border-bottom: 2px solid #212529; }
body.dragging .dragging {
opacity: .6; }
.dragicon {
visibility: hidden; }
.draggable:hover .dragicon {
visibility: visible;
cursor: move; }
.icon {
font-size: 16px;
width: 16px;
@ -20124,6 +20143,10 @@ div.editor_atto_toolbar button .icon {
.courseindex .courseindex-section.current {
border-left: solid 3px #0f6fc5; }
.courseindex .courseindex-section.dropready .courseindex-item-content {
/* Extra dropzone space */
padding-bottom: 1em; }
.courseindex .d-flex-noedit {
display: none; }

View File

@ -12165,6 +12165,25 @@ input[disabled] {
background-color: #a8d2f8;
border-right: 1px solid #fff; }
body.dragging .drop-zone {
border: 2px dashed #212529; }
body.dragging .drop-up {
border-top: 2px solid #212529; }
body.dragging .drop-down {
border-bottom: 2px solid #212529; }
body.dragging .dragging {
opacity: .6; }
.dragicon {
visibility: hidden; }
.draggable:hover .dragicon {
visibility: visible;
cursor: move; }
.icon {
font-size: 16px;
width: 16px;
@ -20316,6 +20335,10 @@ div.editor_atto_toolbar button .icon {
.courseindex .courseindex-section.current {
border-left: solid 3px #0f6fc5; }
.courseindex .courseindex-section.dropready .courseindex-item-content {
/* Extra dropzone space */
padding-bottom: 1em; }
.courseindex .d-flex-noedit {
display: none; }