mirror of
https://github.com/moodle/moodle.git
synced 2025-04-14 04:52:36 +02:00
Merge branch 'MDL-76848-master' of https://github.com/ferranrecio/moodle
This commit is contained in:
commit
4145046020
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -8,6 +8,6 @@ define("core_courseformat/local/courseeditor/exporter",["exports"],(function(_ex
|
||||
* @copyright 2021 Ferran Recio <ferran@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class{constructor(reactive){this.reactive=reactive,this.COMPLETIONS=["incomplete","complete","complete","fail"]}course(state){var _state$course$highlig,_state$course$section;const data={sections:[],editmode:this.reactive.isEditing,highlighted:null!==(_state$course$highlig=state.course.highlighted)&&void 0!==_state$course$highlig?_state$course$highlig:""};return(null!==(_state$course$section=state.course.sectionlist)&&void 0!==_state$course$section?_state$course$section:[]).forEach((sectionid=>{var _state$section$get;const sectioninfo=null!==(_state$section$get=state.section.get(sectionid))&&void 0!==_state$section$get?_state$section$get:{},section=this.section(state,sectioninfo);data.sections.push(section)})),data.hassections=0!=data.sections.length,data}section(state,sectioninfo){var _state$course$highlig2,_sectioninfo$cmlist;const section={...sectioninfo,highlighted:null!==(_state$course$highlig2=state.course.highlighted)&&void 0!==_state$course$highlig2?_state$course$highlig2:"",cms:[]};return(null!==(_sectioninfo$cmlist=sectioninfo.cmlist)&&void 0!==_sectioninfo$cmlist?_sectioninfo$cmlist:[]).forEach((cmid=>{const cminfo=state.cm.get(cmid),cm=this.cm(state,cminfo);section.cms.push(cm)})),section.hascms=0!=section.cms.length,section}cm(state,cminfo){return{...cminfo,isactive:!1}}cmDraggableData(state,cmid){const cminfo=state.cm.get(cmid);if(!cminfo)return null;let nextcmid;const section=state.section.get(cminfo.sectionid),currentindex=null==section?void 0:section.cmlist.indexOf(cminfo.id);return void 0!==currentindex&&(nextcmid=null==section?void 0:section.cmlist[currentindex+1]),{type:"cm",id:cminfo.id,name:cminfo.name,sectionid:cminfo.sectionid,nextcmid:nextcmid}}sectionDraggableData(state,sectionid){const sectioninfo=state.section.get(sectionid);return sectioninfo?{type:"section",id:sectioninfo.id,name:sectioninfo.name,number:sectioninfo.number}:null}fileDraggableData(state,dataTransfer){var _dataTransfer$files;const files=[];return(null===(_dataTransfer$files=dataTransfer.files)||void 0===_dataTransfer$files?void 0:_dataTransfer$files.length)>0&&dataTransfer.files.forEach((file=>{files.push(file)})),{type:"files",files:files}}cmCompletion(state,cminfo){const data={statename:"",state:"NaN"};if(void 0!==cminfo.completionstate){var _this$COMPLETIONS$cmi;data.state=cminfo.completionstate,data.hasstate=!0;const statename=null!==(_this$COMPLETIONS$cmi=this.COMPLETIONS[cminfo.completionstate])&&void 0!==_this$COMPLETIONS$cmi?_this$COMPLETIONS$cmi:"NaN";data["is".concat(statename)]=!0}return data}allItemsArray(state){var _state$course$section2;const items=[];return(null!==(_state$course$section2=state.course.sectionlist)&&void 0!==_state$course$section2?_state$course$section2:[]).forEach((sectionid=>{var _sectioninfo$cmlist2;const sectioninfo=state.section.get(sectionid);items.push({type:"section",id:sectioninfo.id,url:sectioninfo.sectionurl});(null!==(_sectioninfo$cmlist2=sectioninfo.cmlist)&&void 0!==_sectioninfo$cmlist2?_sectioninfo$cmlist2:[]).forEach((cmid=>{const cminfo=state.cm.get(cmid);items.push({type:"cm",id:cminfo.id,url:cminfo.url})}))})),items}},_exports.default}));
|
||||
class{constructor(reactive){this.reactive=reactive,this.COMPLETIONS=["incomplete","complete","complete","fail"]}course(state){var _state$course$highlig,_state$course$section;const data={sections:[],editmode:this.reactive.isEditing,highlighted:null!==(_state$course$highlig=state.course.highlighted)&&void 0!==_state$course$highlig?_state$course$highlig:""};return(null!==(_state$course$section=state.course.sectionlist)&&void 0!==_state$course$section?_state$course$section:[]).forEach((sectionid=>{var _state$section$get;const sectioninfo=null!==(_state$section$get=state.section.get(sectionid))&&void 0!==_state$section$get?_state$section$get:{},section=this.section(state,sectioninfo);data.sections.push(section)})),data.hassections=0!=data.sections.length,data}section(state,sectioninfo){var _state$course$highlig2,_sectioninfo$cmlist;const section={...sectioninfo,highlighted:null!==(_state$course$highlig2=state.course.highlighted)&&void 0!==_state$course$highlig2?_state$course$highlig2:"",cms:[]};return(null!==(_sectioninfo$cmlist=sectioninfo.cmlist)&&void 0!==_sectioninfo$cmlist?_sectioninfo$cmlist:[]).forEach((cmid=>{const cminfo=state.cm.get(cmid),cm=this.cm(state,cminfo);section.cms.push(cm)})),section.hascms=0!=section.cms.length,section}cm(state,cminfo){return{...cminfo,isactive:!1}}cmDraggableData(state,cmid){const cminfo=state.cm.get(cmid);if(!cminfo)return null;let nextcmid;const section=state.section.get(cminfo.sectionid),currentindex=null==section?void 0:section.cmlist.indexOf(cminfo.id);return void 0!==currentindex&&(nextcmid=null==section?void 0:section.cmlist[currentindex+1]),{type:"cm",id:cminfo.id,name:cminfo.name,sectionid:cminfo.sectionid,nextcmid:nextcmid}}sectionDraggableData(state,sectionid){const sectioninfo=state.section.get(sectionid);return sectioninfo?{type:"section",id:sectioninfo.id,name:sectioninfo.name,number:sectioninfo.number}:null}fileDraggableData(state,dataTransfer){var _dataTransfer$files;const files=[];return(null===(_dataTransfer$files=dataTransfer.files)||void 0===_dataTransfer$files?void 0:_dataTransfer$files.length)>0&&dataTransfer.files.forEach((file=>{files.push(file)})),{type:"files",files:files}}cmCompletion(state,cminfo){const data={statename:"",state:"NaN"};if(void 0!==cminfo.completionstate){var _this$COMPLETIONS$cmi;data.state=cminfo.completionstate,data.hasstate=!0;const statename=null!==(_this$COMPLETIONS$cmi=this.COMPLETIONS[cminfo.completionstate])&&void 0!==_this$COMPLETIONS$cmi?_this$COMPLETIONS$cmi:"NaN";data["is".concat(statename)]=!0}return data}allItemsArray(state){var _state$course$section2;const items=[];return(null!==(_state$course$section2=state.course.sectionlist)&&void 0!==_state$course$section2?_state$course$section2:[]).forEach((sectionid=>{var _sectioninfo$cmlist2;const sectioninfo=state.section.get(sectionid);items.push({type:"section",id:sectioninfo.id,url:sectioninfo.sectionurl});(null!==(_sectioninfo$cmlist2=sectioninfo.cmlist)&&void 0!==_sectioninfo$cmlist2?_sectioninfo$cmlist2:[]).forEach((cmid=>{const cminfo=state.cm.get(cmid);items.push({type:"cm",id:cminfo.id,url:cminfo.url})}))})),items}canUseStealth(state,cmIds){return cmIds.some((cmId=>{var _cminfo$allowstealth;const cminfo=state.cm.get(cmId);return null!==(_cminfo$allowstealth=null==cminfo?void 0:cminfo.allowstealth)&&void 0!==_cminfo$allowstealth&&_cminfo$allowstealth}))}},_exports.default}));
|
||||
|
||||
//# sourceMappingURL=exporter.min.js.map
|
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
@ -31,7 +31,7 @@ import ModalEvents from 'core/modal_events';
|
||||
import Templates from 'core/templates';
|
||||
import {prefetchStrings} from 'core/prefetch';
|
||||
import {get_string as getString} from 'core/str';
|
||||
import {getList} from 'core/normalise';
|
||||
import {getList, getFirst} from 'core/normalise';
|
||||
import * as CourseEvents from 'core_course/events';
|
||||
import Pending from 'core/pending';
|
||||
import ContentTree from 'core_courseformat/local/courseeditor/contenttree';
|
||||
@ -72,6 +72,8 @@ export default class extends BaseComponent {
|
||||
CONTENTTREE: `#destination-selector`,
|
||||
ACTIONMENU: `.action-menu`,
|
||||
ACTIONMENUTOGGLER: `[data-toggle="dropdown"]`,
|
||||
// Availability modal selectors.
|
||||
OPTIONSRADIO: `[type='radio']`,
|
||||
};
|
||||
// Component css classes.
|
||||
this.classes = {
|
||||
@ -174,6 +176,31 @@ export default class extends BaseComponent {
|
||||
this._setAddSectionLocked(state.course.sectionlist.length > state.course.maxsections);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the ids represented by this element.
|
||||
*
|
||||
* Depending on the dataset attributes the action could represent a single id
|
||||
* or a bulk actions with all the current selected ids.
|
||||
*
|
||||
* @param {HTMLElement} target
|
||||
* @returns {Number[]} array of Ids
|
||||
*/
|
||||
_getTargetIds(target) {
|
||||
let ids = [];
|
||||
if (target?.dataset?.id) {
|
||||
ids.push(target.dataset.id);
|
||||
}
|
||||
const bulkType = target?.dataset?.bulk;
|
||||
if (!bulkType) {
|
||||
return ids;
|
||||
}
|
||||
const bulk = this.reactive.get('bulk');
|
||||
if (bulk.enabled && bulk.selectedType === bulkType) {
|
||||
ids = [...ids, ...bulk.selection];
|
||||
}
|
||||
return ids;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a move section request.
|
||||
*
|
||||
@ -498,6 +525,99 @@ export default class extends BaseComponent {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a cm availability change request.
|
||||
*
|
||||
* @param {Element} target the dispatch action element
|
||||
*/
|
||||
async _requestCmAvailability(target) {
|
||||
const cmIds = this._getTargetIds(target);
|
||||
if (cmIds.length == 0) {
|
||||
return;
|
||||
}
|
||||
// Show the availability modal to decide which action to trigger.
|
||||
const exporter = this.reactive.getExporter();
|
||||
const data = {
|
||||
allowstealth: exporter.canUseStealth(this.reactive.state, cmIds),
|
||||
};
|
||||
const modalParams = {
|
||||
title: getString('availability', 'core'),
|
||||
body: Templates.render('core_courseformat/local/content/cm/availabilitymodal', data),
|
||||
saveButtonText: getString('apply', 'core'),
|
||||
type: ModalFactory.types.SAVE_CANCEL,
|
||||
};
|
||||
const modal = await this._modalBodyRenderedPromise(modalParams);
|
||||
|
||||
this._setupMutationRadioButtonModal(modal, cmIds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a section availability change request.
|
||||
*
|
||||
* @param {Element} target the dispatch action element
|
||||
*/
|
||||
async _requestSectionAvailability(target) {
|
||||
const sectionIds = this._getTargetIds(target);
|
||||
if (sectionIds.length == 0) {
|
||||
return;
|
||||
}
|
||||
// Show the availability modal to decide which action to trigger.
|
||||
const modalParams = {
|
||||
title: getString('availability', 'core'),
|
||||
body: Templates.render('core_courseformat/local/content/section/availabilitymodal', []),
|
||||
saveButtonText: getString('apply', 'core'),
|
||||
type: ModalFactory.types.SAVE_CANCEL,
|
||||
};
|
||||
const modal = await this._modalBodyRenderedPromise(modalParams);
|
||||
|
||||
this._setupMutationRadioButtonModal(modal, sectionIds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add events to a mutation selector radio buttons modal.
|
||||
* @param {Modal} modal
|
||||
* @param {Number[]} ids the section or cm ids to apply the mutation
|
||||
*/
|
||||
_setupMutationRadioButtonModal(modal, ids) {
|
||||
// The save button is not enabled until the user selects an option.
|
||||
modal.setButtonDisabled('save', true);
|
||||
|
||||
const submitFunction = (radio) => {
|
||||
const mutation = radio?.value;
|
||||
if (!mutation) {
|
||||
return false;
|
||||
}
|
||||
this.reactive.dispatch(mutation, ids);
|
||||
return true;
|
||||
};
|
||||
|
||||
const modalBody = getFirst(modal.getBody());
|
||||
const radioOptions = modalBody.querySelectorAll(this.selectors.OPTIONSRADIO);
|
||||
radioOptions.forEach(radio => {
|
||||
radio.addEventListener('change', () => {
|
||||
modal.setButtonDisabled('save', false);
|
||||
});
|
||||
radio.parentNode.addEventListener('click', () => {
|
||||
radio.checked = true;
|
||||
modal.setButtonDisabled('save', false);
|
||||
});
|
||||
radio.parentNode.addEventListener('dblclick', dbClickEvent => {
|
||||
if (submitFunction(radio)) {
|
||||
dbClickEvent.preventDefault();
|
||||
modal.destroy();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
modal.getRoot().on(
|
||||
ModalEvents.save,
|
||||
() => {
|
||||
const radio = modalBody.querySelector(`${this.selectors.OPTIONSRADIO}:checked`);
|
||||
submitFunction(radio);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable all add sections actions.
|
||||
*
|
||||
|
@ -223,4 +223,18 @@ export default class {
|
||||
});
|
||||
return items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check is some activities of a list can be stealth.
|
||||
*
|
||||
* @param {Object} state the current state.
|
||||
* @param {Number[]} cmIds the module ids to check
|
||||
* @returns {Boolean} if any of the activities can be stealth.
|
||||
*/
|
||||
canUseStealth(state, cmIds) {
|
||||
return cmIds.some(cmId => {
|
||||
const cminfo = state.cm.get(cmId);
|
||||
return cminfo?.allowstealth ?? false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -74,6 +74,7 @@ export default class {
|
||||
targetSectionId,
|
||||
targetCmId
|
||||
);
|
||||
this.bulkReset(stateManager);
|
||||
stateManager.processUpdates(updates);
|
||||
this.sectionLock(stateManager, sectionIds, false);
|
||||
}
|
||||
@ -96,6 +97,7 @@ export default class {
|
||||
targetSectionId,
|
||||
targetCmId
|
||||
);
|
||||
this.bulkReset(stateManager);
|
||||
stateManager.processUpdates(updates);
|
||||
this.cmLock(stateManager, cmIds, false);
|
||||
}
|
||||
|
@ -82,7 +82,23 @@ class bulkedittools implements named_templatable, renderable {
|
||||
* @return array of edit control items
|
||||
*/
|
||||
protected function cm_control_items(): array {
|
||||
global $USER;
|
||||
$format = $this->format;
|
||||
$context = $format->get_context();
|
||||
$user = $USER;
|
||||
|
||||
$controls = [];
|
||||
|
||||
if (has_capability('moodle/course:activityvisibility', $context, $user)) {
|
||||
$controls['availability'] = [
|
||||
'icon' => 't/show',
|
||||
'action' => 'cmAvailability',
|
||||
'name' => get_string('availability'),
|
||||
'title' => get_string('cmavailability', 'core_courseformat'),
|
||||
'bulk' => 'cm',
|
||||
];
|
||||
}
|
||||
|
||||
return $controls;
|
||||
}
|
||||
|
||||
@ -95,7 +111,23 @@ class bulkedittools implements named_templatable, renderable {
|
||||
* @return array of edit control items
|
||||
*/
|
||||
protected function section_control_items(): array {
|
||||
global $USER;
|
||||
$format = $this->format;
|
||||
$context = $format->get_context();
|
||||
$user = $USER;
|
||||
|
||||
$controls = [];
|
||||
|
||||
if (has_capability('moodle/course:sectionvisibility', $context, $user)) {
|
||||
$controls['availability'] = [
|
||||
'icon' => 't/show',
|
||||
'action' => 'sectionAvailability',
|
||||
'name' => get_string('availability'),
|
||||
'title' => get_string('sectionavailability', 'core_courseformat'),
|
||||
'bulk' => 'section',
|
||||
];
|
||||
}
|
||||
|
||||
return $controls;
|
||||
}
|
||||
}
|
||||
|
@ -72,7 +72,7 @@ class cm implements renderable {
|
||||
* @return stdClass data context for a mustache template
|
||||
*/
|
||||
public function export_for_template(renderer_base $output): stdClass {
|
||||
global $USER;
|
||||
global $CFG, $USER;
|
||||
|
||||
$format = $this->format;
|
||||
$section = $this->section;
|
||||
@ -114,6 +114,8 @@ class cm implements renderable {
|
||||
$data->completionstate = $completiondata->completionstate;
|
||||
}
|
||||
|
||||
$data->allowstealth = !empty($CFG->allowstealth) && $format->allow_stealth_module_visibility($cm, $section);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,96 @@
|
||||
{{!
|
||||
This file is part of Moodle - http://moodle.org/
|
||||
|
||||
Moodle is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Moodle is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
}}
|
||||
{{!
|
||||
@template core_courseformat/local/content/cm/availabilitymodal
|
||||
|
||||
Displays the activity availability modal form.
|
||||
|
||||
Example context (json):
|
||||
{
|
||||
"allowstealth": true
|
||||
}
|
||||
|
||||
}}
|
||||
<div class="d-flex flex-column p-3">
|
||||
<form>
|
||||
<div class="d-flex flex-row align-items-start py-3 border-bottom">
|
||||
<div class="icon-box mx-2">
|
||||
{{#pix}} t/hide, core {{/pix}}
|
||||
</div>
|
||||
<input
|
||||
class="mt-2 mx-2"
|
||||
type="radio"
|
||||
id="showRadio"
|
||||
name="option"
|
||||
value="cmShow"
|
||||
aria-describedby="showRadio_help"
|
||||
>
|
||||
<div class="w-100">
|
||||
<label class="mb-1" for="showRadio">
|
||||
{{#str}} availability_show, core_courseformat {{/str}}
|
||||
</label>
|
||||
<div id="showRadio_help" class="small text-muted">
|
||||
{{#str}} availability_show_help, core_courseformat {{/str}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex flex-row align-items-start py-3 {{#allowstealth}} border-bottom {{/allowstealth}}">
|
||||
<div class="icon-box mx-2">
|
||||
{{#pix}} t/show, core {{/pix}}
|
||||
</div>
|
||||
<input
|
||||
class="mt-2 mx-2"
|
||||
type="radio"
|
||||
id="hideRadio"
|
||||
name="option"
|
||||
value="cmHide"
|
||||
aria-describedby="hideRadio_help"
|
||||
>
|
||||
<div class="w-100">
|
||||
<label class="mt-1" for="hideRadio">
|
||||
{{#str}} availability_hide, core_courseformat {{/str}}
|
||||
</label>
|
||||
<div id="hideRadio_help" class="small text-muted">
|
||||
{{#str}} availability_hide_help, core_courseformat {{/str}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{#allowstealth}}
|
||||
<div class="d-flex flex-row align-items-start py-3">
|
||||
<div class="icon-box mx-2">
|
||||
{{#pix}} t/stealth, core {{/pix}}
|
||||
</div>
|
||||
<input
|
||||
class="mt-2 mx-2"
|
||||
type="radio"
|
||||
id="stealthRadio"
|
||||
name="option"
|
||||
value="cmStealth"
|
||||
aria-describedby="stealthRadio_help"
|
||||
>
|
||||
<div class="w-100">
|
||||
<label class="mt-1" for="stealthRadio">
|
||||
{{#str}} availability_stealth, core_courseformat {{/str}}
|
||||
</label>
|
||||
<div id="stealthRadio_help" class="small text-muted">
|
||||
{{#str}} availability_stealth_help, core_courseformat {{/str}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{/allowstealth}}
|
||||
</form>
|
||||
</div>
|
@ -0,0 +1,72 @@
|
||||
{{!
|
||||
This file is part of Moodle - http://moodle.org/
|
||||
|
||||
Moodle is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Moodle is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
}}
|
||||
{{!
|
||||
@template core_courseformat/local/content/section/availabilitymodal
|
||||
|
||||
Displays the section availability modal form.
|
||||
|
||||
Example context (json):
|
||||
{
|
||||
}
|
||||
|
||||
}}
|
||||
<div class="d-flex flex-column p-3">
|
||||
<form>
|
||||
<div class="d-flex flex-row align-items-start py-3 border-bottom">
|
||||
<div class="icon-box mx-2">
|
||||
{{#pix}} t/hide, core {{/pix}}
|
||||
</div>
|
||||
<input
|
||||
class="mt-2 mx-2"
|
||||
type="radio"
|
||||
id="showSectionRadio"
|
||||
name="option"
|
||||
value="sectionShow"
|
||||
aria-describedby="showRadio_help"
|
||||
>
|
||||
<div class="w-100">
|
||||
<label class="mb-1" for="showSectionRadio">
|
||||
{{#str}} availability_show, core_courseformat {{/str}}
|
||||
</label>
|
||||
<div id="showRadio_help" class="small text-muted">
|
||||
{{#str}} availability_show_help, core_courseformat {{/str}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex flex-row align-items-start py-3">
|
||||
<div class="icon-box mx-2">
|
||||
{{#pix}} t/show, core {{/pix}}
|
||||
</div>
|
||||
<input
|
||||
class="mt-2 mx-2"
|
||||
type="radio"
|
||||
id="hideSectionRadio"
|
||||
name="option"
|
||||
value="sectionHide"
|
||||
aria-describedby="hideRadio_help"
|
||||
>
|
||||
<div class="w-100">
|
||||
<label class="mb-1" for="hideSectionRadio">
|
||||
{{#str}} availability_hide, core_courseformat {{/str}}
|
||||
</label>
|
||||
<div id="hideRadio_help" class="small text-muted">
|
||||
{{#str}} availability_hide_help, core_courseformat {{/str}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
99
course/format/tests/behat/bulk_activity_actions.feature
Normal file
99
course/format/tests/behat/bulk_activity_actions.feature
Normal file
@ -0,0 +1,99 @@
|
||||
@core @core_courseformat @core_course @show_editor @javascript
|
||||
Feature: Bulk course activity actions.
|
||||
In order to edit the course activities
|
||||
As a teacher
|
||||
I need to be able to edit activities in bulk.
|
||||
|
||||
Background:
|
||||
Given the following "course" exists:
|
||||
| fullname | Course 1 |
|
||||
| shortname | C1 |
|
||||
| category | 0 |
|
||||
| numsections | 2 |
|
||||
And the following "activities" exist:
|
||||
| activity | name | intro | course | idnumber | section |
|
||||
| assign | Activity sample 1 | Test assignment description | C1 | sample1 | 1 |
|
||||
| assign | Activity sample 2 | Test assignment description | C1 | sample2 | 1 |
|
||||
| assign | Activity sample 3 | Test assignment description | C1 | sample3 | 2 |
|
||||
| assign | Activity sample 4 | Test assignment description | C1 | sample4 | 2 |
|
||||
And the following "users" exist:
|
||||
| username | firstname | lastname | email |
|
||||
| teacher1 | Teacher | 1 | teacher1@example.com |
|
||||
And the following "course enrolments" exist:
|
||||
| user | course | role |
|
||||
| teacher1 | C1 | editingteacher |
|
||||
And the following config values are set as admin:
|
||||
| allowstealth | 1 |
|
||||
And I am on the "C1" "Course" page logged in as "teacher1"
|
||||
And I turn editing mode on
|
||||
And I click on "Bulk edit" "button"
|
||||
And I should see "0 selected" in the "sticky-footer" "region"
|
||||
|
||||
Scenario: Bulk hiding activities
|
||||
Given I should not see "Hidden from students" in the "Activity sample 1" "activity"
|
||||
And I should not see "Hidden from students" in the "Activity sample 2" "activity"
|
||||
And I should not see "Hidden from students" in the "Activity sample 3" "activity"
|
||||
And I should not see "Hidden from students" in the "Activity sample 4" "activity"
|
||||
And I click on "Select activity Activity sample 1" "checkbox"
|
||||
And I click on "Select activity Activity sample 3" "checkbox"
|
||||
And I should see "2 selected" in the "sticky-footer" "region"
|
||||
When I click on "Activity availability" "button" in the "sticky-footer" "region"
|
||||
And I click on "Hide on course page" "radio" in the "Availability" "dialogue"
|
||||
And I click on "Apply" "button" in the "Availability" "dialogue"
|
||||
Then I should see "Hidden from students" in the "Activity sample 1" "activity"
|
||||
And I should not see "Hidden from students" in the "Activity sample 2" "activity"
|
||||
And I should see "Hidden from students" in the "Activity sample 3" "activity"
|
||||
And I should not see "Hidden from students" in the "Activity sample 4" "activity"
|
||||
And I should see "0 selected" in the "sticky-footer" "region"
|
||||
|
||||
Scenario: Bulk showing activities
|
||||
Given the following "activities" exist:
|
||||
| activity | name | intro | course | idnumber | section | visible |
|
||||
| assign | Activity sample 5 | Test assignment description | C1 | sample5 | 1 | 0 |
|
||||
| assign | Activity sample 6 | Test assignment description | C1 | sample6 | 2 | 0 |
|
||||
And I reload the page
|
||||
And I click on "Bulk edit" "button"
|
||||
And I should not see "Hidden from students" in the "Activity sample 4" "activity"
|
||||
And I should see "Hidden from students" in the "Activity sample 5" "activity"
|
||||
And I should see "Hidden from students" in the "Activity sample 6" "activity"
|
||||
And I click on "Select activity Activity sample 4" "checkbox"
|
||||
And I click on "Select activity Activity sample 5" "checkbox"
|
||||
And I click on "Select activity Activity sample 6" "checkbox"
|
||||
And I should see "3 selected" in the "sticky-footer" "region"
|
||||
When I click on "Activity availability" "button" in the "sticky-footer" "region"
|
||||
And I click on "Show on course page" "radio" in the "Availability" "dialogue"
|
||||
And I click on "Apply" "button" in the "Availability" "dialogue"
|
||||
Then I should not see "Hidden from students" in the "Activity sample 4" "activity"
|
||||
And I should not see "Hidden from students" in the "Activity sample 5" "activity"
|
||||
And I should not see "Hidden from students" in the "Activity sample 6" "activity"
|
||||
And I should see "0 selected" in the "sticky-footer" "region"
|
||||
|
||||
Scenario: Bulk stealth is only available if the site has stealth enabled
|
||||
Given I click on "Select activity Activity sample 1" "checkbox"
|
||||
And I should see "1 selected" in the "sticky-footer" "region"
|
||||
And I click on "Activity availability" "button" in the "sticky-footer" "region"
|
||||
And I should see "Make available" in the "Availability" "dialogue"
|
||||
When the following config values are set as admin:
|
||||
| allowstealth | 0 |
|
||||
And I reload the page
|
||||
And I click on "Bulk edit" "button"
|
||||
Then I click on "Select activity Activity sample 1" "checkbox"
|
||||
And I should see "1 selected" in the "sticky-footer" "region"
|
||||
And I click on "Activity availability" "button" in the "sticky-footer" "region"
|
||||
And I should not see "Make available" in the "Availability" "dialogue"
|
||||
|
||||
Scenario: Bulk stealth activities
|
||||
Given I click on "Select activity Activity sample 1" "checkbox"
|
||||
And I click on "Activity availability" "button" in the "sticky-footer" "region"
|
||||
And I click on "Hide on course page" "radio" in the "Availability" "dialogue"
|
||||
And I click on "Apply" "button" in the "Availability" "dialogue"
|
||||
And I should see "Hidden from students" in the "Activity sample 1" "activity"
|
||||
And I should not see "Available but not shown on course page" in the "Activity sample 3" "activity"
|
||||
When I click on "Select activity Activity sample 1" "checkbox"
|
||||
And I click on "Select activity Activity sample 3" "checkbox"
|
||||
And I should see "2 selected" in the "sticky-footer" "region"
|
||||
And I click on "Activity availability" "button" in the "sticky-footer" "region"
|
||||
And I click on "Make available but don't show on course page" "radio" in the "Availability" "dialogue"
|
||||
And I click on "Apply" "button" in the "Availability" "dialogue"
|
||||
Then I should see "Available but not shown on course page" in the "Activity sample 1" "activity"
|
||||
And I should see "Available but not shown on course page" in the "Activity sample 3" "activity"
|
85
course/format/tests/behat/bulk_section_actions.feature
Normal file
85
course/format/tests/behat/bulk_section_actions.feature
Normal file
@ -0,0 +1,85 @@
|
||||
@core @core_courseformat @core_course @show_editor @javascript
|
||||
Feature: Bulk course section actions.
|
||||
In order to edit the course section
|
||||
As a teacher
|
||||
I need to be able to edit sections in bulk.
|
||||
|
||||
Background:
|
||||
Given the following "course" exists:
|
||||
| fullname | Course 1 |
|
||||
| shortname | C1 |
|
||||
| category | 0 |
|
||||
| numsections | 4 |
|
||||
And the following "activities" exist:
|
||||
| activity | name | intro | course | idnumber | section |
|
||||
| assign | Activity sample 1 | Test assignment description | C1 | sample1 | 1 |
|
||||
| assign | Activity sample 2 | Test assignment description | C1 | sample2 | 1 |
|
||||
| assign | Activity sample 3 | Test assignment description | C1 | sample3 | 2 |
|
||||
| assign | Activity sample 4 | Test assignment description | C1 | sample4 | 2 |
|
||||
And the following "users" exist:
|
||||
| username | firstname | lastname | email |
|
||||
| teacher1 | Teacher | 1 | teacher1@example.com |
|
||||
And the following "course enrolments" exist:
|
||||
| user | course | role |
|
||||
| teacher1 | C1 | editingteacher |
|
||||
And the following config values are set as admin:
|
||||
| allowstealth | 1 |
|
||||
And I am on the "C1" "Course" page logged in as "teacher1"
|
||||
And I turn editing mode on
|
||||
And I click on "Bulk edit" "button"
|
||||
And I should see "0 selected" in the "sticky-footer" "region"
|
||||
|
||||
Scenario: Bulk hide sections
|
||||
Given I should not see "Hidden from students" in the "Activity sample 1" "activity"
|
||||
And I should not see "Hidden from students" in the "Activity sample 2" "activity"
|
||||
And I should not see "Hidden from students" in the "Activity sample 3" "activity"
|
||||
And I should not see "Hidden from students" in the "Activity sample 4" "activity"
|
||||
And I should not see "Hidden from students" in the "Topic 1" "section"
|
||||
And I should not see "Hidden from students" in the "Topic 2" "section"
|
||||
And I should not see "Hidden from students" in the "Topic 3" "section"
|
||||
And I should not see "Hidden from students" in the "Topic 4" "section"
|
||||
When I click on "Select section Topic 1" "checkbox"
|
||||
And I click on "Select section Topic 2" "checkbox"
|
||||
And I should see "2 selected" in the "sticky-footer" "region"
|
||||
And I click on "Section availability" "button" in the "sticky-footer" "region"
|
||||
And I click on "Hide on course page" "radio" in the "Availability" "dialogue"
|
||||
And I click on "Apply" "button" in the "Availability" "dialogue"
|
||||
Then I should see "Hidden from students" in the "Activity sample 1" "activity"
|
||||
And I should see "Hidden from students" in the "Activity sample 2" "activity"
|
||||
And I should see "Hidden from students" in the "Activity sample 3" "activity"
|
||||
And I should see "Hidden from students" in the "Activity sample 4" "activity"
|
||||
And I should see "Hidden from students" in the "Topic 1" "section"
|
||||
And I should see "Hidden from students" in the "Topic 2" "section"
|
||||
And I should not see "Hidden from students" in the "Topic 3" "section"
|
||||
And I should not see "Hidden from students" in the "Topic 4" "section"
|
||||
And I should see "0 selected" in the "sticky-footer" "region"
|
||||
|
||||
Scenario: Bulk show sections
|
||||
Given I click on "Select section Topic 1" "checkbox"
|
||||
Given I click on "Select section Topic 3" "checkbox"
|
||||
And I click on "Section availability" "button" in the "sticky-footer" "region"
|
||||
And I click on "Hide on course page" "radio" in the "Availability" "dialogue"
|
||||
And I click on "Apply" "button" in the "Availability" "dialogue"
|
||||
And I should see "Hidden from students" in the "Activity sample 1" "activity"
|
||||
And I should see "Hidden from students" in the "Activity sample 2" "activity"
|
||||
And I should not see "Hidden from students" in the "Activity sample 3" "activity"
|
||||
And I should not see "Hidden from students" in the "Activity sample 4" "activity"
|
||||
And I should see "Hidden from students" in the "Topic 1" "section"
|
||||
And I should not see "Hidden from students" in the "Topic 2" "section"
|
||||
And I should see "Hidden from students" in the "Topic 3" "section"
|
||||
And I should not see "Hidden from students" in the "Topic 4" "section"
|
||||
When I click on "Select section Topic 1" "checkbox"
|
||||
And I click on "Select section Topic 2" "checkbox"
|
||||
And I should see "2 selected" in the "sticky-footer" "region"
|
||||
And I click on "Section availability" "button" in the "sticky-footer" "region"
|
||||
And I click on "Show on course page" "radio" in the "Availability" "dialogue"
|
||||
And I click on "Apply" "button" in the "Availability" "dialogue"
|
||||
Then I should not see "Hidden from students" in the "Activity sample 1" "activity"
|
||||
And I should not see "Hidden from students" in the "Activity sample 2" "activity"
|
||||
And I should not see "Hidden from students" in the "Activity sample 3" "activity"
|
||||
And I should not see "Hidden from students" in the "Activity sample 4" "activity"
|
||||
And I should not see "Hidden from students" in the "Topic 1" "section"
|
||||
And I should not see "Hidden from students" in the "Topic 2" "section"
|
||||
And I should see "Hidden from students" in the "Topic 3" "section"
|
||||
And I should not see "Hidden from students" in the "Topic 4" "section"
|
||||
And I should see "0 selected" in the "sticky-footer" "region"
|
@ -22,13 +22,21 @@
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
$string['availability_show'] = 'Show on course page';
|
||||
$string['availability_show_help'] = 'Available to students (subject to any access restrictions which may be set).';
|
||||
$string['availability_hide'] = 'Hide on course page';
|
||||
$string['availability_hide_help'] = 'Not available to students.';
|
||||
$string['availability_stealth'] = 'Make available but don\'t show on course page';
|
||||
$string['availability_stealth_help'] = 'Available to students if you provide a link. Activities will still appear in the gradebook and other reports.';
|
||||
$string['bulkedit'] = 'Bulk edit';
|
||||
$string['bulkeditoff'] = 'Close bulk edit';
|
||||
$string['bulkcancel'] = 'Close bulk editing';
|
||||
$string['bulkselection'] = '{$a} selected';
|
||||
$string['cmavailability'] = 'Activity availability';
|
||||
$string['courseindex'] = 'Course index';
|
||||
$string['nobulkaction'] = 'No bulk actions available';
|
||||
$string['preference:coursesectionspreferences'] = 'Section user preferences for course {$a}';
|
||||
$string['privacy:metadata:preference:coursesectionspreferences'] = 'Section user preferences like collapsed and expanded.';
|
||||
$string['sectionavailability'] = 'Section availability';
|
||||
$string['selectcm'] = 'Select activity {$a}';
|
||||
$string['selectsection'] = 'Select section {$a}';
|
||||
|
2
lib/amd/build/modal.min.js
vendored
2
lib/amd/build/modal.min.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1008,5 +1008,24 @@ define([
|
||||
this.focusOnClose = element;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the a button enabled or disabled.
|
||||
*
|
||||
* @param {DOMString} action The action of the button
|
||||
* @param {Boolean} disabled the new disabled value
|
||||
*/
|
||||
Modal.prototype.setButtonDisabled = function(action, disabled) {
|
||||
const button = this.getFooter().find(this.getActionSelector(action));
|
||||
|
||||
if (!button) {
|
||||
throw new Error("Unable to find the '" + action + "' button");
|
||||
}
|
||||
if (disabled) {
|
||||
button.attr('disabled', '');
|
||||
} else {
|
||||
button.removeAttr('disabled');
|
||||
}
|
||||
};
|
||||
|
||||
return Modal;
|
||||
});
|
||||
|
@ -435,6 +435,7 @@ class icon_system_fontawesome extends icon_system_font {
|
||||
'core:t/sort_asc' => 'fa-sort-asc',
|
||||
'core:t/sort_desc' => 'fa-sort-desc',
|
||||
'core:t/sort' => 'fa-sort',
|
||||
'core:t/stealth' => 'fa-low-vision',
|
||||
'core:t/stop' => 'fa-stop',
|
||||
'core:t/switch_minus' => 'fa-minus',
|
||||
'core:t/switch_plus' => 'fa-plus',
|
||||
|
@ -65,6 +65,9 @@ information provided here is intended especially for developers.
|
||||
* A `\core\event\role_created` event is now triggered when roles are created via the `create_role` API.
|
||||
* Event \core\event\user_created is now triggered if the user is created during course restore. In this case
|
||||
$event->other has properties 'restoreid' and 'courseid'.
|
||||
* The core/modal module and all their versions (SAVE_CANCEL, DELETE_CANCEL...) now has a setButtonDisable to disable or enable
|
||||
specific modal action buttons. This function allows developers to have modals that could only be submited if the user do some
|
||||
action in the modal body like ticking a checkbox or selecting an element.
|
||||
|
||||
=== 4.1 ===
|
||||
|
||||
|
BIN
pix/t/stealth.png
Normal file
BIN
pix/t/stealth.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 454 B |
33
pix/t/stealth.svg
Normal file
33
pix/t/stealth.svg
Normal file
@ -0,0 +1,33 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
id="svg4"
|
||||
version="1.1"
|
||||
overflow="visible"
|
||||
preserveAspectRatio="xMinYMid meet"
|
||||
viewBox="0 0 12 12"
|
||||
height="12"
|
||||
width="12">
|
||||
<metadata
|
||||
id="metadata10">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs8" />
|
||||
<path
|
||||
id="path817"
|
||||
d="M 0.83007812,0 2.4765625,2.8515625 c -0.00413,0.00253 -0.00759,0.00528 -0.011719,0.00781 -0.6841481,0.4199472 -1.2524659,0.9183683 -1.675781,1.4355494 l -0.001953,-0.00391 C 0.76702729,4.3151909 0.75369622,4.3390817 0.734375,4.3632812 0.61825318,4.5101601 0.50948918,4.6544521 0.41992188,4.8007812 0.16145602,5.2230458 0,5.6382936 0,6 0,6.5777635 0.37498263,7.2840809 0.9921875,7.953125 1.2086374,6.7186864 2.2337061,5.7305349 3.0527344,4.9511719 c 6.855e-4,-5.145e-4 0.00127,-0.00144 0.00195,-0.00195 0.07644,-0.057316 0.1660785,-0.1133854 0.25,-0.1699219 C 3.3706164,4.734993 3.4474482,4.695444 3.517575,4.6523469 L 3.8476562,5.2265625 C 3.7609491,5.4702105 3.6992188,5.7266632 3.6992188,6 c 0,0.9 0.7704406,1.6996094 1.5704406,2.0996094 C 4.9571666,8.0214862 4.3966472,7.9072533 4.1191406,7.7792969 L 5.5019531,10.173828 C 5.6671848,10.1876 5.8322233,10.199219 6,10.199219 c 0.2341941,0 0.4643909,-0.01653 0.6933594,-0.04297 L 7.7578125,12 H 10.048828 L 8.6601562,9.5957031 C 9.9310576,9.031142 10.961746,8.147338 11.527344,7.2871094 c 0.001,0.00257 0.0029,0.00524 0.0039,0.00781 0.02334,-0.035832 0.03292,-0.069737 0.05469,-0.1054688 0.09793,-0.1587294 0.17694,-0.3148157 0.240234,-0.46875 0.0293,-0.07114 0.06244,-0.1415534 0.08398,-0.2109375 0.01215,-0.03944 0.02367,-0.078407 0.0332,-0.1171875 C 11.977033,6.2573717 12,6.1245785 12,6 12,4.4 9.2,1.8007813 6,1.8007812 c -0.2617853,0 -0.5183202,0.02382 -0.7734375,0.056641 -0.066288,0.0086 -0.1315397,0.018649 -0.1972656,0.029297 C 4.7819853,1.9264882 4.5366318,1.9745762 4.2988281,2.041016 L 3.1210938,0 Z M 7,3.9003906 c 0.038317,0.011974 0.073478,0.028259 0.1113281,0.041016 C 8.6539757,4.4612991 9.8047896,5.6095792 10,6 9.8290933,6.4272667 8.9249278,7.3633736 7.6679688,7.8769531 L 7.5664062,7.7011719 C 8.0270421,7.2668549 8.3007814,6.6613617 8.3007812,6 8.3007812,5.1187671 7.8159356,4.3394904 7.0449219,3.9316406 7.0284758,3.9229392 7.0167096,3.9087454 7,3.9003906 Z"
|
||||
style="fill:#888888" />
|
||||
</svg>
|
After Width: | Height: | Size: 2.7 KiB |
@ -5,8 +5,14 @@
|
||||
$icon-width: 16px;
|
||||
$icon-height: 16px;
|
||||
// Size of big icons.
|
||||
$icon-medium-width: 24px;
|
||||
$icon-medium-height: 24px;
|
||||
// Size of big icons.
|
||||
$icon-big-width: 64px;
|
||||
$icon-big-height: 64px;
|
||||
// Size of icon boxes.
|
||||
$icon-box-width: 48px;
|
||||
$icon-box-height: 48px;
|
||||
|
||||
// stylelint-disable
|
||||
$iconsizes: () !default;
|
||||
@ -137,8 +143,8 @@ $iconsizes: map-merge((
|
||||
.activityicon,
|
||||
.icon {
|
||||
margin: 0;
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
height: $icon-medium-width;
|
||||
width: $icon-medium-height;
|
||||
}
|
||||
&.small {
|
||||
width: $activity-iconcontainer-width - 10px;
|
||||
@ -156,6 +162,23 @@ $iconsizes: map-merge((
|
||||
}
|
||||
}
|
||||
|
||||
.icon-box {
|
||||
width: $icon-box-width;
|
||||
height: $icon-box-height;
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: $gray-100;
|
||||
border-radius: 12px;
|
||||
padding: 0.7rem;
|
||||
|
||||
.icon {
|
||||
margin: 0;
|
||||
height: $icon-medium-width;
|
||||
width: $icon-medium-height;
|
||||
}
|
||||
}
|
||||
|
||||
// Make activtity colours available for custom modules.
|
||||
:root {
|
||||
@each $type, $value in $activity-icon-colors {
|
||||
|
@ -12550,6 +12550,20 @@ blockquote {
|
||||
.activityiconcontainer.interface .icon {
|
||||
filter: brightness(0) invert(1); }
|
||||
|
||||
.icon-box {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: #f8f9fa;
|
||||
border-radius: 12px;
|
||||
padding: 0.7rem; }
|
||||
.icon-box .icon {
|
||||
margin: 0;
|
||||
height: 24px;
|
||||
width: 24px; }
|
||||
|
||||
:root {
|
||||
--activityadministration: #5d63f6;
|
||||
--activityassessment: #eb66a2;
|
||||
|
@ -12550,6 +12550,20 @@ blockquote {
|
||||
.activityiconcontainer.interface .icon {
|
||||
filter: brightness(0) invert(1); }
|
||||
|
||||
.icon-box {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: #f8f9fa;
|
||||
border-radius: 12px;
|
||||
padding: 0.7rem; }
|
||||
.icon-box .icon {
|
||||
margin: 0;
|
||||
height: 24px;
|
||||
width: 24px; }
|
||||
|
||||
:root {
|
||||
--activityadministration: #5d63f6;
|
||||
--activityassessment: #eb66a2;
|
||||
|
Loading…
x
Reference in New Issue
Block a user