From e6802896f7f1cd21b38ccbd12aa93f674d7ca4f8 Mon Sep 17 00:00:00 2001 From: Ferran Recio Date: Wed, 18 Jan 2023 16:10:56 +0100 Subject: [PATCH] MDL-76848 core_courseformat: bulk availability --- .../amd/build/local/content/actions.min.js | 2 +- .../build/local/content/actions.min.js.map | 2 +- .../build/local/courseeditor/exporter.min.js | 2 +- .../local/courseeditor/exporter.min.js.map | 2 +- .../build/local/courseeditor/mutations.min.js | 2 +- .../local/courseeditor/mutations.min.js.map | 2 +- .../format/amd/src/local/content/actions.js | 122 +++++++++++++++++- .../amd/src/local/courseeditor/exporter.js | 14 ++ .../amd/src/local/courseeditor/mutations.js | 2 + .../output/local/content/bulkedittools.php | 32 +++++ .../format/classes/output/local/state/cm.php | 4 +- .../content/cm/availabilitymodal.mustache | 96 ++++++++++++++ .../section/availabilitymodal.mustache | 72 +++++++++++ .../tests/behat/bulk_activity_actions.feature | 99 ++++++++++++++ .../tests/behat/bulk_section_actions.feature | 85 ++++++++++++ lang/en/courseformat.php | 8 ++ .../output/icon_system_fontawesome.php | 1 + pix/t/stealth.png | Bin 0 -> 454 bytes pix/t/stealth.svg | 33 +++++ theme/boost/scss/moodle/icons.scss | 27 +++- theme/boost/style/moodle.css | 14 ++ theme/classic/style/moodle.css | 14 ++ 22 files changed, 625 insertions(+), 10 deletions(-) create mode 100644 course/format/templates/local/content/cm/availabilitymodal.mustache create mode 100644 course/format/templates/local/content/section/availabilitymodal.mustache create mode 100644 course/format/tests/behat/bulk_activity_actions.feature create mode 100644 course/format/tests/behat/bulk_section_actions.feature create mode 100644 pix/t/stealth.png create mode 100644 pix/t/stealth.svg diff --git a/course/format/amd/build/local/content/actions.min.js b/course/format/amd/build/local/content/actions.min.js index 8148187d62918..da475cb0675e9 100644 --- a/course/format/amd/build/local/content/actions.min.js +++ b/course/format/amd/build/local/content/actions.min.js @@ -9,6 +9,6 @@ define("core_courseformat/local/content/actions",["exports","core/reactive","cor * @class core_courseformat/local/content/actions * @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,_modal_factory=_interopRequireDefault(_modal_factory),_modal_events=_interopRequireDefault(_modal_events),_templates=_interopRequireDefault(_templates),CourseEvents=function(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}newObj.default=obj,cache&&cache.set(obj,newObj);return newObj}(CourseEvents),_pending=_interopRequireDefault(_pending),_contenttree=_interopRequireDefault(_contenttree),_jquery=_interopRequireDefault(_jquery),(0,_prefetch.prefetchStrings)("core",["movecoursesection","movecoursemodule","confirm","delete"]);const directMutations={sectionHide:"sectionHide",sectionShow:"sectionShow",cmHide:"cmHide",cmShow:"cmShow",cmStealth:"cmStealth"};class _default extends _reactive.BaseComponent{create(){this.name="content_actions",this.selectors={ACTIONLINK:"[data-action]",SECTIONLINK:"[data-for='section']",CMLINK:"[data-for='cm']",SECTIONNODE:"[data-for='sectionnode']",MODALTOGGLER:"[data-toggle='collapse']",ADDSECTION:"[data-action='addSection']",CONTENTTREE:"#destination-selector",ACTIONMENU:".action-menu",ACTIONMENUTOGGLER:'[data-toggle="dropdown"]'},this.classes={DISABLED:"disabled"}}static addActions(actions){for(const[action,mutationReference]of Object.entries(actions)){if("function"!=typeof mutationReference&&"string"!=typeof mutationReference)throw new Error("".concat(action," action must be a mutation name or a function"));directMutations[action]=mutationReference}}stateReady(state){this.addEventListener(this.element,"click",this._dispatchClick),this._checkSectionlist({state:state}),this.addEventListener(this.element,CourseEvents.sectionRefreshed,(()=>this._checkSectionlist({state:state})))}getWatchers(){return[{watch:"course.sectionlist:updated",handler:this._checkSectionlist}]}_dispatchClick(event){const target=event.target.closest(this.selectors.ACTIONLINK);if(!target)return;if(target.classList.contains(this.classes.DISABLED))return void event.preventDefault();const actionName=target.dataset.action,methodName=this._actionMethodName(actionName);if(void 0===this[methodName])return void 0!==directMutations[actionName]?"function"==typeof directMutations[actionName]?void directMutations[actionName](target,event):void this._requestMutationAction(target,event,directMutations[actionName]):void 0;this[methodName](target,event)}_actionMethodName(name){const requestName=name.charAt(0).toUpperCase()+name.slice(1);return"_request".concat(requestName)}_checkSectionlist(_ref){let{state:state}=_ref;this._setAddSectionLocked(state.course.sectionlist.length>state.course.maxsections)}async _requestMoveSection(target,event){const sectionId=target.dataset.id;if(!sectionId)return;const sectionInfo=this.reactive.get("section",sectionId);event.preventDefault();const editTools=this._getClosestActionMenuToogler(target),data=this.reactive.getExporter().course(this.reactive.state);data.sectionid=sectionInfo.id,data.sectiontitle=sectionInfo.title;const modalParams={title:(0,_str.get_string)("movecoursesection","core"),body:_templates.default.render("core_courseformat/local/content/movesection",data)},modal=await this._modalBodyRenderedPromise(modalParams),modalBody=(0,_normalise.getList)(modal.getBody())[0],currentElement=modalBody.querySelector("".concat(this.selectors.SECTIONLINK,"[data-id='").concat(sectionId,"']"));this._disableLink(currentElement);const generalSection=modalBody.querySelector("".concat(this.selectors.SECTIONLINK,"[data-number='0']"));this._disableLink(generalSection),new _contenttree.default(modalBody.querySelector(this.selectors.CONTENTTREE),{SECTION:this.selectors.SECTIONNODE,TOGGLER:this.selectors.MODALTOGGLER,COLLAPSE:this.selectors.MODALTOGGLER},!0),modalBody.addEventListener("click",(event=>{const target=event.target;target.matches("a")&&"section"==target.dataset.for&&void 0!==target.dataset.id&&(target.getAttribute("aria-disabled")||(event.preventDefault(),this.reactive.dispatch("sectionMove",[sectionId],target.dataset.id),this._destroyModal(modal,editTools)))}))}async _requestMoveCm(target,event){var _toggler$data;const cmId=target.dataset.id;if(!cmId)return;const cmInfo=this.reactive.get("cm",cmId);event.preventDefault();const editTools=this._getClosestActionMenuToogler(target),exporter=this.reactive.getExporter(),data=exporter.course(this.reactive.state);data.cmid=cmInfo.id,data.cmname=cmInfo.name;const modalParams={title:(0,_str.get_string)("movecoursemodule","core"),body:_templates.default.render("core_courseformat/local/content/movecm",data)},modal=await this._modalBodyRenderedPromise(modalParams),modalBody=(0,_normalise.getList)(modal.getBody())[0];let currentElement=modalBody.querySelector("".concat(this.selectors.CMLINK,"[data-id='").concat(cmId,"']"));this._disableLink(currentElement),new _contenttree.default(modalBody.querySelector(this.selectors.CONTENTTREE),{SECTION:this.selectors.SECTIONNODE,TOGGLER:this.selectors.MODALTOGGLER,COLLAPSE:this.selectors.MODALTOGGLER,ENTER:this.selectors.SECTIONLINK});const sectionnode=currentElement.closest(this.selectors.SECTIONNODE),toggler=(0,_jquery.default)(sectionnode).find(this.selectors.MODALTOGGLER);let collapsibleId=null!==(_toggler$data=toggler.data("target"))&&void 0!==_toggler$data?_toggler$data:toggler.attr("href");collapsibleId&&(collapsibleId=collapsibleId.replace("#",""),(0,_jquery.default)("#".concat(collapsibleId)).collapse("toggle")),modalBody.addEventListener("click",(event=>{const target=event.target;if(!target.matches("a")||void 0===target.dataset.for||void 0===target.dataset.id)return;if(target.getAttribute("aria-disabled"))return;let targetSectionId,targetCmId;if(event.preventDefault(),"cm"==target.dataset.for){const dropData=exporter.cmDraggableData(this.reactive.state,target.dataset.id);targetSectionId=dropData.sectionid,targetCmId=dropData.nextcmid}else{const section=this.reactive.get("section",target.dataset.id);targetSectionId=target.dataset.id,targetCmId=null==section?void 0:section.cmlist[0]}this.reactive.dispatch("cmMove",[cmId],targetSectionId,targetCmId),this._destroyModal(modal,editTools)}))}async _requestAddSection(target,event){var _target$dataset$id;event.preventDefault(),this.reactive.dispatch("addSection",null!==(_target$dataset$id=target.dataset.id)&&void 0!==_target$dataset$id?_target$dataset$id:0)}async _requestDeleteSection(target,event){var _sectionInfo$cmlist;const sectionId=target.dataset.id;if(!sectionId)return;const sectionInfo=this.reactive.get("section",sectionId);event.preventDefault();if((null!==(_sectionInfo$cmlist=sectionInfo.cmlist)&&void 0!==_sectionInfo$cmlist?_sectionInfo$cmlist:[]).length||sectionInfo.hassummary||sectionInfo.rawtitle){const modalParams={title:(0,_str.get_string)("confirm","core"),body:(0,_str.get_string)("confirmdeletesection","moodle",sectionInfo.title),saveButtonText:(0,_str.get_string)("delete","core"),type:_modal_factory.default.types.SAVE_CANCEL},modal=await this._modalBodyRenderedPromise(modalParams);modal.getRoot().on(_modal_events.default.save,(e=>{e.preventDefault(),modal.destroy(),this.reactive.dispatch("sectionDelete",[sectionId])}))}else this.reactive.dispatch("sectionDelete",[sectionId])}async _requestToggleSelectionCm(target){var _target$checked;const cmId=target.dataset.id;if(!cmId)return;const mutation=null!==(_target$checked=target.checked)&&void 0!==_target$checked&&_target$checked?"cmSelect":"cmUnselect";this.reactive.dispatch(mutation,[cmId])}async _requestToggleSelectionSection(target){var _target$checked2;const sectionId=target.dataset.id;if(!sectionId)return;const mutation=null!==(_target$checked2=target.checked)&&void 0!==_target$checked2&&_target$checked2?"sectionSelect":"sectionUnselect";this.reactive.dispatch(mutation,[sectionId])}async _requestMutationAction(target,event,mutationName){target.dataset.id&&(event.preventDefault(),this.reactive.dispatch(mutationName,[target.dataset.id]))}async _requestCmDuplicate(target,event){var _target$dataset$secti;const cmId=target.dataset.id;if(!cmId)return;const sectionId=null!==(_target$dataset$secti=target.dataset.sectionid)&&void 0!==_target$dataset$secti?_target$dataset$secti:null;event.preventDefault(),this.reactive.dispatch("cmDuplicate",[cmId],sectionId)}async _requestCmDelete(target,event){const cmId=target.dataset.id;if(!cmId)return;const cmInfo=this.reactive.get("cm",cmId);event.preventDefault();const modalParams={title:(0,_str.get_string)("confirm","core"),body:(0,_str.get_string)("deletechecktypename","moodle",{type:cmInfo.modname,name:cmInfo.name}),saveButtonText:(0,_str.get_string)("delete","core"),type:_modal_factory.default.types.SAVE_CANCEL},modal=await this._modalBodyRenderedPromise(modalParams);modal.getRoot().on(_modal_events.default.save,(e=>{e.preventDefault(),modal.destroy(),this.reactive.dispatch("cmDelete",[cmId])}))}_setAddSectionLocked(locked){this.getElements(this.selectors.ADDSECTION).forEach((element=>{element.classList.toggle(this.classes.DISABLED,locked),this.setElementLocked(element,locked)}))}_disableLink(element){element&&(element.style.pointerEvents="none",element.style.userSelect="none",element.classList.add(this.classes.DISABLED),element.setAttribute("aria-disabled",!0),element.addEventListener("click",(event=>event.preventDefault())))}_modalBodyRenderedPromise(modalParams){return new Promise(((resolve,reject)=>{_modal_factory.default.create(modalParams).then((modal=>{modal.setRemoveOnClose(!0),modal.getRoot().on(_modal_events.default.bodyRendered,(()=>{resolve(modal)})),void 0!==modalParams.saveButtonText&&modal.setSaveButtonText(modalParams.saveButtonText),modal.show()})).catch((()=>{reject("Cannot load modal content")}))}))}_destroyModal(modal,element){modal.hide();const pendingDestroy=new _pending.default("courseformat/actions:destroyModal");element&&element.focus(),setTimeout((()=>{modal.destroy(),pendingDestroy.resolve()}),500)}_getClosestActionMenuToogler(element){const actionMenu=element.closest(this.selectors.ACTIONMENU);if(actionMenu)return actionMenu.querySelector(this.selectors.ACTIONMENUTOGGLER)}}return _exports.default=_default,_exports.default})); + */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_modal_factory=_interopRequireDefault(_modal_factory),_modal_events=_interopRequireDefault(_modal_events),_templates=_interopRequireDefault(_templates),CourseEvents=function(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}newObj.default=obj,cache&&cache.set(obj,newObj);return newObj}(CourseEvents),_pending=_interopRequireDefault(_pending),_contenttree=_interopRequireDefault(_contenttree),_jquery=_interopRequireDefault(_jquery),(0,_prefetch.prefetchStrings)("core",["movecoursesection","movecoursemodule","confirm","delete"]);const directMutations={sectionHide:"sectionHide",sectionShow:"sectionShow",cmHide:"cmHide",cmShow:"cmShow",cmStealth:"cmStealth"};class _default extends _reactive.BaseComponent{create(){this.name="content_actions",this.selectors={ACTIONLINK:"[data-action]",SECTIONLINK:"[data-for='section']",CMLINK:"[data-for='cm']",SECTIONNODE:"[data-for='sectionnode']",MODALTOGGLER:"[data-toggle='collapse']",ADDSECTION:"[data-action='addSection']",CONTENTTREE:"#destination-selector",ACTIONMENU:".action-menu",ACTIONMENUTOGGLER:'[data-toggle="dropdown"]',OPTIONSRADIO:"[type='radio']"},this.classes={DISABLED:"disabled"}}static addActions(actions){for(const[action,mutationReference]of Object.entries(actions)){if("function"!=typeof mutationReference&&"string"!=typeof mutationReference)throw new Error("".concat(action," action must be a mutation name or a function"));directMutations[action]=mutationReference}}stateReady(state){this.addEventListener(this.element,"click",this._dispatchClick),this._checkSectionlist({state:state}),this.addEventListener(this.element,CourseEvents.sectionRefreshed,(()=>this._checkSectionlist({state:state})))}getWatchers(){return[{watch:"course.sectionlist:updated",handler:this._checkSectionlist}]}_dispatchClick(event){const target=event.target.closest(this.selectors.ACTIONLINK);if(!target)return;if(target.classList.contains(this.classes.DISABLED))return void event.preventDefault();const actionName=target.dataset.action,methodName=this._actionMethodName(actionName);if(void 0===this[methodName])return void 0!==directMutations[actionName]?"function"==typeof directMutations[actionName]?void directMutations[actionName](target,event):void this._requestMutationAction(target,event,directMutations[actionName]):void 0;this[methodName](target,event)}_actionMethodName(name){const requestName=name.charAt(0).toUpperCase()+name.slice(1);return"_request".concat(requestName)}_checkSectionlist(_ref){let{state:state}=_ref;this._setAddSectionLocked(state.course.sectionlist.length>state.course.maxsections)}_getTargetIds(target){var _target$dataset,_target$dataset2;let ids=[];null!=target&&null!==(_target$dataset=target.dataset)&&void 0!==_target$dataset&&_target$dataset.id&&ids.push(target.dataset.id);const bulkType=null==target||null===(_target$dataset2=target.dataset)||void 0===_target$dataset2?void 0:_target$dataset2.bulk;if(!bulkType)return ids;const bulk=this.reactive.get("bulk");return bulk.enabled&&bulk.selectedType===bulkType&&(ids=[...ids,...bulk.selection]),ids}async _requestMoveSection(target,event){const sectionId=target.dataset.id;if(!sectionId)return;const sectionInfo=this.reactive.get("section",sectionId);event.preventDefault();const editTools=this._getClosestActionMenuToogler(target),data=this.reactive.getExporter().course(this.reactive.state);data.sectionid=sectionInfo.id,data.sectiontitle=sectionInfo.title;const modalParams={title:(0,_str.get_string)("movecoursesection","core"),body:_templates.default.render("core_courseformat/local/content/movesection",data)},modal=await this._modalBodyRenderedPromise(modalParams),modalBody=(0,_normalise.getList)(modal.getBody())[0],currentElement=modalBody.querySelector("".concat(this.selectors.SECTIONLINK,"[data-id='").concat(sectionId,"']"));this._disableLink(currentElement);const generalSection=modalBody.querySelector("".concat(this.selectors.SECTIONLINK,"[data-number='0']"));this._disableLink(generalSection),new _contenttree.default(modalBody.querySelector(this.selectors.CONTENTTREE),{SECTION:this.selectors.SECTIONNODE,TOGGLER:this.selectors.MODALTOGGLER,COLLAPSE:this.selectors.MODALTOGGLER},!0),modalBody.addEventListener("click",(event=>{const target=event.target;target.matches("a")&&"section"==target.dataset.for&&void 0!==target.dataset.id&&(target.getAttribute("aria-disabled")||(event.preventDefault(),this.reactive.dispatch("sectionMove",[sectionId],target.dataset.id),this._destroyModal(modal,editTools)))}))}async _requestMoveCm(target,event){var _toggler$data;const cmId=target.dataset.id;if(!cmId)return;const cmInfo=this.reactive.get("cm",cmId);event.preventDefault();const editTools=this._getClosestActionMenuToogler(target),exporter=this.reactive.getExporter(),data=exporter.course(this.reactive.state);data.cmid=cmInfo.id,data.cmname=cmInfo.name;const modalParams={title:(0,_str.get_string)("movecoursemodule","core"),body:_templates.default.render("core_courseformat/local/content/movecm",data)},modal=await this._modalBodyRenderedPromise(modalParams),modalBody=(0,_normalise.getList)(modal.getBody())[0];let currentElement=modalBody.querySelector("".concat(this.selectors.CMLINK,"[data-id='").concat(cmId,"']"));this._disableLink(currentElement),new _contenttree.default(modalBody.querySelector(this.selectors.CONTENTTREE),{SECTION:this.selectors.SECTIONNODE,TOGGLER:this.selectors.MODALTOGGLER,COLLAPSE:this.selectors.MODALTOGGLER,ENTER:this.selectors.SECTIONLINK});const sectionnode=currentElement.closest(this.selectors.SECTIONNODE),toggler=(0,_jquery.default)(sectionnode).find(this.selectors.MODALTOGGLER);let collapsibleId=null!==(_toggler$data=toggler.data("target"))&&void 0!==_toggler$data?_toggler$data:toggler.attr("href");collapsibleId&&(collapsibleId=collapsibleId.replace("#",""),(0,_jquery.default)("#".concat(collapsibleId)).collapse("toggle")),modalBody.addEventListener("click",(event=>{const target=event.target;if(!target.matches("a")||void 0===target.dataset.for||void 0===target.dataset.id)return;if(target.getAttribute("aria-disabled"))return;let targetSectionId,targetCmId;if(event.preventDefault(),"cm"==target.dataset.for){const dropData=exporter.cmDraggableData(this.reactive.state,target.dataset.id);targetSectionId=dropData.sectionid,targetCmId=dropData.nextcmid}else{const section=this.reactive.get("section",target.dataset.id);targetSectionId=target.dataset.id,targetCmId=null==section?void 0:section.cmlist[0]}this.reactive.dispatch("cmMove",[cmId],targetSectionId,targetCmId),this._destroyModal(modal,editTools)}))}async _requestAddSection(target,event){var _target$dataset$id;event.preventDefault(),this.reactive.dispatch("addSection",null!==(_target$dataset$id=target.dataset.id)&&void 0!==_target$dataset$id?_target$dataset$id:0)}async _requestDeleteSection(target,event){var _sectionInfo$cmlist;const sectionId=target.dataset.id;if(!sectionId)return;const sectionInfo=this.reactive.get("section",sectionId);event.preventDefault();if((null!==(_sectionInfo$cmlist=sectionInfo.cmlist)&&void 0!==_sectionInfo$cmlist?_sectionInfo$cmlist:[]).length||sectionInfo.hassummary||sectionInfo.rawtitle){const modalParams={title:(0,_str.get_string)("confirm","core"),body:(0,_str.get_string)("confirmdeletesection","moodle",sectionInfo.title),saveButtonText:(0,_str.get_string)("delete","core"),type:_modal_factory.default.types.SAVE_CANCEL},modal=await this._modalBodyRenderedPromise(modalParams);modal.getRoot().on(_modal_events.default.save,(e=>{e.preventDefault(),modal.destroy(),this.reactive.dispatch("sectionDelete",[sectionId])}))}else this.reactive.dispatch("sectionDelete",[sectionId])}async _requestToggleSelectionCm(target){var _target$checked;const cmId=target.dataset.id;if(!cmId)return;const mutation=null!==(_target$checked=target.checked)&&void 0!==_target$checked&&_target$checked?"cmSelect":"cmUnselect";this.reactive.dispatch(mutation,[cmId])}async _requestToggleSelectionSection(target){var _target$checked2;const sectionId=target.dataset.id;if(!sectionId)return;const mutation=null!==(_target$checked2=target.checked)&&void 0!==_target$checked2&&_target$checked2?"sectionSelect":"sectionUnselect";this.reactive.dispatch(mutation,[sectionId])}async _requestMutationAction(target,event,mutationName){target.dataset.id&&(event.preventDefault(),this.reactive.dispatch(mutationName,[target.dataset.id]))}async _requestCmDuplicate(target,event){var _target$dataset$secti;const cmId=target.dataset.id;if(!cmId)return;const sectionId=null!==(_target$dataset$secti=target.dataset.sectionid)&&void 0!==_target$dataset$secti?_target$dataset$secti:null;event.preventDefault(),this.reactive.dispatch("cmDuplicate",[cmId],sectionId)}async _requestCmDelete(target,event){const cmId=target.dataset.id;if(!cmId)return;const cmInfo=this.reactive.get("cm",cmId);event.preventDefault();const modalParams={title:(0,_str.get_string)("confirm","core"),body:(0,_str.get_string)("deletechecktypename","moodle",{type:cmInfo.modname,name:cmInfo.name}),saveButtonText:(0,_str.get_string)("delete","core"),type:_modal_factory.default.types.SAVE_CANCEL},modal=await this._modalBodyRenderedPromise(modalParams);modal.getRoot().on(_modal_events.default.save,(e=>{e.preventDefault(),modal.destroy(),this.reactive.dispatch("cmDelete",[cmId])}))}async _requestCmAvailability(target){const cmIds=this._getTargetIds(target);if(0==cmIds.length)return;const data={allowstealth:this.reactive.getExporter().canUseStealth(this.reactive.state,cmIds)},modalParams={title:(0,_str.get_string)("availability","core"),body:_templates.default.render("core_courseformat/local/content/cm/availabilitymodal",data),saveButtonText:(0,_str.get_string)("apply","core"),type:_modal_factory.default.types.SAVE_CANCEL},modal=await this._modalBodyRenderedPromise(modalParams);this._setupMutationRadioButtonModal(modal,cmIds)}async _requestSectionAvailability(target){const sectionIds=this._getTargetIds(target);if(0==sectionIds.length)return;const modalParams={title:(0,_str.get_string)("availability","core"),body:_templates.default.render("core_courseformat/local/content/section/availabilitymodal",[]),saveButtonText:(0,_str.get_string)("apply","core"),type:_modal_factory.default.types.SAVE_CANCEL},modal=await this._modalBodyRenderedPromise(modalParams);this._setupMutationRadioButtonModal(modal,sectionIds)}_setupMutationRadioButtonModal(modal,ids){modal.setButtonDisabled("save",!0);const submitFunction=radio=>{const mutation=null==radio?void 0:radio.value;return!!mutation&&(this.reactive.dispatch(mutation,ids),!0)},modalBody=(0,_normalise.getFirst)(modal.getBody());modalBody.querySelectorAll(this.selectors.OPTIONSRADIO).forEach((radio=>{radio.addEventListener("change",(()=>{modal.setButtonDisabled("save",!1)})),radio.parentNode.addEventListener("click",(()=>{radio.checked=!0,modal.setButtonDisabled("save",!1)})),radio.parentNode.addEventListener("dblclick",(dbClickEvent=>{submitFunction(radio)&&(dbClickEvent.preventDefault(),modal.destroy())}))})),modal.getRoot().on(_modal_events.default.save,(()=>{const radio=modalBody.querySelector("".concat(this.selectors.OPTIONSRADIO,":checked"));submitFunction(radio)}))}_setAddSectionLocked(locked){this.getElements(this.selectors.ADDSECTION).forEach((element=>{element.classList.toggle(this.classes.DISABLED,locked),this.setElementLocked(element,locked)}))}_disableLink(element){element&&(element.style.pointerEvents="none",element.style.userSelect="none",element.classList.add(this.classes.DISABLED),element.setAttribute("aria-disabled",!0),element.addEventListener("click",(event=>event.preventDefault())))}_modalBodyRenderedPromise(modalParams){return new Promise(((resolve,reject)=>{_modal_factory.default.create(modalParams).then((modal=>{modal.setRemoveOnClose(!0),modal.getRoot().on(_modal_events.default.bodyRendered,(()=>{resolve(modal)})),void 0!==modalParams.saveButtonText&&modal.setSaveButtonText(modalParams.saveButtonText),modal.show()})).catch((()=>{reject("Cannot load modal content")}))}))}_destroyModal(modal,element){modal.hide();const pendingDestroy=new _pending.default("courseformat/actions:destroyModal");element&&element.focus(),setTimeout((()=>{modal.destroy(),pendingDestroy.resolve()}),500)}_getClosestActionMenuToogler(element){const actionMenu=element.closest(this.selectors.ACTIONMENU);if(actionMenu)return actionMenu.querySelector(this.selectors.ACTIONMENUTOGGLER)}}return _exports.default=_default,_exports.default})); //# sourceMappingURL=actions.min.js.map \ No newline at end of file diff --git a/course/format/amd/build/local/content/actions.min.js.map b/course/format/amd/build/local/content/actions.min.js.map index 25a4d88046fbc..27f602c103482 100644 --- a/course/format/amd/build/local/content/actions.min.js.map +++ b/course/format/amd/build/local/content/actions.min.js.map @@ -1 +1 @@ -{"version":3,"file":"actions.min.js","sources":["../../../src/local/content/actions.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 state actions dispatcher.\n *\n * This module captures all data-dispatch links in the course content and dispatch the proper\n * state mutation, including any confirmation and modal required.\n *\n * @module core_courseformat/local/content/actions\n * @class core_courseformat/local/content/actions\n * @copyright 2021 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {BaseComponent} from 'core/reactive';\nimport ModalFactory from 'core/modal_factory';\nimport ModalEvents from 'core/modal_events';\nimport Templates from 'core/templates';\nimport {prefetchStrings} from 'core/prefetch';\nimport {get_string as getString} from 'core/str';\nimport {getList} from 'core/normalise';\nimport * as CourseEvents from 'core_course/events';\nimport Pending from 'core/pending';\nimport ContentTree from 'core_courseformat/local/courseeditor/contenttree';\n// The jQuery module is only used for interacting with Boostrap 4. It can we removed when MDL-71979 is integrated.\nimport jQuery from 'jquery';\n\n// Load global strings.\nprefetchStrings('core', ['movecoursesection', 'movecoursemodule', 'confirm', 'delete']);\n\n// Mutations are dispatched by the course content actions.\n// Formats can use this module addActions static method to add custom actions.\n// Direct mutations can be simple strings (mutation) name or functions.\nconst directMutations = {\n sectionHide: 'sectionHide',\n sectionShow: 'sectionShow',\n cmHide: 'cmHide',\n cmShow: 'cmShow',\n cmStealth: 'cmStealth',\n};\n\nexport default class extends BaseComponent {\n\n /**\n * Constructor hook.\n */\n create() {\n // Optional component name for debugging.\n this.name = 'content_actions';\n // Default query selectors.\n this.selectors = {\n ACTIONLINK: `[data-action]`,\n // Move modal selectors.\n SECTIONLINK: `[data-for='section']`,\n CMLINK: `[data-for='cm']`,\n SECTIONNODE: `[data-for='sectionnode']`,\n MODALTOGGLER: `[data-toggle='collapse']`,\n ADDSECTION: `[data-action='addSection']`,\n CONTENTTREE: `#destination-selector`,\n ACTIONMENU: `.action-menu`,\n ACTIONMENUTOGGLER: `[data-toggle=\"dropdown\"]`,\n };\n // Component css classes.\n this.classes = {\n DISABLED: `disabled`,\n };\n }\n\n /**\n * Add extra actions to the module.\n *\n * @param {array} actions array of methods to execute\n */\n static addActions(actions) {\n for (const [action, mutationReference] of Object.entries(actions)) {\n if (typeof mutationReference !== 'function' && typeof mutationReference !== 'string') {\n throw new Error(`${action} action must be a mutation name or a function`);\n }\n directMutations[action] = mutationReference;\n }\n }\n\n /**\n * Initial state ready method.\n *\n * @param {Object} state the state data.\n *\n */\n stateReady(state) {\n // Delegate dispatch clicks.\n this.addEventListener(\n this.element,\n 'click',\n this._dispatchClick\n );\n // Check section limit.\n this._checkSectionlist({state});\n // Add an Event listener to recalculate limits it if a section HTML is altered.\n this.addEventListener(\n this.element,\n CourseEvents.sectionRefreshed,\n () => this._checkSectionlist({state})\n );\n }\n\n /**\n * Return the component watchers.\n *\n * @returns {Array} of watchers\n */\n getWatchers() {\n return [\n // Check section limit.\n {watch: `course.sectionlist:updated`, handler: this._checkSectionlist},\n ];\n }\n\n _dispatchClick(event) {\n const target = event.target.closest(this.selectors.ACTIONLINK);\n if (!target) {\n return;\n }\n if (target.classList.contains(this.classes.DISABLED)) {\n event.preventDefault();\n return;\n }\n\n // Invoke proper method.\n const actionName = target.dataset.action;\n const methodName = this._actionMethodName(actionName);\n\n if (this[methodName] !== undefined) {\n this[methodName](target, event);\n return;\n }\n\n // Check direct mutations or mutations handlers.\n if (directMutations[actionName] !== undefined) {\n if (typeof directMutations[actionName] === 'function') {\n directMutations[actionName](target, event);\n return;\n }\n this._requestMutationAction(target, event, directMutations[actionName]);\n return;\n }\n }\n\n _actionMethodName(name) {\n const requestName = name.charAt(0).toUpperCase() + name.slice(1);\n return `_request${requestName}`;\n }\n\n /**\n * Check the section list and disable some options if needed.\n *\n * @param {Object} detail the update details.\n * @param {Object} detail.state the state object.\n */\n _checkSectionlist({state}) {\n // Disable \"add section\" actions if the course max sections has been exceeded.\n this._setAddSectionLocked(state.course.sectionlist.length > state.course.maxsections);\n }\n\n /**\n * Handle a move section request.\n *\n * @param {Element} target the dispatch action element\n * @param {Event} event the triggered event\n */\n async _requestMoveSection(target, event) {\n // Check we have an id.\n const sectionId = target.dataset.id;\n if (!sectionId) {\n return;\n }\n const sectionInfo = this.reactive.get('section', sectionId);\n\n event.preventDefault();\n\n // The section edit menu to refocus on end.\n const editTools = this._getClosestActionMenuToogler(target);\n\n // Collect section information from the state.\n const exporter = this.reactive.getExporter();\n const data = exporter.course(this.reactive.state);\n\n // Add the target section id and title.\n data.sectionid = sectionInfo.id;\n data.sectiontitle = sectionInfo.title;\n\n // Build the modal parameters from the event data.\n const modalParams = {\n title: getString('movecoursesection', 'core'),\n body: Templates.render('core_courseformat/local/content/movesection', data),\n };\n\n // Create the modal.\n const modal = await this._modalBodyRenderedPromise(modalParams);\n\n const modalBody = getList(modal.getBody())[0];\n\n // Disable current element and section zero.\n const currentElement = modalBody.querySelector(`${this.selectors.SECTIONLINK}[data-id='${sectionId}']`);\n this._disableLink(currentElement);\n const generalSection = modalBody.querySelector(`${this.selectors.SECTIONLINK}[data-number='0']`);\n this._disableLink(generalSection);\n\n // Setup keyboard navigation.\n new ContentTree(\n modalBody.querySelector(this.selectors.CONTENTTREE),\n {\n SECTION: this.selectors.SECTIONNODE,\n TOGGLER: this.selectors.MODALTOGGLER,\n COLLAPSE: this.selectors.MODALTOGGLER,\n },\n true\n );\n\n // Capture click.\n modalBody.addEventListener('click', (event) => {\n const target = event.target;\n if (!target.matches('a') || target.dataset.for != 'section' || target.dataset.id === undefined) {\n return;\n }\n if (target.getAttribute('aria-disabled')) {\n return;\n }\n event.preventDefault();\n this.reactive.dispatch('sectionMove', [sectionId], target.dataset.id);\n this._destroyModal(modal, editTools);\n });\n }\n\n /**\n * Handle a move cm request.\n *\n * @param {Element} target the dispatch action element\n * @param {Event} event the triggered event\n */\n async _requestMoveCm(target, event) {\n // Check we have an id.\n const cmId = target.dataset.id;\n if (!cmId) {\n return;\n }\n const cmInfo = this.reactive.get('cm', cmId);\n\n event.preventDefault();\n\n // The section edit menu to refocus on end.\n const editTools = this._getClosestActionMenuToogler(target);\n\n // Collect section information from the state.\n const exporter = this.reactive.getExporter();\n const data = exporter.course(this.reactive.state);\n\n // Add the target cm info.\n data.cmid = cmInfo.id;\n data.cmname = cmInfo.name;\n\n // Build the modal parameters from the event data.\n const modalParams = {\n title: getString('movecoursemodule', 'core'),\n body: Templates.render('core_courseformat/local/content/movecm', data),\n };\n\n // Create the modal.\n const modal = await this._modalBodyRenderedPromise(modalParams);\n\n const modalBody = getList(modal.getBody())[0];\n\n // Disable current element.\n let currentElement = modalBody.querySelector(`${this.selectors.CMLINK}[data-id='${cmId}']`);\n this._disableLink(currentElement);\n\n // Setup keyboard navigation.\n new ContentTree(\n modalBody.querySelector(this.selectors.CONTENTTREE),\n {\n SECTION: this.selectors.SECTIONNODE,\n TOGGLER: this.selectors.MODALTOGGLER,\n COLLAPSE: this.selectors.MODALTOGGLER,\n ENTER: this.selectors.SECTIONLINK,\n }\n );\n\n // Open the cm section node if possible (Bootstrap 4 uses jQuery to interact with collapsibles).\n // All jQuery int this code can be replaced when MDL-71979 is integrated.\n const sectionnode = currentElement.closest(this.selectors.SECTIONNODE);\n const toggler = jQuery(sectionnode).find(this.selectors.MODALTOGGLER);\n let collapsibleId = toggler.data('target') ?? toggler.attr('href');\n if (collapsibleId) {\n // We cannot be sure we have # in the id element name.\n collapsibleId = collapsibleId.replace('#', '');\n jQuery(`#${collapsibleId}`).collapse('toggle');\n }\n\n // Capture click.\n modalBody.addEventListener('click', (event) => {\n const target = event.target;\n if (!target.matches('a') || target.dataset.for === undefined || target.dataset.id === undefined) {\n return;\n }\n if (target.getAttribute('aria-disabled')) {\n return;\n }\n event.preventDefault();\n\n // Get draggable data from cm or section to dispatch.\n let targetSectionId;\n let targetCmId;\n if (target.dataset.for == 'cm') {\n const dropData = exporter.cmDraggableData(this.reactive.state, target.dataset.id);\n targetSectionId = dropData.sectionid;\n targetCmId = dropData.nextcmid;\n } else {\n const section = this.reactive.get('section', target.dataset.id);\n targetSectionId = target.dataset.id;\n targetCmId = section?.cmlist[0];\n }\n\n this.reactive.dispatch('cmMove', [cmId], targetSectionId, targetCmId);\n this._destroyModal(modal, editTools);\n });\n }\n\n /**\n * Handle a create section request.\n *\n * @param {Element} target the dispatch action element\n * @param {Event} event the triggered event\n */\n async _requestAddSection(target, event) {\n event.preventDefault();\n this.reactive.dispatch('addSection', target.dataset.id ?? 0);\n }\n\n /**\n * Handle a delete section request.\n *\n * @param {Element} target the dispatch action element\n * @param {Event} event the triggered event\n */\n async _requestDeleteSection(target, event) {\n // Check we have an id.\n const sectionId = target.dataset.id;\n\n if (!sectionId) {\n return;\n }\n const sectionInfo = this.reactive.get('section', sectionId);\n\n event.preventDefault();\n\n const cmList = sectionInfo.cmlist ?? [];\n if (cmList.length || sectionInfo.hassummary || sectionInfo.rawtitle) {\n // We need confirmation if the section has something.\n const modalParams = {\n title: getString('confirm', 'core'),\n body: getString('confirmdeletesection', 'moodle', sectionInfo.title),\n saveButtonText: getString('delete', 'core'),\n type: ModalFactory.types.SAVE_CANCEL,\n };\n\n const modal = await this._modalBodyRenderedPromise(modalParams);\n\n modal.getRoot().on(\n ModalEvents.save,\n e => {\n // Stop the default save button behaviour which is to close the modal.\n e.preventDefault();\n modal.destroy();\n this.reactive.dispatch('sectionDelete', [sectionId]);\n }\n );\n return;\n } else {\n // We don't need confirmation to delete empty sections.\n this.reactive.dispatch('sectionDelete', [sectionId]);\n }\n }\n\n /**\n * Handle a toggle cm selection.\n *\n * @param {Element} target the dispatch action element\n */\n async _requestToggleSelectionCm(target) {\n const cmId = target.dataset.id;\n if (!cmId) {\n return;\n }\n const value = target.checked ?? false;\n const mutation = (value) ? 'cmSelect' : 'cmUnselect';\n this.reactive.dispatch(mutation, [cmId]);\n }\n\n /**\n * Handle a toggle section selection.\n *\n * @param {Element} target the dispatch action element\n */\n async _requestToggleSelectionSection(target) {\n const sectionId = target.dataset.id;\n if (!sectionId) {\n return;\n }\n const value = target.checked ?? false;\n const mutation = (value) ? 'sectionSelect' : 'sectionUnselect';\n this.reactive.dispatch(mutation, [sectionId]);\n }\n\n /**\n * Basic mutation action helper.\n *\n * @param {Element} target the dispatch action element\n * @param {Event} event the triggered event\n * @param {string} mutationName the mutation name\n */\n async _requestMutationAction(target, event, mutationName) {\n if (!target.dataset.id) {\n return;\n }\n event.preventDefault();\n this.reactive.dispatch(mutationName, [target.dataset.id]);\n }\n\n /**\n * Handle a course module duplicate request.\n *\n * @param {Element} target the dispatch action element\n * @param {Event} event the triggered event\n */\n async _requestCmDuplicate(target, event) {\n const cmId = target.dataset.id;\n if (!cmId) {\n return;\n }\n const sectionId = target.dataset.sectionid ?? null;\n event.preventDefault();\n this.reactive.dispatch('cmDuplicate', [cmId], sectionId);\n }\n\n /**\n * Handle a delete cm request.\n *\n * @param {Element} target the dispatch action element\n * @param {Event} event the triggered event\n */\n async _requestCmDelete(target, event) {\n // Check we have an id.\n const cmId = target.dataset.id;\n\n if (!cmId) {\n return;\n }\n const cmInfo = this.reactive.get('cm', cmId);\n\n event.preventDefault();\n\n const modalParams = {\n title: getString('confirm', 'core'),\n body: getString(\n 'deletechecktypename',\n 'moodle',\n {\n type: cmInfo.modname,\n name: cmInfo.name,\n }\n ),\n saveButtonText: getString('delete', 'core'),\n type: ModalFactory.types.SAVE_CANCEL,\n };\n\n const modal = await this._modalBodyRenderedPromise(modalParams);\n\n modal.getRoot().on(\n ModalEvents.save,\n e => {\n // Stop the default save button behaviour which is to close the modal.\n e.preventDefault();\n modal.destroy();\n this.reactive.dispatch('cmDelete', [cmId]);\n }\n );\n }\n\n /**\n * Disable all add sections actions.\n *\n * @param {boolean} locked the new locked value.\n */\n _setAddSectionLocked(locked) {\n const targets = this.getElements(this.selectors.ADDSECTION);\n targets.forEach(element => {\n element.classList.toggle(this.classes.DISABLED, locked);\n this.setElementLocked(element, locked);\n });\n }\n\n /**\n * Replace an element with a copy with a different tag name.\n *\n * @param {Element} element the original element\n */\n _disableLink(element) {\n if (element) {\n element.style.pointerEvents = 'none';\n element.style.userSelect = 'none';\n element.classList.add(this.classes.DISABLED);\n element.setAttribute('aria-disabled', true);\n element.addEventListener('click', event => event.preventDefault());\n }\n }\n\n /**\n * Render a modal and return a body ready promise.\n *\n * @param {object} modalParams the modal params\n * @return {Promise} the modal body ready promise\n */\n _modalBodyRenderedPromise(modalParams) {\n return new Promise((resolve, reject) => {\n ModalFactory.create(modalParams).then((modal) => {\n modal.setRemoveOnClose(true);\n // Handle body loading event.\n modal.getRoot().on(ModalEvents.bodyRendered, () => {\n resolve(modal);\n });\n // Configure some extra modal params.\n if (modalParams.saveButtonText !== undefined) {\n modal.setSaveButtonText(modalParams.saveButtonText);\n }\n modal.show();\n return;\n }).catch(() => {\n reject(`Cannot load modal content`);\n });\n });\n }\n\n /**\n * Hide and later destroy a modal.\n *\n * Behat will fail if we remove the modal while some boostrap collapse is executing.\n *\n * @param {Modal} modal\n * @param {HTMLElement} element the dom element to focus on.\n */\n _destroyModal(modal, element) {\n modal.hide();\n const pendingDestroy = new Pending(`courseformat/actions:destroyModal`);\n if (element) {\n element.focus();\n }\n setTimeout(() =>{\n modal.destroy();\n pendingDestroy.resolve();\n }, 500);\n }\n\n /**\n * Get the closest actions menu toggler to an action element.\n *\n * @param {HTMLElement} element the action link element\n * @returns {HTMLElement|undefined}\n */\n _getClosestActionMenuToogler(element) {\n const actionMenu = element.closest(this.selectors.ACTIONMENU);\n if (!actionMenu) {\n return undefined;\n }\n return actionMenu.querySelector(this.selectors.ACTIONMENUTOGGLER);\n }\n}\n"],"names":["directMutations","sectionHide","sectionShow","cmHide","cmShow","cmStealth","BaseComponent","create","name","selectors","ACTIONLINK","SECTIONLINK","CMLINK","SECTIONNODE","MODALTOGGLER","ADDSECTION","CONTENTTREE","ACTIONMENU","ACTIONMENUTOGGLER","classes","DISABLED","actions","action","mutationReference","Object","entries","Error","stateReady","state","addEventListener","this","element","_dispatchClick","_checkSectionlist","CourseEvents","sectionRefreshed","getWatchers","watch","handler","event","target","closest","classList","contains","preventDefault","actionName","dataset","methodName","_actionMethodName","undefined","_requestMutationAction","requestName","charAt","toUpperCase","slice","_setAddSectionLocked","course","sectionlist","length","maxsections","sectionId","id","sectionInfo","reactive","get","editTools","_getClosestActionMenuToogler","data","getExporter","sectionid","sectiontitle","title","modalParams","body","Templates","render","modal","_modalBodyRenderedPromise","modalBody","getBody","currentElement","querySelector","_disableLink","generalSection","ContentTree","SECTION","TOGGLER","COLLAPSE","matches","for","getAttribute","dispatch","_destroyModal","cmId","cmInfo","exporter","cmid","cmname","ENTER","sectionnode","toggler","find","collapsibleId","attr","replace","collapse","targetSectionId","targetCmId","dropData","cmDraggableData","nextcmid","section","cmlist","hassummary","rawtitle","saveButtonText","type","ModalFactory","types","SAVE_CANCEL","getRoot","on","ModalEvents","save","e","destroy","mutation","checked","mutationName","modname","locked","getElements","forEach","toggle","setElementLocked","style","pointerEvents","userSelect","add","setAttribute","Promise","resolve","reject","then","setRemoveOnClose","bodyRendered","setSaveButtonText","show","catch","hide","pendingDestroy","Pending","focus","setTimeout","actionMenu"],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"actions.min.js","sources":["../../../src/local/content/actions.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 state actions dispatcher.\n *\n * This module captures all data-dispatch links in the course content and dispatch the proper\n * state mutation, including any confirmation and modal required.\n *\n * @module core_courseformat/local/content/actions\n * @class core_courseformat/local/content/actions\n * @copyright 2021 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {BaseComponent} from 'core/reactive';\nimport ModalFactory from 'core/modal_factory';\nimport ModalEvents from 'core/modal_events';\nimport Templates from 'core/templates';\nimport {prefetchStrings} from 'core/prefetch';\nimport {get_string as getString} from 'core/str';\nimport {getList, getFirst} from 'core/normalise';\nimport * as CourseEvents from 'core_course/events';\nimport Pending from 'core/pending';\nimport ContentTree from 'core_courseformat/local/courseeditor/contenttree';\n// The jQuery module is only used for interacting with Boostrap 4. It can we removed when MDL-71979 is integrated.\nimport jQuery from 'jquery';\n\n// Load global strings.\nprefetchStrings('core', ['movecoursesection', 'movecoursemodule', 'confirm', 'delete']);\n\n// Mutations are dispatched by the course content actions.\n// Formats can use this module addActions static method to add custom actions.\n// Direct mutations can be simple strings (mutation) name or functions.\nconst directMutations = {\n sectionHide: 'sectionHide',\n sectionShow: 'sectionShow',\n cmHide: 'cmHide',\n cmShow: 'cmShow',\n cmStealth: 'cmStealth',\n};\n\nexport default class extends BaseComponent {\n\n /**\n * Constructor hook.\n */\n create() {\n // Optional component name for debugging.\n this.name = 'content_actions';\n // Default query selectors.\n this.selectors = {\n ACTIONLINK: `[data-action]`,\n // Move modal selectors.\n SECTIONLINK: `[data-for='section']`,\n CMLINK: `[data-for='cm']`,\n SECTIONNODE: `[data-for='sectionnode']`,\n MODALTOGGLER: `[data-toggle='collapse']`,\n ADDSECTION: `[data-action='addSection']`,\n CONTENTTREE: `#destination-selector`,\n ACTIONMENU: `.action-menu`,\n ACTIONMENUTOGGLER: `[data-toggle=\"dropdown\"]`,\n // Availability modal selectors.\n OPTIONSRADIO: `[type='radio']`,\n };\n // Component css classes.\n this.classes = {\n DISABLED: `disabled`,\n };\n }\n\n /**\n * Add extra actions to the module.\n *\n * @param {array} actions array of methods to execute\n */\n static addActions(actions) {\n for (const [action, mutationReference] of Object.entries(actions)) {\n if (typeof mutationReference !== 'function' && typeof mutationReference !== 'string') {\n throw new Error(`${action} action must be a mutation name or a function`);\n }\n directMutations[action] = mutationReference;\n }\n }\n\n /**\n * Initial state ready method.\n *\n * @param {Object} state the state data.\n *\n */\n stateReady(state) {\n // Delegate dispatch clicks.\n this.addEventListener(\n this.element,\n 'click',\n this._dispatchClick\n );\n // Check section limit.\n this._checkSectionlist({state});\n // Add an Event listener to recalculate limits it if a section HTML is altered.\n this.addEventListener(\n this.element,\n CourseEvents.sectionRefreshed,\n () => this._checkSectionlist({state})\n );\n }\n\n /**\n * Return the component watchers.\n *\n * @returns {Array} of watchers\n */\n getWatchers() {\n return [\n // Check section limit.\n {watch: `course.sectionlist:updated`, handler: this._checkSectionlist},\n ];\n }\n\n _dispatchClick(event) {\n const target = event.target.closest(this.selectors.ACTIONLINK);\n if (!target) {\n return;\n }\n if (target.classList.contains(this.classes.DISABLED)) {\n event.preventDefault();\n return;\n }\n\n // Invoke proper method.\n const actionName = target.dataset.action;\n const methodName = this._actionMethodName(actionName);\n\n if (this[methodName] !== undefined) {\n this[methodName](target, event);\n return;\n }\n\n // Check direct mutations or mutations handlers.\n if (directMutations[actionName] !== undefined) {\n if (typeof directMutations[actionName] === 'function') {\n directMutations[actionName](target, event);\n return;\n }\n this._requestMutationAction(target, event, directMutations[actionName]);\n return;\n }\n }\n\n _actionMethodName(name) {\n const requestName = name.charAt(0).toUpperCase() + name.slice(1);\n return `_request${requestName}`;\n }\n\n /**\n * Check the section list and disable some options if needed.\n *\n * @param {Object} detail the update details.\n * @param {Object} detail.state the state object.\n */\n _checkSectionlist({state}) {\n // Disable \"add section\" actions if the course max sections has been exceeded.\n this._setAddSectionLocked(state.course.sectionlist.length > state.course.maxsections);\n }\n\n /**\n * Return the ids represented by this element.\n *\n * Depending on the dataset attributes the action could represent a single id\n * or a bulk actions with all the current selected ids.\n *\n * @param {HTMLElement} target\n * @returns {Number[]} array of Ids\n */\n _getTargetIds(target) {\n let ids = [];\n if (target?.dataset?.id) {\n ids.push(target.dataset.id);\n }\n const bulkType = target?.dataset?.bulk;\n if (!bulkType) {\n return ids;\n }\n const bulk = this.reactive.get('bulk');\n if (bulk.enabled && bulk.selectedType === bulkType) {\n ids = [...ids, ...bulk.selection];\n }\n return ids;\n }\n\n /**\n * Handle a move section request.\n *\n * @param {Element} target the dispatch action element\n * @param {Event} event the triggered event\n */\n async _requestMoveSection(target, event) {\n // Check we have an id.\n const sectionId = target.dataset.id;\n if (!sectionId) {\n return;\n }\n const sectionInfo = this.reactive.get('section', sectionId);\n\n event.preventDefault();\n\n // The section edit menu to refocus on end.\n const editTools = this._getClosestActionMenuToogler(target);\n\n // Collect section information from the state.\n const exporter = this.reactive.getExporter();\n const data = exporter.course(this.reactive.state);\n\n // Add the target section id and title.\n data.sectionid = sectionInfo.id;\n data.sectiontitle = sectionInfo.title;\n\n // Build the modal parameters from the event data.\n const modalParams = {\n title: getString('movecoursesection', 'core'),\n body: Templates.render('core_courseformat/local/content/movesection', data),\n };\n\n // Create the modal.\n const modal = await this._modalBodyRenderedPromise(modalParams);\n\n const modalBody = getList(modal.getBody())[0];\n\n // Disable current element and section zero.\n const currentElement = modalBody.querySelector(`${this.selectors.SECTIONLINK}[data-id='${sectionId}']`);\n this._disableLink(currentElement);\n const generalSection = modalBody.querySelector(`${this.selectors.SECTIONLINK}[data-number='0']`);\n this._disableLink(generalSection);\n\n // Setup keyboard navigation.\n new ContentTree(\n modalBody.querySelector(this.selectors.CONTENTTREE),\n {\n SECTION: this.selectors.SECTIONNODE,\n TOGGLER: this.selectors.MODALTOGGLER,\n COLLAPSE: this.selectors.MODALTOGGLER,\n },\n true\n );\n\n // Capture click.\n modalBody.addEventListener('click', (event) => {\n const target = event.target;\n if (!target.matches('a') || target.dataset.for != 'section' || target.dataset.id === undefined) {\n return;\n }\n if (target.getAttribute('aria-disabled')) {\n return;\n }\n event.preventDefault();\n this.reactive.dispatch('sectionMove', [sectionId], target.dataset.id);\n this._destroyModal(modal, editTools);\n });\n }\n\n /**\n * Handle a move cm request.\n *\n * @param {Element} target the dispatch action element\n * @param {Event} event the triggered event\n */\n async _requestMoveCm(target, event) {\n // Check we have an id.\n const cmId = target.dataset.id;\n if (!cmId) {\n return;\n }\n const cmInfo = this.reactive.get('cm', cmId);\n\n event.preventDefault();\n\n // The section edit menu to refocus on end.\n const editTools = this._getClosestActionMenuToogler(target);\n\n // Collect section information from the state.\n const exporter = this.reactive.getExporter();\n const data = exporter.course(this.reactive.state);\n\n // Add the target cm info.\n data.cmid = cmInfo.id;\n data.cmname = cmInfo.name;\n\n // Build the modal parameters from the event data.\n const modalParams = {\n title: getString('movecoursemodule', 'core'),\n body: Templates.render('core_courseformat/local/content/movecm', data),\n };\n\n // Create the modal.\n const modal = await this._modalBodyRenderedPromise(modalParams);\n\n const modalBody = getList(modal.getBody())[0];\n\n // Disable current element.\n let currentElement = modalBody.querySelector(`${this.selectors.CMLINK}[data-id='${cmId}']`);\n this._disableLink(currentElement);\n\n // Setup keyboard navigation.\n new ContentTree(\n modalBody.querySelector(this.selectors.CONTENTTREE),\n {\n SECTION: this.selectors.SECTIONNODE,\n TOGGLER: this.selectors.MODALTOGGLER,\n COLLAPSE: this.selectors.MODALTOGGLER,\n ENTER: this.selectors.SECTIONLINK,\n }\n );\n\n // Open the cm section node if possible (Bootstrap 4 uses jQuery to interact with collapsibles).\n // All jQuery int this code can be replaced when MDL-71979 is integrated.\n const sectionnode = currentElement.closest(this.selectors.SECTIONNODE);\n const toggler = jQuery(sectionnode).find(this.selectors.MODALTOGGLER);\n let collapsibleId = toggler.data('target') ?? toggler.attr('href');\n if (collapsibleId) {\n // We cannot be sure we have # in the id element name.\n collapsibleId = collapsibleId.replace('#', '');\n jQuery(`#${collapsibleId}`).collapse('toggle');\n }\n\n // Capture click.\n modalBody.addEventListener('click', (event) => {\n const target = event.target;\n if (!target.matches('a') || target.dataset.for === undefined || target.dataset.id === undefined) {\n return;\n }\n if (target.getAttribute('aria-disabled')) {\n return;\n }\n event.preventDefault();\n\n // Get draggable data from cm or section to dispatch.\n let targetSectionId;\n let targetCmId;\n if (target.dataset.for == 'cm') {\n const dropData = exporter.cmDraggableData(this.reactive.state, target.dataset.id);\n targetSectionId = dropData.sectionid;\n targetCmId = dropData.nextcmid;\n } else {\n const section = this.reactive.get('section', target.dataset.id);\n targetSectionId = target.dataset.id;\n targetCmId = section?.cmlist[0];\n }\n\n this.reactive.dispatch('cmMove', [cmId], targetSectionId, targetCmId);\n this._destroyModal(modal, editTools);\n });\n }\n\n /**\n * Handle a create section request.\n *\n * @param {Element} target the dispatch action element\n * @param {Event} event the triggered event\n */\n async _requestAddSection(target, event) {\n event.preventDefault();\n this.reactive.dispatch('addSection', target.dataset.id ?? 0);\n }\n\n /**\n * Handle a delete section request.\n *\n * @param {Element} target the dispatch action element\n * @param {Event} event the triggered event\n */\n async _requestDeleteSection(target, event) {\n // Check we have an id.\n const sectionId = target.dataset.id;\n\n if (!sectionId) {\n return;\n }\n const sectionInfo = this.reactive.get('section', sectionId);\n\n event.preventDefault();\n\n const cmList = sectionInfo.cmlist ?? [];\n if (cmList.length || sectionInfo.hassummary || sectionInfo.rawtitle) {\n // We need confirmation if the section has something.\n const modalParams = {\n title: getString('confirm', 'core'),\n body: getString('confirmdeletesection', 'moodle', sectionInfo.title),\n saveButtonText: getString('delete', 'core'),\n type: ModalFactory.types.SAVE_CANCEL,\n };\n\n const modal = await this._modalBodyRenderedPromise(modalParams);\n\n modal.getRoot().on(\n ModalEvents.save,\n e => {\n // Stop the default save button behaviour which is to close the modal.\n e.preventDefault();\n modal.destroy();\n this.reactive.dispatch('sectionDelete', [sectionId]);\n }\n );\n return;\n } else {\n // We don't need confirmation to delete empty sections.\n this.reactive.dispatch('sectionDelete', [sectionId]);\n }\n }\n\n /**\n * Handle a toggle cm selection.\n *\n * @param {Element} target the dispatch action element\n */\n async _requestToggleSelectionCm(target) {\n const cmId = target.dataset.id;\n if (!cmId) {\n return;\n }\n const value = target.checked ?? false;\n const mutation = (value) ? 'cmSelect' : 'cmUnselect';\n this.reactive.dispatch(mutation, [cmId]);\n }\n\n /**\n * Handle a toggle section selection.\n *\n * @param {Element} target the dispatch action element\n */\n async _requestToggleSelectionSection(target) {\n const sectionId = target.dataset.id;\n if (!sectionId) {\n return;\n }\n const value = target.checked ?? false;\n const mutation = (value) ? 'sectionSelect' : 'sectionUnselect';\n this.reactive.dispatch(mutation, [sectionId]);\n }\n\n /**\n * Basic mutation action helper.\n *\n * @param {Element} target the dispatch action element\n * @param {Event} event the triggered event\n * @param {string} mutationName the mutation name\n */\n async _requestMutationAction(target, event, mutationName) {\n if (!target.dataset.id) {\n return;\n }\n event.preventDefault();\n this.reactive.dispatch(mutationName, [target.dataset.id]);\n }\n\n /**\n * Handle a course module duplicate request.\n *\n * @param {Element} target the dispatch action element\n * @param {Event} event the triggered event\n */\n async _requestCmDuplicate(target, event) {\n const cmId = target.dataset.id;\n if (!cmId) {\n return;\n }\n const sectionId = target.dataset.sectionid ?? null;\n event.preventDefault();\n this.reactive.dispatch('cmDuplicate', [cmId], sectionId);\n }\n\n /**\n * Handle a delete cm request.\n *\n * @param {Element} target the dispatch action element\n * @param {Event} event the triggered event\n */\n async _requestCmDelete(target, event) {\n // Check we have an id.\n const cmId = target.dataset.id;\n\n if (!cmId) {\n return;\n }\n const cmInfo = this.reactive.get('cm', cmId);\n\n event.preventDefault();\n\n const modalParams = {\n title: getString('confirm', 'core'),\n body: getString(\n 'deletechecktypename',\n 'moodle',\n {\n type: cmInfo.modname,\n name: cmInfo.name,\n }\n ),\n saveButtonText: getString('delete', 'core'),\n type: ModalFactory.types.SAVE_CANCEL,\n };\n\n const modal = await this._modalBodyRenderedPromise(modalParams);\n\n modal.getRoot().on(\n ModalEvents.save,\n e => {\n // Stop the default save button behaviour which is to close the modal.\n e.preventDefault();\n modal.destroy();\n this.reactive.dispatch('cmDelete', [cmId]);\n }\n );\n }\n\n /**\n * Handle a cm availability change request.\n *\n * @param {Element} target the dispatch action element\n */\n async _requestCmAvailability(target) {\n const cmIds = this._getTargetIds(target);\n if (cmIds.length == 0) {\n return;\n }\n // Show the availability modal to decide which action to trigger.\n const exporter = this.reactive.getExporter();\n const data = {\n allowstealth: exporter.canUseStealth(this.reactive.state, cmIds),\n };\n const modalParams = {\n title: getString('availability', 'core'),\n body: Templates.render('core_courseformat/local/content/cm/availabilitymodal', data),\n saveButtonText: getString('apply', 'core'),\n type: ModalFactory.types.SAVE_CANCEL,\n };\n const modal = await this._modalBodyRenderedPromise(modalParams);\n\n this._setupMutationRadioButtonModal(modal, cmIds);\n }\n\n /**\n * Handle a section availability change request.\n *\n * @param {Element} target the dispatch action element\n */\n async _requestSectionAvailability(target) {\n const sectionIds = this._getTargetIds(target);\n if (sectionIds.length == 0) {\n return;\n }\n // Show the availability modal to decide which action to trigger.\n const modalParams = {\n title: getString('availability', 'core'),\n body: Templates.render('core_courseformat/local/content/section/availabilitymodal', []),\n saveButtonText: getString('apply', 'core'),\n type: ModalFactory.types.SAVE_CANCEL,\n };\n const modal = await this._modalBodyRenderedPromise(modalParams);\n\n this._setupMutationRadioButtonModal(modal, sectionIds);\n }\n\n /**\n * Add events to a mutation selector radio buttons modal.\n * @param {Modal} modal\n * @param {Number[]} ids the section or cm ids to apply the mutation\n */\n _setupMutationRadioButtonModal(modal, ids) {\n // The save button is not enabled until the user selects an option.\n modal.setButtonDisabled('save', true);\n\n const submitFunction = (radio) => {\n const mutation = radio?.value;\n if (!mutation) {\n return false;\n }\n this.reactive.dispatch(mutation, ids);\n return true;\n };\n\n const modalBody = getFirst(modal.getBody());\n const radioOptions = modalBody.querySelectorAll(this.selectors.OPTIONSRADIO);\n radioOptions.forEach(radio => {\n radio.addEventListener('change', () => {\n modal.setButtonDisabled('save', false);\n });\n radio.parentNode.addEventListener('click', () => {\n radio.checked = true;\n modal.setButtonDisabled('save', false);\n });\n radio.parentNode.addEventListener('dblclick', dbClickEvent => {\n if (submitFunction(radio)) {\n dbClickEvent.preventDefault();\n modal.destroy();\n }\n });\n });\n\n modal.getRoot().on(\n ModalEvents.save,\n () => {\n const radio = modalBody.querySelector(`${this.selectors.OPTIONSRADIO}:checked`);\n submitFunction(radio);\n }\n );\n }\n\n /**\n * Disable all add sections actions.\n *\n * @param {boolean} locked the new locked value.\n */\n _setAddSectionLocked(locked) {\n const targets = this.getElements(this.selectors.ADDSECTION);\n targets.forEach(element => {\n element.classList.toggle(this.classes.DISABLED, locked);\n this.setElementLocked(element, locked);\n });\n }\n\n /**\n * Replace an element with a copy with a different tag name.\n *\n * @param {Element} element the original element\n */\n _disableLink(element) {\n if (element) {\n element.style.pointerEvents = 'none';\n element.style.userSelect = 'none';\n element.classList.add(this.classes.DISABLED);\n element.setAttribute('aria-disabled', true);\n element.addEventListener('click', event => event.preventDefault());\n }\n }\n\n /**\n * Render a modal and return a body ready promise.\n *\n * @param {object} modalParams the modal params\n * @return {Promise} the modal body ready promise\n */\n _modalBodyRenderedPromise(modalParams) {\n return new Promise((resolve, reject) => {\n ModalFactory.create(modalParams).then((modal) => {\n modal.setRemoveOnClose(true);\n // Handle body loading event.\n modal.getRoot().on(ModalEvents.bodyRendered, () => {\n resolve(modal);\n });\n // Configure some extra modal params.\n if (modalParams.saveButtonText !== undefined) {\n modal.setSaveButtonText(modalParams.saveButtonText);\n }\n modal.show();\n return;\n }).catch(() => {\n reject(`Cannot load modal content`);\n });\n });\n }\n\n /**\n * Hide and later destroy a modal.\n *\n * Behat will fail if we remove the modal while some boostrap collapse is executing.\n *\n * @param {Modal} modal\n * @param {HTMLElement} element the dom element to focus on.\n */\n _destroyModal(modal, element) {\n modal.hide();\n const pendingDestroy = new Pending(`courseformat/actions:destroyModal`);\n if (element) {\n element.focus();\n }\n setTimeout(() =>{\n modal.destroy();\n pendingDestroy.resolve();\n }, 500);\n }\n\n /**\n * Get the closest actions menu toggler to an action element.\n *\n * @param {HTMLElement} element the action link element\n * @returns {HTMLElement|undefined}\n */\n _getClosestActionMenuToogler(element) {\n const actionMenu = element.closest(this.selectors.ACTIONMENU);\n if (!actionMenu) {\n return undefined;\n }\n return actionMenu.querySelector(this.selectors.ACTIONMENUTOGGLER);\n }\n}\n"],"names":["directMutations","sectionHide","sectionShow","cmHide","cmShow","cmStealth","BaseComponent","create","name","selectors","ACTIONLINK","SECTIONLINK","CMLINK","SECTIONNODE","MODALTOGGLER","ADDSECTION","CONTENTTREE","ACTIONMENU","ACTIONMENUTOGGLER","OPTIONSRADIO","classes","DISABLED","actions","action","mutationReference","Object","entries","Error","stateReady","state","addEventListener","this","element","_dispatchClick","_checkSectionlist","CourseEvents","sectionRefreshed","getWatchers","watch","handler","event","target","closest","classList","contains","preventDefault","actionName","dataset","methodName","_actionMethodName","undefined","_requestMutationAction","requestName","charAt","toUpperCase","slice","_setAddSectionLocked","course","sectionlist","length","maxsections","_getTargetIds","ids","_target$dataset","id","push","bulkType","_target$dataset2","bulk","reactive","get","enabled","selectedType","selection","sectionId","sectionInfo","editTools","_getClosestActionMenuToogler","data","getExporter","sectionid","sectiontitle","title","modalParams","body","Templates","render","modal","_modalBodyRenderedPromise","modalBody","getBody","currentElement","querySelector","_disableLink","generalSection","ContentTree","SECTION","TOGGLER","COLLAPSE","matches","for","getAttribute","dispatch","_destroyModal","cmId","cmInfo","exporter","cmid","cmname","ENTER","sectionnode","toggler","find","collapsibleId","attr","replace","collapse","targetSectionId","targetCmId","dropData","cmDraggableData","nextcmid","section","cmlist","hassummary","rawtitle","saveButtonText","type","ModalFactory","types","SAVE_CANCEL","getRoot","on","ModalEvents","save","e","destroy","mutation","checked","mutationName","modname","cmIds","allowstealth","canUseStealth","_setupMutationRadioButtonModal","sectionIds","setButtonDisabled","submitFunction","radio","value","querySelectorAll","forEach","parentNode","dbClickEvent","locked","getElements","toggle","setElementLocked","style","pointerEvents","userSelect","add","setAttribute","Promise","resolve","reject","then","setRemoveOnClose","bodyRendered","setSaveButtonText","show","catch","hide","pendingDestroy","Pending","focus","setTimeout","actionMenu"],"mappings":"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"} \ No newline at end of file diff --git a/course/format/amd/build/local/courseeditor/exporter.min.js b/course/format/amd/build/local/courseeditor/exporter.min.js index f6aa2488e5b99..ba422519a9ad3 100644 --- a/course/format/amd/build/local/courseeditor/exporter.min.js +++ b/course/format/amd/build/local/courseeditor/exporter.min.js @@ -8,6 +8,6 @@ define("core_courseformat/local/courseeditor/exporter",["exports"],(function(_ex * @copyright 2021 Ferran Recio * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -class{constructor(reactive){this.reactive=reactive,this.COMPLETIONS=["incomplete","complete","complete","fail"]}course(state){var _state$course$highlig,_state$course$section;const data={sections:[],editmode:this.reactive.isEditing,highlighted:null!==(_state$course$highlig=state.course.highlighted)&&void 0!==_state$course$highlig?_state$course$highlig:""};return(null!==(_state$course$section=state.course.sectionlist)&&void 0!==_state$course$section?_state$course$section:[]).forEach((sectionid=>{var _state$section$get;const sectioninfo=null!==(_state$section$get=state.section.get(sectionid))&&void 0!==_state$section$get?_state$section$get:{},section=this.section(state,sectioninfo);data.sections.push(section)})),data.hassections=0!=data.sections.length,data}section(state,sectioninfo){var _state$course$highlig2,_sectioninfo$cmlist;const section={...sectioninfo,highlighted:null!==(_state$course$highlig2=state.course.highlighted)&&void 0!==_state$course$highlig2?_state$course$highlig2:"",cms:[]};return(null!==(_sectioninfo$cmlist=sectioninfo.cmlist)&&void 0!==_sectioninfo$cmlist?_sectioninfo$cmlist:[]).forEach((cmid=>{const cminfo=state.cm.get(cmid),cm=this.cm(state,cminfo);section.cms.push(cm)})),section.hascms=0!=section.cms.length,section}cm(state,cminfo){return{...cminfo,isactive:!1}}cmDraggableData(state,cmid){const cminfo=state.cm.get(cmid);if(!cminfo)return null;let nextcmid;const section=state.section.get(cminfo.sectionid),currentindex=null==section?void 0:section.cmlist.indexOf(cminfo.id);return void 0!==currentindex&&(nextcmid=null==section?void 0:section.cmlist[currentindex+1]),{type:"cm",id:cminfo.id,name:cminfo.name,sectionid:cminfo.sectionid,nextcmid:nextcmid}}sectionDraggableData(state,sectionid){const sectioninfo=state.section.get(sectionid);return sectioninfo?{type:"section",id:sectioninfo.id,name:sectioninfo.name,number:sectioninfo.number}:null}fileDraggableData(state,dataTransfer){var _dataTransfer$files;const files=[];return(null===(_dataTransfer$files=dataTransfer.files)||void 0===_dataTransfer$files?void 0:_dataTransfer$files.length)>0&&dataTransfer.files.forEach((file=>{files.push(file)})),{type:"files",files:files}}cmCompletion(state,cminfo){const data={statename:"",state:"NaN"};if(void 0!==cminfo.completionstate){var _this$COMPLETIONS$cmi;data.state=cminfo.completionstate,data.hasstate=!0;const statename=null!==(_this$COMPLETIONS$cmi=this.COMPLETIONS[cminfo.completionstate])&&void 0!==_this$COMPLETIONS$cmi?_this$COMPLETIONS$cmi:"NaN";data["is".concat(statename)]=!0}return data}allItemsArray(state){var _state$course$section2;const items=[];return(null!==(_state$course$section2=state.course.sectionlist)&&void 0!==_state$course$section2?_state$course$section2:[]).forEach((sectionid=>{var _sectioninfo$cmlist2;const sectioninfo=state.section.get(sectionid);items.push({type:"section",id:sectioninfo.id,url:sectioninfo.sectionurl});(null!==(_sectioninfo$cmlist2=sectioninfo.cmlist)&&void 0!==_sectioninfo$cmlist2?_sectioninfo$cmlist2:[]).forEach((cmid=>{const cminfo=state.cm.get(cmid);items.push({type:"cm",id:cminfo.id,url:cminfo.url})}))})),items}},_exports.default})); +class{constructor(reactive){this.reactive=reactive,this.COMPLETIONS=["incomplete","complete","complete","fail"]}course(state){var _state$course$highlig,_state$course$section;const data={sections:[],editmode:this.reactive.isEditing,highlighted:null!==(_state$course$highlig=state.course.highlighted)&&void 0!==_state$course$highlig?_state$course$highlig:""};return(null!==(_state$course$section=state.course.sectionlist)&&void 0!==_state$course$section?_state$course$section:[]).forEach((sectionid=>{var _state$section$get;const sectioninfo=null!==(_state$section$get=state.section.get(sectionid))&&void 0!==_state$section$get?_state$section$get:{},section=this.section(state,sectioninfo);data.sections.push(section)})),data.hassections=0!=data.sections.length,data}section(state,sectioninfo){var _state$course$highlig2,_sectioninfo$cmlist;const section={...sectioninfo,highlighted:null!==(_state$course$highlig2=state.course.highlighted)&&void 0!==_state$course$highlig2?_state$course$highlig2:"",cms:[]};return(null!==(_sectioninfo$cmlist=sectioninfo.cmlist)&&void 0!==_sectioninfo$cmlist?_sectioninfo$cmlist:[]).forEach((cmid=>{const cminfo=state.cm.get(cmid),cm=this.cm(state,cminfo);section.cms.push(cm)})),section.hascms=0!=section.cms.length,section}cm(state,cminfo){return{...cminfo,isactive:!1}}cmDraggableData(state,cmid){const cminfo=state.cm.get(cmid);if(!cminfo)return null;let nextcmid;const section=state.section.get(cminfo.sectionid),currentindex=null==section?void 0:section.cmlist.indexOf(cminfo.id);return void 0!==currentindex&&(nextcmid=null==section?void 0:section.cmlist[currentindex+1]),{type:"cm",id:cminfo.id,name:cminfo.name,sectionid:cminfo.sectionid,nextcmid:nextcmid}}sectionDraggableData(state,sectionid){const sectioninfo=state.section.get(sectionid);return sectioninfo?{type:"section",id:sectioninfo.id,name:sectioninfo.name,number:sectioninfo.number}:null}fileDraggableData(state,dataTransfer){var _dataTransfer$files;const files=[];return(null===(_dataTransfer$files=dataTransfer.files)||void 0===_dataTransfer$files?void 0:_dataTransfer$files.length)>0&&dataTransfer.files.forEach((file=>{files.push(file)})),{type:"files",files:files}}cmCompletion(state,cminfo){const data={statename:"",state:"NaN"};if(void 0!==cminfo.completionstate){var _this$COMPLETIONS$cmi;data.state=cminfo.completionstate,data.hasstate=!0;const statename=null!==(_this$COMPLETIONS$cmi=this.COMPLETIONS[cminfo.completionstate])&&void 0!==_this$COMPLETIONS$cmi?_this$COMPLETIONS$cmi:"NaN";data["is".concat(statename)]=!0}return data}allItemsArray(state){var _state$course$section2;const items=[];return(null!==(_state$course$section2=state.course.sectionlist)&&void 0!==_state$course$section2?_state$course$section2:[]).forEach((sectionid=>{var _sectioninfo$cmlist2;const sectioninfo=state.section.get(sectionid);items.push({type:"section",id:sectioninfo.id,url:sectioninfo.sectionurl});(null!==(_sectioninfo$cmlist2=sectioninfo.cmlist)&&void 0!==_sectioninfo$cmlist2?_sectioninfo$cmlist2:[]).forEach((cmid=>{const cminfo=state.cm.get(cmid);items.push({type:"cm",id:cminfo.id,url:cminfo.url})}))})),items}canUseStealth(state,cmIds){return cmIds.some((cmId=>{var _cminfo$allowstealth;const cminfo=state.cm.get(cmId);return null!==(_cminfo$allowstealth=null==cminfo?void 0:cminfo.allowstealth)&&void 0!==_cminfo$allowstealth&&_cminfo$allowstealth}))}},_exports.default})); //# sourceMappingURL=exporter.min.js.map \ No newline at end of file diff --git a/course/format/amd/build/local/courseeditor/exporter.min.js.map b/course/format/amd/build/local/courseeditor/exporter.min.js.map index c38cb0f2d9640..473e8a9e95d14 100644 --- a/course/format/amd/build/local/courseeditor/exporter.min.js.map +++ b/course/format/amd/build/local/courseeditor/exporter.min.js.map @@ -1 +1 @@ -{"version":3,"file":"exporter.min.js","sources":["../../../src/local/courseeditor/exporter.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Module to export parts of the state and transform them to be used in templates\n * and as draggable data.\n *\n * @module core_courseformat/local/courseeditor/exporter\n * @class core_courseformat/local/courseeditor/exporter\n * @copyright 2021 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nexport default class {\n\n /**\n * Class constructor.\n *\n * @param {CourseEditor} reactive the course editor object\n */\n constructor(reactive) {\n this.reactive = reactive;\n\n // Completions states are defined in lib/completionlib.php. There are 4 different completion\n // state values, however, the course index uses the same state for complete and complete_pass.\n // This is the reason why completed appears twice in the array.\n this.COMPLETIONS = ['incomplete', 'complete', 'complete', 'fail'];\n }\n\n /**\n * Generate the course export data from the state.\n *\n * @param {Object} state the current state.\n * @returns {Object}\n */\n course(state) {\n // Collect section information from the state.\n const data = {\n sections: [],\n editmode: this.reactive.isEditing,\n highlighted: state.course.highlighted ?? '',\n };\n const sectionlist = state.course.sectionlist ?? [];\n sectionlist.forEach(sectionid => {\n const sectioninfo = state.section.get(sectionid) ?? {};\n const section = this.section(state, sectioninfo);\n data.sections.push(section);\n });\n data.hassections = (data.sections.length != 0);\n\n return data;\n }\n\n /**\n * Generate a section export data from the state.\n *\n * @param {Object} state the current state.\n * @param {Object} sectioninfo the section state data.\n * @returns {Object}\n */\n section(state, sectioninfo) {\n const section = {\n ...sectioninfo,\n highlighted: state.course.highlighted ?? '',\n cms: [],\n };\n const cmlist = sectioninfo.cmlist ?? [];\n cmlist.forEach(cmid => {\n const cminfo = state.cm.get(cmid);\n const cm = this.cm(state, cminfo);\n section.cms.push(cm);\n });\n section.hascms = (section.cms.length != 0);\n\n return section;\n }\n\n /**\n * Generate a cm export data from the state.\n *\n * @param {Object} state the current state.\n * @param {Object} cminfo the course module state data.\n * @returns {Object}\n */\n cm(state, cminfo) {\n const cm = {\n ...cminfo,\n isactive: false,\n };\n return cm;\n }\n\n /**\n * Generate a dragable cm data structure.\n *\n * This method is used by any draggable course module element to generate drop data\n * for its reactive/dragdrop instance.\n *\n * @param {*} state the state object\n * @param {*} cmid the cours emodule id\n * @returns {Object|null}\n */\n cmDraggableData(state, cmid) {\n const cminfo = state.cm.get(cmid);\n if (!cminfo) {\n return null;\n }\n\n // Drop an activity over the next activity is the same as doing anything.\n let nextcmid;\n const section = state.section.get(cminfo.sectionid);\n const currentindex = section?.cmlist.indexOf(cminfo.id);\n if (currentindex !== undefined) {\n nextcmid = section?.cmlist[currentindex + 1];\n }\n\n return {\n type: 'cm',\n id: cminfo.id,\n name: cminfo.name,\n sectionid: cminfo.sectionid,\n nextcmid,\n };\n }\n\n /**\n * Generate a dragable cm data structure.\n *\n * This method is used by any draggable section element to generate drop data\n * for its reactive/dragdrop instance.\n *\n * @param {*} state the state object\n * @param {*} sectionid the cours section id\n * @returns {Object|null}\n */\n sectionDraggableData(state, sectionid) {\n const sectioninfo = state.section.get(sectionid);\n if (!sectioninfo) {\n return null;\n }\n return {\n type: 'section',\n id: sectioninfo.id,\n name: sectioninfo.name,\n number: sectioninfo.number,\n };\n }\n\n /**\n * Generate a file draggable structure.\n *\n * This method is used when files are dragged on the browser.\n *\n * @param {*} state the state object\n * @param {*} dataTransfer the current data tranfer data\n * @returns {Object|null}\n */\n fileDraggableData(state, dataTransfer) {\n const files = [];\n // Browsers do not provide the file list until the drop event.\n if (dataTransfer.files?.length > 0) {\n dataTransfer.files.forEach(file => {\n files.push(file);\n });\n }\n return {\n type: 'files',\n files,\n };\n }\n\n /**\n * Generate a completion export data from the cm element.\n *\n * @param {Object} state the current state.\n * @param {Object} cminfo the course module state data.\n * @returns {Object}\n */\n cmCompletion(state, cminfo) {\n const data = {\n statename: '',\n state: 'NaN',\n };\n if (cminfo.completionstate !== undefined) {\n data.state = cminfo.completionstate;\n data.hasstate = true;\n const statename = this.COMPLETIONS[cminfo.completionstate] ?? 'NaN';\n data[`is${statename}`] = true;\n }\n return data;\n }\n\n /**\n * Return a sorted list of all sections and cms items in the state.\n *\n * @param {Object} state the current state.\n * @returns {Array} all sections and cms items in the state.\n */\n allItemsArray(state) {\n const items = [];\n const sectionlist = state.course.sectionlist ?? [];\n // Add sections.\n sectionlist.forEach(sectionid => {\n const sectioninfo = state.section.get(sectionid);\n items.push({type: 'section', id: sectioninfo.id, url: sectioninfo.sectionurl});\n // Add cms.\n const cmlist = sectioninfo.cmlist ?? [];\n cmlist.forEach(cmid => {\n const cminfo = state.cm.get(cmid);\n items.push({type: 'cm', id: cminfo.id, url: cminfo.url});\n });\n });\n return items;\n }\n}\n"],"names":["constructor","reactive","COMPLETIONS","course","state","data","sections","editmode","this","isEditing","highlighted","sectionlist","forEach","sectionid","sectioninfo","section","get","push","hassections","length","cms","cmlist","cmid","cminfo","cm","hascms","isactive","cmDraggableData","nextcmid","currentindex","indexOf","id","undefined","type","name","sectionDraggableData","number","fileDraggableData","dataTransfer","files","file","cmCompletion","statename","completionstate","hasstate","allItemsArray","items","url","sectionurl"],"mappings":";;;;;;;;;;MA+BIA,YAAYC,eACHA,SAAWA,cAKXC,YAAc,CAAC,aAAc,WAAY,WAAY,QAS9DC,OAAOC,6DAEGC,KAAO,CACTC,SAAU,GACVC,SAAUC,KAAKP,SAASQ,UACxBC,0CAAaN,MAAMD,OAAOO,mEAAe,yCAEzBN,MAAMD,OAAOQ,mEAAe,IACpCC,SAAQC,yCACVC,uCAAcV,MAAMW,QAAQC,IAAIH,4DAAc,GAC9CE,QAAUP,KAAKO,QAAQX,MAAOU,aACpCT,KAAKC,SAASW,KAAKF,YAEvBV,KAAKa,YAAuC,GAAxBb,KAAKC,SAASa,OAE3Bd,KAUXU,QAAQX,MAAOU,kEACLC,QAAU,IACTD,YACHJ,2CAAaN,MAAMD,OAAOO,qEAAe,GACzCU,IAAK,uCAEMN,YAAYO,0DAAU,IAC9BT,SAAQU,aACLC,OAASnB,MAAMoB,GAAGR,IAAIM,MACtBE,GAAKhB,KAAKgB,GAAGpB,MAAOmB,QAC1BR,QAAQK,IAAIH,KAAKO,OAErBT,QAAQU,OAAgC,GAAtBV,QAAQK,IAAID,OAEvBJ,QAUXS,GAAGpB,MAAOmB,cACK,IACJA,OACHG,UAAU,GAelBC,gBAAgBvB,MAAOkB,YACbC,OAASnB,MAAMoB,GAAGR,IAAIM,UACvBC,cACM,SAIPK,eACEb,QAAUX,MAAMW,QAAQC,IAAIO,OAAOV,WACnCgB,aAAed,MAAAA,eAAAA,QAASM,OAAOS,QAAQP,OAAOQ,gBAC/BC,IAAjBH,eACAD,SAAWb,MAAAA,eAAAA,QAASM,OAAOQ,aAAe,IAGvC,CACHI,KAAM,KACNF,GAAIR,OAAOQ,GACXG,KAAMX,OAAOW,KACbrB,UAAWU,OAAOV,UAClBe,SAAAA,UAcRO,qBAAqB/B,MAAOS,iBAClBC,YAAcV,MAAMW,QAAQC,IAAIH,kBACjCC,YAGE,CACHmB,KAAM,UACNF,GAAIjB,YAAYiB,GAChBG,KAAMpB,YAAYoB,KAClBE,OAAQtB,YAAYsB,QANb,KAmBfC,kBAAkBjC,MAAOkC,4CACfC,MAAQ,sCAEVD,aAAaC,gEAAOpB,QAAS,GAC7BmB,aAAaC,MAAM3B,SAAQ4B,OACvBD,MAAMtB,KAAKuB,SAGZ,CACHP,KAAM,QACNM,MAAAA,OAWRE,aAAarC,MAAOmB,cACVlB,KAAO,CACTqC,UAAW,GACXtC,MAAO,eAEoB4B,IAA3BT,OAAOoB,gBAA+B,2BACtCtC,KAAKD,MAAQmB,OAAOoB,gBACpBtC,KAAKuC,UAAW,QACVF,wCAAYlC,KAAKN,YAAYqB,OAAOoB,wEAAoB,MAC9DtC,iBAAUqC,aAAe,SAEtBrC,KASXwC,cAAczC,wCACJ0C,MAAQ,yCACM1C,MAAMD,OAAOQ,qEAAe,IAEpCC,SAAQC,2CACVC,YAAcV,MAAMW,QAAQC,IAAIH,WACtCiC,MAAM7B,KAAK,CAACgB,KAAM,UAAWF,GAAIjB,YAAYiB,GAAIgB,IAAKjC,YAAYkC,2CAEnDlC,YAAYO,4DAAU,IAC9BT,SAAQU,aACLC,OAASnB,MAAMoB,GAAGR,IAAIM,MAC5BwB,MAAM7B,KAAK,CAACgB,KAAM,KAAMF,GAAIR,OAAOQ,GAAIgB,IAAKxB,OAAOwB,YAGpDD"} \ No newline at end of file +{"version":3,"file":"exporter.min.js","sources":["../../../src/local/courseeditor/exporter.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Module to export parts of the state and transform them to be used in templates\n * and as draggable data.\n *\n * @module core_courseformat/local/courseeditor/exporter\n * @class core_courseformat/local/courseeditor/exporter\n * @copyright 2021 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nexport default class {\n\n /**\n * Class constructor.\n *\n * @param {CourseEditor} reactive the course editor object\n */\n constructor(reactive) {\n this.reactive = reactive;\n\n // Completions states are defined in lib/completionlib.php. There are 4 different completion\n // state values, however, the course index uses the same state for complete and complete_pass.\n // This is the reason why completed appears twice in the array.\n this.COMPLETIONS = ['incomplete', 'complete', 'complete', 'fail'];\n }\n\n /**\n * Generate the course export data from the state.\n *\n * @param {Object} state the current state.\n * @returns {Object}\n */\n course(state) {\n // Collect section information from the state.\n const data = {\n sections: [],\n editmode: this.reactive.isEditing,\n highlighted: state.course.highlighted ?? '',\n };\n const sectionlist = state.course.sectionlist ?? [];\n sectionlist.forEach(sectionid => {\n const sectioninfo = state.section.get(sectionid) ?? {};\n const section = this.section(state, sectioninfo);\n data.sections.push(section);\n });\n data.hassections = (data.sections.length != 0);\n\n return data;\n }\n\n /**\n * Generate a section export data from the state.\n *\n * @param {Object} state the current state.\n * @param {Object} sectioninfo the section state data.\n * @returns {Object}\n */\n section(state, sectioninfo) {\n const section = {\n ...sectioninfo,\n highlighted: state.course.highlighted ?? '',\n cms: [],\n };\n const cmlist = sectioninfo.cmlist ?? [];\n cmlist.forEach(cmid => {\n const cminfo = state.cm.get(cmid);\n const cm = this.cm(state, cminfo);\n section.cms.push(cm);\n });\n section.hascms = (section.cms.length != 0);\n\n return section;\n }\n\n /**\n * Generate a cm export data from the state.\n *\n * @param {Object} state the current state.\n * @param {Object} cminfo the course module state data.\n * @returns {Object}\n */\n cm(state, cminfo) {\n const cm = {\n ...cminfo,\n isactive: false,\n };\n return cm;\n }\n\n /**\n * Generate a dragable cm data structure.\n *\n * This method is used by any draggable course module element to generate drop data\n * for its reactive/dragdrop instance.\n *\n * @param {*} state the state object\n * @param {*} cmid the cours emodule id\n * @returns {Object|null}\n */\n cmDraggableData(state, cmid) {\n const cminfo = state.cm.get(cmid);\n if (!cminfo) {\n return null;\n }\n\n // Drop an activity over the next activity is the same as doing anything.\n let nextcmid;\n const section = state.section.get(cminfo.sectionid);\n const currentindex = section?.cmlist.indexOf(cminfo.id);\n if (currentindex !== undefined) {\n nextcmid = section?.cmlist[currentindex + 1];\n }\n\n return {\n type: 'cm',\n id: cminfo.id,\n name: cminfo.name,\n sectionid: cminfo.sectionid,\n nextcmid,\n };\n }\n\n /**\n * Generate a dragable cm data structure.\n *\n * This method is used by any draggable section element to generate drop data\n * for its reactive/dragdrop instance.\n *\n * @param {*} state the state object\n * @param {*} sectionid the cours section id\n * @returns {Object|null}\n */\n sectionDraggableData(state, sectionid) {\n const sectioninfo = state.section.get(sectionid);\n if (!sectioninfo) {\n return null;\n }\n return {\n type: 'section',\n id: sectioninfo.id,\n name: sectioninfo.name,\n number: sectioninfo.number,\n };\n }\n\n /**\n * Generate a file draggable structure.\n *\n * This method is used when files are dragged on the browser.\n *\n * @param {*} state the state object\n * @param {*} dataTransfer the current data tranfer data\n * @returns {Object|null}\n */\n fileDraggableData(state, dataTransfer) {\n const files = [];\n // Browsers do not provide the file list until the drop event.\n if (dataTransfer.files?.length > 0) {\n dataTransfer.files.forEach(file => {\n files.push(file);\n });\n }\n return {\n type: 'files',\n files,\n };\n }\n\n /**\n * Generate a completion export data from the cm element.\n *\n * @param {Object} state the current state.\n * @param {Object} cminfo the course module state data.\n * @returns {Object}\n */\n cmCompletion(state, cminfo) {\n const data = {\n statename: '',\n state: 'NaN',\n };\n if (cminfo.completionstate !== undefined) {\n data.state = cminfo.completionstate;\n data.hasstate = true;\n const statename = this.COMPLETIONS[cminfo.completionstate] ?? 'NaN';\n data[`is${statename}`] = true;\n }\n return data;\n }\n\n /**\n * Return a sorted list of all sections and cms items in the state.\n *\n * @param {Object} state the current state.\n * @returns {Array} all sections and cms items in the state.\n */\n allItemsArray(state) {\n const items = [];\n const sectionlist = state.course.sectionlist ?? [];\n // Add sections.\n sectionlist.forEach(sectionid => {\n const sectioninfo = state.section.get(sectionid);\n items.push({type: 'section', id: sectioninfo.id, url: sectioninfo.sectionurl});\n // Add cms.\n const cmlist = sectioninfo.cmlist ?? [];\n cmlist.forEach(cmid => {\n const cminfo = state.cm.get(cmid);\n items.push({type: 'cm', id: cminfo.id, url: cminfo.url});\n });\n });\n return items;\n }\n\n /**\n * Check is some activities of a list can be stealth.\n *\n * @param {Object} state the current state.\n * @param {Number[]} cmIds the module ids to check\n * @returns {Boolean} if any of the activities can be stealth.\n */\n canUseStealth(state, cmIds) {\n return cmIds.some(cmId => {\n const cminfo = state.cm.get(cmId);\n return cminfo?.allowstealth ?? false;\n });\n }\n}\n"],"names":["constructor","reactive","COMPLETIONS","course","state","data","sections","editmode","this","isEditing","highlighted","sectionlist","forEach","sectionid","sectioninfo","section","get","push","hassections","length","cms","cmlist","cmid","cminfo","cm","hascms","isactive","cmDraggableData","nextcmid","currentindex","indexOf","id","undefined","type","name","sectionDraggableData","number","fileDraggableData","dataTransfer","files","file","cmCompletion","statename","completionstate","hasstate","allItemsArray","items","url","sectionurl","canUseStealth","cmIds","some","cmId","allowstealth"],"mappings":";;;;;;;;;;MA+BIA,YAAYC,eACHA,SAAWA,cAKXC,YAAc,CAAC,aAAc,WAAY,WAAY,QAS9DC,OAAOC,6DAEGC,KAAO,CACTC,SAAU,GACVC,SAAUC,KAAKP,SAASQ,UACxBC,0CAAaN,MAAMD,OAAOO,mEAAe,yCAEzBN,MAAMD,OAAOQ,mEAAe,IACpCC,SAAQC,yCACVC,uCAAcV,MAAMW,QAAQC,IAAIH,4DAAc,GAC9CE,QAAUP,KAAKO,QAAQX,MAAOU,aACpCT,KAAKC,SAASW,KAAKF,YAEvBV,KAAKa,YAAuC,GAAxBb,KAAKC,SAASa,OAE3Bd,KAUXU,QAAQX,MAAOU,kEACLC,QAAU,IACTD,YACHJ,2CAAaN,MAAMD,OAAOO,qEAAe,GACzCU,IAAK,uCAEMN,YAAYO,0DAAU,IAC9BT,SAAQU,aACLC,OAASnB,MAAMoB,GAAGR,IAAIM,MACtBE,GAAKhB,KAAKgB,GAAGpB,MAAOmB,QAC1BR,QAAQK,IAAIH,KAAKO,OAErBT,QAAQU,OAAgC,GAAtBV,QAAQK,IAAID,OAEvBJ,QAUXS,GAAGpB,MAAOmB,cACK,IACJA,OACHG,UAAU,GAelBC,gBAAgBvB,MAAOkB,YACbC,OAASnB,MAAMoB,GAAGR,IAAIM,UACvBC,cACM,SAIPK,eACEb,QAAUX,MAAMW,QAAQC,IAAIO,OAAOV,WACnCgB,aAAed,MAAAA,eAAAA,QAASM,OAAOS,QAAQP,OAAOQ,gBAC/BC,IAAjBH,eACAD,SAAWb,MAAAA,eAAAA,QAASM,OAAOQ,aAAe,IAGvC,CACHI,KAAM,KACNF,GAAIR,OAAOQ,GACXG,KAAMX,OAAOW,KACbrB,UAAWU,OAAOV,UAClBe,SAAAA,UAcRO,qBAAqB/B,MAAOS,iBAClBC,YAAcV,MAAMW,QAAQC,IAAIH,kBACjCC,YAGE,CACHmB,KAAM,UACNF,GAAIjB,YAAYiB,GAChBG,KAAMpB,YAAYoB,KAClBE,OAAQtB,YAAYsB,QANb,KAmBfC,kBAAkBjC,MAAOkC,4CACfC,MAAQ,sCAEVD,aAAaC,gEAAOpB,QAAS,GAC7BmB,aAAaC,MAAM3B,SAAQ4B,OACvBD,MAAMtB,KAAKuB,SAGZ,CACHP,KAAM,QACNM,MAAAA,OAWRE,aAAarC,MAAOmB,cACVlB,KAAO,CACTqC,UAAW,GACXtC,MAAO,eAEoB4B,IAA3BT,OAAOoB,gBAA+B,2BACtCtC,KAAKD,MAAQmB,OAAOoB,gBACpBtC,KAAKuC,UAAW,QACVF,wCAAYlC,KAAKN,YAAYqB,OAAOoB,wEAAoB,MAC9DtC,iBAAUqC,aAAe,SAEtBrC,KASXwC,cAAczC,wCACJ0C,MAAQ,yCACM1C,MAAMD,OAAOQ,qEAAe,IAEpCC,SAAQC,2CACVC,YAAcV,MAAMW,QAAQC,IAAIH,WACtCiC,MAAM7B,KAAK,CAACgB,KAAM,UAAWF,GAAIjB,YAAYiB,GAAIgB,IAAKjC,YAAYkC,2CAEnDlC,YAAYO,4DAAU,IAC9BT,SAAQU,aACLC,OAASnB,MAAMoB,GAAGR,IAAIM,MAC5BwB,MAAM7B,KAAK,CAACgB,KAAM,KAAMF,GAAIR,OAAOQ,GAAIgB,IAAKxB,OAAOwB,YAGpDD,MAUXG,cAAc7C,MAAO8C,cACVA,MAAMC,MAAKC,sCACR7B,OAASnB,MAAMoB,GAAGR,IAAIoC,0CACrB7B,MAAAA,cAAAA,OAAQ8B"} \ No newline at end of file diff --git a/course/format/amd/build/local/courseeditor/mutations.min.js b/course/format/amd/build/local/courseeditor/mutations.min.js index 2bf8a5f5c82d8..e226be7a848e9 100644 --- a/course/format/amd/build/local/courseeditor/mutations.min.js +++ b/course/format/amd/build/local/courseeditor/mutations.min.js @@ -6,6 +6,6 @@ define("core_courseformat/local/courseeditor/mutations",["exports","core/ajax"], * @class core_courseformat/local/courseeditor/mutations * @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,_ajax=(obj=_ajax)&&obj.__esModule?obj:{default:obj};return _exports.default=class{async _callEditWebservice(action,courseId,ids,targetSectionId,targetCmId){const args={action:action,courseid:courseId,ids:ids};targetSectionId&&(args.targetsectionid=targetSectionId),targetCmId&&(args.targetcmid=targetCmId);let ajaxresult=await _ajax.default.call([{methodname:"core_courseformat_update_course",args:args}])[0];return JSON.parse(ajaxresult)}async _sectionBasicAction(stateManager,action,sectionIds,targetSectionId,targetCmId){const course=stateManager.get("course");this.sectionLock(stateManager,sectionIds,!0);const updates=await this._callEditWebservice(action,course.id,sectionIds,targetSectionId,targetCmId);stateManager.processUpdates(updates),this.sectionLock(stateManager,sectionIds,!1)}async _cmBasicAction(stateManager,action,cmIds,targetSectionId,targetCmId){const course=stateManager.get("course");this.cmLock(stateManager,cmIds,!0);const updates=await this._callEditWebservice(action,course.id,cmIds,targetSectionId,targetCmId);stateManager.processUpdates(updates),this.cmLock(stateManager,cmIds,!1)}init(stateManager){stateManager.addUpdateTypes({prepareFields:this._prepareFields})}_prepareFields(stateManager,updateName,fields){return fields.locked=!1,fields}async sectionHide(stateManager,sectionIds){await this._sectionBasicAction(stateManager,"section_hide",sectionIds)}async sectionShow(stateManager,sectionIds){await this._sectionBasicAction(stateManager,"section_show",sectionIds)}async cmShow(stateManager,cmIds){await this._cmBasicAction(stateManager,"cm_show",cmIds)}async cmHide(stateManager,cmIds){await this._cmBasicAction(stateManager,"cm_hide",cmIds)}async cmStealth(stateManager,cmIds){await this._cmBasicAction(stateManager,"cm_stealth",cmIds)}async cmDuplicate(stateManager,cmIds,targetSectionId,targetCmId){const course=stateManager.get("course"),sectionIds=new Set;targetSectionId?sectionIds.add(targetSectionId):cmIds.forEach((cmId=>{const cm=stateManager.get("cm",cmId);sectionIds.add(cm.sectionid)})),this.sectionLock(stateManager,Array.from(sectionIds),!0);const updates=await this._callEditWebservice("cm_duplicate",course.id,cmIds,targetSectionId,targetCmId);stateManager.processUpdates(updates),this.sectionLock(stateManager,Array.from(sectionIds),!1)}async cmMove(stateManager,cmids,targetSectionId,targetCmId){if(!targetSectionId&&!targetCmId)throw new Error("Mutation cmMove requires targetSectionId or targetCmId");const course=stateManager.get("course");this.cmLock(stateManager,cmids,!0);const updates=await this._callEditWebservice("cm_move",course.id,cmids,targetSectionId,targetCmId);stateManager.processUpdates(updates),this.cmLock(stateManager,cmids,!1)}async sectionMove(stateManager,sectionIds,targetSectionId){if(!targetSectionId)throw new Error("Mutation sectionMove requires targetSectionId");const course=stateManager.get("course");this.sectionLock(stateManager,sectionIds,!0);const updates=await this._callEditWebservice("section_move",course.id,sectionIds,targetSectionId);stateManager.processUpdates(updates),this.sectionLock(stateManager,sectionIds,!1)}async addSection(stateManager,targetSectionId){targetSectionId||(targetSectionId=0);const course=stateManager.get("course"),updates=await this._callEditWebservice("section_add",course.id,[],targetSectionId);stateManager.processUpdates(updates)}async sectionDelete(stateManager,sectionIds){const course=stateManager.get("course"),updates=await this._callEditWebservice("section_delete",course.id,sectionIds);stateManager.processUpdates(updates)}async cmDelete(stateManager,cmIds){const course=stateManager.get("course");this.cmLock(stateManager,cmIds,!0);const updates=await this._callEditWebservice("cm_delete",course.id,cmIds);this.cmLock(stateManager,cmIds,!1),stateManager.processUpdates(updates)}cmDrag(stateManager,cmIds,dragValue){this.setPageItem(stateManager),this._setElementsValue(stateManager,"cm",cmIds,"dragging",dragValue)}sectionDrag(stateManager,sectionIds,dragValue){this.setPageItem(stateManager),this._setElementsValue(stateManager,"section",sectionIds,"dragging",dragValue)}cmCompletion(stateManager,cmIds,complete){const newValue=complete?1:0;this._setElementsValue(stateManager,"cm",cmIds,"completionstate",newValue)}cmLock(stateManager,cmIds,lockValue){this._setElementsValue(stateManager,"cm",cmIds,"locked",lockValue)}sectionLock(stateManager,sectionIds,lockValue){this._setElementsValue(stateManager,"section",sectionIds,"locked",lockValue)}_setElementsValue(stateManager,name,ids,fieldName,newValue){stateManager.setReadOnly(!1),ids.forEach((id=>{const element=stateManager.get(name,id);element&&(element[fieldName]=newValue)})),stateManager.setReadOnly(!0)}setPageItem(stateManager,type,id,isStatic){let newPageItem;if(void 0!==type&&(newPageItem=stateManager.get(type,id),!newPageItem))return;stateManager.setReadOnly(!1);const course=stateManager.get("course");course.pageItem=null,newPageItem&&(course.pageItem={id:id,type:type,sectionId:"section"==type?newPageItem.id:newPageItem.sectionid,isStatic:isStatic}),stateManager.setReadOnly(!0)}unlockAll(stateManager){const state=stateManager.state;stateManager.setReadOnly(!1),state.section.forEach((section=>{section.locked=!1})),state.cm.forEach((cm=>{cm.locked=!1})),stateManager.setReadOnly(!0)}async sectionIndexCollapsed(stateManager,sectionIds,collapsed){const collapsedIds=this._updateStateSectionPreference(stateManager,"indexcollapsed",sectionIds,collapsed),course=stateManager.get("course");await this._callEditWebservice("section_index_collapsed",course.id,collapsedIds)}async sectionContentCollapsed(stateManager,sectionIds,collapsed){const collapsedIds=this._updateStateSectionPreference(stateManager,"contentcollapsed",sectionIds,collapsed),course=stateManager.get("course");await this._callEditWebservice("section_content_collapsed",course.id,collapsedIds)}_updateStateSectionPreference(stateManager,preferenceName,sectionIds,preferenceValue){stateManager.setReadOnly(!1);const affectedSections=new Set;if(sectionIds.forEach((sectionId=>{const section=stateManager.get("section",sectionId);if(void 0===section)return;const newValue=null!=preferenceValue?preferenceValue:section[preferenceName];section[preferenceName]!=newValue&&(section[preferenceName]=newValue,affectedSections.add(section.id))})),stateManager.setReadOnly(!0),0==affectedSections.size)return[];const collapsedSectionIds=[];return stateManager.state.section.forEach((section=>{section[preferenceName]&&collapsedSectionIds.push(section.id)})),collapsedSectionIds}bulkEnable(stateManager,enabled){const state=stateManager.state;stateManager.setReadOnly(!1),state.bulk.enabled=enabled,state.bulk.selectedType="",state.bulk.selection=[],stateManager.setReadOnly(!0)}bulkReset(stateManager){const state=stateManager.state;stateManager.setReadOnly(!1),state.bulk.selectedType="",state.bulk.selection=[],stateManager.setReadOnly(!0)}cmSelect(stateManager,cmIds){this._addIdsToSelection(stateManager,"cm",cmIds)}cmUnselect(stateManager,cmIds){this._removeIdsFromSelection(stateManager,"cm",cmIds)}sectionSelect(stateManager,sectionIds){this._addIdsToSelection(stateManager,"section",sectionIds)}sectionUnselect(stateManager,sectionIds){this._removeIdsFromSelection(stateManager,"section",sectionIds)}_addIdsToSelection(stateManager,typeName,ids){const bulk=stateManager.state.bulk;if(null==bulk||!bulk.enabled)throw new Error("Bulk is not enabled");if(""!==(null==bulk?void 0:bulk.selectedType)&&(null==bulk?void 0:bulk.selectedType)!==typeName)throw new Error("Cannot add ".concat(typeName," to the current selection"));ids=ids.map((value=>value.toString())),stateManager.setReadOnly(!1),bulk.selectedType=typeName;const newSelection=new Set([...bulk.selection,...ids]);bulk.selection=[...newSelection],stateManager.setReadOnly(!0)}_removeIdsFromSelection(stateManager,typeName,ids){const bulk=stateManager.state.bulk;if(null==bulk||!bulk.enabled)throw new Error("Bulk is not enabled");if(""!==(null==bulk?void 0:bulk.selectedType)&&(null==bulk?void 0:bulk.selectedType)!==typeName)throw new Error("Cannot remove ".concat(typeName," from the current selection"));ids=ids.map((value=>value.toString())),stateManager.setReadOnly(!1);const IdsToFilter=new Set(ids);bulk.selection=bulk.selection.filter((current=>!IdsToFilter.has(current))),0===bulk.selection.length&&(bulk.selectedType=""),stateManager.setReadOnly(!0)}async cmState(stateManager,cmids){this.cmLock(stateManager,cmids,!0);const course=stateManager.get("course"),updates=await this._callEditWebservice("cm_state",course.id,cmids);stateManager.processUpdates(updates),this.cmLock(stateManager,cmids,!1)}async sectionState(stateManager,sectionIds){this.sectionLock(stateManager,sectionIds,!0);const course=stateManager.get("course"),updates=await this._callEditWebservice("section_state",course.id,sectionIds);stateManager.processUpdates(updates),this.sectionLock(stateManager,sectionIds,!1)}async courseState(stateManager){const course=stateManager.get("course"),updates=await this._callEditWebservice("course_state",course.id);stateManager.processUpdates(updates)}},_exports.default})); + */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_ajax=(obj=_ajax)&&obj.__esModule?obj:{default:obj};return _exports.default=class{async _callEditWebservice(action,courseId,ids,targetSectionId,targetCmId){const args={action:action,courseid:courseId,ids:ids};targetSectionId&&(args.targetsectionid=targetSectionId),targetCmId&&(args.targetcmid=targetCmId);let ajaxresult=await _ajax.default.call([{methodname:"core_courseformat_update_course",args:args}])[0];return JSON.parse(ajaxresult)}async _sectionBasicAction(stateManager,action,sectionIds,targetSectionId,targetCmId){const course=stateManager.get("course");this.sectionLock(stateManager,sectionIds,!0);const updates=await this._callEditWebservice(action,course.id,sectionIds,targetSectionId,targetCmId);this.bulkReset(stateManager),stateManager.processUpdates(updates),this.sectionLock(stateManager,sectionIds,!1)}async _cmBasicAction(stateManager,action,cmIds,targetSectionId,targetCmId){const course=stateManager.get("course");this.cmLock(stateManager,cmIds,!0);const updates=await this._callEditWebservice(action,course.id,cmIds,targetSectionId,targetCmId);this.bulkReset(stateManager),stateManager.processUpdates(updates),this.cmLock(stateManager,cmIds,!1)}init(stateManager){stateManager.addUpdateTypes({prepareFields:this._prepareFields})}_prepareFields(stateManager,updateName,fields){return fields.locked=!1,fields}async sectionHide(stateManager,sectionIds){await this._sectionBasicAction(stateManager,"section_hide",sectionIds)}async sectionShow(stateManager,sectionIds){await this._sectionBasicAction(stateManager,"section_show",sectionIds)}async cmShow(stateManager,cmIds){await this._cmBasicAction(stateManager,"cm_show",cmIds)}async cmHide(stateManager,cmIds){await this._cmBasicAction(stateManager,"cm_hide",cmIds)}async cmStealth(stateManager,cmIds){await this._cmBasicAction(stateManager,"cm_stealth",cmIds)}async cmDuplicate(stateManager,cmIds,targetSectionId,targetCmId){const course=stateManager.get("course"),sectionIds=new Set;targetSectionId?sectionIds.add(targetSectionId):cmIds.forEach((cmId=>{const cm=stateManager.get("cm",cmId);sectionIds.add(cm.sectionid)})),this.sectionLock(stateManager,Array.from(sectionIds),!0);const updates=await this._callEditWebservice("cm_duplicate",course.id,cmIds,targetSectionId,targetCmId);stateManager.processUpdates(updates),this.sectionLock(stateManager,Array.from(sectionIds),!1)}async cmMove(stateManager,cmids,targetSectionId,targetCmId){if(!targetSectionId&&!targetCmId)throw new Error("Mutation cmMove requires targetSectionId or targetCmId");const course=stateManager.get("course");this.cmLock(stateManager,cmids,!0);const updates=await this._callEditWebservice("cm_move",course.id,cmids,targetSectionId,targetCmId);stateManager.processUpdates(updates),this.cmLock(stateManager,cmids,!1)}async sectionMove(stateManager,sectionIds,targetSectionId){if(!targetSectionId)throw new Error("Mutation sectionMove requires targetSectionId");const course=stateManager.get("course");this.sectionLock(stateManager,sectionIds,!0);const updates=await this._callEditWebservice("section_move",course.id,sectionIds,targetSectionId);stateManager.processUpdates(updates),this.sectionLock(stateManager,sectionIds,!1)}async addSection(stateManager,targetSectionId){targetSectionId||(targetSectionId=0);const course=stateManager.get("course"),updates=await this._callEditWebservice("section_add",course.id,[],targetSectionId);stateManager.processUpdates(updates)}async sectionDelete(stateManager,sectionIds){const course=stateManager.get("course"),updates=await this._callEditWebservice("section_delete",course.id,sectionIds);stateManager.processUpdates(updates)}async cmDelete(stateManager,cmIds){const course=stateManager.get("course");this.cmLock(stateManager,cmIds,!0);const updates=await this._callEditWebservice("cm_delete",course.id,cmIds);this.cmLock(stateManager,cmIds,!1),stateManager.processUpdates(updates)}cmDrag(stateManager,cmIds,dragValue){this.setPageItem(stateManager),this._setElementsValue(stateManager,"cm",cmIds,"dragging",dragValue)}sectionDrag(stateManager,sectionIds,dragValue){this.setPageItem(stateManager),this._setElementsValue(stateManager,"section",sectionIds,"dragging",dragValue)}cmCompletion(stateManager,cmIds,complete){const newValue=complete?1:0;this._setElementsValue(stateManager,"cm",cmIds,"completionstate",newValue)}cmLock(stateManager,cmIds,lockValue){this._setElementsValue(stateManager,"cm",cmIds,"locked",lockValue)}sectionLock(stateManager,sectionIds,lockValue){this._setElementsValue(stateManager,"section",sectionIds,"locked",lockValue)}_setElementsValue(stateManager,name,ids,fieldName,newValue){stateManager.setReadOnly(!1),ids.forEach((id=>{const element=stateManager.get(name,id);element&&(element[fieldName]=newValue)})),stateManager.setReadOnly(!0)}setPageItem(stateManager,type,id,isStatic){let newPageItem;if(void 0!==type&&(newPageItem=stateManager.get(type,id),!newPageItem))return;stateManager.setReadOnly(!1);const course=stateManager.get("course");course.pageItem=null,newPageItem&&(course.pageItem={id:id,type:type,sectionId:"section"==type?newPageItem.id:newPageItem.sectionid,isStatic:isStatic}),stateManager.setReadOnly(!0)}unlockAll(stateManager){const state=stateManager.state;stateManager.setReadOnly(!1),state.section.forEach((section=>{section.locked=!1})),state.cm.forEach((cm=>{cm.locked=!1})),stateManager.setReadOnly(!0)}async sectionIndexCollapsed(stateManager,sectionIds,collapsed){const collapsedIds=this._updateStateSectionPreference(stateManager,"indexcollapsed",sectionIds,collapsed),course=stateManager.get("course");await this._callEditWebservice("section_index_collapsed",course.id,collapsedIds)}async sectionContentCollapsed(stateManager,sectionIds,collapsed){const collapsedIds=this._updateStateSectionPreference(stateManager,"contentcollapsed",sectionIds,collapsed),course=stateManager.get("course");await this._callEditWebservice("section_content_collapsed",course.id,collapsedIds)}_updateStateSectionPreference(stateManager,preferenceName,sectionIds,preferenceValue){stateManager.setReadOnly(!1);const affectedSections=new Set;if(sectionIds.forEach((sectionId=>{const section=stateManager.get("section",sectionId);if(void 0===section)return;const newValue=null!=preferenceValue?preferenceValue:section[preferenceName];section[preferenceName]!=newValue&&(section[preferenceName]=newValue,affectedSections.add(section.id))})),stateManager.setReadOnly(!0),0==affectedSections.size)return[];const collapsedSectionIds=[];return stateManager.state.section.forEach((section=>{section[preferenceName]&&collapsedSectionIds.push(section.id)})),collapsedSectionIds}bulkEnable(stateManager,enabled){const state=stateManager.state;stateManager.setReadOnly(!1),state.bulk.enabled=enabled,state.bulk.selectedType="",state.bulk.selection=[],stateManager.setReadOnly(!0)}bulkReset(stateManager){const state=stateManager.state;stateManager.setReadOnly(!1),state.bulk.selectedType="",state.bulk.selection=[],stateManager.setReadOnly(!0)}cmSelect(stateManager,cmIds){this._addIdsToSelection(stateManager,"cm",cmIds)}cmUnselect(stateManager,cmIds){this._removeIdsFromSelection(stateManager,"cm",cmIds)}sectionSelect(stateManager,sectionIds){this._addIdsToSelection(stateManager,"section",sectionIds)}sectionUnselect(stateManager,sectionIds){this._removeIdsFromSelection(stateManager,"section",sectionIds)}_addIdsToSelection(stateManager,typeName,ids){const bulk=stateManager.state.bulk;if(null==bulk||!bulk.enabled)throw new Error("Bulk is not enabled");if(""!==(null==bulk?void 0:bulk.selectedType)&&(null==bulk?void 0:bulk.selectedType)!==typeName)throw new Error("Cannot add ".concat(typeName," to the current selection"));ids=ids.map((value=>value.toString())),stateManager.setReadOnly(!1),bulk.selectedType=typeName;const newSelection=new Set([...bulk.selection,...ids]);bulk.selection=[...newSelection],stateManager.setReadOnly(!0)}_removeIdsFromSelection(stateManager,typeName,ids){const bulk=stateManager.state.bulk;if(null==bulk||!bulk.enabled)throw new Error("Bulk is not enabled");if(""!==(null==bulk?void 0:bulk.selectedType)&&(null==bulk?void 0:bulk.selectedType)!==typeName)throw new Error("Cannot remove ".concat(typeName," from the current selection"));ids=ids.map((value=>value.toString())),stateManager.setReadOnly(!1);const IdsToFilter=new Set(ids);bulk.selection=bulk.selection.filter((current=>!IdsToFilter.has(current))),0===bulk.selection.length&&(bulk.selectedType=""),stateManager.setReadOnly(!0)}async cmState(stateManager,cmids){this.cmLock(stateManager,cmids,!0);const course=stateManager.get("course"),updates=await this._callEditWebservice("cm_state",course.id,cmids);stateManager.processUpdates(updates),this.cmLock(stateManager,cmids,!1)}async sectionState(stateManager,sectionIds){this.sectionLock(stateManager,sectionIds,!0);const course=stateManager.get("course"),updates=await this._callEditWebservice("section_state",course.id,sectionIds);stateManager.processUpdates(updates),this.sectionLock(stateManager,sectionIds,!1)}async courseState(stateManager){const course=stateManager.get("course"),updates=await this._callEditWebservice("course_state",course.id);stateManager.processUpdates(updates)}},_exports.default})); //# sourceMappingURL=mutations.min.js.map \ No newline at end of file diff --git a/course/format/amd/build/local/courseeditor/mutations.min.js.map b/course/format/amd/build/local/courseeditor/mutations.min.js.map index f8a4dc9ad25a5..8dcfbe831a94d 100644 --- a/course/format/amd/build/local/courseeditor/mutations.min.js.map +++ b/course/format/amd/build/local/courseeditor/mutations.min.js.map @@ -1 +1 @@ -{"version":3,"file":"mutations.min.js","sources":["../../../src/local/courseeditor/mutations.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\nimport ajax from 'core/ajax';\n\n/**\n * Default mutation manager\n *\n * @module core_courseformat/local/courseeditor/mutations\n * @class core_courseformat/local/courseeditor/mutations\n * @copyright 2021 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nexport default class {\n\n // All course editor mutations for Moodle 4.0 will be located in this file.\n\n /**\n * Private method to call core_courseformat_update_course webservice.\n *\n * @method _callEditWebservice\n * @param {string} action\n * @param {number} courseId\n * @param {array} ids\n * @param {number} targetSectionId optional target section id (for moving actions)\n * @param {number} targetCmId optional target cm id (for moving actions)\n */\n async _callEditWebservice(action, courseId, ids, targetSectionId, targetCmId) {\n const args = {\n action,\n courseid: courseId,\n ids,\n };\n if (targetSectionId) {\n args.targetsectionid = targetSectionId;\n }\n if (targetCmId) {\n args.targetcmid = targetCmId;\n }\n let ajaxresult = await ajax.call([{\n methodname: 'core_courseformat_update_course',\n args,\n }])[0];\n return JSON.parse(ajaxresult);\n }\n\n /**\n * Execute a basic section state action.\n * @param {StateManager} stateManager the current state manager\n * @param {string} action the action name\n * @param {array} sectionIds the section ids\n * @param {number} targetSectionId optional target section id (for moving actions)\n * @param {number} targetCmId optional target cm id (for moving actions)\n */\n async _sectionBasicAction(stateManager, action, sectionIds, targetSectionId, targetCmId) {\n const course = stateManager.get('course');\n this.sectionLock(stateManager, sectionIds, true);\n const updates = await this._callEditWebservice(\n action,\n course.id,\n sectionIds,\n targetSectionId,\n targetCmId\n );\n stateManager.processUpdates(updates);\n this.sectionLock(stateManager, sectionIds, false);\n }\n\n /**\n * Execute a basic course module state action.\n * @param {StateManager} stateManager the current state manager\n * @param {string} action the action name\n * @param {array} cmIds the cm ids\n * @param {number} targetSectionId optional target section id (for moving actions)\n * @param {number} targetCmId optional target cm id (for moving actions)\n */\n async _cmBasicAction(stateManager, action, cmIds, targetSectionId, targetCmId) {\n const course = stateManager.get('course');\n this.cmLock(stateManager, cmIds, true);\n const updates = await this._callEditWebservice(\n action,\n course.id,\n cmIds,\n targetSectionId,\n targetCmId\n );\n stateManager.processUpdates(updates);\n this.cmLock(stateManager, cmIds, false);\n }\n\n /**\n * Mutation module initialize.\n *\n * The reactive instance will execute this method when addMutations or setMutation is invoked.\n *\n * @param {StateManager} stateManager the state manager\n */\n init(stateManager) {\n // Add a method to prepare the fields when some update is comming from the server.\n stateManager.addUpdateTypes({\n prepareFields: this._prepareFields,\n });\n }\n\n /**\n * Add default values to state elements.\n *\n * This method is called every time a webservice returns a update state message.\n *\n * @param {Object} stateManager the state manager\n * @param {String} updateName the state element to update\n * @param {Object} fields the new data\n * @returns {Object} final fields data\n */\n _prepareFields(stateManager, updateName, fields) {\n // Any update should unlock the element.\n fields.locked = false;\n return fields;\n }\n\n /**\n * Hides sections.\n * @param {StateManager} stateManager the current state manager\n * @param {array} sectionIds the list of section ids\n */\n async sectionHide(stateManager, sectionIds) {\n await this._sectionBasicAction(stateManager, 'section_hide', sectionIds);\n }\n\n /**\n * Show sections.\n * @param {StateManager} stateManager the current state manager\n * @param {array} sectionIds the list of section ids\n */\n async sectionShow(stateManager, sectionIds) {\n await this._sectionBasicAction(stateManager, 'section_show', sectionIds);\n }\n\n /**\n * Show cms.\n * @param {StateManager} stateManager the current state manager\n * @param {array} cmIds the list of cm ids\n */\n async cmShow(stateManager, cmIds) {\n await this._cmBasicAction(stateManager, 'cm_show', cmIds);\n }\n\n /**\n * Hide cms.\n * @param {StateManager} stateManager the current state manager\n * @param {array} cmIds the list of cm ids\n */\n async cmHide(stateManager, cmIds) {\n await this._cmBasicAction(stateManager, 'cm_hide', cmIds);\n }\n\n /**\n * Stealth cms.\n * @param {StateManager} stateManager the current state manager\n * @param {array} cmIds the list of cm ids\n */\n async cmStealth(stateManager, cmIds) {\n await this._cmBasicAction(stateManager, 'cm_stealth', cmIds);\n }\n\n /**\n * Duplicate course modules\n * @param {StateManager} stateManager the current state manager\n * @param {array} cmIds the list of course modules ids\n * @param {number|undefined} targetSectionId the optional target sectionId\n * @param {number|undefined} targetCmId the target course module id\n */\n async cmDuplicate(stateManager, cmIds, targetSectionId, targetCmId) {\n const course = stateManager.get('course');\n // Lock all target sections.\n const sectionIds = new Set();\n if (targetSectionId) {\n sectionIds.add(targetSectionId);\n } else {\n cmIds.forEach((cmId) => {\n const cm = stateManager.get('cm', cmId);\n sectionIds.add(cm.sectionid);\n });\n }\n this.sectionLock(stateManager, Array.from(sectionIds), true);\n\n const updates = await this._callEditWebservice('cm_duplicate', course.id, cmIds, targetSectionId, targetCmId);\n stateManager.processUpdates(updates);\n\n this.sectionLock(stateManager, Array.from(sectionIds), false);\n }\n\n /**\n * Move course modules to specific course location.\n *\n * Note that one of targetSectionId or targetCmId should be provided in order to identify the\n * new location:\n * - targetCmId: the activities will be located avobe the target cm. The targetSectionId\n * value will be ignored in this case.\n * - targetSectionId: the activities will be appended to the section. In this case\n * targetSectionId should not be present.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} cmids the list of cm ids to move\n * @param {number} targetSectionId the target section id\n * @param {number} targetCmId the target course module id\n */\n async cmMove(stateManager, cmids, targetSectionId, targetCmId) {\n if (!targetSectionId && !targetCmId) {\n throw new Error(`Mutation cmMove requires targetSectionId or targetCmId`);\n }\n const course = stateManager.get('course');\n this.cmLock(stateManager, cmids, true);\n const updates = await this._callEditWebservice('cm_move', course.id, cmids, targetSectionId, targetCmId);\n stateManager.processUpdates(updates);\n this.cmLock(stateManager, cmids, false);\n }\n\n /**\n * Move course modules to specific course location.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} sectionIds the list of section ids to move\n * @param {number} targetSectionId the target section id\n */\n async sectionMove(stateManager, sectionIds, targetSectionId) {\n if (!targetSectionId) {\n throw new Error(`Mutation sectionMove requires targetSectionId`);\n }\n const course = stateManager.get('course');\n this.sectionLock(stateManager, sectionIds, true);\n const updates = await this._callEditWebservice('section_move', course.id, sectionIds, targetSectionId);\n stateManager.processUpdates(updates);\n this.sectionLock(stateManager, sectionIds, false);\n }\n\n /**\n * Add a new section to a specific course location.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {number} targetSectionId optional the target section id\n */\n async addSection(stateManager, targetSectionId) {\n if (!targetSectionId) {\n targetSectionId = 0;\n }\n const course = stateManager.get('course');\n const updates = await this._callEditWebservice('section_add', course.id, [], targetSectionId);\n stateManager.processUpdates(updates);\n }\n\n /**\n * Delete sections.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} sectionIds the list of course modules ids\n */\n async sectionDelete(stateManager, sectionIds) {\n const course = stateManager.get('course');\n const updates = await this._callEditWebservice('section_delete', course.id, sectionIds);\n stateManager.processUpdates(updates);\n }\n\n /**\n * Delete cms.\n * @param {StateManager} stateManager the current state manager\n * @param {array} cmIds the list of section ids\n */\n async cmDelete(stateManager, cmIds) {\n const course = stateManager.get('course');\n this.cmLock(stateManager, cmIds, true);\n const updates = await this._callEditWebservice('cm_delete', course.id, cmIds);\n this.cmLock(stateManager, cmIds, false);\n stateManager.processUpdates(updates);\n }\n\n /**\n * Mark or unmark course modules as dragging.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} cmIds the list of course modules ids\n * @param {bool} dragValue the new dragging value\n */\n cmDrag(stateManager, cmIds, dragValue) {\n this.setPageItem(stateManager);\n this._setElementsValue(stateManager, 'cm', cmIds, 'dragging', dragValue);\n }\n\n /**\n * Mark or unmark course sections as dragging.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} sectionIds the list of section ids\n * @param {bool} dragValue the new dragging value\n */\n sectionDrag(stateManager, sectionIds, dragValue) {\n this.setPageItem(stateManager);\n this._setElementsValue(stateManager, 'section', sectionIds, 'dragging', dragValue);\n }\n\n /**\n * Mark or unmark course modules as complete.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} cmIds the list of course modules ids\n * @param {bool} complete the new completion value\n */\n cmCompletion(stateManager, cmIds, complete) {\n const newValue = (complete) ? 1 : 0;\n this._setElementsValue(stateManager, 'cm', cmIds, 'completionstate', newValue);\n }\n\n /**\n * Lock or unlock course modules.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} cmIds the list of course modules ids\n * @param {bool} lockValue the new locked value\n */\n cmLock(stateManager, cmIds, lockValue) {\n this._setElementsValue(stateManager, 'cm', cmIds, 'locked', lockValue);\n }\n\n /**\n * Lock or unlock course sections.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} sectionIds the list of section ids\n * @param {bool} lockValue the new locked value\n */\n sectionLock(stateManager, sectionIds, lockValue) {\n this._setElementsValue(stateManager, 'section', sectionIds, 'locked', lockValue);\n }\n\n _setElementsValue(stateManager, name, ids, fieldName, newValue) {\n stateManager.setReadOnly(false);\n ids.forEach((id) => {\n const element = stateManager.get(name, id);\n if (element) {\n element[fieldName] = newValue;\n }\n });\n stateManager.setReadOnly(true);\n }\n\n /**\n * Set the page current item.\n *\n * Only one element of the course state can be the page item at a time.\n *\n * There are several actions that can alter the page current item. For example, when the user is in an activity\n * page, the page item is always the activity one. However, in a course page, when the user scrolls to an element,\n * this element get the page item.\n *\n * If the page item is static means that it is not meant to change. This is important because\n * static page items has some special logic. For example, if a cm is the static page item\n * and it is inside a collapsed section, the course index will expand the section to make it visible.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {String|undefined} type the element type (section or cm). Undefined will remove the current page item.\n * @param {Number|undefined} id the element id\n * @param {boolean|undefined} isStatic if the page item is static\n */\n setPageItem(stateManager, type, id, isStatic) {\n let newPageItem;\n if (type !== undefined) {\n newPageItem = stateManager.get(type, id);\n if (!newPageItem) {\n return;\n }\n }\n stateManager.setReadOnly(false);\n // Remove the current page item.\n const course = stateManager.get('course');\n course.pageItem = null;\n // Save the new page item.\n if (newPageItem) {\n course.pageItem = {\n id,\n type,\n sectionId: (type == 'section') ? newPageItem.id : newPageItem.sectionid,\n isStatic,\n };\n }\n stateManager.setReadOnly(true);\n }\n\n /**\n * Unlock all course elements.\n *\n * @param {StateManager} stateManager the current state manager\n */\n unlockAll(stateManager) {\n const state = stateManager.state;\n stateManager.setReadOnly(false);\n state.section.forEach((section) => {\n section.locked = false;\n });\n state.cm.forEach((cm) => {\n cm.locked = false;\n });\n stateManager.setReadOnly(true);\n }\n\n /**\n * Update the course index collapsed attribute of some sections.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} sectionIds the affected section ids\n * @param {boolean} collapsed the new collapsed value\n */\n async sectionIndexCollapsed(stateManager, sectionIds, collapsed) {\n const collapsedIds = this._updateStateSectionPreference(stateManager, 'indexcollapsed', sectionIds, collapsed);\n const course = stateManager.get('course');\n await this._callEditWebservice('section_index_collapsed', course.id, collapsedIds);\n }\n\n /**\n * Update the course content collapsed attribute of some sections.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} sectionIds the affected section ids\n * @param {boolean} collapsed the new collapsed value\n */\n async sectionContentCollapsed(stateManager, sectionIds, collapsed) {\n const collapsedIds = this._updateStateSectionPreference(stateManager, 'contentcollapsed', sectionIds, collapsed);\n const course = stateManager.get('course');\n await this._callEditWebservice('section_content_collapsed', course.id, collapsedIds);\n }\n\n /**\n * Private batch update for a section preference attribute.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {string} preferenceName the preference name\n * @param {array} sectionIds the affected section ids\n * @param {boolean} preferenceValue the new preferenceValue value\n * @return {array} the list of all sections with that preference set to true\n */\n _updateStateSectionPreference(stateManager, preferenceName, sectionIds, preferenceValue) {\n stateManager.setReadOnly(false);\n const affectedSections = new Set();\n // Check if we need to update preferences.\n sectionIds.forEach(sectionId => {\n const section = stateManager.get('section', sectionId);\n if (section === undefined) {\n return;\n }\n const newValue = preferenceValue ?? section[preferenceName];\n if (section[preferenceName] != newValue) {\n section[preferenceName] = newValue;\n affectedSections.add(section.id);\n }\n });\n stateManager.setReadOnly(true);\n if (affectedSections.size == 0) {\n return [];\n }\n // Get all collapsed section ids.\n const collapsedSectionIds = [];\n const state = stateManager.state;\n state.section.forEach(section => {\n if (section[preferenceName]) {\n collapsedSectionIds.push(section.id);\n }\n });\n return collapsedSectionIds;\n }\n\n /**\n * Enable/disable bulk editing.\n *\n * Note: reenabling the bulk will clean the current selection.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {Boolean} enabled the new bulk state.\n */\n bulkEnable(stateManager, enabled) {\n const state = stateManager.state;\n stateManager.setReadOnly(false);\n state.bulk.enabled = enabled;\n state.bulk.selectedType = '';\n state.bulk.selection = [];\n stateManager.setReadOnly(true);\n }\n\n /**\n * Reset the current selection.\n * @param {StateManager} stateManager the current state manager\n */\n bulkReset(stateManager) {\n const state = stateManager.state;\n stateManager.setReadOnly(false);\n state.bulk.selectedType = '';\n state.bulk.selection = [];\n stateManager.setReadOnly(true);\n }\n\n /**\n * Select a list of cms.\n * @param {StateManager} stateManager the current state manager\n * @param {array} cmIds the list of cm ids\n */\n cmSelect(stateManager, cmIds) {\n this._addIdsToSelection(stateManager, 'cm', cmIds);\n }\n\n /**\n * Unselect a list of cms.\n * @param {StateManager} stateManager the current state manager\n * @param {array} cmIds the list of cm ids\n */\n cmUnselect(stateManager, cmIds) {\n this._removeIdsFromSelection(stateManager, 'cm', cmIds);\n }\n\n /**\n * Select a list of sections.\n * @param {StateManager} stateManager the current state manager\n * @param {array} sectionIds the list of cm ids\n */\n sectionSelect(stateManager, sectionIds) {\n this._addIdsToSelection(stateManager, 'section', sectionIds);\n }\n\n /**\n * Unselect a list of sections.\n * @param {StateManager} stateManager the current state manager\n * @param {array} sectionIds the list of cm ids\n */\n sectionUnselect(stateManager, sectionIds) {\n this._removeIdsFromSelection(stateManager, 'section', sectionIds);\n }\n\n /**\n * Add some ids to the current bulk selection.\n * @param {StateManager} stateManager the current state manager\n * @param {String} typeName the type name (section/cm)\n * @param {array} ids the list of ids\n */\n _addIdsToSelection(stateManager, typeName, ids) {\n const bulk = stateManager.state.bulk;\n if (!bulk?.enabled) {\n throw new Error(`Bulk is not enabled`);\n }\n if (bulk?.selectedType !== \"\" && bulk?.selectedType !== typeName) {\n throw new Error(`Cannot add ${typeName} to the current selection`);\n }\n\n // Stored ids are strings for compatability with HTML data attributes.\n ids = ids.map(value => value.toString());\n\n stateManager.setReadOnly(false);\n bulk.selectedType = typeName;\n const newSelection = new Set([...bulk.selection, ...ids]);\n bulk.selection = [...newSelection];\n stateManager.setReadOnly(true);\n }\n\n /**\n * Remove some ids to the current bulk selection.\n *\n * The method resets the selection type if the current selection is empty.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {String} typeName the type name (section/cm)\n * @param {array} ids the list of ids\n */\n _removeIdsFromSelection(stateManager, typeName, ids) {\n const bulk = stateManager.state.bulk;\n if (!bulk?.enabled) {\n throw new Error(`Bulk is not enabled`);\n }\n if (bulk?.selectedType !== \"\" && bulk?.selectedType !== typeName) {\n throw new Error(`Cannot remove ${typeName} from the current selection`);\n }\n\n // Stored ids are strings for compatability with HTML data attributes.\n ids = ids.map(value => value.toString());\n\n stateManager.setReadOnly(false);\n const IdsToFilter = new Set(ids);\n bulk.selection = bulk.selection.filter(current => !IdsToFilter.has(current));\n if (bulk.selection.length === 0) {\n bulk.selectedType = '';\n }\n stateManager.setReadOnly(true);\n }\n\n /**\n * Get updated state data related to some cm ids.\n *\n * @method cmState\n * @param {StateManager} stateManager the current state\n * @param {array} cmids the list of cm ids to update\n */\n async cmState(stateManager, cmids) {\n this.cmLock(stateManager, cmids, true);\n const course = stateManager.get('course');\n const updates = await this._callEditWebservice('cm_state', course.id, cmids);\n stateManager.processUpdates(updates);\n this.cmLock(stateManager, cmids, false);\n }\n\n /**\n * Get updated state data related to some section ids.\n *\n * @method sectionState\n * @param {StateManager} stateManager the current state\n * @param {array} sectionIds the list of section ids to update\n */\n async sectionState(stateManager, sectionIds) {\n this.sectionLock(stateManager, sectionIds, true);\n const course = stateManager.get('course');\n const updates = await this._callEditWebservice('section_state', course.id, sectionIds);\n stateManager.processUpdates(updates);\n this.sectionLock(stateManager, sectionIds, false);\n }\n\n /**\n * Get the full updated state data of the course.\n *\n * @param {StateManager} stateManager the current state\n */\n async courseState(stateManager) {\n const course = stateManager.get('course');\n const updates = await this._callEditWebservice('course_state', course.id);\n stateManager.processUpdates(updates);\n }\n\n}\n"],"names":["action","courseId","ids","targetSectionId","targetCmId","args","courseid","targetsectionid","targetcmid","ajaxresult","ajax","call","methodname","JSON","parse","stateManager","sectionIds","course","get","sectionLock","updates","this","_callEditWebservice","id","processUpdates","cmIds","cmLock","init","addUpdateTypes","prepareFields","_prepareFields","updateName","fields","locked","_sectionBasicAction","_cmBasicAction","Set","add","forEach","cmId","cm","sectionid","Array","from","cmids","Error","cmDrag","dragValue","setPageItem","_setElementsValue","sectionDrag","cmCompletion","complete","newValue","lockValue","name","fieldName","setReadOnly","element","type","isStatic","newPageItem","undefined","pageItem","sectionId","unlockAll","state","section","collapsed","collapsedIds","_updateStateSectionPreference","preferenceName","preferenceValue","affectedSections","size","collapsedSectionIds","push","bulkEnable","enabled","bulk","selectedType","selection","bulkReset","cmSelect","_addIdsToSelection","cmUnselect","_removeIdsFromSelection","sectionSelect","sectionUnselect","typeName","map","value","toString","newSelection","IdsToFilter","filter","current","has","length"],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"mutations.min.js","sources":["../../../src/local/courseeditor/mutations.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\nimport ajax from 'core/ajax';\n\n/**\n * Default mutation manager\n *\n * @module core_courseformat/local/courseeditor/mutations\n * @class core_courseformat/local/courseeditor/mutations\n * @copyright 2021 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nexport default class {\n\n // All course editor mutations for Moodle 4.0 will be located in this file.\n\n /**\n * Private method to call core_courseformat_update_course webservice.\n *\n * @method _callEditWebservice\n * @param {string} action\n * @param {number} courseId\n * @param {array} ids\n * @param {number} targetSectionId optional target section id (for moving actions)\n * @param {number} targetCmId optional target cm id (for moving actions)\n */\n async _callEditWebservice(action, courseId, ids, targetSectionId, targetCmId) {\n const args = {\n action,\n courseid: courseId,\n ids,\n };\n if (targetSectionId) {\n args.targetsectionid = targetSectionId;\n }\n if (targetCmId) {\n args.targetcmid = targetCmId;\n }\n let ajaxresult = await ajax.call([{\n methodname: 'core_courseformat_update_course',\n args,\n }])[0];\n return JSON.parse(ajaxresult);\n }\n\n /**\n * Execute a basic section state action.\n * @param {StateManager} stateManager the current state manager\n * @param {string} action the action name\n * @param {array} sectionIds the section ids\n * @param {number} targetSectionId optional target section id (for moving actions)\n * @param {number} targetCmId optional target cm id (for moving actions)\n */\n async _sectionBasicAction(stateManager, action, sectionIds, targetSectionId, targetCmId) {\n const course = stateManager.get('course');\n this.sectionLock(stateManager, sectionIds, true);\n const updates = await this._callEditWebservice(\n action,\n course.id,\n sectionIds,\n targetSectionId,\n targetCmId\n );\n this.bulkReset(stateManager);\n stateManager.processUpdates(updates);\n this.sectionLock(stateManager, sectionIds, false);\n }\n\n /**\n * Execute a basic course module state action.\n * @param {StateManager} stateManager the current state manager\n * @param {string} action the action name\n * @param {array} cmIds the cm ids\n * @param {number} targetSectionId optional target section id (for moving actions)\n * @param {number} targetCmId optional target cm id (for moving actions)\n */\n async _cmBasicAction(stateManager, action, cmIds, targetSectionId, targetCmId) {\n const course = stateManager.get('course');\n this.cmLock(stateManager, cmIds, true);\n const updates = await this._callEditWebservice(\n action,\n course.id,\n cmIds,\n targetSectionId,\n targetCmId\n );\n this.bulkReset(stateManager);\n stateManager.processUpdates(updates);\n this.cmLock(stateManager, cmIds, false);\n }\n\n /**\n * Mutation module initialize.\n *\n * The reactive instance will execute this method when addMutations or setMutation is invoked.\n *\n * @param {StateManager} stateManager the state manager\n */\n init(stateManager) {\n // Add a method to prepare the fields when some update is comming from the server.\n stateManager.addUpdateTypes({\n prepareFields: this._prepareFields,\n });\n }\n\n /**\n * Add default values to state elements.\n *\n * This method is called every time a webservice returns a update state message.\n *\n * @param {Object} stateManager the state manager\n * @param {String} updateName the state element to update\n * @param {Object} fields the new data\n * @returns {Object} final fields data\n */\n _prepareFields(stateManager, updateName, fields) {\n // Any update should unlock the element.\n fields.locked = false;\n return fields;\n }\n\n /**\n * Hides sections.\n * @param {StateManager} stateManager the current state manager\n * @param {array} sectionIds the list of section ids\n */\n async sectionHide(stateManager, sectionIds) {\n await this._sectionBasicAction(stateManager, 'section_hide', sectionIds);\n }\n\n /**\n * Show sections.\n * @param {StateManager} stateManager the current state manager\n * @param {array} sectionIds the list of section ids\n */\n async sectionShow(stateManager, sectionIds) {\n await this._sectionBasicAction(stateManager, 'section_show', sectionIds);\n }\n\n /**\n * Show cms.\n * @param {StateManager} stateManager the current state manager\n * @param {array} cmIds the list of cm ids\n */\n async cmShow(stateManager, cmIds) {\n await this._cmBasicAction(stateManager, 'cm_show', cmIds);\n }\n\n /**\n * Hide cms.\n * @param {StateManager} stateManager the current state manager\n * @param {array} cmIds the list of cm ids\n */\n async cmHide(stateManager, cmIds) {\n await this._cmBasicAction(stateManager, 'cm_hide', cmIds);\n }\n\n /**\n * Stealth cms.\n * @param {StateManager} stateManager the current state manager\n * @param {array} cmIds the list of cm ids\n */\n async cmStealth(stateManager, cmIds) {\n await this._cmBasicAction(stateManager, 'cm_stealth', cmIds);\n }\n\n /**\n * Duplicate course modules\n * @param {StateManager} stateManager the current state manager\n * @param {array} cmIds the list of course modules ids\n * @param {number|undefined} targetSectionId the optional target sectionId\n * @param {number|undefined} targetCmId the target course module id\n */\n async cmDuplicate(stateManager, cmIds, targetSectionId, targetCmId) {\n const course = stateManager.get('course');\n // Lock all target sections.\n const sectionIds = new Set();\n if (targetSectionId) {\n sectionIds.add(targetSectionId);\n } else {\n cmIds.forEach((cmId) => {\n const cm = stateManager.get('cm', cmId);\n sectionIds.add(cm.sectionid);\n });\n }\n this.sectionLock(stateManager, Array.from(sectionIds), true);\n\n const updates = await this._callEditWebservice('cm_duplicate', course.id, cmIds, targetSectionId, targetCmId);\n stateManager.processUpdates(updates);\n\n this.sectionLock(stateManager, Array.from(sectionIds), false);\n }\n\n /**\n * Move course modules to specific course location.\n *\n * Note that one of targetSectionId or targetCmId should be provided in order to identify the\n * new location:\n * - targetCmId: the activities will be located avobe the target cm. The targetSectionId\n * value will be ignored in this case.\n * - targetSectionId: the activities will be appended to the section. In this case\n * targetSectionId should not be present.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} cmids the list of cm ids to move\n * @param {number} targetSectionId the target section id\n * @param {number} targetCmId the target course module id\n */\n async cmMove(stateManager, cmids, targetSectionId, targetCmId) {\n if (!targetSectionId && !targetCmId) {\n throw new Error(`Mutation cmMove requires targetSectionId or targetCmId`);\n }\n const course = stateManager.get('course');\n this.cmLock(stateManager, cmids, true);\n const updates = await this._callEditWebservice('cm_move', course.id, cmids, targetSectionId, targetCmId);\n stateManager.processUpdates(updates);\n this.cmLock(stateManager, cmids, false);\n }\n\n /**\n * Move course modules to specific course location.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} sectionIds the list of section ids to move\n * @param {number} targetSectionId the target section id\n */\n async sectionMove(stateManager, sectionIds, targetSectionId) {\n if (!targetSectionId) {\n throw new Error(`Mutation sectionMove requires targetSectionId`);\n }\n const course = stateManager.get('course');\n this.sectionLock(stateManager, sectionIds, true);\n const updates = await this._callEditWebservice('section_move', course.id, sectionIds, targetSectionId);\n stateManager.processUpdates(updates);\n this.sectionLock(stateManager, sectionIds, false);\n }\n\n /**\n * Add a new section to a specific course location.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {number} targetSectionId optional the target section id\n */\n async addSection(stateManager, targetSectionId) {\n if (!targetSectionId) {\n targetSectionId = 0;\n }\n const course = stateManager.get('course');\n const updates = await this._callEditWebservice('section_add', course.id, [], targetSectionId);\n stateManager.processUpdates(updates);\n }\n\n /**\n * Delete sections.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} sectionIds the list of course modules ids\n */\n async sectionDelete(stateManager, sectionIds) {\n const course = stateManager.get('course');\n const updates = await this._callEditWebservice('section_delete', course.id, sectionIds);\n stateManager.processUpdates(updates);\n }\n\n /**\n * Delete cms.\n * @param {StateManager} stateManager the current state manager\n * @param {array} cmIds the list of section ids\n */\n async cmDelete(stateManager, cmIds) {\n const course = stateManager.get('course');\n this.cmLock(stateManager, cmIds, true);\n const updates = await this._callEditWebservice('cm_delete', course.id, cmIds);\n this.cmLock(stateManager, cmIds, false);\n stateManager.processUpdates(updates);\n }\n\n /**\n * Mark or unmark course modules as dragging.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} cmIds the list of course modules ids\n * @param {bool} dragValue the new dragging value\n */\n cmDrag(stateManager, cmIds, dragValue) {\n this.setPageItem(stateManager);\n this._setElementsValue(stateManager, 'cm', cmIds, 'dragging', dragValue);\n }\n\n /**\n * Mark or unmark course sections as dragging.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} sectionIds the list of section ids\n * @param {bool} dragValue the new dragging value\n */\n sectionDrag(stateManager, sectionIds, dragValue) {\n this.setPageItem(stateManager);\n this._setElementsValue(stateManager, 'section', sectionIds, 'dragging', dragValue);\n }\n\n /**\n * Mark or unmark course modules as complete.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} cmIds the list of course modules ids\n * @param {bool} complete the new completion value\n */\n cmCompletion(stateManager, cmIds, complete) {\n const newValue = (complete) ? 1 : 0;\n this._setElementsValue(stateManager, 'cm', cmIds, 'completionstate', newValue);\n }\n\n /**\n * Lock or unlock course modules.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} cmIds the list of course modules ids\n * @param {bool} lockValue the new locked value\n */\n cmLock(stateManager, cmIds, lockValue) {\n this._setElementsValue(stateManager, 'cm', cmIds, 'locked', lockValue);\n }\n\n /**\n * Lock or unlock course sections.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} sectionIds the list of section ids\n * @param {bool} lockValue the new locked value\n */\n sectionLock(stateManager, sectionIds, lockValue) {\n this._setElementsValue(stateManager, 'section', sectionIds, 'locked', lockValue);\n }\n\n _setElementsValue(stateManager, name, ids, fieldName, newValue) {\n stateManager.setReadOnly(false);\n ids.forEach((id) => {\n const element = stateManager.get(name, id);\n if (element) {\n element[fieldName] = newValue;\n }\n });\n stateManager.setReadOnly(true);\n }\n\n /**\n * Set the page current item.\n *\n * Only one element of the course state can be the page item at a time.\n *\n * There are several actions that can alter the page current item. For example, when the user is in an activity\n * page, the page item is always the activity one. However, in a course page, when the user scrolls to an element,\n * this element get the page item.\n *\n * If the page item is static means that it is not meant to change. This is important because\n * static page items has some special logic. For example, if a cm is the static page item\n * and it is inside a collapsed section, the course index will expand the section to make it visible.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {String|undefined} type the element type (section or cm). Undefined will remove the current page item.\n * @param {Number|undefined} id the element id\n * @param {boolean|undefined} isStatic if the page item is static\n */\n setPageItem(stateManager, type, id, isStatic) {\n let newPageItem;\n if (type !== undefined) {\n newPageItem = stateManager.get(type, id);\n if (!newPageItem) {\n return;\n }\n }\n stateManager.setReadOnly(false);\n // Remove the current page item.\n const course = stateManager.get('course');\n course.pageItem = null;\n // Save the new page item.\n if (newPageItem) {\n course.pageItem = {\n id,\n type,\n sectionId: (type == 'section') ? newPageItem.id : newPageItem.sectionid,\n isStatic,\n };\n }\n stateManager.setReadOnly(true);\n }\n\n /**\n * Unlock all course elements.\n *\n * @param {StateManager} stateManager the current state manager\n */\n unlockAll(stateManager) {\n const state = stateManager.state;\n stateManager.setReadOnly(false);\n state.section.forEach((section) => {\n section.locked = false;\n });\n state.cm.forEach((cm) => {\n cm.locked = false;\n });\n stateManager.setReadOnly(true);\n }\n\n /**\n * Update the course index collapsed attribute of some sections.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} sectionIds the affected section ids\n * @param {boolean} collapsed the new collapsed value\n */\n async sectionIndexCollapsed(stateManager, sectionIds, collapsed) {\n const collapsedIds = this._updateStateSectionPreference(stateManager, 'indexcollapsed', sectionIds, collapsed);\n const course = stateManager.get('course');\n await this._callEditWebservice('section_index_collapsed', course.id, collapsedIds);\n }\n\n /**\n * Update the course content collapsed attribute of some sections.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} sectionIds the affected section ids\n * @param {boolean} collapsed the new collapsed value\n */\n async sectionContentCollapsed(stateManager, sectionIds, collapsed) {\n const collapsedIds = this._updateStateSectionPreference(stateManager, 'contentcollapsed', sectionIds, collapsed);\n const course = stateManager.get('course');\n await this._callEditWebservice('section_content_collapsed', course.id, collapsedIds);\n }\n\n /**\n * Private batch update for a section preference attribute.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {string} preferenceName the preference name\n * @param {array} sectionIds the affected section ids\n * @param {boolean} preferenceValue the new preferenceValue value\n * @return {array} the list of all sections with that preference set to true\n */\n _updateStateSectionPreference(stateManager, preferenceName, sectionIds, preferenceValue) {\n stateManager.setReadOnly(false);\n const affectedSections = new Set();\n // Check if we need to update preferences.\n sectionIds.forEach(sectionId => {\n const section = stateManager.get('section', sectionId);\n if (section === undefined) {\n return;\n }\n const newValue = preferenceValue ?? section[preferenceName];\n if (section[preferenceName] != newValue) {\n section[preferenceName] = newValue;\n affectedSections.add(section.id);\n }\n });\n stateManager.setReadOnly(true);\n if (affectedSections.size == 0) {\n return [];\n }\n // Get all collapsed section ids.\n const collapsedSectionIds = [];\n const state = stateManager.state;\n state.section.forEach(section => {\n if (section[preferenceName]) {\n collapsedSectionIds.push(section.id);\n }\n });\n return collapsedSectionIds;\n }\n\n /**\n * Enable/disable bulk editing.\n *\n * Note: reenabling the bulk will clean the current selection.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {Boolean} enabled the new bulk state.\n */\n bulkEnable(stateManager, enabled) {\n const state = stateManager.state;\n stateManager.setReadOnly(false);\n state.bulk.enabled = enabled;\n state.bulk.selectedType = '';\n state.bulk.selection = [];\n stateManager.setReadOnly(true);\n }\n\n /**\n * Reset the current selection.\n * @param {StateManager} stateManager the current state manager\n */\n bulkReset(stateManager) {\n const state = stateManager.state;\n stateManager.setReadOnly(false);\n state.bulk.selectedType = '';\n state.bulk.selection = [];\n stateManager.setReadOnly(true);\n }\n\n /**\n * Select a list of cms.\n * @param {StateManager} stateManager the current state manager\n * @param {array} cmIds the list of cm ids\n */\n cmSelect(stateManager, cmIds) {\n this._addIdsToSelection(stateManager, 'cm', cmIds);\n }\n\n /**\n * Unselect a list of cms.\n * @param {StateManager} stateManager the current state manager\n * @param {array} cmIds the list of cm ids\n */\n cmUnselect(stateManager, cmIds) {\n this._removeIdsFromSelection(stateManager, 'cm', cmIds);\n }\n\n /**\n * Select a list of sections.\n * @param {StateManager} stateManager the current state manager\n * @param {array} sectionIds the list of cm ids\n */\n sectionSelect(stateManager, sectionIds) {\n this._addIdsToSelection(stateManager, 'section', sectionIds);\n }\n\n /**\n * Unselect a list of sections.\n * @param {StateManager} stateManager the current state manager\n * @param {array} sectionIds the list of cm ids\n */\n sectionUnselect(stateManager, sectionIds) {\n this._removeIdsFromSelection(stateManager, 'section', sectionIds);\n }\n\n /**\n * Add some ids to the current bulk selection.\n * @param {StateManager} stateManager the current state manager\n * @param {String} typeName the type name (section/cm)\n * @param {array} ids the list of ids\n */\n _addIdsToSelection(stateManager, typeName, ids) {\n const bulk = stateManager.state.bulk;\n if (!bulk?.enabled) {\n throw new Error(`Bulk is not enabled`);\n }\n if (bulk?.selectedType !== \"\" && bulk?.selectedType !== typeName) {\n throw new Error(`Cannot add ${typeName} to the current selection`);\n }\n\n // Stored ids are strings for compatability with HTML data attributes.\n ids = ids.map(value => value.toString());\n\n stateManager.setReadOnly(false);\n bulk.selectedType = typeName;\n const newSelection = new Set([...bulk.selection, ...ids]);\n bulk.selection = [...newSelection];\n stateManager.setReadOnly(true);\n }\n\n /**\n * Remove some ids to the current bulk selection.\n *\n * The method resets the selection type if the current selection is empty.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {String} typeName the type name (section/cm)\n * @param {array} ids the list of ids\n */\n _removeIdsFromSelection(stateManager, typeName, ids) {\n const bulk = stateManager.state.bulk;\n if (!bulk?.enabled) {\n throw new Error(`Bulk is not enabled`);\n }\n if (bulk?.selectedType !== \"\" && bulk?.selectedType !== typeName) {\n throw new Error(`Cannot remove ${typeName} from the current selection`);\n }\n\n // Stored ids are strings for compatability with HTML data attributes.\n ids = ids.map(value => value.toString());\n\n stateManager.setReadOnly(false);\n const IdsToFilter = new Set(ids);\n bulk.selection = bulk.selection.filter(current => !IdsToFilter.has(current));\n if (bulk.selection.length === 0) {\n bulk.selectedType = '';\n }\n stateManager.setReadOnly(true);\n }\n\n /**\n * Get updated state data related to some cm ids.\n *\n * @method cmState\n * @param {StateManager} stateManager the current state\n * @param {array} cmids the list of cm ids to update\n */\n async cmState(stateManager, cmids) {\n this.cmLock(stateManager, cmids, true);\n const course = stateManager.get('course');\n const updates = await this._callEditWebservice('cm_state', course.id, cmids);\n stateManager.processUpdates(updates);\n this.cmLock(stateManager, cmids, false);\n }\n\n /**\n * Get updated state data related to some section ids.\n *\n * @method sectionState\n * @param {StateManager} stateManager the current state\n * @param {array} sectionIds the list of section ids to update\n */\n async sectionState(stateManager, sectionIds) {\n this.sectionLock(stateManager, sectionIds, true);\n const course = stateManager.get('course');\n const updates = await this._callEditWebservice('section_state', course.id, sectionIds);\n stateManager.processUpdates(updates);\n this.sectionLock(stateManager, sectionIds, false);\n }\n\n /**\n * Get the full updated state data of the course.\n *\n * @param {StateManager} stateManager the current state\n */\n async courseState(stateManager) {\n const course = stateManager.get('course');\n const updates = await this._callEditWebservice('course_state', course.id);\n stateManager.processUpdates(updates);\n }\n\n}\n"],"names":["action","courseId","ids","targetSectionId","targetCmId","args","courseid","targetsectionid","targetcmid","ajaxresult","ajax","call","methodname","JSON","parse","stateManager","sectionIds","course","get","sectionLock","updates","this","_callEditWebservice","id","bulkReset","processUpdates","cmIds","cmLock","init","addUpdateTypes","prepareFields","_prepareFields","updateName","fields","locked","_sectionBasicAction","_cmBasicAction","Set","add","forEach","cmId","cm","sectionid","Array","from","cmids","Error","cmDrag","dragValue","setPageItem","_setElementsValue","sectionDrag","cmCompletion","complete","newValue","lockValue","name","fieldName","setReadOnly","element","type","isStatic","newPageItem","undefined","pageItem","sectionId","unlockAll","state","section","collapsed","collapsedIds","_updateStateSectionPreference","preferenceName","preferenceValue","affectedSections","size","collapsedSectionIds","push","bulkEnable","enabled","bulk","selectedType","selection","cmSelect","_addIdsToSelection","cmUnselect","_removeIdsFromSelection","sectionSelect","sectionUnselect","typeName","map","value","toString","newSelection","IdsToFilter","filter","current","has","length"],"mappings":"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"} \ No newline at end of file diff --git a/course/format/amd/src/local/content/actions.js b/course/format/amd/src/local/content/actions.js index 425c6ad28b4c9..c2f3f7c94d22d 100644 --- a/course/format/amd/src/local/content/actions.js +++ b/course/format/amd/src/local/content/actions.js @@ -31,7 +31,7 @@ import ModalEvents from 'core/modal_events'; import Templates from 'core/templates'; import {prefetchStrings} from 'core/prefetch'; import {get_string as getString} from 'core/str'; -import {getList} from 'core/normalise'; +import {getList, getFirst} from 'core/normalise'; import * as CourseEvents from 'core_course/events'; import Pending from 'core/pending'; import ContentTree from 'core_courseformat/local/courseeditor/contenttree'; @@ -72,6 +72,8 @@ export default class extends BaseComponent { CONTENTTREE: `#destination-selector`, ACTIONMENU: `.action-menu`, ACTIONMENUTOGGLER: `[data-toggle="dropdown"]`, + // Availability modal selectors. + OPTIONSRADIO: `[type='radio']`, }; // Component css classes. this.classes = { @@ -174,6 +176,31 @@ export default class extends BaseComponent { this._setAddSectionLocked(state.course.sectionlist.length > state.course.maxsections); } + /** + * Return the ids represented by this element. + * + * Depending on the dataset attributes the action could represent a single id + * or a bulk actions with all the current selected ids. + * + * @param {HTMLElement} target + * @returns {Number[]} array of Ids + */ + _getTargetIds(target) { + let ids = []; + if (target?.dataset?.id) { + ids.push(target.dataset.id); + } + const bulkType = target?.dataset?.bulk; + if (!bulkType) { + return ids; + } + const bulk = this.reactive.get('bulk'); + if (bulk.enabled && bulk.selectedType === bulkType) { + ids = [...ids, ...bulk.selection]; + } + return ids; + } + /** * Handle a move section request. * @@ -498,6 +525,99 @@ export default class extends BaseComponent { ); } + /** + * Handle a cm availability change request. + * + * @param {Element} target the dispatch action element + */ + async _requestCmAvailability(target) { + const cmIds = this._getTargetIds(target); + if (cmIds.length == 0) { + return; + } + // Show the availability modal to decide which action to trigger. + const exporter = this.reactive.getExporter(); + const data = { + allowstealth: exporter.canUseStealth(this.reactive.state, cmIds), + }; + const modalParams = { + title: getString('availability', 'core'), + body: Templates.render('core_courseformat/local/content/cm/availabilitymodal', data), + saveButtonText: getString('apply', 'core'), + type: ModalFactory.types.SAVE_CANCEL, + }; + const modal = await this._modalBodyRenderedPromise(modalParams); + + this._setupMutationRadioButtonModal(modal, cmIds); + } + + /** + * Handle a section availability change request. + * + * @param {Element} target the dispatch action element + */ + async _requestSectionAvailability(target) { + const sectionIds = this._getTargetIds(target); + if (sectionIds.length == 0) { + return; + } + // Show the availability modal to decide which action to trigger. + const modalParams = { + title: getString('availability', 'core'), + body: Templates.render('core_courseformat/local/content/section/availabilitymodal', []), + saveButtonText: getString('apply', 'core'), + type: ModalFactory.types.SAVE_CANCEL, + }; + const modal = await this._modalBodyRenderedPromise(modalParams); + + this._setupMutationRadioButtonModal(modal, sectionIds); + } + + /** + * Add events to a mutation selector radio buttons modal. + * @param {Modal} modal + * @param {Number[]} ids the section or cm ids to apply the mutation + */ + _setupMutationRadioButtonModal(modal, ids) { + // The save button is not enabled until the user selects an option. + modal.setButtonDisabled('save', true); + + const submitFunction = (radio) => { + const mutation = radio?.value; + if (!mutation) { + return false; + } + this.reactive.dispatch(mutation, ids); + return true; + }; + + const modalBody = getFirst(modal.getBody()); + const radioOptions = modalBody.querySelectorAll(this.selectors.OPTIONSRADIO); + radioOptions.forEach(radio => { + radio.addEventListener('change', () => { + modal.setButtonDisabled('save', false); + }); + radio.parentNode.addEventListener('click', () => { + radio.checked = true; + modal.setButtonDisabled('save', false); + }); + radio.parentNode.addEventListener('dblclick', dbClickEvent => { + if (submitFunction(radio)) { + dbClickEvent.preventDefault(); + modal.destroy(); + } + }); + }); + + modal.getRoot().on( + ModalEvents.save, + () => { + const radio = modalBody.querySelector(`${this.selectors.OPTIONSRADIO}:checked`); + submitFunction(radio); + } + ); + } + /** * Disable all add sections actions. * diff --git a/course/format/amd/src/local/courseeditor/exporter.js b/course/format/amd/src/local/courseeditor/exporter.js index 1dc64af12df9c..196b7f59c1cd9 100644 --- a/course/format/amd/src/local/courseeditor/exporter.js +++ b/course/format/amd/src/local/courseeditor/exporter.js @@ -223,4 +223,18 @@ export default class { }); return items; } + + /** + * Check is some activities of a list can be stealth. + * + * @param {Object} state the current state. + * @param {Number[]} cmIds the module ids to check + * @returns {Boolean} if any of the activities can be stealth. + */ + canUseStealth(state, cmIds) { + return cmIds.some(cmId => { + const cminfo = state.cm.get(cmId); + return cminfo?.allowstealth ?? false; + }); + } } diff --git a/course/format/amd/src/local/courseeditor/mutations.js b/course/format/amd/src/local/courseeditor/mutations.js index 79473d7bc67c6..1d12bc8b4bbe4 100644 --- a/course/format/amd/src/local/courseeditor/mutations.js +++ b/course/format/amd/src/local/courseeditor/mutations.js @@ -74,6 +74,7 @@ export default class { targetSectionId, targetCmId ); + this.bulkReset(stateManager); stateManager.processUpdates(updates); this.sectionLock(stateManager, sectionIds, false); } @@ -96,6 +97,7 @@ export default class { targetSectionId, targetCmId ); + this.bulkReset(stateManager); stateManager.processUpdates(updates); this.cmLock(stateManager, cmIds, false); } diff --git a/course/format/classes/output/local/content/bulkedittools.php b/course/format/classes/output/local/content/bulkedittools.php index 2a23897939a1f..5adc9e109d880 100644 --- a/course/format/classes/output/local/content/bulkedittools.php +++ b/course/format/classes/output/local/content/bulkedittools.php @@ -82,7 +82,23 @@ protected function get_toolbar_actions(): array { * @return array of edit control items */ protected function cm_control_items(): array { + global $USER; + $format = $this->format; + $context = $format->get_context(); + $user = $USER; + $controls = []; + + if (has_capability('moodle/course:activityvisibility', $context, $user)) { + $controls['availability'] = [ + 'icon' => 't/show', + 'action' => 'cmAvailability', + 'name' => get_string('availability'), + 'title' => get_string('cmavailability', 'core_courseformat'), + 'bulk' => 'cm', + ]; + } + return $controls; } @@ -95,7 +111,23 @@ protected function cm_control_items(): array { * @return array of edit control items */ protected function section_control_items(): array { + global $USER; + $format = $this->format; + $context = $format->get_context(); + $user = $USER; + $controls = []; + + if (has_capability('moodle/course:sectionvisibility', $context, $user)) { + $controls['availability'] = [ + 'icon' => 't/show', + 'action' => 'sectionAvailability', + 'name' => get_string('availability'), + 'title' => get_string('sectionavailability', 'core_courseformat'), + 'bulk' => 'section', + ]; + } + return $controls; } } diff --git a/course/format/classes/output/local/state/cm.php b/course/format/classes/output/local/state/cm.php index 8d6fbc49556c7..a28a49aa0a96a 100644 --- a/course/format/classes/output/local/state/cm.php +++ b/course/format/classes/output/local/state/cm.php @@ -72,7 +72,7 @@ public function __construct(course_format $format, section_info $section, cm_inf * @return stdClass data context for a mustache template */ public function export_for_template(renderer_base $output): stdClass { - global $USER; + global $CFG, $USER; $format = $this->format; $section = $this->section; @@ -114,6 +114,8 @@ public function export_for_template(renderer_base $output): stdClass { $data->completionstate = $completiondata->completionstate; } + $data->allowstealth = !empty($CFG->allowstealth) && $format->allow_stealth_module_visibility($cm, $section); + return $data; } diff --git a/course/format/templates/local/content/cm/availabilitymodal.mustache b/course/format/templates/local/content/cm/availabilitymodal.mustache new file mode 100644 index 0000000000000..566d1cd7a8f9e --- /dev/null +++ b/course/format/templates/local/content/cm/availabilitymodal.mustache @@ -0,0 +1,96 @@ +{{! + 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 . +}} +{{! + @template core_courseformat/local/content/cm/availabilitymodal + + Displays the activity availability modal form. + + Example context (json): + { + "allowstealth": true + } + +}} +
+
+
+
+ {{#pix}} t/hide, core {{/pix}} +
+ +
+ +
+ {{#str}} availability_show_help, core_courseformat {{/str}} +
+
+
+
+
+ {{#pix}} t/show, core {{/pix}} +
+ +
+ +
+ {{#str}} availability_hide_help, core_courseformat {{/str}} +
+
+
+ {{#allowstealth}} +
+
+ {{#pix}} t/stealth, core {{/pix}} +
+ +
+ +
+ {{#str}} availability_stealth_help, core_courseformat {{/str}} +
+
+
+ {{/allowstealth}} +
+
diff --git a/course/format/templates/local/content/section/availabilitymodal.mustache b/course/format/templates/local/content/section/availabilitymodal.mustache new file mode 100644 index 0000000000000..bc18ffd21df2b --- /dev/null +++ b/course/format/templates/local/content/section/availabilitymodal.mustache @@ -0,0 +1,72 @@ +{{! + 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 . +}} +{{! + @template core_courseformat/local/content/section/availabilitymodal + + Displays the section availability modal form. + + Example context (json): + { + } + +}} +
+
+
+
+ {{#pix}} t/hide, core {{/pix}} +
+ +
+ +
+ {{#str}} availability_show_help, core_courseformat {{/str}} +
+
+
+
+
+ {{#pix}} t/show, core {{/pix}} +
+ +
+ +
+ {{#str}} availability_hide_help, core_courseformat {{/str}} +
+
+
+
+
diff --git a/course/format/tests/behat/bulk_activity_actions.feature b/course/format/tests/behat/bulk_activity_actions.feature new file mode 100644 index 0000000000000..53a2033d15800 --- /dev/null +++ b/course/format/tests/behat/bulk_activity_actions.feature @@ -0,0 +1,99 @@ +@core @core_courseformat @core_course @show_editor @javascript +Feature: Bulk course activity actions. + In order to edit the course activities + As a teacher + I need to be able to edit activities in bulk. + + Background: + Given the following "course" exists: + | fullname | Course 1 | + | shortname | C1 | + | category | 0 | + | numsections | 2 | + And the following "activities" exist: + | activity | name | intro | course | idnumber | section | + | assign | Activity sample 1 | Test assignment description | C1 | sample1 | 1 | + | assign | Activity sample 2 | Test assignment description | C1 | sample2 | 1 | + | assign | Activity sample 3 | Test assignment description | C1 | sample3 | 2 | + | assign | Activity sample 4 | Test assignment description | C1 | sample4 | 2 | + And the following "users" exist: + | username | firstname | lastname | email | + | teacher1 | Teacher | 1 | teacher1@example.com | + And the following "course enrolments" exist: + | user | course | role | + | teacher1 | C1 | editingteacher | + And the following config values are set as admin: + | allowstealth | 1 | + And I am on the "C1" "Course" page logged in as "teacher1" + And I turn editing mode on + And I click on "Bulk edit" "button" + And I should see "0 selected" in the "sticky-footer" "region" + + Scenario: Bulk hiding activities + Given I should not see "Hidden from students" in the "Activity sample 1" "activity" + And I should not see "Hidden from students" in the "Activity sample 2" "activity" + And I should not see "Hidden from students" in the "Activity sample 3" "activity" + And I should not see "Hidden from students" in the "Activity sample 4" "activity" + And I click on "Select activity Activity sample 1" "checkbox" + And I click on "Select activity Activity sample 3" "checkbox" + And I should see "2 selected" in the "sticky-footer" "region" + When I click on "Activity availability" "button" in the "sticky-footer" "region" + And I click on "Hide on course page" "radio" in the "Availability" "dialogue" + And I click on "Apply" "button" in the "Availability" "dialogue" + Then I should see "Hidden from students" in the "Activity sample 1" "activity" + And I should not see "Hidden from students" in the "Activity sample 2" "activity" + And I should see "Hidden from students" in the "Activity sample 3" "activity" + And I should not see "Hidden from students" in the "Activity sample 4" "activity" + And I should see "0 selected" in the "sticky-footer" "region" + + Scenario: Bulk showing activities + Given the following "activities" exist: + | activity | name | intro | course | idnumber | section | visible | + | assign | Activity sample 5 | Test assignment description | C1 | sample5 | 1 | 0 | + | assign | Activity sample 6 | Test assignment description | C1 | sample6 | 2 | 0 | + And I reload the page + And I click on "Bulk edit" "button" + And I should not see "Hidden from students" in the "Activity sample 4" "activity" + And I should see "Hidden from students" in the "Activity sample 5" "activity" + And I should see "Hidden from students" in the "Activity sample 6" "activity" + And I click on "Select activity Activity sample 4" "checkbox" + And I click on "Select activity Activity sample 5" "checkbox" + And I click on "Select activity Activity sample 6" "checkbox" + And I should see "3 selected" in the "sticky-footer" "region" + When I click on "Activity availability" "button" in the "sticky-footer" "region" + And I click on "Show on course page" "radio" in the "Availability" "dialogue" + And I click on "Apply" "button" in the "Availability" "dialogue" + Then I should not see "Hidden from students" in the "Activity sample 4" "activity" + And I should not see "Hidden from students" in the "Activity sample 5" "activity" + And I should not see "Hidden from students" in the "Activity sample 6" "activity" + And I should see "0 selected" in the "sticky-footer" "region" + + Scenario: Bulk stealth is only available if the site has stealth enabled + Given I click on "Select activity Activity sample 1" "checkbox" + And I should see "1 selected" in the "sticky-footer" "region" + And I click on "Activity availability" "button" in the "sticky-footer" "region" + And I should see "Make available" in the "Availability" "dialogue" + When the following config values are set as admin: + | allowstealth | 0 | + And I reload the page + And I click on "Bulk edit" "button" + Then I click on "Select activity Activity sample 1" "checkbox" + And I should see "1 selected" in the "sticky-footer" "region" + And I click on "Activity availability" "button" in the "sticky-footer" "region" + And I should not see "Make available" in the "Availability" "dialogue" + + Scenario: Bulk stealth activities + Given I click on "Select activity Activity sample 1" "checkbox" + And I click on "Activity availability" "button" in the "sticky-footer" "region" + And I click on "Hide on course page" "radio" in the "Availability" "dialogue" + And I click on "Apply" "button" in the "Availability" "dialogue" + And I should see "Hidden from students" in the "Activity sample 1" "activity" + And I should not see "Available but not shown on course page" in the "Activity sample 3" "activity" + When I click on "Select activity Activity sample 1" "checkbox" + And I click on "Select activity Activity sample 3" "checkbox" + And I should see "2 selected" in the "sticky-footer" "region" + And I click on "Activity availability" "button" in the "sticky-footer" "region" + And I click on "Make available but don't show on course page" "radio" in the "Availability" "dialogue" + And I click on "Apply" "button" in the "Availability" "dialogue" + Then I should see "Available but not shown on course page" in the "Activity sample 1" "activity" + And I should see "Available but not shown on course page" in the "Activity sample 3" "activity" diff --git a/course/format/tests/behat/bulk_section_actions.feature b/course/format/tests/behat/bulk_section_actions.feature new file mode 100644 index 0000000000000..88ec632017a44 --- /dev/null +++ b/course/format/tests/behat/bulk_section_actions.feature @@ -0,0 +1,85 @@ +@core @core_courseformat @core_course @show_editor @javascript +Feature: Bulk course section actions. + In order to edit the course section + As a teacher + I need to be able to edit sections in bulk. + + Background: + Given the following "course" exists: + | fullname | Course 1 | + | shortname | C1 | + | category | 0 | + | numsections | 4 | + And the following "activities" exist: + | activity | name | intro | course | idnumber | section | + | assign | Activity sample 1 | Test assignment description | C1 | sample1 | 1 | + | assign | Activity sample 2 | Test assignment description | C1 | sample2 | 1 | + | assign | Activity sample 3 | Test assignment description | C1 | sample3 | 2 | + | assign | Activity sample 4 | Test assignment description | C1 | sample4 | 2 | + And the following "users" exist: + | username | firstname | lastname | email | + | teacher1 | Teacher | 1 | teacher1@example.com | + And the following "course enrolments" exist: + | user | course | role | + | teacher1 | C1 | editingteacher | + And the following config values are set as admin: + | allowstealth | 1 | + And I am on the "C1" "Course" page logged in as "teacher1" + And I turn editing mode on + And I click on "Bulk edit" "button" + And I should see "0 selected" in the "sticky-footer" "region" + + Scenario: Bulk hide sections + Given I should not see "Hidden from students" in the "Activity sample 1" "activity" + And I should not see "Hidden from students" in the "Activity sample 2" "activity" + And I should not see "Hidden from students" in the "Activity sample 3" "activity" + And I should not see "Hidden from students" in the "Activity sample 4" "activity" + And I should not see "Hidden from students" in the "Topic 1" "section" + And I should not see "Hidden from students" in the "Topic 2" "section" + And I should not see "Hidden from students" in the "Topic 3" "section" + And I should not see "Hidden from students" in the "Topic 4" "section" + When I click on "Select section Topic 1" "checkbox" + And I click on "Select section Topic 2" "checkbox" + And I should see "2 selected" in the "sticky-footer" "region" + And I click on "Section availability" "button" in the "sticky-footer" "region" + And I click on "Hide on course page" "radio" in the "Availability" "dialogue" + And I click on "Apply" "button" in the "Availability" "dialogue" + Then I should see "Hidden from students" in the "Activity sample 1" "activity" + And I should see "Hidden from students" in the "Activity sample 2" "activity" + And I should see "Hidden from students" in the "Activity sample 3" "activity" + And I should see "Hidden from students" in the "Activity sample 4" "activity" + And I should see "Hidden from students" in the "Topic 1" "section" + And I should see "Hidden from students" in the "Topic 2" "section" + And I should not see "Hidden from students" in the "Topic 3" "section" + And I should not see "Hidden from students" in the "Topic 4" "section" + And I should see "0 selected" in the "sticky-footer" "region" + + Scenario: Bulk show sections + Given I click on "Select section Topic 1" "checkbox" + Given I click on "Select section Topic 3" "checkbox" + And I click on "Section availability" "button" in the "sticky-footer" "region" + And I click on "Hide on course page" "radio" in the "Availability" "dialogue" + And I click on "Apply" "button" in the "Availability" "dialogue" + And I should see "Hidden from students" in the "Activity sample 1" "activity" + And I should see "Hidden from students" in the "Activity sample 2" "activity" + And I should not see "Hidden from students" in the "Activity sample 3" "activity" + And I should not see "Hidden from students" in the "Activity sample 4" "activity" + And I should see "Hidden from students" in the "Topic 1" "section" + And I should not see "Hidden from students" in the "Topic 2" "section" + And I should see "Hidden from students" in the "Topic 3" "section" + And I should not see "Hidden from students" in the "Topic 4" "section" + When I click on "Select section Topic 1" "checkbox" + And I click on "Select section Topic 2" "checkbox" + And I should see "2 selected" in the "sticky-footer" "region" + And I click on "Section availability" "button" in the "sticky-footer" "region" + And I click on "Show on course page" "radio" in the "Availability" "dialogue" + And I click on "Apply" "button" in the "Availability" "dialogue" + Then I should not see "Hidden from students" in the "Activity sample 1" "activity" + And I should not see "Hidden from students" in the "Activity sample 2" "activity" + And I should not see "Hidden from students" in the "Activity sample 3" "activity" + And I should not see "Hidden from students" in the "Activity sample 4" "activity" + And I should not see "Hidden from students" in the "Topic 1" "section" + And I should not see "Hidden from students" in the "Topic 2" "section" + And I should see "Hidden from students" in the "Topic 3" "section" + And I should not see "Hidden from students" in the "Topic 4" "section" + And I should see "0 selected" in the "sticky-footer" "region" diff --git a/lang/en/courseformat.php b/lang/en/courseformat.php index 4a581d860920d..46943a10491fb 100644 --- a/lang/en/courseformat.php +++ b/lang/en/courseformat.php @@ -22,13 +22,21 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ +$string['availability_show'] = 'Show on course page'; +$string['availability_show_help'] = 'Available to students (subject to any access restrictions which may be set).'; +$string['availability_hide'] = 'Hide on course page'; +$string['availability_hide_help'] = 'Not available to students.'; +$string['availability_stealth'] = 'Make available but don\'t show on course page'; +$string['availability_stealth_help'] = 'Available to students if you provide a link. Activities will still appear in the gradebook and other reports.'; $string['bulkedit'] = 'Bulk edit'; $string['bulkeditoff'] = 'Close bulk edit'; $string['bulkcancel'] = 'Close bulk editing'; $string['bulkselection'] = '{$a} selected'; +$string['cmavailability'] = 'Activity availability'; $string['courseindex'] = 'Course index'; $string['nobulkaction'] = 'No bulk actions available'; $string['preference:coursesectionspreferences'] = 'Section user preferences for course {$a}'; $string['privacy:metadata:preference:coursesectionspreferences'] = 'Section user preferences like collapsed and expanded.'; +$string['sectionavailability'] = 'Section availability'; $string['selectcm'] = 'Select activity {$a}'; $string['selectsection'] = 'Select section {$a}'; diff --git a/lib/classes/output/icon_system_fontawesome.php b/lib/classes/output/icon_system_fontawesome.php index 6e8ef7cd72f25..f28ef45e6c8f1 100644 --- a/lib/classes/output/icon_system_fontawesome.php +++ b/lib/classes/output/icon_system_fontawesome.php @@ -433,6 +433,7 @@ public function get_core_icon_map() { 'core:t/sort_asc' => 'fa-sort-asc', 'core:t/sort_desc' => 'fa-sort-desc', 'core:t/sort' => 'fa-sort', + 'core:t/stealth' => 'fa-low-vision', 'core:t/stop' => 'fa-stop', 'core:t/switch_minus' => 'fa-minus', 'core:t/switch_plus' => 'fa-plus', diff --git a/pix/t/stealth.png b/pix/t/stealth.png new file mode 100644 index 0000000000000000000000000000000000000000..8f453cf4f46f20ce149eef061f79809ee4338c73 GIT binary patch literal 454 zcmV;%0XhDOP)0ORra5*Pqp;QvUvu>GN` zstG`7#k;`k=OIAH_9pPNuIqgOjYgxllD@XW8(?j&wtXe(8hF=I+W{B>l2qrMxBlrX zy^{19IBWwx21zqX-(rka2;ukASENTt_wB4;`+d&&2%xH}iKNr^a3o#loOf)00kZ9b zBBk_wF)`*U90MVQaAI2kPY^PI@_V6OE&u=k07*qoM6N<$f;w@-3jhEB literal 0 HcmV?d00001 diff --git a/pix/t/stealth.svg b/pix/t/stealth.svg new file mode 100644 index 0000000000000..9cbbd482dc20e --- /dev/null +++ b/pix/t/stealth.svg @@ -0,0 +1,33 @@ + + + + + + image/svg+xml + + + + + + + + diff --git a/theme/boost/scss/moodle/icons.scss b/theme/boost/scss/moodle/icons.scss index 17bf8fe0cae55..81397c4dbc089 100644 --- a/theme/boost/scss/moodle/icons.scss +++ b/theme/boost/scss/moodle/icons.scss @@ -5,8 +5,14 @@ $icon-width: 16px; $icon-height: 16px; // Size of big icons. +$icon-medium-width: 24px; +$icon-medium-height: 24px; +// Size of big icons. $icon-big-width: 64px; $icon-big-height: 64px; +// Size of icon boxes. +$icon-box-width: 48px; +$icon-box-height: 48px; // stylelint-disable $iconsizes: () !default; @@ -137,8 +143,8 @@ $iconsizes: map-merge(( .activityicon, .icon { margin: 0; - height: 24px; - width: 24px; + height: $icon-medium-width; + width: $icon-medium-height; } &.small { width: $activity-iconcontainer-width - 10px; @@ -156,6 +162,23 @@ $iconsizes: map-merge(( } } +.icon-box { + width: $icon-box-width; + height: $icon-box-height; + display: inline-flex; + justify-content: center; + align-items: center; + background-color: $gray-100; + border-radius: 12px; + padding: 0.7rem; + + .icon { + margin: 0; + height: $icon-medium-width; + width: $icon-medium-height; + } +} + // Make activtity colours available for custom modules. :root { @each $type, $value in $activity-icon-colors { diff --git a/theme/boost/style/moodle.css b/theme/boost/style/moodle.css index 8e86c40194be3..2f334a5e2995a 100644 --- a/theme/boost/style/moodle.css +++ b/theme/boost/style/moodle.css @@ -12550,6 +12550,20 @@ blockquote { .activityiconcontainer.interface .icon { filter: brightness(0) invert(1); } +.icon-box { + width: 48px; + height: 48px; + display: inline-flex; + justify-content: center; + align-items: center; + background-color: #f8f9fa; + border-radius: 12px; + padding: 0.7rem; } + .icon-box .icon { + margin: 0; + height: 24px; + width: 24px; } + :root { --activityadministration: #5d63f6; --activityassessment: #eb66a2; diff --git a/theme/classic/style/moodle.css b/theme/classic/style/moodle.css index ba3f8f23f5ae5..f328f41b52f36 100644 --- a/theme/classic/style/moodle.css +++ b/theme/classic/style/moodle.css @@ -12550,6 +12550,20 @@ blockquote { .activityiconcontainer.interface .icon { filter: brightness(0) invert(1); } +.icon-box { + width: 48px; + height: 48px; + display: inline-flex; + justify-content: center; + align-items: center; + background-color: #f8f9fa; + border-radius: 12px; + padding: 0.7rem; } + .icon-box .icon { + margin: 0; + height: 24px; + width: 24px; } + :root { --activityadministration: #5d63f6; --activityassessment: #eb66a2;