From 5ca968fea4c45ea292078dbc05eda8a91dc9a25c Mon Sep 17 00:00:00 2001 From: ferranrecio Date: Fri, 5 Jul 2024 16:21:18 +0200 Subject: [PATCH] MDL-82324 course: fix empty section dropzone --- .../amd/build/local/content/section.min.js | 2 +- .../amd/build/local/content/section.min.js.map | 2 +- .../build/local/courseeditor/dndsection.min.js | 2 +- .../local/courseeditor/dndsection.min.js.map | 2 +- course/format/amd/src/local/content/section.js | 12 +++++++++++- .../amd/src/local/courseeditor/dndsection.js | 18 +++++++++++++++++- theme/boost/scss/moodle/course.scss | 11 +++++++++++ theme/boost/style/moodle.css | 6 ++++++ theme/classic/style/moodle.css | 6 ++++++ 9 files changed, 55 insertions(+), 6 deletions(-) diff --git a/course/format/amd/build/local/content/section.min.js b/course/format/amd/build/local/content/section.min.js index 5b97746cd21..ab69426c5ac 100644 --- a/course/format/amd/build/local/content/section.min.js +++ b/course/format/amd/build/local/content/section.min.js @@ -6,6 +6,6 @@ define("core_courseformat/local/content/section",["exports","core_courseformat/l * @class core_courseformat/local/content/section * @copyright 2021 Ferran Recio * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_header=_interopRequireDefault(_header),_dndsection=_interopRequireDefault(_dndsection),_templates=_interopRequireDefault(_templates),_pending=_interopRequireDefault(_pending);class _default extends _dndsection.default{create(){this.name="content_section",this.selectors={SECTION_ITEM:"[data-for='section_title']",CM:'[data-for="cmitem"]',SECTIONINFO:'[data-for="sectioninfo"]',SECTIONBADGES:'[data-region="sectionbadges"]',SHOWSECTION:'[data-action="sectionShow"]',HIDESECTION:'[data-action="sectionHide"]',ACTIONTEXT:".menu-action-text",ICON:".icon"},this.classes={LOCKED:"editinprogress",HASDESCRIPTION:"description",HIDE:"d-none",HIDDEN:"hidden",CURRENT:"current"},this.id=this.element.dataset.id}stateReady(state){if(this.configState(state),this.reactive.isEditing&&this.reactive.supportComponents){const sectionItem=this.getElement(this.selectors.SECTION_ITEM);if(sectionItem){const headerComponent=new _header.default({...this,element:sectionItem,fullregion:this.element});this.configDragDrop(headerComponent)}}this._openSectionIfNecessary()}async _openSectionIfNecessary(){const pageCmInfo=this.reactive.getPageAnchorCmInfo();if(!pageCmInfo||pageCmInfo.sectionid!==this.id)return;await this.reactive.dispatch("sectionContentCollapsed",[this.id],!1);const pendingOpen=new _pending.default("courseformat/section:openSectionIfNecessary");this.element.scrollIntoView({block:"center"}),setTimeout((()=>{this.reactive.dispatch("setPageItem","cm",pageCmInfo.id),pendingOpen.resolve()}),250)}getWatchers(){return[{watch:"section[".concat(this.id,"]:updated"),handler:this._refreshSection}]}validateDropData(dropdata){return("section"!==(null==dropdata?void 0:dropdata.type)||null===this.reactive.sectionReturn)&&super.validateDropData(dropdata)}getLastCm(){const cms=this.getElements(this.selectors.CM);return cms&&0!==cms.length?cms[cms.length-1]:null}_refreshSection(_ref){var _element$dragging,_element$locked,_element$visible,_element$current;let{element:element}=_ref;this.element.classList.toggle(this.classes.DRAGGING,null!==(_element$dragging=element.dragging)&&void 0!==_element$dragging&&_element$dragging),this.element.classList.toggle(this.classes.LOCKED,null!==(_element$locked=element.locked)&&void 0!==_element$locked&&_element$locked),this.element.classList.toggle(this.classes.HIDDEN,null!==(_element$visible=!element.visible)&&void 0!==_element$visible&&_element$visible),this.element.classList.toggle(this.classes.CURRENT,null!==(_element$current=element.current)&&void 0!==_element$current&&_element$current),this.locked=element.locked;const sectioninfo=this.getElement(this.selectors.SECTIONINFO);sectioninfo&§ioninfo.classList.toggle(this.classes.HASDESCRIPTION,element.hasrestrictions),this._updateBadges(element),this._updateActionsMenu(element)}_updateBadges(section){const current=this.getElement("".concat(this.selectors.SECTIONBADGES," [data-type='iscurrent']"));null==current||current.classList.toggle(this.classes.HIDE,!section.current);const hiddenFromStudents=this.getElement("".concat(this.selectors.SECTIONBADGES," [data-type='hiddenfromstudents']"));null==hiddenFromStudents||hiddenFromStudents.classList.toggle(this.classes.HIDE,section.visible)}async _updateActionsMenu(section){var _affectedAction$datas,_affectedAction$datas2;let selector,newAction;section.visible?(selector=this.selectors.SHOWSECTION,newAction="sectionHide"):(selector=this.selectors.HIDESECTION,newAction="sectionShow");const affectedAction=this._getActionMenu(selector);if(!affectedAction)return;affectedAction.dataset.action=newAction;const actionText=affectedAction.querySelector(this.selectors.ACTIONTEXT);if(null!==(_affectedAction$datas=affectedAction.dataset)&&void 0!==_affectedAction$datas&&_affectedAction$datas.swapname&&actionText){const oldText=null==actionText?void 0:actionText.innerText;actionText.innerText=affectedAction.dataset.swapname,affectedAction.dataset.swapname=oldText}const icon=affectedAction.querySelector(this.selectors.ICON);if(null!==(_affectedAction$datas2=affectedAction.dataset)&&void 0!==_affectedAction$datas2&&_affectedAction$datas2.swapicon&&icon){const newIcon=affectedAction.dataset.swapicon;if(newIcon){const pixHtml=await _templates.default.renderPix(newIcon,"core");_templates.default.replaceNode(icon,pixHtml,"")}}}_getActionMenu(selector){return this.getElement(".section_action_menu")?this.getElement(selector):document.querySelector(selector)}}return _exports.default=_default,_exports.default})); + */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_header=_interopRequireDefault(_header),_dndsection=_interopRequireDefault(_dndsection),_templates=_interopRequireDefault(_templates),_pending=_interopRequireDefault(_pending);class _default extends _dndsection.default{create(){this.name="content_section",this.selectors={SECTION_ITEM:"[data-for='section_title']",CM:'[data-for="cmitem"]',SECTIONINFO:'[data-for="sectioninfo"]',SECTIONBADGES:'[data-region="sectionbadges"]',SHOWSECTION:'[data-action="sectionShow"]',HIDESECTION:'[data-action="sectionHide"]',ACTIONTEXT:".menu-action-text",ICON:".icon"},this.classes={LOCKED:"editinprogress",HASDESCRIPTION:"description",HIDE:"d-none",HIDDEN:"hidden",CURRENT:"current"},this.id=this.element.dataset.id}stateReady(state){if(this.configState(state),this.reactive.isEditing&&this.reactive.supportComponents){const sectionItem=this.getElement(this.selectors.SECTION_ITEM);if(sectionItem){const headerComponent=new _header.default({...this,element:sectionItem,fullregion:this.element});this.configDragDrop(headerComponent)}}this._openSectionIfNecessary()}async _openSectionIfNecessary(){const pageCmInfo=this.reactive.getPageAnchorCmInfo();if(!pageCmInfo||pageCmInfo.sectionid!==this.id)return;await this.reactive.dispatch("sectionContentCollapsed",[this.id],!1);const pendingOpen=new _pending.default("courseformat/section:openSectionIfNecessary");this.element.scrollIntoView({block:"center"}),setTimeout((()=>{this.reactive.dispatch("setPageItem","cm",pageCmInfo.id),pendingOpen.resolve()}),250)}getWatchers(){return[{watch:"section[".concat(this.id,"]:updated"),handler:this._refreshSection}]}validateDropData(dropdata){return("section"!==(null==dropdata?void 0:dropdata.type)||null===this.reactive.sectionReturn)&&super.validateDropData(dropdata)}getLastCm(){const cms=this.getElements(this.selectors.CM);return cms&&0!==cms.length?cms[cms.length-1]:null}getLastCmFallback(){return this.getElement(this.selectors.SECTIONINFO)}_refreshSection(_ref){var _element$dragging,_element$locked,_element$visible,_element$current;let{element:element}=_ref;this.element.classList.toggle(this.classes.DRAGGING,null!==(_element$dragging=element.dragging)&&void 0!==_element$dragging&&_element$dragging),this.element.classList.toggle(this.classes.LOCKED,null!==(_element$locked=element.locked)&&void 0!==_element$locked&&_element$locked),this.element.classList.toggle(this.classes.HIDDEN,null!==(_element$visible=!element.visible)&&void 0!==_element$visible&&_element$visible),this.element.classList.toggle(this.classes.CURRENT,null!==(_element$current=element.current)&&void 0!==_element$current&&_element$current),this.locked=element.locked;const sectioninfo=this.getElement(this.selectors.SECTIONINFO);sectioninfo&§ioninfo.classList.toggle(this.classes.HASDESCRIPTION,element.hasrestrictions),this._updateBadges(element),this._updateActionsMenu(element)}_updateBadges(section){const current=this.getElement("".concat(this.selectors.SECTIONBADGES," [data-type='iscurrent']"));null==current||current.classList.toggle(this.classes.HIDE,!section.current);const hiddenFromStudents=this.getElement("".concat(this.selectors.SECTIONBADGES," [data-type='hiddenfromstudents']"));null==hiddenFromStudents||hiddenFromStudents.classList.toggle(this.classes.HIDE,section.visible)}async _updateActionsMenu(section){var _affectedAction$datas,_affectedAction$datas2;let selector,newAction;section.visible?(selector=this.selectors.SHOWSECTION,newAction="sectionHide"):(selector=this.selectors.HIDESECTION,newAction="sectionShow");const affectedAction=this._getActionMenu(selector);if(!affectedAction)return;affectedAction.dataset.action=newAction;const actionText=affectedAction.querySelector(this.selectors.ACTIONTEXT);if(null!==(_affectedAction$datas=affectedAction.dataset)&&void 0!==_affectedAction$datas&&_affectedAction$datas.swapname&&actionText){const oldText=null==actionText?void 0:actionText.innerText;actionText.innerText=affectedAction.dataset.swapname,affectedAction.dataset.swapname=oldText}const icon=affectedAction.querySelector(this.selectors.ICON);if(null!==(_affectedAction$datas2=affectedAction.dataset)&&void 0!==_affectedAction$datas2&&_affectedAction$datas2.swapicon&&icon){const newIcon=affectedAction.dataset.swapicon;if(newIcon){const pixHtml=await _templates.default.renderPix(newIcon,"core");_templates.default.replaceNode(icon,pixHtml,"")}}}_getActionMenu(selector){return this.getElement(".section_action_menu")?this.getElement(selector):document.querySelector(selector)}}return _exports.default=_default,_exports.default})); //# sourceMappingURL=section.min.js.map \ No newline at end of file diff --git a/course/format/amd/build/local/content/section.min.js.map b/course/format/amd/build/local/content/section.min.js.map index bcab747c356..593f3bbd3d2 100644 --- a/course/format/amd/build/local/content/section.min.js.map +++ b/course/format/amd/build/local/content/section.min.js.map @@ -1 +1 @@ -{"version":3,"file":"section.min.js","sources":["../../../src/local/content/section.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Course section format component.\n *\n * @module core_courseformat/local/content/section\n * @class core_courseformat/local/content/section\n * @copyright 2021 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Header from 'core_courseformat/local/content/section/header';\nimport DndSection from 'core_courseformat/local/courseeditor/dndsection';\nimport Templates from 'core/templates';\nimport Pending from \"core/pending\";\n\nexport default class extends DndSection {\n\n /**\n * Constructor hook.\n */\n create() {\n // Optional component name for debugging.\n this.name = 'content_section';\n // Default query selectors.\n this.selectors = {\n SECTION_ITEM: `[data-for='section_title']`,\n CM: `[data-for=\"cmitem\"]`,\n SECTIONINFO: `[data-for=\"sectioninfo\"]`,\n SECTIONBADGES: `[data-region=\"sectionbadges\"]`,\n SHOWSECTION: `[data-action=\"sectionShow\"]`,\n HIDESECTION: `[data-action=\"sectionHide\"]`,\n ACTIONTEXT: `.menu-action-text`,\n ICON: `.icon`,\n };\n // Most classes will be loaded later by DndCmItem.\n this.classes = {\n LOCKED: 'editinprogress',\n HASDESCRIPTION: 'description',\n HIDE: 'd-none',\n HIDDEN: 'hidden',\n CURRENT: 'current',\n };\n\n // We need our id to watch specific events.\n this.id = this.element.dataset.id;\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 // Section zero and other formats sections may not have a title to drag.\n const sectionItem = this.getElement(this.selectors.SECTION_ITEM);\n if (sectionItem) {\n // Init the inner dragable element.\n const headerComponent = new Header({\n ...this,\n element: sectionItem,\n fullregion: this.element,\n });\n this.configDragDrop(headerComponent);\n }\n }\n this._openSectionIfNecessary();\n }\n\n /**\n * Open the section if the anchored activity is inside.\n */\n async _openSectionIfNecessary() {\n const pageCmInfo = this.reactive.getPageAnchorCmInfo();\n if (!pageCmInfo || pageCmInfo.sectionid !== this.id) {\n return;\n }\n await this.reactive.dispatch('sectionContentCollapsed', [this.id], false);\n const pendingOpen = new Pending(`courseformat/section:openSectionIfNecessary`);\n this.element.scrollIntoView({block: \"center\"});\n setTimeout(() => {\n this.reactive.dispatch('setPageItem', 'cm', pageCmInfo.id);\n pendingOpen.resolve();\n }, 250);\n }\n\n /**\n * Component watchers.\n *\n * @returns {Array} of watchers\n */\n getWatchers() {\n return [\n {watch: `section[${this.id}]:updated`, handler: this._refreshSection},\n ];\n }\n\n /**\n * Validate if the drop data can be dropped over the component.\n *\n * @param {Object} dropdata the exported drop data.\n * @returns {boolean}\n */\n validateDropData(dropdata) {\n // If the format uses one section per page sections dropping in the content is ignored.\n if (dropdata?.type === 'section' && this.reactive.sectionReturn !== null) {\n return false;\n }\n return super.validateDropData(dropdata);\n }\n\n /**\n * Get the last CM element of that section.\n *\n * @returns {element|null}\n */\n getLastCm() {\n const cms = this.getElements(this.selectors.CM);\n // DndUpload may add extra elements so :last-child selector cannot be used.\n if (!cms || cms.length === 0) {\n return null;\n }\n return cms[cms.length - 1];\n }\n\n /**\n * Update a content section using the state information.\n *\n * @param {object} param\n * @param {Object} param.element details the update details.\n */\n _refreshSection({element}) {\n // Update classes.\n this.element.classList.toggle(this.classes.DRAGGING, element.dragging ?? false);\n this.element.classList.toggle(this.classes.LOCKED, element.locked ?? false);\n this.element.classList.toggle(this.classes.HIDDEN, !element.visible ?? false);\n this.element.classList.toggle(this.classes.CURRENT, element.current ?? false);\n this.locked = element.locked;\n // The description box classes depends on the section state.\n const sectioninfo = this.getElement(this.selectors.SECTIONINFO);\n if (sectioninfo) {\n sectioninfo.classList.toggle(this.classes.HASDESCRIPTION, element.hasrestrictions);\n }\n // Update section badges and menus.\n this._updateBadges(element);\n this._updateActionsMenu(element);\n }\n\n /**\n * Update a section badges using the state information.\n *\n * @param {object} section the section state.\n */\n _updateBadges(section) {\n const current = this.getElement(`${this.selectors.SECTIONBADGES} [data-type='iscurrent']`);\n current?.classList.toggle(this.classes.HIDE, !section.current);\n\n const hiddenFromStudents = this.getElement(`${this.selectors.SECTIONBADGES} [data-type='hiddenfromstudents']`);\n hiddenFromStudents?.classList.toggle(this.classes.HIDE, section.visible);\n }\n\n /**\n * Update a section action menus.\n *\n * @param {object} section the section state.\n */\n async _updateActionsMenu(section) {\n let selector;\n let newAction;\n if (section.visible) {\n selector = this.selectors.SHOWSECTION;\n newAction = 'sectionHide';\n } else {\n selector = this.selectors.HIDESECTION;\n newAction = 'sectionShow';\n }\n // Find the affected action.\n const affectedAction = this._getActionMenu(selector);\n if (!affectedAction) {\n return;\n }\n // Change action.\n affectedAction.dataset.action = newAction;\n // Change text.\n const actionText = affectedAction.querySelector(this.selectors.ACTIONTEXT);\n if (affectedAction.dataset?.swapname && actionText) {\n const oldText = actionText?.innerText;\n actionText.innerText = affectedAction.dataset.swapname;\n affectedAction.dataset.swapname = oldText;\n }\n // Change icon.\n const icon = affectedAction.querySelector(this.selectors.ICON);\n if (affectedAction.dataset?.swapicon && icon) {\n const newIcon = affectedAction.dataset.swapicon;\n if (newIcon) {\n const pixHtml = await Templates.renderPix(newIcon, 'core');\n Templates.replaceNode(icon, pixHtml, '');\n }\n }\n }\n\n /**\n * Get the action menu element from the selector.\n *\n * @param {string} selector The selector to find the action menu.\n * @returns The action menu element.\n */\n _getActionMenu(selector) {\n if (this.getElement('.section_action_menu')) {\n return this.getElement(selector);\n }\n\n return document.querySelector(selector);\n }\n}\n"],"names":["DndSection","create","name","selectors","SECTION_ITEM","CM","SECTIONINFO","SECTIONBADGES","SHOWSECTION","HIDESECTION","ACTIONTEXT","ICON","classes","LOCKED","HASDESCRIPTION","HIDE","HIDDEN","CURRENT","id","this","element","dataset","stateReady","state","configState","reactive","isEditing","supportComponents","sectionItem","getElement","headerComponent","Header","fullregion","configDragDrop","_openSectionIfNecessary","pageCmInfo","getPageAnchorCmInfo","sectionid","dispatch","pendingOpen","Pending","scrollIntoView","block","setTimeout","resolve","getWatchers","watch","handler","_refreshSection","validateDropData","dropdata","type","sectionReturn","super","getLastCm","cms","getElements","length","classList","toggle","DRAGGING","dragging","locked","visible","current","sectioninfo","hasrestrictions","_updateBadges","_updateActionsMenu","section","hiddenFromStudents","selector","newAction","affectedAction","_getActionMenu","action","actionText","querySelector","swapname","oldText","innerText","icon","swapicon","newIcon","pixHtml","Templates","renderPix","replaceNode","document"],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"section.min.js","sources":["../../../src/local/content/section.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Course section format component.\n *\n * @module core_courseformat/local/content/section\n * @class core_courseformat/local/content/section\n * @copyright 2021 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Header from 'core_courseformat/local/content/section/header';\nimport DndSection from 'core_courseformat/local/courseeditor/dndsection';\nimport Templates from 'core/templates';\nimport Pending from \"core/pending\";\n\nexport default class extends DndSection {\n\n /**\n * Constructor hook.\n */\n create() {\n // Optional component name for debugging.\n this.name = 'content_section';\n // Default query selectors.\n this.selectors = {\n SECTION_ITEM: `[data-for='section_title']`,\n CM: `[data-for=\"cmitem\"]`,\n SECTIONINFO: `[data-for=\"sectioninfo\"]`,\n SECTIONBADGES: `[data-region=\"sectionbadges\"]`,\n SHOWSECTION: `[data-action=\"sectionShow\"]`,\n HIDESECTION: `[data-action=\"sectionHide\"]`,\n ACTIONTEXT: `.menu-action-text`,\n ICON: `.icon`,\n };\n // Most classes will be loaded later by DndCmItem.\n this.classes = {\n LOCKED: 'editinprogress',\n HASDESCRIPTION: 'description',\n HIDE: 'd-none',\n HIDDEN: 'hidden',\n CURRENT: 'current',\n };\n\n // We need our id to watch specific events.\n this.id = this.element.dataset.id;\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 // Section zero and other formats sections may not have a title to drag.\n const sectionItem = this.getElement(this.selectors.SECTION_ITEM);\n if (sectionItem) {\n // Init the inner dragable element.\n const headerComponent = new Header({\n ...this,\n element: sectionItem,\n fullregion: this.element,\n });\n this.configDragDrop(headerComponent);\n }\n }\n this._openSectionIfNecessary();\n }\n\n /**\n * Open the section if the anchored activity is inside.\n */\n async _openSectionIfNecessary() {\n const pageCmInfo = this.reactive.getPageAnchorCmInfo();\n if (!pageCmInfo || pageCmInfo.sectionid !== this.id) {\n return;\n }\n await this.reactive.dispatch('sectionContentCollapsed', [this.id], false);\n const pendingOpen = new Pending(`courseformat/section:openSectionIfNecessary`);\n this.element.scrollIntoView({block: \"center\"});\n setTimeout(() => {\n this.reactive.dispatch('setPageItem', 'cm', pageCmInfo.id);\n pendingOpen.resolve();\n }, 250);\n }\n\n /**\n * Component watchers.\n *\n * @returns {Array} of watchers\n */\n getWatchers() {\n return [\n {watch: `section[${this.id}]:updated`, handler: this._refreshSection},\n ];\n }\n\n /**\n * Validate if the drop data can be dropped over the component.\n *\n * @param {Object} dropdata the exported drop data.\n * @returns {boolean}\n */\n validateDropData(dropdata) {\n // If the format uses one section per page sections dropping in the content is ignored.\n if (dropdata?.type === 'section' && this.reactive.sectionReturn !== null) {\n return false;\n }\n return super.validateDropData(dropdata);\n }\n\n /**\n * Get the last CM element of that section.\n *\n * @returns {element|null}\n */\n getLastCm() {\n const cms = this.getElements(this.selectors.CM);\n // DndUpload may add extra elements so :last-child selector cannot be used.\n if (!cms || cms.length === 0) {\n return null;\n }\n return cms[cms.length - 1];\n }\n\n /**\n * Get a fallback element when there is no CM in the section.\n *\n * @returns {element|null} the las course module element of the section.\n */\n getLastCmFallback() {\n // The sectioninfo is always present, even when the section is empty.\n return this.getElement(this.selectors.SECTIONINFO);\n }\n\n /**\n * Update a content section using the state information.\n *\n * @param {object} param\n * @param {Object} param.element details the update details.\n */\n _refreshSection({element}) {\n // Update classes.\n this.element.classList.toggle(this.classes.DRAGGING, element.dragging ?? false);\n this.element.classList.toggle(this.classes.LOCKED, element.locked ?? false);\n this.element.classList.toggle(this.classes.HIDDEN, !element.visible ?? false);\n this.element.classList.toggle(this.classes.CURRENT, element.current ?? false);\n this.locked = element.locked;\n // The description box classes depends on the section state.\n const sectioninfo = this.getElement(this.selectors.SECTIONINFO);\n if (sectioninfo) {\n sectioninfo.classList.toggle(this.classes.HASDESCRIPTION, element.hasrestrictions);\n }\n // Update section badges and menus.\n this._updateBadges(element);\n this._updateActionsMenu(element);\n }\n\n /**\n * Update a section badges using the state information.\n *\n * @param {object} section the section state.\n */\n _updateBadges(section) {\n const current = this.getElement(`${this.selectors.SECTIONBADGES} [data-type='iscurrent']`);\n current?.classList.toggle(this.classes.HIDE, !section.current);\n\n const hiddenFromStudents = this.getElement(`${this.selectors.SECTIONBADGES} [data-type='hiddenfromstudents']`);\n hiddenFromStudents?.classList.toggle(this.classes.HIDE, section.visible);\n }\n\n /**\n * Update a section action menus.\n *\n * @param {object} section the section state.\n */\n async _updateActionsMenu(section) {\n let selector;\n let newAction;\n if (section.visible) {\n selector = this.selectors.SHOWSECTION;\n newAction = 'sectionHide';\n } else {\n selector = this.selectors.HIDESECTION;\n newAction = 'sectionShow';\n }\n // Find the affected action.\n const affectedAction = this._getActionMenu(selector);\n if (!affectedAction) {\n return;\n }\n // Change action.\n affectedAction.dataset.action = newAction;\n // Change text.\n const actionText = affectedAction.querySelector(this.selectors.ACTIONTEXT);\n if (affectedAction.dataset?.swapname && actionText) {\n const oldText = actionText?.innerText;\n actionText.innerText = affectedAction.dataset.swapname;\n affectedAction.dataset.swapname = oldText;\n }\n // Change icon.\n const icon = affectedAction.querySelector(this.selectors.ICON);\n if (affectedAction.dataset?.swapicon && icon) {\n const newIcon = affectedAction.dataset.swapicon;\n if (newIcon) {\n const pixHtml = await Templates.renderPix(newIcon, 'core');\n Templates.replaceNode(icon, pixHtml, '');\n }\n }\n }\n\n /**\n * Get the action menu element from the selector.\n *\n * @param {string} selector The selector to find the action menu.\n * @returns The action menu element.\n */\n _getActionMenu(selector) {\n if (this.getElement('.section_action_menu')) {\n return this.getElement(selector);\n }\n\n return document.querySelector(selector);\n }\n}\n"],"names":["DndSection","create","name","selectors","SECTION_ITEM","CM","SECTIONINFO","SECTIONBADGES","SHOWSECTION","HIDESECTION","ACTIONTEXT","ICON","classes","LOCKED","HASDESCRIPTION","HIDE","HIDDEN","CURRENT","id","this","element","dataset","stateReady","state","configState","reactive","isEditing","supportComponents","sectionItem","getElement","headerComponent","Header","fullregion","configDragDrop","_openSectionIfNecessary","pageCmInfo","getPageAnchorCmInfo","sectionid","dispatch","pendingOpen","Pending","scrollIntoView","block","setTimeout","resolve","getWatchers","watch","handler","_refreshSection","validateDropData","dropdata","type","sectionReturn","super","getLastCm","cms","getElements","length","getLastCmFallback","classList","toggle","DRAGGING","dragging","locked","visible","current","sectioninfo","hasrestrictions","_updateBadges","_updateActionsMenu","section","hiddenFromStudents","selector","newAction","affectedAction","_getActionMenu","action","actionText","querySelector","swapname","oldText","innerText","icon","swapicon","newIcon","pixHtml","Templates","renderPix","replaceNode","document"],"mappings":"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"} \ No newline at end of file diff --git a/course/format/amd/build/local/courseeditor/dndsection.min.js b/course/format/amd/build/local/courseeditor/dndsection.min.js index 928f50c95d4..81316b9dc0f 100644 --- a/course/format/amd/build/local/courseeditor/dndsection.min.js +++ b/course/format/amd/build/local/courseeditor/dndsection.min.js @@ -9,6 +9,6 @@ define("core_courseformat/local/courseeditor/dndsection",["exports","core/reacti * @class core_courseformat/local/courseeditor/dndsection * @copyright 2021 Ferran Recio * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_templates=(obj=_templates)&&obj.__esModule?obj:{default:obj},(0,_prefetch.prefetchStrings)("core",["addfilehere"]);class _default extends _reactive.BaseComponent{configState(state){this.id=this.element.dataset.id,this.section=state.section.get(this.id),this.course=state.course}configDragDrop(sectionitem){this.reactive.isEditing&&this.reactive.supportComponents&&(this.sectionitem=sectionitem,this.dragdrop=new _reactive.DragDrop(this),this.classes=this.dragdrop.getClasses())}destroy(){void 0!==this.sectionitem&&this.sectionitem.unregister(),void 0!==this.dragdrop&&this.dragdrop.unregister()}getLastCm(){return null}dragStart(dropdata){this.reactive.dispatch("sectionDrag",[dropdata.id],!0)}dragEnd(dropdata){this.reactive.dispatch("sectionDrag",[dropdata.id],!1)}validateDropData(dropdata){return"files"===(null==dropdata?void 0:dropdata.type)||("cm"===(null==dropdata?void 0:dropdata.type)?null===(_this$section=this.section)||void 0===_this$section||!_this$section.component||!0!==(null==dropdata?void 0:dropdata.hasdelegatedsection):"section"===(null==dropdata?void 0:dropdata.type)&&(null===this.section.component&&((null==dropdata?void 0:dropdata.id)!=this.id&&(null==dropdata?void 0:dropdata.number)!=this.section.number+1)));var _this$section}showDropZone(dropdata){var _this$getLastCm;("files"==dropdata.type&&this.addOverlay({content:(0,_str.getString)("addfilehere","core"),icon:_templates.default.renderPix("t/download","core")}).then((()=>{var _this$dragdrop;null!==(_this$dragdrop=this.dragdrop)&&void 0!==_this$dragdrop&&_this$dragdrop.isDropzoneVisible()||this.removeOverlay()})).catch((error=>{throw error})),"cm"==dropdata.type)&&(null===(_this$getLastCm=this.getLastCm())||void 0===_this$getLastCm||_this$getLastCm.classList.add(this.classes.DROPDOWN));"section"==dropdata.type&&(this.element.classList.remove(this.classes.DROPUP),this.element.classList.add(this.classes.DROPDOWN))}hideDropZone(){var _this$getLastCm2;null===(_this$getLastCm2=this.getLastCm())||void 0===_this$getLastCm2||_this$getLastCm2.classList.remove(this.classes.DROPDOWN),this.element.classList.remove(this.classes.DROPUP),this.element.classList.remove(this.classes.DROPDOWN),this.removeOverlay()}drop(dropdata,event){if("files"!=dropdata.type){if("cm"==dropdata.type){const mutation=event.altKey?"cmDuplicate":"cmMove";this.reactive.dispatch(mutation,[dropdata.id],this.id)}"section"==dropdata.type&&this.reactive.dispatch("sectionMoveAfter",[dropdata.id],this.id)}else this.reactive.uploadFiles(this.section.id,this.section.number,dropdata.files)}}return _exports.default=_default,_exports.default})); + */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_templates=(obj=_templates)&&obj.__esModule?obj:{default:obj},(0,_prefetch.prefetchStrings)("core",["addfilehere"]);class _default extends _reactive.BaseComponent{configState(state){this.id=this.element.dataset.id,this.section=state.section.get(this.id),this.course=state.course}configDragDrop(sectionitem){this.reactive.isEditing&&this.reactive.supportComponents&&(this.sectionitem=sectionitem,this.dragdrop=new _reactive.DragDrop(this),this.classes=this.dragdrop.getClasses())}destroy(){void 0!==this.sectionitem&&this.sectionitem.unregister(),void 0!==this.dragdrop&&this.dragdrop.unregister()}getLastCm(){return null}getLastCmFallback(){return null}dragStart(dropdata){this.reactive.dispatch("sectionDrag",[dropdata.id],!0)}dragEnd(dropdata){this.reactive.dispatch("sectionDrag",[dropdata.id],!1)}validateDropData(dropdata){return"files"===(null==dropdata?void 0:dropdata.type)||("cm"===(null==dropdata?void 0:dropdata.type)?null===(_this$section=this.section)||void 0===_this$section||!_this$section.component||!0!==(null==dropdata?void 0:dropdata.hasdelegatedsection):"section"===(null==dropdata?void 0:dropdata.type)&&(null===this.section.component&&((null==dropdata?void 0:dropdata.id)!=this.id&&(null==dropdata?void 0:dropdata.number)!=this.section.number+1)));var _this$section}showDropZone(dropdata){if("files"==dropdata.type&&this.addOverlay({content:(0,_str.getString)("addfilehere","core"),icon:_templates.default.renderPix("t/download","core")}).then((()=>{var _this$dragdrop;null!==(_this$dragdrop=this.dragdrop)&&void 0!==_this$dragdrop&&_this$dragdrop.isDropzoneVisible()||this.removeOverlay()})).catch((error=>{throw error})),"cm"==dropdata.type){const lastCm=this.getLastCm();var _this$getLastCmFallba;if(null==lastCm||lastCm.classList.add(this.classes.DROPDOWN),!lastCm)null===(_this$getLastCmFallba=this.getLastCmFallback())||void 0===_this$getLastCmFallba||_this$getLastCmFallba.classList.add(this.classes.DROPDOWN)}"section"==dropdata.type&&(this.element.classList.remove(this.classes.DROPUP),this.element.classList.add(this.classes.DROPDOWN))}hideDropZone(){var _this$getLastCm,_this$getLastCmFallba2;null===(_this$getLastCm=this.getLastCm())||void 0===_this$getLastCm||_this$getLastCm.classList.remove(this.classes.DROPDOWN),null===(_this$getLastCmFallba2=this.getLastCmFallback())||void 0===_this$getLastCmFallba2||_this$getLastCmFallba2.classList.remove(this.classes.DROPDOWN),this.element.classList.remove(this.classes.DROPUP),this.element.classList.remove(this.classes.DROPDOWN),this.removeOverlay()}drop(dropdata,event){if("files"!=dropdata.type){if("cm"==dropdata.type){const mutation=event.altKey?"cmDuplicate":"cmMove";this.reactive.dispatch(mutation,[dropdata.id],this.id)}"section"==dropdata.type&&this.reactive.dispatch("sectionMoveAfter",[dropdata.id],this.id)}else this.reactive.uploadFiles(this.section.id,this.section.number,dropdata.files)}}return _exports.default=_default,_exports.default})); //# sourceMappingURL=dndsection.min.js.map \ No newline at end of file diff --git a/course/format/amd/build/local/courseeditor/dndsection.min.js.map b/course/format/amd/build/local/courseeditor/dndsection.min.js.map index 7bbd1de85ee..e5146182651 100644 --- a/course/format/amd/build/local/courseeditor/dndsection.min.js.map +++ b/course/format/amd/build/local/courseeditor/dndsection.min.js.map @@ -1 +1 @@ -{"version":3,"file":"dndsection.min.js","sources":["../../../src/local/courseeditor/dndsection.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Course index section component.\n *\n * This component is used to control specific course section interactions like drag and drop\n * in both course index and course content.\n *\n * @module core_courseformat/local/courseeditor/dndsection\n * @class core_courseformat/local/courseeditor/dndsection\n * @copyright 2021 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {BaseComponent, DragDrop} from 'core/reactive';\nimport {getString} from 'core/str';\nimport {prefetchStrings} from 'core/prefetch';\nimport Templates from 'core/templates';\n\n// Load global strings.\nprefetchStrings('core', ['addfilehere']);\n\nexport default class extends BaseComponent {\n\n /**\n * Save some values form the state.\n *\n * @param {Object} state the current state\n */\n configState(state) {\n this.id = this.element.dataset.id;\n this.section = state.section.get(this.id);\n this.course = state.course;\n }\n\n /**\n * Register state values and the drag and drop subcomponent.\n *\n * @param {BaseComponent} sectionitem section item component\n */\n configDragDrop(sectionitem) {\n // Drag and drop is only available for components compatible course formats.\n if (this.reactive.isEditing && this.reactive.supportComponents) {\n // Init the inner dragable element.\n this.sectionitem = sectionitem;\n // Init the dropzone.\n this.dragdrop = new DragDrop(this);\n // Save dropzone classes.\n this.classes = this.dragdrop.getClasses();\n }\n }\n\n /**\n * Remove all subcomponents dependencies.\n */\n destroy() {\n if (this.sectionitem !== undefined) {\n this.sectionitem.unregister();\n }\n if (this.dragdrop !== undefined) {\n this.dragdrop.unregister();\n }\n }\n\n /**\n * Get the last CM element of that section.\n *\n * @returns {element|null} the las course module element of the section.\n */\n getLastCm() {\n return null;\n }\n\n // Drag and drop methods.\n\n /**\n * The element drop start hook.\n *\n * @param {Object} dropdata the dropdata\n */\n dragStart(dropdata) {\n this.reactive.dispatch('sectionDrag', [dropdata.id], true);\n }\n\n /**\n * The element drop end hook.\n *\n * @param {Object} dropdata the dropdata\n */\n dragEnd(dropdata) {\n this.reactive.dispatch('sectionDrag', [dropdata.id], false);\n }\n\n /**\n * Validate if the drop data can be dropped over the component.\n *\n * @param {Object} dropdata the exported drop data.\n * @returns {boolean}\n */\n validateDropData(dropdata) {\n // We accept files.\n if (dropdata?.type === 'files') {\n return true;\n }\n // We accept any course module unless it can form a subsection loop.\n if (dropdata?.type === 'cm') {\n if (this.section?.component && dropdata?.hasdelegatedsection === true) {\n return false;\n }\n return true;\n }\n if (dropdata?.type === 'section') {\n // Sections controlled by a plugin cannot accept sections.\n if (this.section.component !== null) {\n return false;\n }\n // We accept any section but yourself and the next one.\n return dropdata?.id != this.id && dropdata?.number != this.section.number + 1;\n }\n return false;\n }\n\n /**\n * Display the component dropzone.\n *\n * @param {Object} dropdata the accepted drop data\n */\n showDropZone(dropdata) {\n if (dropdata.type == 'files') {\n this.addOverlay({\n content: getString('addfilehere', 'core'),\n icon: Templates.renderPix('t/download', 'core'),\n }).then(() => {\n // Check if we still need the file dropzone.\n if (!this.dragdrop?.isDropzoneVisible()) {\n this.removeOverlay();\n }\n return;\n }).catch((error) => {\n throw error;\n });\n }\n if (dropdata.type == 'cm') {\n this.getLastCm()?.classList.add(this.classes.DROPDOWN);\n }\n if (dropdata.type == 'section') {\n this.element.classList.remove(this.classes.DROPUP);\n this.element.classList.add(this.classes.DROPDOWN);\n }\n }\n\n /**\n * Hide the component dropzone.\n */\n hideDropZone() {\n this.getLastCm()?.classList.remove(this.classes.DROPDOWN);\n this.element.classList.remove(this.classes.DROPUP);\n this.element.classList.remove(this.classes.DROPDOWN);\n this.removeOverlay();\n }\n\n /**\n * Drop event handler.\n *\n * @param {Object} dropdata the accepted drop data\n * @param {Event} event the drop event\n */\n drop(dropdata, event) {\n // File handling.\n if (dropdata.type == 'files') {\n this.reactive.uploadFiles(\n this.section.id,\n this.section.number,\n dropdata.files\n );\n return;\n }\n // Call the move mutation.\n if (dropdata.type == 'cm') {\n const mutation = (event.altKey) ? 'cmDuplicate' : 'cmMove';\n this.reactive.dispatch(mutation, [dropdata.id], this.id);\n }\n if (dropdata.type == 'section') {\n this.reactive.dispatch('sectionMoveAfter', [dropdata.id], this.id);\n }\n }\n}\n"],"names":["BaseComponent","configState","state","id","this","element","dataset","section","get","course","configDragDrop","sectionitem","reactive","isEditing","supportComponents","dragdrop","DragDrop","classes","getClasses","destroy","undefined","unregister","getLastCm","dragStart","dropdata","dispatch","dragEnd","validateDropData","type","component","hasdelegatedsection","number","showDropZone","addOverlay","content","icon","Templates","renderPix","then","_this$dragdrop","isDropzoneVisible","removeOverlay","catch","error","classList","add","DROPDOWN","remove","DROPUP","hideDropZone","drop","event","mutation","altKey","uploadFiles","files"],"mappings":";;;;;;;;;;;iLAiCgB,OAAQ,CAAC,uCAEIA,wBAOzBC,YAAYC,YACHC,GAAKC,KAAKC,QAAQC,QAAQH,QAC1BI,QAAUL,MAAMK,QAAQC,IAAIJ,KAAKD,SACjCM,OAASP,MAAMO,OAQxBC,eAAeC,aAEPP,KAAKQ,SAASC,WAAaT,KAAKQ,SAASE,yBAEpCH,YAAcA,iBAEdI,SAAW,IAAIC,mBAASZ,WAExBa,QAAUb,KAAKW,SAASG,cAOrCC,eAC6BC,IAArBhB,KAAKO,kBACAA,YAAYU,kBAECD,IAAlBhB,KAAKW,eACAA,SAASM,aAStBC,mBACW,KAUXC,UAAUC,eACDZ,SAASa,SAAS,cAAe,CAACD,SAASrB,KAAK,GAQzDuB,QAAQF,eACCZ,SAASa,SAAS,cAAe,CAACD,SAASrB,KAAK,GASzDwB,iBAAiBH,gBAEU,WAAnBA,MAAAA,gBAAAA,SAAUI,QAIS,QAAnBJ,MAAAA,gBAAAA,SAAUI,iCACDrB,iDAASsB,YAA+C,KAAlCL,MAAAA,gBAAAA,SAAUM,qBAKtB,aAAnBN,MAAAA,gBAAAA,SAAUI,QAEqB,OAA3BxB,KAAKG,QAAQsB,aAIVL,MAAAA,gBAAAA,SAAUrB,KAAMC,KAAKD,KAAMqB,MAAAA,gBAAAA,SAAUO,SAAU3B,KAAKG,QAAQwB,OAAS,uBAUpFC,aAAaR,+BACY,SAAjBA,SAASI,WACJK,WAAW,CACZC,SAAS,kBAAU,cAAe,QAClCC,KAAMC,mBAAUC,UAAU,aAAc,UACzCC,MAAK,+CAEClC,KAAKW,oCAALwB,eAAeC,0BACXC,mBAGVC,OAAOC,cACAA,SAGO,MAAjBnB,SAASI,qCACJN,wDAAasB,UAAUC,IAAIzC,KAAKa,QAAQ6B,WAE5B,WAAjBtB,SAASI,YACJvB,QAAQuC,UAAUG,OAAO3C,KAAKa,QAAQ+B,aACtC3C,QAAQuC,UAAUC,IAAIzC,KAAKa,QAAQ6B,WAOhDG,kEACS3B,0DAAasB,UAAUG,OAAO3C,KAAKa,QAAQ6B,eAC3CzC,QAAQuC,UAAUG,OAAO3C,KAAKa,QAAQ+B,aACtC3C,QAAQuC,UAAUG,OAAO3C,KAAKa,QAAQ6B,eACtCL,gBASTS,KAAK1B,SAAU2B,UAEU,SAAjB3B,SAASI,SASQ,MAAjBJ,SAASI,KAAc,OACjBwB,SAAYD,MAAME,OAAU,cAAgB,cAC7CzC,SAASa,SAAS2B,SAAU,CAAC5B,SAASrB,IAAKC,KAAKD,IAEpC,WAAjBqB,SAASI,WACJhB,SAASa,SAAS,mBAAoB,CAACD,SAASrB,IAAKC,KAAKD,cAb1DS,SAAS0C,YACVlD,KAAKG,QAAQJ,GACbC,KAAKG,QAAQwB,OACbP,SAAS+B"} \ No newline at end of file +{"version":3,"file":"dndsection.min.js","sources":["../../../src/local/courseeditor/dndsection.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Course index section component.\n *\n * This component is used to control specific course section interactions like drag and drop\n * in both course index and course content.\n *\n * @module core_courseformat/local/courseeditor/dndsection\n * @class core_courseformat/local/courseeditor/dndsection\n * @copyright 2021 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {BaseComponent, DragDrop} from 'core/reactive';\nimport {getString} from 'core/str';\nimport {prefetchStrings} from 'core/prefetch';\nimport Templates from 'core/templates';\n\n// Load global strings.\nprefetchStrings('core', ['addfilehere']);\n\nexport default class extends BaseComponent {\n\n /**\n * Save some values form the state.\n *\n * @param {Object} state the current state\n */\n configState(state) {\n this.id = this.element.dataset.id;\n this.section = state.section.get(this.id);\n this.course = state.course;\n }\n\n /**\n * Register state values and the drag and drop subcomponent.\n *\n * @param {BaseComponent} sectionitem section item component\n */\n configDragDrop(sectionitem) {\n // Drag and drop is only available for components compatible course formats.\n if (this.reactive.isEditing && this.reactive.supportComponents) {\n // Init the inner dragable element.\n this.sectionitem = sectionitem;\n // Init the dropzone.\n this.dragdrop = new DragDrop(this);\n // Save dropzone classes.\n this.classes = this.dragdrop.getClasses();\n }\n }\n\n /**\n * Remove all subcomponents dependencies.\n */\n destroy() {\n if (this.sectionitem !== undefined) {\n this.sectionitem.unregister();\n }\n if (this.dragdrop !== undefined) {\n this.dragdrop.unregister();\n }\n }\n\n /**\n * Get the last CM element of that section.\n *\n * @returns {element|null} the las course module element of the section.\n */\n getLastCm() {\n return null;\n }\n\n /**\n * Get a fallback element when there is no CM in the section.\n *\n * This is used to show the correct dropzone position.\n *\n * @returns {element|null} the las course module element of the section.\n */\n getLastCmFallback() {\n return null;\n }\n\n // Drag and drop methods.\n\n /**\n * The element drop start hook.\n *\n * @param {Object} dropdata the dropdata\n */\n dragStart(dropdata) {\n this.reactive.dispatch('sectionDrag', [dropdata.id], true);\n }\n\n /**\n * The element drop end hook.\n *\n * @param {Object} dropdata the dropdata\n */\n dragEnd(dropdata) {\n this.reactive.dispatch('sectionDrag', [dropdata.id], false);\n }\n\n /**\n * Validate if the drop data can be dropped over the component.\n *\n * @param {Object} dropdata the exported drop data.\n * @returns {boolean}\n */\n validateDropData(dropdata) {\n // We accept files.\n if (dropdata?.type === 'files') {\n return true;\n }\n // We accept any course module unless it can form a subsection loop.\n if (dropdata?.type === 'cm') {\n if (this.section?.component && dropdata?.hasdelegatedsection === true) {\n return false;\n }\n return true;\n }\n if (dropdata?.type === 'section') {\n // Sections controlled by a plugin cannot accept sections.\n if (this.section.component !== null) {\n return false;\n }\n // We accept any section but yourself and the next one.\n return dropdata?.id != this.id && dropdata?.number != this.section.number + 1;\n }\n return false;\n }\n\n /**\n * Display the component dropzone.\n *\n * @param {Object} dropdata the accepted drop data\n */\n showDropZone(dropdata) {\n if (dropdata.type == 'files') {\n this.addOverlay({\n content: getString('addfilehere', 'core'),\n icon: Templates.renderPix('t/download', 'core'),\n }).then(() => {\n // Check if we still need the file dropzone.\n if (!this.dragdrop?.isDropzoneVisible()) {\n this.removeOverlay();\n }\n return;\n }).catch((error) => {\n throw error;\n });\n }\n if (dropdata.type == 'cm') {\n const lastCm = this.getLastCm();\n lastCm?.classList.add(this.classes.DROPDOWN);\n if (!lastCm) {\n this.getLastCmFallback()?.classList.add(this.classes.DROPDOWN);\n }\n }\n if (dropdata.type == 'section') {\n this.element.classList.remove(this.classes.DROPUP);\n this.element.classList.add(this.classes.DROPDOWN);\n }\n }\n\n /**\n * Hide the component dropzone.\n */\n hideDropZone() {\n this.getLastCm()?.classList.remove(this.classes.DROPDOWN);\n this.getLastCmFallback()?.classList.remove(this.classes.DROPDOWN);\n this.element.classList.remove(this.classes.DROPUP);\n this.element.classList.remove(this.classes.DROPDOWN);\n this.removeOverlay();\n }\n\n /**\n * Drop event handler.\n *\n * @param {Object} dropdata the accepted drop data\n * @param {Event} event the drop event\n */\n drop(dropdata, event) {\n // File handling.\n if (dropdata.type == 'files') {\n this.reactive.uploadFiles(\n this.section.id,\n this.section.number,\n dropdata.files\n );\n return;\n }\n // Call the move mutation.\n if (dropdata.type == 'cm') {\n const mutation = (event.altKey) ? 'cmDuplicate' : 'cmMove';\n this.reactive.dispatch(mutation, [dropdata.id], this.id);\n }\n if (dropdata.type == 'section') {\n this.reactive.dispatch('sectionMoveAfter', [dropdata.id], this.id);\n }\n }\n}\n"],"names":["BaseComponent","configState","state","id","this","element","dataset","section","get","course","configDragDrop","sectionitem","reactive","isEditing","supportComponents","dragdrop","DragDrop","classes","getClasses","destroy","undefined","unregister","getLastCm","getLastCmFallback","dragStart","dropdata","dispatch","dragEnd","validateDropData","type","component","hasdelegatedsection","number","showDropZone","addOverlay","content","icon","Templates","renderPix","then","_this$dragdrop","isDropzoneVisible","removeOverlay","catch","error","lastCm","classList","add","DROPDOWN","remove","DROPUP","hideDropZone","drop","event","mutation","altKey","uploadFiles","files"],"mappings":";;;;;;;;;;;iLAiCgB,OAAQ,CAAC,uCAEIA,wBAOzBC,YAAYC,YACHC,GAAKC,KAAKC,QAAQC,QAAQH,QAC1BI,QAAUL,MAAMK,QAAQC,IAAIJ,KAAKD,SACjCM,OAASP,MAAMO,OAQxBC,eAAeC,aAEPP,KAAKQ,SAASC,WAAaT,KAAKQ,SAASE,yBAEpCH,YAAcA,iBAEdI,SAAW,IAAIC,mBAASZ,WAExBa,QAAUb,KAAKW,SAASG,cAOrCC,eAC6BC,IAArBhB,KAAKO,kBACAA,YAAYU,kBAECD,IAAlBhB,KAAKW,eACAA,SAASM,aAStBC,mBACW,KAUXC,2BACW,KAUXC,UAAUC,eACDb,SAASc,SAAS,cAAe,CAACD,SAAStB,KAAK,GAQzDwB,QAAQF,eACCb,SAASc,SAAS,cAAe,CAACD,SAAStB,KAAK,GASzDyB,iBAAiBH,gBAEU,WAAnBA,MAAAA,gBAAAA,SAAUI,QAIS,QAAnBJ,MAAAA,gBAAAA,SAAUI,iCACDtB,iDAASuB,YAA+C,KAAlCL,MAAAA,gBAAAA,SAAUM,qBAKtB,aAAnBN,MAAAA,gBAAAA,SAAUI,QAEqB,OAA3BzB,KAAKG,QAAQuB,aAIVL,MAAAA,gBAAAA,SAAUtB,KAAMC,KAAKD,KAAMsB,MAAAA,gBAAAA,SAAUO,SAAU5B,KAAKG,QAAQyB,OAAS,uBAUpFC,aAAaR,aACY,SAAjBA,SAASI,WACJK,WAAW,CACZC,SAAS,kBAAU,cAAe,QAClCC,KAAMC,mBAAUC,UAAU,aAAc,UACzCC,MAAK,+CAECnC,KAAKW,oCAALyB,eAAeC,0BACXC,mBAGVC,OAAOC,cACAA,SAGO,MAAjBnB,SAASI,KAAc,OACjBgB,OAASzC,KAAKkB,yCACpBuB,MAAAA,QAAAA,OAAQC,UAAUC,IAAI3C,KAAKa,QAAQ+B,WAC9BH,0CACItB,4EAAqBuB,UAAUC,IAAI3C,KAAKa,QAAQ+B,UAGxC,WAAjBvB,SAASI,YACJxB,QAAQyC,UAAUG,OAAO7C,KAAKa,QAAQiC,aACtC7C,QAAQyC,UAAUC,IAAI3C,KAAKa,QAAQ+B,WAOhDG,uFACS7B,wDAAawB,UAAUG,OAAO7C,KAAKa,QAAQ+B,8CAC3CzB,8EAAqBuB,UAAUG,OAAO7C,KAAKa,QAAQ+B,eACnD3C,QAAQyC,UAAUG,OAAO7C,KAAKa,QAAQiC,aACtC7C,QAAQyC,UAAUG,OAAO7C,KAAKa,QAAQ+B,eACtCN,gBASTU,KAAK3B,SAAU4B,UAEU,SAAjB5B,SAASI,SASQ,MAAjBJ,SAASI,KAAc,OACjByB,SAAYD,MAAME,OAAU,cAAgB,cAC7C3C,SAASc,SAAS4B,SAAU,CAAC7B,SAAStB,IAAKC,KAAKD,IAEpC,WAAjBsB,SAASI,WACJjB,SAASc,SAAS,mBAAoB,CAACD,SAAStB,IAAKC,KAAKD,cAb1DS,SAAS4C,YACVpD,KAAKG,QAAQJ,GACbC,KAAKG,QAAQyB,OACbP,SAASgC"} \ No newline at end of file diff --git a/course/format/amd/src/local/content/section.js b/course/format/amd/src/local/content/section.js index 135e55f355e..0a621301c0e 100644 --- a/course/format/amd/src/local/content/section.js +++ b/course/format/amd/src/local/content/section.js @@ -119,7 +119,7 @@ export default class extends DndSection { */ validateDropData(dropdata) { // If the format uses one section per page sections dropping in the content is ignored. - if (dropdata?.type === 'section' && this.reactive.sectionReturn !== null) { + if (dropdata?.type === 'section' && this.reactive.sectionReturn !== null) { return false; } return super.validateDropData(dropdata); @@ -139,6 +139,16 @@ export default class extends DndSection { return cms[cms.length - 1]; } + /** + * Get a fallback element when there is no CM in the section. + * + * @returns {element|null} the las course module element of the section. + */ + getLastCmFallback() { + // The sectioninfo is always present, even when the section is empty. + return this.getElement(this.selectors.SECTIONINFO); + } + /** * Update a content section using the state information. * diff --git a/course/format/amd/src/local/courseeditor/dndsection.js b/course/format/amd/src/local/courseeditor/dndsection.js index ad0aaf8078e..a6d8b3f74c6 100644 --- a/course/format/amd/src/local/courseeditor/dndsection.js +++ b/course/format/amd/src/local/courseeditor/dndsection.js @@ -84,6 +84,17 @@ export default class extends BaseComponent { return null; } + /** + * Get a fallback element when there is no CM in the section. + * + * This is used to show the correct dropzone position. + * + * @returns {element|null} the las course module element of the section. + */ + getLastCmFallback() { + return null; + } + // Drag and drop methods. /** @@ -154,7 +165,11 @@ export default class extends BaseComponent { }); } if (dropdata.type == 'cm') { - this.getLastCm()?.classList.add(this.classes.DROPDOWN); + const lastCm = this.getLastCm(); + lastCm?.classList.add(this.classes.DROPDOWN); + if (!lastCm) { + this.getLastCmFallback()?.classList.add(this.classes.DROPDOWN); + } } if (dropdata.type == 'section') { this.element.classList.remove(this.classes.DROPUP); @@ -167,6 +182,7 @@ export default class extends BaseComponent { */ hideDropZone() { this.getLastCm()?.classList.remove(this.classes.DROPDOWN); + this.getLastCmFallback()?.classList.remove(this.classes.DROPDOWN); this.element.classList.remove(this.classes.DROPUP); this.element.classList.remove(this.classes.DROPDOWN); this.removeOverlay(); diff --git a/theme/boost/scss/moodle/course.scss b/theme/boost/scss/moodle/course.scss index c49311a4acd..4e0a8e3fb05 100644 --- a/theme/boost/scss/moodle/course.scss +++ b/theme/boost/scss/moodle/course.scss @@ -257,6 +257,17 @@ border-top: 1px solid $dropzone-border; margin-top: -1px; } + + [data-for="sectioninfo"] { + // When a section is empty, the activity dropzone indicator is below + // the section info. This ensures the dropzone will not displace the content + // even if the section has no restrictions or info to display. + min-height: 1px; + } + + [data-for="sectioninfo"].drop-down { + margin-top: -1px; + } } .section .activity .activityinstance .groupinglabel { diff --git a/theme/boost/style/moodle.css b/theme/boost/style/moodle.css index e018963bac2..f6b37f0ba52 100644 --- a/theme/boost/style/moodle.css +++ b/theme/boost/style/moodle.css @@ -28211,6 +28211,12 @@ table.calendartable caption { border-top: 1px solid #1d2125; margin-top: -1px; } +.course-content .section.dropready [data-for=sectioninfo] { + min-height: 1px; +} +.course-content .section.dropready [data-for=sectioninfo].drop-down { + margin-top: -1px; +} .section .activity .activityinstance .groupinglabel { padding-left: 30px; diff --git a/theme/classic/style/moodle.css b/theme/classic/style/moodle.css index 8dce6f612b0..6571fbfe131 100644 --- a/theme/classic/style/moodle.css +++ b/theme/classic/style/moodle.css @@ -28211,6 +28211,12 @@ table.calendartable caption { border-top: 1px solid #1d2125; margin-top: -1px; } +.course-content .section.dropready [data-for=sectioninfo] { + min-height: 1px; +} +.course-content .section.dropready [data-for=sectioninfo].drop-down { + margin-top: -1px; +} .section .activity .activityinstance .groupinglabel { padding-left: 30px;