diff --git a/course/format/amd/build/local/content/actions.min.js b/course/format/amd/build/local/content/actions.min.js index 085c716cd06..aa5c0aac5da 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",cmMoveRight:"cmMoveRight",cmMoveLeft:"cmMoveLeft"};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 sectionIds=this._getTargetIds(target);if(0==sectionIds.length)return;event.preventDefault();const editTools=this._getClosestActionMenuToogler(target),data=this.reactive.getExporter().course(this.reactive.state);let titleText=null,sectionInfo=null;1==sectionIds.length?(sectionInfo=this.reactive.get("section",sectionIds[0]),data.sectionid=sectionInfo.id,data.sectiontitle=sectionInfo.title,data.information=await this.reactive.getFormatString("sectionmove_info",data.sectiontitle),titleText=this.reactive.getFormatString("sectionmove_title")):(data.information=await this.reactive.getFormatString("sectionsmove_info",sectionIds.length),titleText=this.reactive.getFormatString("sectionsmove_title"));const modalParams={title:titleText,body:_templates.default.render("core_courseformat/local/content/movesection",data)},modal=await this._modalBodyRenderedPromise(modalParams),modalBody=(0,_normalise.getFirst)(modal.getBody());sectionIds.forEach((sectionId=>{const currentElement=modalBody.querySelector("".concat(this.selectors.SECTIONLINK,"[data-id='").concat(sectionId,"']"));this._disableLink(currentElement)})),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("sectionMoveAfter",sectionIds,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){const sectionIds=this._getTargetIds(target);if(0==sectionIds.length)return;if(event.preventDefault(),!sectionIds.some((sectionId=>{var _sectionInfo$cmlist;const sectionInfo=this.reactive.get("section",sectionId);return(null!==(_sectionInfo$cmlist=sectionInfo.cmlist)&&void 0!==_sectionInfo$cmlist?_sectionInfo$cmlist:[]).length||sectionInfo.hassummary||sectionInfo.rawtitle})))return void this.reactive.dispatch("sectionDelete",sectionIds);let bodyText=null,titleText=null;if(1==sectionIds.length){titleText=this.reactive.getFormatString("sectiondelete_title");const sectionInfo=this.reactive.get("section",sectionIds[0]);bodyText=this.reactive.getFormatString("sectiondelete_info",{name:sectionInfo.title})}else titleText=this.reactive.getFormatString("sectionsdelete_title"),bodyText=this.reactive.getFormatString("sectionsdelete_info",{count:sectionIds.length});const modalParams={title:titleText,body:bodyText,type:_modal_factory.default.types.DELETE_CANCEL},modal=await this._modalBodyRenderedPromise(modalParams);modal.getRoot().on(_modal_events.default.delete,(e=>{e.preventDefault(),modal.destroy(),this.reactive.dispatch("sectionDelete",sectionIds)}))}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 cmIds=this._getTargetIds(target);if(0==cmIds.length)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",cmIds,sectionId)}async _requestCmDelete(target,event){const cmIds=this._getTargetIds(target);if(0==cmIds.length)return;event.preventDefault();let bodyText=null,titleText=null;if(1==cmIds.length){const cmInfo=this.reactive.get("cm",cmIds[0]);titleText=(0,_str.get_string)("cmdelete_title","core_courseformat"),bodyText=(0,_str.get_string)("cmdelete_info","core_courseformat",{type:cmInfo.modname,name:cmInfo.name})}else titleText=(0,_str.get_string)("cmsdelete_title","core_courseformat"),bodyText=(0,_str.get_string)("cmsdelete_info","core_courseformat",{count:cmIds.length});const modalParams={title:titleText,body:bodyText,type:_modal_factory.default.types.DELETE_CANCEL},modal=await this._modalBodyRenderedPromise(modalParams);modal.getRoot().on(_modal_events.default.delete,(e=>{e.preventDefault(),modal.destroy(),this.reactive.dispatch("cmDelete",cmIds)}))}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),void 0!==modalParams.deleteButtonText&&modal.setDeleteButtonText(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",cmMoveRight:"cmMoveRight",cmMoveLeft:"cmMoveLeft"};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 sectionIds=this._getTargetIds(target);if(0==sectionIds.length)return;event.preventDefault();const editTools=this._getClosestActionMenuToogler(target),data=this.reactive.getExporter().course(this.reactive.state);let titleText=null,sectionInfo=null;1==sectionIds.length?(sectionInfo=this.reactive.get("section",sectionIds[0]),data.sectionid=sectionInfo.id,data.sectiontitle=sectionInfo.title,data.information=await this.reactive.getFormatString("sectionmove_info",data.sectiontitle),titleText=this.reactive.getFormatString("sectionmove_title")):(data.information=await this.reactive.getFormatString("sectionsmove_info",sectionIds.length),titleText=this.reactive.getFormatString("sectionsmove_title"));const modalParams={title:titleText,body:_templates.default.render("core_courseformat/local/content/movesection",data)},modal=await this._modalBodyRenderedPromise(modalParams),modalBody=(0,_normalise.getFirst)(modal.getBody());sectionIds.forEach((sectionId=>{const currentElement=modalBody.querySelector("".concat(this.selectors.SECTIONLINK,"[data-id='").concat(sectionId,"']"));this._disableLink(currentElement)})),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("sectionMoveAfter",sectionIds,target.dataset.id),this._destroyModal(modal,editTools)))}))}async _requestMoveCm(target,event){const cmIds=this._getTargetIds(target);if(0==cmIds.length)return;event.preventDefault();const editTools=this._getClosestActionMenuToogler(target),exporter=this.reactive.getExporter(),data=exporter.course(this.reactive.state);let titleText=null;if(1==cmIds.length){const cmInfo=this.reactive.get("cm",cmIds[0]);data.cmid=cmInfo.id,data.cmname=cmInfo.name,data.information=await this.reactive.getFormatString("cmmove_info",data.cmname),titleText=this.reactive.getFormatString("cmmove_title")}else data.information=await this.reactive.getFormatString("cmsmove_info",cmIds.length),titleText=this.reactive.getFormatString("cmsmove_title");const modalParams={title:titleText,body:_templates.default.render("core_courseformat/local/content/movecm",data)},modal=await this._modalBodyRenderedPromise(modalParams),modalBody=(0,_normalise.getFirst)(modal.getBody());cmIds.forEach((cmId=>{const 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}),cmIds.forEach((cmId=>{var _toggler$data;const sectionnode=modalBody.querySelector("".concat(this.selectors.CMLINK,"[data-id='").concat(cmId,"']")).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");if(collapsibleId){collapsibleId=collapsibleId.replace("#","");const expandNode=modalBody.querySelector("#".concat(collapsibleId));(0,_jquery.default)(expandNode).collapse("show")}})),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",cmIds,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){const sectionIds=this._getTargetIds(target);if(0==sectionIds.length)return;if(event.preventDefault(),!sectionIds.some((sectionId=>{var _sectionInfo$cmlist;const sectionInfo=this.reactive.get("section",sectionId);return(null!==(_sectionInfo$cmlist=sectionInfo.cmlist)&&void 0!==_sectionInfo$cmlist?_sectionInfo$cmlist:[]).length||sectionInfo.hassummary||sectionInfo.rawtitle})))return void this.reactive.dispatch("sectionDelete",sectionIds);let bodyText=null,titleText=null;if(1==sectionIds.length){titleText=this.reactive.getFormatString("sectiondelete_title");const sectionInfo=this.reactive.get("section",sectionIds[0]);bodyText=this.reactive.getFormatString("sectiondelete_info",{name:sectionInfo.title})}else titleText=this.reactive.getFormatString("sectionsdelete_title"),bodyText=this.reactive.getFormatString("sectionsdelete_info",{count:sectionIds.length});const modalParams={title:titleText,body:bodyText,type:_modal_factory.default.types.DELETE_CANCEL},modal=await this._modalBodyRenderedPromise(modalParams);modal.getRoot().on(_modal_events.default.delete,(e=>{e.preventDefault(),modal.destroy(),this.reactive.dispatch("sectionDelete",sectionIds)}))}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 cmIds=this._getTargetIds(target);if(0==cmIds.length)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",cmIds,sectionId)}async _requestCmDelete(target,event){const cmIds=this._getTargetIds(target);if(0==cmIds.length)return;event.preventDefault();let bodyText=null,titleText=null;if(1==cmIds.length){const cmInfo=this.reactive.get("cm",cmIds[0]);titleText=(0,_str.get_string)("cmdelete_title","core_courseformat"),bodyText=(0,_str.get_string)("cmdelete_info","core_courseformat",{type:cmInfo.modname,name:cmInfo.name})}else titleText=(0,_str.get_string)("cmsdelete_title","core_courseformat"),bodyText=(0,_str.get_string)("cmsdelete_info","core_courseformat",{count:cmIds.length});const modalParams={title:titleText,body:bodyText,type:_modal_factory.default.types.DELETE_CANCEL},modal=await this._modalBodyRenderedPromise(modalParams);modal.getRoot().on(_modal_events.default.delete,(e=>{e.preventDefault(),modal.destroy(),this.reactive.dispatch("cmDelete",cmIds)}))}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),void 0!==modalParams.deleteButtonText&&modal.setDeleteButtonText(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 01b229d754f..880a2e82357 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, 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 cmMoveRight: 'cmMoveRight',\n cmMoveLeft: 'cmMoveLeft',\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 sectionIds = this._getTargetIds(target);\n if (sectionIds.length == 0) {\n return;\n }\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 let titleText = null;\n\n // Add the target section id and title.\n let sectionInfo = null;\n if (sectionIds.length == 1) {\n sectionInfo = this.reactive.get('section', sectionIds[0]);\n data.sectionid = sectionInfo.id;\n data.sectiontitle = sectionInfo.title;\n data.information = await this.reactive.getFormatString('sectionmove_info', data.sectiontitle);\n titleText = this.reactive.getFormatString('sectionmove_title');\n } else {\n data.information = await this.reactive.getFormatString('sectionsmove_info', sectionIds.length);\n titleText = this.reactive.getFormatString('sectionsmove_title');\n }\n\n\n // Build the modal parameters from the event data.\n const modalParams = {\n title: titleText,\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 = getFirst(modal.getBody());\n\n // Disable current selected section ids.\n sectionIds.forEach(sectionId => {\n const currentElement = modalBody.querySelector(`${this.selectors.SECTIONLINK}[data-id='${sectionId}']`);\n this._disableLink(currentElement);\n });\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('sectionMoveAfter', sectionIds, 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 const sectionIds = this._getTargetIds(target);\n if (sectionIds.length == 0) {\n return;\n }\n\n event.preventDefault();\n\n // We don't need confirmation to delete empty sections.\n let needsConfirmation = sectionIds.some(sectionId => {\n const sectionInfo = this.reactive.get('section', sectionId);\n const cmList = sectionInfo.cmlist ?? [];\n return (cmList.length || sectionInfo.hassummary || sectionInfo.rawtitle);\n });\n if (!needsConfirmation) {\n this.reactive.dispatch('sectionDelete', sectionIds);\n return;\n }\n\n let bodyText = null;\n let titleText = null;\n if (sectionIds.length == 1) {\n titleText = this.reactive.getFormatString('sectiondelete_title');\n const sectionInfo = this.reactive.get('section', sectionIds[0]);\n bodyText = this.reactive.getFormatString('sectiondelete_info', {name: sectionInfo.title});\n } else {\n titleText = this.reactive.getFormatString('sectionsdelete_title');\n bodyText = this.reactive.getFormatString('sectionsdelete_info', {count: sectionIds.length});\n }\n\n const modalParams = {\n title: titleText,\n body: bodyText,\n type: ModalFactory.types.DELETE_CANCEL,\n };\n\n const modal = await this._modalBodyRenderedPromise(modalParams);\n\n modal.getRoot().on(\n ModalEvents.delete,\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', sectionIds);\n }\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 cmIds = this._getTargetIds(target);\n if (cmIds.length == 0) {\n return;\n }\n const sectionId = target.dataset.sectionid ?? null;\n event.preventDefault();\n this.reactive.dispatch('cmDuplicate', cmIds, 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 const cmIds = this._getTargetIds(target);\n if (cmIds.length == 0) {\n return;\n }\n\n event.preventDefault();\n\n let bodyText = null;\n let titleText = null;\n if (cmIds.length == 1) {\n const cmInfo = this.reactive.get('cm', cmIds[0]);\n titleText = getString('cmdelete_title', 'core_courseformat');\n bodyText = getString(\n 'cmdelete_info',\n 'core_courseformat',\n {\n type: cmInfo.modname,\n name: cmInfo.name,\n }\n );\n } else {\n titleText = getString('cmsdelete_title', 'core_courseformat');\n bodyText = getString(\n 'cmsdelete_info',\n 'core_courseformat',\n {count: cmIds.length}\n );\n }\n\n const modalParams = {\n title: titleText,\n body: bodyText,\n type: ModalFactory.types.DELETE_CANCEL,\n };\n\n const modal = await this._modalBodyRenderedPromise(modalParams);\n\n modal.getRoot().on(\n ModalEvents.delete,\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', cmIds);\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 if (modalParams.deleteButtonText !== undefined) {\n modal.setDeleteButtonText(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","cmMoveRight","cmMoveLeft","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","sectionIds","editTools","_getClosestActionMenuToogler","data","getExporter","titleText","sectionInfo","sectionid","sectiontitle","title","information","getFormatString","modalParams","body","Templates","render","modal","_modalBodyRenderedPromise","modalBody","getBody","forEach","sectionId","currentElement","querySelector","_disableLink","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","some","hassummary","rawtitle","bodyText","count","type","ModalFactory","types","DELETE_CANCEL","getRoot","on","ModalEvents","delete","e","destroy","mutation","checked","mutationName","cmIds","modname","allowstealth","canUseStealth","saveButtonText","SAVE_CANCEL","_setupMutationRadioButtonModal","setButtonDisabled","submitFunction","radio","value","querySelectorAll","parentNode","dbClickEvent","save","locked","getElements","toggle","setElementLocked","style","pointerEvents","userSelect","add","setAttribute","Promise","resolve","reject","then","setRemoveOnClose","bodyRendered","setSaveButtonText","deleteButtonText","setDeleteButtonText","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 {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 cmMoveRight: 'cmMoveRight',\n cmMoveLeft: 'cmMoveLeft',\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 sectionIds = this._getTargetIds(target);\n if (sectionIds.length == 0) {\n return;\n }\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 let titleText = null;\n\n // Add the target section id and title.\n let sectionInfo = null;\n if (sectionIds.length == 1) {\n sectionInfo = this.reactive.get('section', sectionIds[0]);\n data.sectionid = sectionInfo.id;\n data.sectiontitle = sectionInfo.title;\n data.information = await this.reactive.getFormatString('sectionmove_info', data.sectiontitle);\n titleText = this.reactive.getFormatString('sectionmove_title');\n } else {\n data.information = await this.reactive.getFormatString('sectionsmove_info', sectionIds.length);\n titleText = this.reactive.getFormatString('sectionsmove_title');\n }\n\n\n // Build the modal parameters from the event data.\n const modalParams = {\n title: titleText,\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 = getFirst(modal.getBody());\n\n // Disable current selected section ids.\n sectionIds.forEach(sectionId => {\n const currentElement = modalBody.querySelector(`${this.selectors.SECTIONLINK}[data-id='${sectionId}']`);\n this._disableLink(currentElement);\n });\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('sectionMoveAfter', sectionIds, 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 cmIds = this._getTargetIds(target);\n if (cmIds.length == 0) {\n return;\n }\n\n event.preventDefault();\n\n // The section edit menu to refocus on end.\n const editTools = this._getClosestActionMenuToogler(target);\n\n // Collect information from the state.\n const exporter = this.reactive.getExporter();\n const data = exporter.course(this.reactive.state);\n\n let titleText = null;\n if (cmIds.length == 1) {\n const cmInfo = this.reactive.get('cm', cmIds[0]);\n data.cmid = cmInfo.id;\n data.cmname = cmInfo.name;\n data.information = await this.reactive.getFormatString('cmmove_info', data.cmname);\n titleText = this.reactive.getFormatString('cmmove_title');\n } else {\n data.information = await this.reactive.getFormatString('cmsmove_info', cmIds.length);\n titleText = this.reactive.getFormatString('cmsmove_title');\n }\n\n // Build the modal parameters from the event data.\n const modalParams = {\n title: titleText,\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 = getFirst(modal.getBody());\n\n // Disable current selected section ids.\n cmIds.forEach(cmId => {\n const currentElement = modalBody.querySelector(`${this.selectors.CMLINK}[data-id='${cmId}']`);\n this._disableLink(currentElement);\n });\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 in this code can be replaced when MDL-71979 is integrated.\n cmIds.forEach(cmId => {\n const currentElement = modalBody.querySelector(`${this.selectors.CMLINK}[data-id='${cmId}']`);\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 const expandNode = modalBody.querySelector(`#${collapsibleId}`);\n jQuery(expandNode).collapse('show');\n }\n });\n\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 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 this.reactive.dispatch('cmMove', cmIds, 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 const sectionIds = this._getTargetIds(target);\n if (sectionIds.length == 0) {\n return;\n }\n\n event.preventDefault();\n\n // We don't need confirmation to delete empty sections.\n let needsConfirmation = sectionIds.some(sectionId => {\n const sectionInfo = this.reactive.get('section', sectionId);\n const cmList = sectionInfo.cmlist ?? [];\n return (cmList.length || sectionInfo.hassummary || sectionInfo.rawtitle);\n });\n if (!needsConfirmation) {\n this.reactive.dispatch('sectionDelete', sectionIds);\n return;\n }\n\n let bodyText = null;\n let titleText = null;\n if (sectionIds.length == 1) {\n titleText = this.reactive.getFormatString('sectiondelete_title');\n const sectionInfo = this.reactive.get('section', sectionIds[0]);\n bodyText = this.reactive.getFormatString('sectiondelete_info', {name: sectionInfo.title});\n } else {\n titleText = this.reactive.getFormatString('sectionsdelete_title');\n bodyText = this.reactive.getFormatString('sectionsdelete_info', {count: sectionIds.length});\n }\n\n const modalParams = {\n title: titleText,\n body: bodyText,\n type: ModalFactory.types.DELETE_CANCEL,\n };\n\n const modal = await this._modalBodyRenderedPromise(modalParams);\n\n modal.getRoot().on(\n ModalEvents.delete,\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', sectionIds);\n }\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 cmIds = this._getTargetIds(target);\n if (cmIds.length == 0) {\n return;\n }\n const sectionId = target.dataset.sectionid ?? null;\n event.preventDefault();\n this.reactive.dispatch('cmDuplicate', cmIds, 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 const cmIds = this._getTargetIds(target);\n if (cmIds.length == 0) {\n return;\n }\n\n event.preventDefault();\n\n let bodyText = null;\n let titleText = null;\n if (cmIds.length == 1) {\n const cmInfo = this.reactive.get('cm', cmIds[0]);\n titleText = getString('cmdelete_title', 'core_courseformat');\n bodyText = getString(\n 'cmdelete_info',\n 'core_courseformat',\n {\n type: cmInfo.modname,\n name: cmInfo.name,\n }\n );\n } else {\n titleText = getString('cmsdelete_title', 'core_courseformat');\n bodyText = getString(\n 'cmsdelete_info',\n 'core_courseformat',\n {count: cmIds.length}\n );\n }\n\n const modalParams = {\n title: titleText,\n body: bodyText,\n type: ModalFactory.types.DELETE_CANCEL,\n };\n\n const modal = await this._modalBodyRenderedPromise(modalParams);\n\n modal.getRoot().on(\n ModalEvents.delete,\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', cmIds);\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 if (modalParams.deleteButtonText !== undefined) {\n modal.setDeleteButtonText(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","cmMoveRight","cmMoveLeft","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","sectionIds","editTools","_getClosestActionMenuToogler","data","getExporter","titleText","sectionInfo","sectionid","sectiontitle","title","information","getFormatString","modalParams","body","Templates","render","modal","_modalBodyRenderedPromise","modalBody","getBody","forEach","sectionId","currentElement","querySelector","_disableLink","ContentTree","SECTION","TOGGLER","COLLAPSE","matches","for","getAttribute","dispatch","_destroyModal","cmIds","exporter","cmInfo","cmid","cmname","cmId","ENTER","sectionnode","toggler","find","collapsibleId","attr","replace","expandNode","collapse","targetSectionId","targetCmId","dropData","cmDraggableData","nextcmid","section","cmlist","some","hassummary","rawtitle","bodyText","count","type","ModalFactory","types","DELETE_CANCEL","getRoot","on","ModalEvents","delete","e","destroy","mutation","checked","mutationName","modname","allowstealth","canUseStealth","saveButtonText","SAVE_CANCEL","_setupMutationRadioButtonModal","setButtonDisabled","submitFunction","radio","value","querySelectorAll","parentNode","dbClickEvent","save","locked","getElements","toggle","setElementLocked","style","pointerEvents","userSelect","add","setAttribute","Promise","resolve","reject","then","setRemoveOnClose","bodyRendered","setSaveButtonText","deleteButtonText","setDeleteButtonText","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/mutations.min.js b/course/format/amd/build/local/courseeditor/mutations.min.js index 9767bb7087f..65d4a18ccf9 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);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);this.bulkReset(stateManager),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);this.bulkReset(stateManager),stateManager.processUpdates(updates),this.sectionLock(stateManager,sectionIds,!1)}async sectionMoveAfter(stateManager,sectionIds,targetSectionId){if(!targetSectionId)throw new Error("Mutation sectionMoveAfter requires targetSectionId");const course=stateManager.get("course");this.sectionLock(stateManager,sectionIds,!0);const updates=await this._callEditWebservice("section_move_after",course.id,sectionIds,targetSectionId);this.bulkReset(stateManager),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);this.bulkReset(stateManager),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.bulkReset(stateManager),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)}async cmMoveRight(stateManager,cmIds){await this._cmBasicAction(stateManager,"cm_moveright",cmIds)}async cmMoveLeft(stateManager,cmIds){await this._cmBasicAction(stateManager,"cm_moveleft",cmIds)}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);if(!collapsedIds)return;const 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);if(!collapsedIds)return;const 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 null;const newValue=null!=preferenceValue?preferenceValue:section[preferenceName];section[preferenceName]!=newValue&&(section[preferenceName]=newValue,affectedSections.add(section.id))})),stateManager.setReadOnly(!0),0==affectedSections.size)return null;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);this.bulkReset(stateManager),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);this.bulkReset(stateManager),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);this.bulkReset(stateManager),stateManager.processUpdates(updates),this.sectionLock(stateManager,sectionIds,!1)}async sectionMoveAfter(stateManager,sectionIds,targetSectionId){if(!targetSectionId)throw new Error("Mutation sectionMoveAfter requires targetSectionId");const course=stateManager.get("course");this.sectionLock(stateManager,sectionIds,!0);const updates=await this._callEditWebservice("section_move_after",course.id,sectionIds,targetSectionId);this.bulkReset(stateManager),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);this.bulkReset(stateManager),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.bulkReset(stateManager),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)}async cmMoveRight(stateManager,cmIds){await this._cmBasicAction(stateManager,"cm_moveright",cmIds)}async cmMoveLeft(stateManager,cmIds){await this._cmBasicAction(stateManager,"cm_moveleft",cmIds)}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);if(!collapsedIds)return;const 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);if(!collapsedIds)return;const 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 null;const newValue=null!=preferenceValue?preferenceValue:section[preferenceName];section[preferenceName]!=newValue&&(section[preferenceName]=newValue,affectedSections.add(section.id))})),stateManager.setReadOnly(!0),0==affectedSections.size)return null;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 46dce71a6b0..d292396fb3e 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 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 this.bulkReset(stateManager);\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 this.bulkReset(stateManager);\n stateManager.processUpdates(updates);\n this.sectionLock(stateManager, sectionIds, false);\n }\n\n /**\n * Move course modules after a 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 sectionMoveAfter(stateManager, sectionIds, targetSectionId) {\n if (!targetSectionId) {\n throw new Error(`Mutation sectionMoveAfter requires targetSectionId`);\n }\n const course = stateManager.get('course');\n this.sectionLock(stateManager, sectionIds, true);\n const updates = await this._callEditWebservice('section_move_after', course.id, sectionIds, targetSectionId);\n this.bulkReset(stateManager);\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 this.bulkReset(stateManager);\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.bulkReset(stateManager);\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 * Move cms to the right: indent = 1.\n * @param {StateManager} stateManager the current state manager\n * @param {array} cmIds the list of cm ids\n */\n async cmMoveRight(stateManager, cmIds) {\n await this._cmBasicAction(stateManager, 'cm_moveright', cmIds);\n }\n\n /**\n * Move cms to the left: indent = 0.\n * @param {StateManager} stateManager the current state manager\n * @param {array} cmIds the list of cm ids\n */\n async cmMoveLeft(stateManager, cmIds) {\n await this._cmBasicAction(stateManager, 'cm_moveleft', cmIds);\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 if (!collapsedIds) {\n return;\n }\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 if (!collapsedIds) {\n return;\n }\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 {Number[]|null} sections ids with the preference value true or null if no update is required\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 null;\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 null;\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 +{"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 this.bulkReset(stateManager);\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 this.bulkReset(stateManager);\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 this.bulkReset(stateManager);\n stateManager.processUpdates(updates);\n this.sectionLock(stateManager, sectionIds, false);\n }\n\n /**\n * Move course modules after a 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 sectionMoveAfter(stateManager, sectionIds, targetSectionId) {\n if (!targetSectionId) {\n throw new Error(`Mutation sectionMoveAfter requires targetSectionId`);\n }\n const course = stateManager.get('course');\n this.sectionLock(stateManager, sectionIds, true);\n const updates = await this._callEditWebservice('section_move_after', course.id, sectionIds, targetSectionId);\n this.bulkReset(stateManager);\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 this.bulkReset(stateManager);\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.bulkReset(stateManager);\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 * Move cms to the right: indent = 1.\n * @param {StateManager} stateManager the current state manager\n * @param {array} cmIds the list of cm ids\n */\n async cmMoveRight(stateManager, cmIds) {\n await this._cmBasicAction(stateManager, 'cm_moveright', cmIds);\n }\n\n /**\n * Move cms to the left: indent = 0.\n * @param {StateManager} stateManager the current state manager\n * @param {array} cmIds the list of cm ids\n */\n async cmMoveLeft(stateManager, cmIds) {\n await this._cmBasicAction(stateManager, 'cm_moveleft', cmIds);\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 if (!collapsedIds) {\n return;\n }\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 if (!collapsedIds) {\n return;\n }\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 {Number[]|null} sections ids with the preference value true or null if no update is required\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 null;\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 null;\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 d2596cc2c3e..213256a449c 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, getFirst} from 'core/normalise'; +import {getFirst} from 'core/normalise'; import * as CourseEvents from 'core_course/events'; import Pending from 'core/pending'; import ContentTree from 'core_courseformat/local/courseeditor/contenttree'; @@ -291,39 +291,48 @@ export default class extends BaseComponent { */ async _requestMoveCm(target, event) { // Check we have an id. - const cmId = target.dataset.id; - if (!cmId) { + const cmIds = this._getTargetIds(target); + if (cmIds.length == 0) { return; } - const cmInfo = this.reactive.get('cm', cmId); event.preventDefault(); // The section edit menu to refocus on end. const editTools = this._getClosestActionMenuToogler(target); - // Collect section information from the state. + // Collect information from the state. const exporter = this.reactive.getExporter(); const data = exporter.course(this.reactive.state); - // Add the target cm info. - data.cmid = cmInfo.id; - data.cmname = cmInfo.name; + let titleText = null; + if (cmIds.length == 1) { + const cmInfo = this.reactive.get('cm', cmIds[0]); + data.cmid = cmInfo.id; + data.cmname = cmInfo.name; + data.information = await this.reactive.getFormatString('cmmove_info', data.cmname); + titleText = this.reactive.getFormatString('cmmove_title'); + } else { + data.information = await this.reactive.getFormatString('cmsmove_info', cmIds.length); + titleText = this.reactive.getFormatString('cmsmove_title'); + } // Build the modal parameters from the event data. const modalParams = { - title: getString('movecoursemodule', 'core'), + title: titleText, body: Templates.render('core_courseformat/local/content/movecm', data), }; // Create the modal. const modal = await this._modalBodyRenderedPromise(modalParams); - const modalBody = getList(modal.getBody())[0]; + const modalBody = getFirst(modal.getBody()); - // Disable current element. - let currentElement = modalBody.querySelector(`${this.selectors.CMLINK}[data-id='${cmId}']`); - this._disableLink(currentElement); + // Disable current selected section ids. + cmIds.forEach(cmId => { + const currentElement = modalBody.querySelector(`${this.selectors.CMLINK}[data-id='${cmId}']`); + this._disableLink(currentElement); + }); // Setup keyboard navigation. new ContentTree( @@ -337,17 +346,20 @@ export default class extends BaseComponent { ); // Open the cm section node if possible (Bootstrap 4 uses jQuery to interact with collapsibles). - // All jQuery int this code can be replaced when MDL-71979 is integrated. - const sectionnode = currentElement.closest(this.selectors.SECTIONNODE); - const toggler = jQuery(sectionnode).find(this.selectors.MODALTOGGLER); - let collapsibleId = toggler.data('target') ?? toggler.attr('href'); - if (collapsibleId) { - // We cannot be sure we have # in the id element name. - collapsibleId = collapsibleId.replace('#', ''); - jQuery(`#${collapsibleId}`).collapse('toggle'); - } + // All jQuery in this code can be replaced when MDL-71979 is integrated. + cmIds.forEach(cmId => { + const currentElement = modalBody.querySelector(`${this.selectors.CMLINK}[data-id='${cmId}']`); + const sectionnode = currentElement.closest(this.selectors.SECTIONNODE); + const toggler = jQuery(sectionnode).find(this.selectors.MODALTOGGLER); + let collapsibleId = toggler.data('target') ?? toggler.attr('href'); + if (collapsibleId) { + // We cannot be sure we have # in the id element name. + collapsibleId = collapsibleId.replace('#', ''); + const expandNode = modalBody.querySelector(`#${collapsibleId}`); + jQuery(expandNode).collapse('show'); + } + }); - // Capture click. modalBody.addEventListener('click', (event) => { const target = event.target; if (!target.matches('a') || target.dataset.for === undefined || target.dataset.id === undefined) { @@ -358,7 +370,6 @@ export default class extends BaseComponent { } event.preventDefault(); - // Get draggable data from cm or section to dispatch. let targetSectionId; let targetCmId; if (target.dataset.for == 'cm') { @@ -370,8 +381,7 @@ export default class extends BaseComponent { targetSectionId = target.dataset.id; targetCmId = section?.cmlist[0]; } - - this.reactive.dispatch('cmMove', [cmId], targetSectionId, targetCmId); + this.reactive.dispatch('cmMove', cmIds, targetSectionId, targetCmId); this._destroyModal(modal, editTools); }); } diff --git a/course/format/amd/src/local/courseeditor/mutations.js b/course/format/amd/src/local/courseeditor/mutations.js index 33e1f70b8d2..c2dc9df224d 100644 --- a/course/format/amd/src/local/courseeditor/mutations.js +++ b/course/format/amd/src/local/courseeditor/mutations.js @@ -227,6 +227,7 @@ export default class { const course = stateManager.get('course'); this.cmLock(stateManager, cmids, true); const updates = await this._callEditWebservice('cm_move', course.id, cmids, 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 90ba756a452..47dab958e5d 100644 --- a/course/format/classes/output/local/content/bulkedittools.php +++ b/course/format/classes/output/local/content/bulkedittools.php @@ -113,6 +113,14 @@ class bulkedittools implements named_templatable, renderable { $hasmanageactivities = has_capability('moodle/course:manageactivities', $context, $user); if ($hasmanageactivities) { + $controls['move'] = [ + 'icon' => 'i/dragdrop', + 'action' => 'moveCm', + 'name' => get_string('move'), + 'title' => get_string('cmsmove', 'core_courseformat'), + 'bulk' => 'cm', + ]; + $controls['delete'] = [ 'icon' => 'i/delete', 'action' => 'cmDelete', diff --git a/course/format/classes/stateactions.php b/course/format/classes/stateactions.php index 883a0c19ae2..e3d6c5bf03a 100644 --- a/course/format/classes/stateactions.php +++ b/course/format/classes/stateactions.php @@ -62,41 +62,42 @@ class stateactions { throw new moodle_exception("Action cm_move requires targetsectionid or targetcmid"); } - $this->validate_cms($course, $ids, __FUNCTION__); - - // Check capabilities on every activity context. - foreach ($ids as $cmid) { - $modcontext = context_module::instance($cmid); - require_capability('moodle/course:manageactivities', $modcontext); - } - - $modinfo = get_fast_modinfo($course); + $this->validate_cms($course, $ids, __FUNCTION__, ['moodle/course:manageactivities']); + // The moveto_module function move elements before a specific target. + // To keep the order the movements must be done in descending order (last activity first). + $ids = $this->sort_cm_ids_by_course_position($course, $ids, true); // Target cm has more priority than target section. if (!empty($targetcmid)) { $this->validate_cms($course, [$targetcmid], __FUNCTION__); - $targetcm = $modinfo->get_cm($targetcmid); - $targetsection = $modinfo->get_section_info_by_id($targetcm->section, MUST_EXIST); + $targetcm = get_fast_modinfo($course)->get_cm($targetcmid); + $targetsectionid = $targetcm->section; } else { $this->validate_sections($course, [$targetsectionid], __FUNCTION__); - $targetcm = null; - $targetsection = $modinfo->get_section_info_by_id($targetsectionid, MUST_EXIST); } // The origin sections must be updated as well. $originalsections = []; - $cms = $this->get_cm_info($modinfo, $ids); - foreach ($cms as $cm) { - $currentsection = $modinfo->get_section_info_by_id($cm->section, MUST_EXIST); - moveto_module($cm, $targetsection, $targetcm); + $beforecmdid = $targetcmid; + foreach ($ids as $cmid) { + // An updated $modinfo is needed on every loop as activities list change. + $modinfo = get_fast_modinfo($course); + $cm = $modinfo->get_cm($cmid); + $currentsectionid = $cm->section; + $targetsection = $modinfo->get_section_info_by_id($targetsectionid, MUST_EXIST); + $beforecm = (!empty($beforecmdid)) ? $modinfo->get_cm($beforecmdid) : null; + if ($beforecm === null || $beforecm->id != $cmid) { + moveto_module($cm, $targetsection, $beforecm); + } + $beforecmdid = $cm->id; $updates->add_cm_put($cm->id); - if ($currentsection->id != $targetsection->id) { - $originalsections[$currentsection->id] = true; + if ($currentsectionid != $targetsectionid) { + $originalsections[$currentsectionid] = true; } // If some of the original sections are also target sections, we don't need to update them. - if (array_key_exists($targetsection->id, $originalsections)) { - unset($originalsections[$targetsection->id]); + if (array_key_exists($targetsectionid, $originalsections)) { + unset($originalsections[$targetsectionid]); } } @@ -108,6 +109,35 @@ class stateactions { } } + /** + * Sort the cm ids list depending on the course position. + * + * Some actions like move should be done in an specific order. + * + * @param stdClass $course the course object + * @param int[] $cmids the array of section $ids + * @param bool $descending if the sort order must be descending instead of ascending + * @return int[] the array of section ids sorted by section number + */ + protected function sort_cm_ids_by_course_position( + stdClass $course, + array $cmids, + bool $descending = false + ): array { + $modinfo = get_fast_modinfo($course); + $cmlist = array_keys($modinfo->get_cms()); + $cmposition = []; + foreach ($cmids as $cmid) { + $cmposition[$cmid] = array_search($cmid, $cmlist); + } + $sorting = ($descending) ? -1 : 1; + $sortfunction = function ($acmid, $bcmid) use ($sorting, $cmposition) { + return ($cmposition[$acmid] <=> $cmposition[$bcmid]) * $sorting; + }; + usort($cmids, $sortfunction); + return $cmids; + } + /** * Move course sections to another location in the same course. * @@ -942,14 +972,15 @@ class stateactions { } /** - * Checks related to course modules: all given cm exist. + * Checks related to course modules: all given cm exist and the user has the required capabilities. * * @param stdClass $course The course where given $cmids belong. * @param array $cmids List of course module ids to validate. * @param string $info additional information in case of error. + * @param array $capabilities optional capabilities checks per each cm context. * @throws moodle_exception if any id is not valid */ - protected function validate_cms(stdClass $course, array $cmids, ?string $info = null): void { + protected function validate_cms(stdClass $course, array $cmids, ?string $info = null, array $capabilities = []): void { if (empty($cmids)) { throw new moodle_exception('emptycmids', 'core', null, $info); @@ -960,5 +991,11 @@ class stateactions { if (count($cmids) != count($intersect)) { throw new moodle_exception('unexistingcmid', 'core', null, $info); } + if (!empty($capabilities)) { + foreach ($cmids as $cmid) { + $modcontext = context_module::instance($cmid); + require_all_capabilities($capabilities, $modcontext); + } + } } } diff --git a/course/format/templates/local/content/movecm.mustache b/course/format/templates/local/content/movecm.mustache index 308c13551d5..0d31441a1fc 100644 --- a/course/format/templates/local/content/movecm.mustache +++ b/course/format/templates/local/content/movecm.mustache @@ -76,7 +76,7 @@ } }} -

{{#str}} movefull, moodle, {{cmname}} {{/str}}:

+

{{information}}: