MDL-74987 core_courseformat: alt to duplicate modules

If a user drags an activity and press the ALT key, the activity will be
duplicated in the new place instead of simply moved. This behaviour
is quite normal in many softwares and it is a non intrusive usability
improvement.
This commit is contained in:
Ferran Recio
2023-01-04 09:25:52 +01:00
parent 35a12cfffb
commit 302a5e43a6
13 changed files with 33 additions and 19 deletions
@@ -10,6 +10,6 @@ define("core_courseformat/local/courseeditor/dndcmitem",["exports","core/reactiv
* @copyright 2021 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class _default extends _reactive.BaseComponent{configDragDrop(cmid){this.id=cmid,this.reactive.isEditing&&this.reactive.supportComponents&&(this.dragdrop=new _reactive.DragDrop(this),this.classes=this.dragdrop.getClasses())}destroy(){void 0!==this.dragdrop&&this.dragdrop.unregister()}dragStart(dropdata){this.reactive.dispatch("cmDrag",[dropdata.id],!0)}dragEnd(dropdata){this.reactive.dispatch("cmDrag",[dropdata.id],!1)}getDraggableData(){return this.reactive.getExporter().cmDraggableData(this.reactive.state,this.id)}validateDropData(dropdata){return"cm"===(null==dropdata?void 0:dropdata.type)}showDropZone(dropdata){dropdata.nextcmid!=this.id&&dropdata.id!=this.id&&this.element.classList.add(this.classes.DROPUP)}hideDropZone(){this.element.classList.remove(this.classes.DROPUP)}drop(dropdata){dropdata.id!=this.id&&dropdata.nextcmid!=this.id&&this.reactive.dispatch("cmMove",[dropdata.id],null,this.id)}}return _exports.default=_default,_exports.default}));
class _default extends _reactive.BaseComponent{configDragDrop(cmid){this.id=cmid,this.reactive.isEditing&&this.reactive.supportComponents&&(this.dragdrop=new _reactive.DragDrop(this),this.classes=this.dragdrop.getClasses())}destroy(){void 0!==this.dragdrop&&this.dragdrop.unregister()}dragStart(dropdata){this.reactive.dispatch("cmDrag",[dropdata.id],!0)}dragEnd(dropdata){this.reactive.dispatch("cmDrag",[dropdata.id],!1)}getDraggableData(){return this.reactive.getExporter().cmDraggableData(this.reactive.state,this.id)}validateDropData(dropdata){return"cm"===(null==dropdata?void 0:dropdata.type)}showDropZone(dropdata){dropdata.nextcmid!=this.id&&dropdata.id!=this.id&&this.element.classList.add(this.classes.DROPUP)}hideDropZone(){this.element.classList.remove(this.classes.DROPUP)}drop(dropdata,event){if(dropdata.id!=this.id&&dropdata.nextcmid!=this.id){const mutation=event.altKey?"cmDuplicate":"cmMove";this.reactive.dispatch(mutation,[dropdata.id],null,this.id)}}}return _exports.default=_default,_exports.default}));
//# sourceMappingURL=dndcmitem.min.js.map
File diff suppressed because one or more lines are too long
@@ -10,6 +10,6 @@ define("core_courseformat/local/courseeditor/dndsection",["exports","core/reacti
* @copyright 2021 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class _default extends _reactive.BaseComponent{configState(state){this.id=this.element.dataset.id,this.section=state.section.get(this.id),this.course=state.course}configDragDrop(sectionitem){this.reactive.isEditing&&this.reactive.supportComponents&&(this.sectionitem=sectionitem,this.dragdrop=new _reactive.DragDrop(this),this.classes=this.dragdrop.getClasses())}destroy(){void 0!==this.sectionitem&&this.sectionitem.unregister(),void 0!==this.dragdrop&&this.dragdrop.unregister()}getLastCm(){return null}dragStart(dropdata){this.reactive.dispatch("sectionDrag",[dropdata.id],!0)}dragEnd(dropdata){this.reactive.dispatch("sectionDrag",[dropdata.id],!1)}validateDropData(dropdata){if("cm"===(null==dropdata?void 0:dropdata.type))return!0;if("section"===(null==dropdata?void 0:dropdata.type)){const sectionzeroid=this.course.sectionlist[0];return(null==dropdata?void 0:dropdata.id)!=this.id&&(null==dropdata?void 0:dropdata.id)!=sectionzeroid&&this.id!=sectionzeroid}return!1}showDropZone(dropdata){var _this$getLastCm;"cm"==dropdata.type&&(null===(_this$getLastCm=this.getLastCm())||void 0===_this$getLastCm||_this$getLastCm.classList.add(this.classes.DROPDOWN));"section"==dropdata.type&&(this.section.number>dropdata.number?(this.element.classList.remove(this.classes.DROPUP),this.element.classList.add(this.classes.DROPDOWN)):(this.element.classList.add(this.classes.DROPUP),this.element.classList.remove(this.classes.DROPDOWN)))}hideDropZone(){var _this$getLastCm2;null===(_this$getLastCm2=this.getLastCm())||void 0===_this$getLastCm2||_this$getLastCm2.classList.remove(this.classes.DROPDOWN),this.element.classList.remove(this.classes.DROPUP),this.element.classList.remove(this.classes.DROPDOWN)}drop(dropdata){"cm"==dropdata.type&&this.reactive.dispatch("cmMove",[dropdata.id],this.id),"section"==dropdata.type&&this.reactive.dispatch("sectionMove",[dropdata.id],this.id)}}return _exports.default=_default,_exports.default}));
class _default extends _reactive.BaseComponent{configState(state){this.id=this.element.dataset.id,this.section=state.section.get(this.id),this.course=state.course}configDragDrop(sectionitem){this.reactive.isEditing&&this.reactive.supportComponents&&(this.sectionitem=sectionitem,this.dragdrop=new _reactive.DragDrop(this),this.classes=this.dragdrop.getClasses())}destroy(){void 0!==this.sectionitem&&this.sectionitem.unregister(),void 0!==this.dragdrop&&this.dragdrop.unregister()}getLastCm(){return null}dragStart(dropdata){this.reactive.dispatch("sectionDrag",[dropdata.id],!0)}dragEnd(dropdata){this.reactive.dispatch("sectionDrag",[dropdata.id],!1)}validateDropData(dropdata){if("cm"===(null==dropdata?void 0:dropdata.type))return!0;if("section"===(null==dropdata?void 0:dropdata.type)){const sectionzeroid=this.course.sectionlist[0];return(null==dropdata?void 0:dropdata.id)!=this.id&&(null==dropdata?void 0:dropdata.id)!=sectionzeroid&&this.id!=sectionzeroid}return!1}showDropZone(dropdata){var _this$getLastCm;"cm"==dropdata.type&&(null===(_this$getLastCm=this.getLastCm())||void 0===_this$getLastCm||_this$getLastCm.classList.add(this.classes.DROPDOWN));"section"==dropdata.type&&(this.section.number>dropdata.number?(this.element.classList.remove(this.classes.DROPUP),this.element.classList.add(this.classes.DROPDOWN)):(this.element.classList.add(this.classes.DROPUP),this.element.classList.remove(this.classes.DROPDOWN)))}hideDropZone(){var _this$getLastCm2;null===(_this$getLastCm2=this.getLastCm())||void 0===_this$getLastCm2||_this$getLastCm2.classList.remove(this.classes.DROPDOWN),this.element.classList.remove(this.classes.DROPUP),this.element.classList.remove(this.classes.DROPDOWN)}drop(dropdata,event){if("cm"==dropdata.type){const mutation=event.altKey?"cmDuplicate":"cmMove";this.reactive.dispatch(mutation,[dropdata.id],this.id)}"section"==dropdata.type&&this.reactive.dispatch("sectionMove",[dropdata.id],this.id)}}return _exports.default=_default,_exports.default}));
//# sourceMappingURL=dndsection.min.js.map
File diff suppressed because one or more lines are too long
@@ -10,6 +10,6 @@ define("core_courseformat/local/courseeditor/dndsectionitem",["exports","core/re
* @copyright 2021 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class _default extends _reactive.BaseComponent{configDragDrop(sectionid,state,fullregion){this.id=sectionid,void 0===this.section&&(this.section=state.section.get(this.id)),void 0===this.course&&(this.course=state.course),this.section.number>0&&(this.getDraggableData=this._getDraggableData),this.fullregion=fullregion,this.reactive.isEditing&&this.reactive.supportComponents&&(this.dragdrop=new _reactive.DragDrop(this),this.classes=this.dragdrop.getClasses())}destroy(){void 0!==this.dragdrop&&this.dragdrop.unregister()}dragStart(dropdata){this.reactive.dispatch("sectionDrag",[dropdata.id],!0)}dragEnd(dropdata){this.reactive.dispatch("sectionDrag",[dropdata.id],!1)}_getDraggableData(){return this.reactive.getExporter().sectionDraggableData(this.reactive.state,this.id)}validateDropData(dropdata){if("cm"===(null==dropdata?void 0:dropdata.type)){var _this$section;const firstcmid=null===(_this$section=this.section)||void 0===_this$section?void 0:_this$section.cmlist[0];return dropdata.id!==firstcmid}return!1}showDropZone(){this.element.classList.add(this.classes.DROPZONE)}hideDropZone(){this.element.classList.remove(this.classes.DROPZONE)}drop(dropdata){var _this$section2;"cm"==dropdata.type&&this.reactive.dispatch("cmMove",[dropdata.id],this.id,null===(_this$section2=this.section)||void 0===_this$section2?void 0:_this$section2.cmlist[0])}}return _exports.default=_default,_exports.default}));
class _default extends _reactive.BaseComponent{configDragDrop(sectionid,state,fullregion){this.id=sectionid,void 0===this.section&&(this.section=state.section.get(this.id)),void 0===this.course&&(this.course=state.course),this.section.number>0&&(this.getDraggableData=this._getDraggableData),this.fullregion=fullregion,this.reactive.isEditing&&this.reactive.supportComponents&&(this.dragdrop=new _reactive.DragDrop(this),this.classes=this.dragdrop.getClasses())}destroy(){void 0!==this.dragdrop&&this.dragdrop.unregister()}dragStart(dropdata){this.reactive.dispatch("sectionDrag",[dropdata.id],!0)}dragEnd(dropdata){this.reactive.dispatch("sectionDrag",[dropdata.id],!1)}_getDraggableData(){return this.reactive.getExporter().sectionDraggableData(this.reactive.state,this.id)}validateDropData(dropdata){if("cm"===(null==dropdata?void 0:dropdata.type)){var _this$section;const firstcmid=null===(_this$section=this.section)||void 0===_this$section?void 0:_this$section.cmlist[0];return dropdata.id!==firstcmid}return!1}showDropZone(){this.element.classList.add(this.classes.DROPZONE)}hideDropZone(){this.element.classList.remove(this.classes.DROPZONE)}drop(dropdata,event){if("cm"==dropdata.type){var _this$section2;const mutation=event.altKey?"cmDuplicate":"cmMove";this.reactive.dispatch(mutation,[dropdata.id],this.id,null===(_this$section2=this.section)||void 0===_this$section2?void 0:_this$section2.cmlist[0])}}}return _exports.default=_default,_exports.default}));
//# sourceMappingURL=dndsectionitem.min.js.map
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -120,11 +120,13 @@ export default class extends BaseComponent {
* Drop event handler.
*
* @param {Object} dropdata the accepted drop data
* @param {Event} event the drop event
*/
drop(dropdata) {
drop(dropdata, event) {
// Call the move mutation if necessary.
if (dropdata.id != this.id && dropdata.nextcmid != this.id) {
this.reactive.dispatch('cmMove', [dropdata.id], null, this.id);
const mutation = (event.altKey) ? 'cmDuplicate' : 'cmMove';
this.reactive.dispatch(mutation, [dropdata.id], null, this.id);
}
}
@@ -151,11 +151,13 @@ export default class extends BaseComponent {
* Drop event handler.
*
* @param {Object} dropdata the accepted drop data
* @param {Event} event the drop event
*/
drop(dropdata) {
drop(dropdata, event) {
// Call the move mutation.
if (dropdata.type == 'cm') {
this.reactive.dispatch('cmMove', [dropdata.id], this.id);
const mutation = (event.altKey) ? 'cmDuplicate' : 'cmMove';
this.reactive.dispatch(mutation, [dropdata.id], this.id);
}
if (dropdata.type == 'section') {
this.reactive.dispatch('sectionMove', [dropdata.id], this.id);
@@ -135,11 +135,13 @@ export default class extends BaseComponent {
* Drop event handler.
*
* @param {Object} dropdata the accepted drop data
* @param {Event} event the drop event
*/
drop(dropdata) {
drop(dropdata, event) {
// Call the move mutation.
if (dropdata.type == 'cm') {
this.reactive.dispatch('cmMove', [dropdata.id], this.id, this.section?.cmlist[0]);
const mutation = (event.altKey) ? 'cmDuplicate' : 'cmMove';
this.reactive.dispatch(mutation, [dropdata.id], this.id, this.section?.cmlist[0]);
}
}
}
@@ -180,8 +180,9 @@ export default class {
* @param {StateManager} stateManager the current state manager
* @param {array} cmIds the list of course modules ids
* @param {number|undefined} targetSectionId the optional target sectionId
* @param {number|undefined} targetCmId the target course module id
*/
async cmDuplicate(stateManager, cmIds, targetSectionId) {
async cmDuplicate(stateManager, cmIds, targetSectionId, targetCmId) {
const course = stateManager.get('course');
// Lock all target sections.
const sectionIds = new Set();
@@ -195,7 +196,7 @@ export default class {
}
this.sectionLock(stateManager, Array.from(sectionIds), true);
const updates = await this._callEditWebservice('cm_duplicate', course.id, cmIds, targetSectionId);
const updates = await this._callEditWebservice('cm_duplicate', course.id, cmIds, targetSectionId, targetCmId);
stateManager.processUpdates(updates);
this.sectionLock(stateManager, Array.from(sectionIds), false);
+10 -3
View File
@@ -428,8 +428,8 @@ class stateactions {
* @param stateupdates $updates the affected course elements track
* @param stdClass $course the course object
* @param int[] $ids course modules ids to duplicate
* @param int $targetsectionid optional target section id destination
* @param int $targetcmid not used
* @param int|null $targetsectionid optional target section id destination
* @param int|null $targetcmid optional target before cm id destination
*/
public function cm_duplicate(
stateupdates $updates,
@@ -461,12 +461,19 @@ class stateactions {
$targetsection = $modinfo->get_section_info_by_id($targetsectionid, MUST_EXIST);
}
$beforecm = null;
if (!empty($targetcmid)) {
$this->validate_cms($course, [$targetcmid], __FUNCTION__);
$beforecm = $modinfo->get_cm($targetcmid);
$targetsection = $modinfo->get_section_info_by_id($beforecm->section, MUST_EXIST);
}
// Duplicate course modules.
$affectedcmids = [];
foreach ($cms as $cm) {
if ($newcm = duplicate_module($course, $cm)) {
if ($targetsection) {
moveto_module($newcm, $targetsection);
moveto_module($newcm, $targetsection, $beforecm);
} else {
$affectedcmids[] = $newcm->id;
}