MDL-72660 core_courseformat: course index completion icons
This commit is contained in:
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,2 +1,2 @@
|
||||
define ("core_courseformat/local/courseeditor/exporter",["exports"],function(a){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;function b(a,b){var c=Object.keys(a);if(Object.getOwnPropertySymbols){var d=Object.getOwnPropertySymbols(a);if(b)d=d.filter(function(b){return Object.getOwnPropertyDescriptor(a,b).enumerable});c.push.apply(c,d)}return c}function c(a){for(var c=1,e;c<arguments.length;c++){e=null!=arguments[c]?arguments[c]:{};if(c%2){b(Object(e),!0).forEach(function(b){d(a,b,e[b])})}else if(Object.getOwnPropertyDescriptors){Object.defineProperties(a,Object.getOwnPropertyDescriptors(e))}else{b(Object(e)).forEach(function(b){Object.defineProperty(a,b,Object.getOwnPropertyDescriptor(e,b))})}}return a}function d(a,b,c){if(b in a){Object.defineProperty(a,b,{value:c,enumerable:!0,configurable:!0,writable:!0})}else{a[b]=c}return a}function e(a,b){if(!(a instanceof b)){throw new TypeError("Cannot call a class as a function")}}function f(a,b){for(var c=0,d;c<b.length;c++){d=b[c];d.enumerable=d.enumerable||!1;d.configurable=!0;if("value"in d)d.writable=!0;Object.defineProperty(a,d.key,d)}}function g(a,b,c){if(b)f(a.prototype,b);if(c)f(a,c);return a}var h=function(){function a(b){e(this,a);this.reactive=b}g(a,[{key:"course",value:function course(a){var b,c,d=this,e={sections:[],editmode:this.reactive.isEditing,highlighted:null!==(b=a.course.highlighted)&&void 0!==b?b:""},f=null!==(c=a.course.sectionlist)&&void 0!==c?c:[];f.forEach(function(b){var c,f=null!==(c=a.section.get(b))&&void 0!==c?c:{},g=d.section(a,f);e.sections.push(g)});e.hassections=0!=e.sections.length;return e}},{key:"section",value:function(a,b){var d,e=this,f=c({},b,{cms:[]}),g=null!==(d=b.cmlist)&&void 0!==d?d:[];g.forEach(function(b){var c=a.cm.get(b),d=e.cm(a,c);f.cms.push(d)});f.hascms=0!=f.cms.length;return f}},{key:"cm",value:function(a,b){var d=c({},b,{isactive:!1});return d}},{key:"cmDraggableData",value:function cmDraggableData(a,b){var c=a.cm.get(b);if(!c){return null}var d,e=a.section.get(c.sectionid),f=null===e||void 0===e?void 0:e.cmlist.indexOf(c.id);if(f!==void 0){d=null===e||void 0===e?void 0:e.cmlist[f+1]}return{type:"cm",id:c.id,name:c.name,sectionid:c.sectionid,nextcmid:d}}},{key:"sectionDraggableData",value:function sectionDraggableData(a,b){var c=a.section.get(b);if(!c){return null}return{type:"section",id:c.id,name:c.name,number:c.number}}}]);return a}();a.default=h;return a.default});
|
||||
define ("core_courseformat/local/courseeditor/exporter",["exports"],function(a){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;function b(a,b){var c=Object.keys(a);if(Object.getOwnPropertySymbols){var d=Object.getOwnPropertySymbols(a);if(b)d=d.filter(function(b){return Object.getOwnPropertyDescriptor(a,b).enumerable});c.push.apply(c,d)}return c}function c(a){for(var c=1,e;c<arguments.length;c++){e=null!=arguments[c]?arguments[c]:{};if(c%2){b(Object(e),!0).forEach(function(b){d(a,b,e[b])})}else if(Object.getOwnPropertyDescriptors){Object.defineProperties(a,Object.getOwnPropertyDescriptors(e))}else{b(Object(e)).forEach(function(b){Object.defineProperty(a,b,Object.getOwnPropertyDescriptor(e,b))})}}return a}function d(a,b,c){if(b in a){Object.defineProperty(a,b,{value:c,enumerable:!0,configurable:!0,writable:!0})}else{a[b]=c}return a}function e(a,b){if(!(a instanceof b)){throw new TypeError("Cannot call a class as a function")}}function f(a,b){for(var c=0,d;c<b.length;c++){d=b[c];d.enumerable=d.enumerable||!1;d.configurable=!0;if("value"in d)d.writable=!0;Object.defineProperty(a,d.key,d)}}function g(a,b,c){if(b)f(a.prototype,b);if(c)f(a,c);return a}var h=function(){function a(b){e(this,a);this.reactive=b;this.COMPLETIONS=["incomplete","complete","complete","fail"]}g(a,[{key:"course",value:function course(a){var b,c,d=this,e={sections:[],editmode:this.reactive.isEditing,highlighted:null!==(b=a.course.highlighted)&&void 0!==b?b:""},f=null!==(c=a.course.sectionlist)&&void 0!==c?c:[];f.forEach(function(b){var c,f=null!==(c=a.section.get(b))&&void 0!==c?c:{},g=d.section(a,f);e.sections.push(g)});e.hassections=0!=e.sections.length;return e}},{key:"section",value:function(a,b){var d,e=this,f=c({},b,{cms:[]}),g=null!==(d=b.cmlist)&&void 0!==d?d:[];g.forEach(function(b){var c=a.cm.get(b),d=e.cm(a,c);f.cms.push(d)});f.hascms=0!=f.cms.length;return f}},{key:"cm",value:function(a,b){var d=c({},b,{isactive:!1});return d}},{key:"cmDraggableData",value:function cmDraggableData(a,b){var c=a.cm.get(b);if(!c){return null}var d,e=a.section.get(c.sectionid),f=null===e||void 0===e?void 0:e.cmlist.indexOf(c.id);if(f!==void 0){d=null===e||void 0===e?void 0:e.cmlist[f+1]}return{type:"cm",id:c.id,name:c.name,sectionid:c.sectionid,nextcmid:d}}},{key:"sectionDraggableData",value:function sectionDraggableData(a,b){var c=a.section.get(b);if(!c){return null}return{type:"section",id:c.id,name:c.name,number:c.number}}},{key:"cmCompletion",value:function cmCompletion(a,b){var c={statename:"",state:"NaN"};if(b.completionstate!==void 0){var d;c.state=b.completionstate;c.hasstate=!0;var e=null!==(d=this.COMPLETIONS[b.completionstate])&&void 0!==d?d:"NaN";c["is".concat(e)]=!0}return c}}]);return a}();a.default=h;return a.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
+1
-1
@@ -1,2 +1,2 @@
|
||||
define ("core_courseformat/local/courseindex/cm",["exports","core_courseformat/local/courseeditor/dndcmitem"],function(a,b){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;b=function(a){return a&&a.__esModule?a:{default:a}}(b);function c(a){"@babel/helpers - typeof";if("function"==typeof Symbol&&"symbol"==typeof Symbol.iterator){c=function(a){return typeof a}}else{c=function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?"symbol":typeof a}}return c(a)}function d(a,b){if(!(a instanceof b)){throw new TypeError("Cannot call a class as a function")}}function e(a,b){for(var c=0,d;c<b.length;c++){d=b[c];d.enumerable=d.enumerable||!1;d.configurable=!0;if("value"in d)d.writable=!0;Object.defineProperty(a,d.key,d)}}function f(a,b,c){if(b)e(a.prototype,b);if(c)e(a,c);return a}function g(a,b){if("function"!=typeof b&&null!==b){throw new TypeError("Super expression must either be null or a function")}a.prototype=Object.create(b&&b.prototype,{constructor:{value:a,writable:!0,configurable:!0}});if(b)h(a,b)}function h(a,b){h=Object.setPrototypeOf||function(a,b){a.__proto__=b;return a};return h(a,b)}function i(a){return function(){var b=m(a),c;if(l()){var d=m(this).constructor;c=Reflect.construct(b,arguments,d)}else{c=b.apply(this,arguments)}return j(this,c)}}function j(a,b){if(b&&("object"===c(b)||"function"==typeof b)){return b}return k(a)}function k(a){if(void 0===a){throw new ReferenceError("this hasn't been initialised - super() hasn't been called")}return a}function l(){if("undefined"==typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if("function"==typeof Proxy)return!0;try{Date.prototype.toString.call(Reflect.construct(Date,[],function(){}));return!0}catch(a){return!1}}function m(a){m=Object.setPrototypeOf?Object.getPrototypeOf:function(a){return a.__proto__||Object.getPrototypeOf(a)};return m(a)}var n=function(a){g(b,a);var c=i(b);function b(){d(this,b);return c.apply(this,arguments)}f(b,[{key:"create",value:function create(){this.name="courseindex_cm";this.selectors={CM_NAME:"[data-for='cm_name']"};this.classes={CMHIDDEN:"dimmed",LOCKED:"editinprogress"};this.id=this.element.dataset.id}},{key:"stateReady",value:function stateReady(){this.configDragDrop(this.id)}},{key:"getWatchers",value:function getWatchers(){return[{watch:"cm[".concat(this.id,"]:deleted"),handler:this.remove},{watch:"cm[".concat(this.id,"]:updated"),handler:this._refreshCm}]}},{key:"_refreshCm",value:function _refreshCm(a){var b,c,d=a.element;this.element.classList.toggle(this.classes.CMHIDDEN,!d.visible);this.getElement(this.selectors.CM_NAME).innerHTML=d.name;this.element.classList.toggle(this.classes.DRAGGING,null!==(b=d.dragging)&&void 0!==b?b:!1);this.element.classList.toggle(this.classes.LOCKED,null!==(c=d.locked)&&void 0!==c?c:!1);this.locked=d.locked}}],[{key:"init",value:function init(a,c){return new b({element:document.getElementById(a),selectors:c})}}]);return b}(b.default);a.default=n;return a.default});
|
||||
define ("core_courseformat/local/courseindex/cm",["exports","core_courseformat/local/courseeditor/dndcmitem","core/templates","core/prefetch"],function(a,b,c,d){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;b=e(b);c=e(c);d=e(d);function e(a){return a&&a.__esModule?a:{default:a}}function f(a){"@babel/helpers - typeof";if("function"==typeof Symbol&&"symbol"==typeof Symbol.iterator){f=function(a){return typeof a}}else{f=function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?"symbol":typeof a}}return f(a)}function g(a,b,c,d,e,f,g){try{var h=a[f](g),i=h.value}catch(a){c(a);return}if(h.done){b(i)}else{Promise.resolve(i).then(d,e)}}function h(a){return function(){var b=this,c=arguments;return new Promise(function(d,e){var i=a.apply(b,c);function f(a){g(i,d,e,f,h,"next",a)}function h(a){g(i,d,e,f,h,"throw",a)}f(void 0)})}}function i(a,b){if(!(a instanceof b)){throw new TypeError("Cannot call a class as a function")}}function j(a,b){for(var c=0,d;c<b.length;c++){d=b[c];d.enumerable=d.enumerable||!1;d.configurable=!0;if("value"in d)d.writable=!0;Object.defineProperty(a,d.key,d)}}function k(a,b,c){if(b)j(a.prototype,b);if(c)j(a,c);return a}function l(a,b){if("function"!=typeof b&&null!==b){throw new TypeError("Super expression must either be null or a function")}a.prototype=Object.create(b&&b.prototype,{constructor:{value:a,writable:!0,configurable:!0}});if(b)m(a,b)}function m(a,b){m=Object.setPrototypeOf||function(a,b){a.__proto__=b;return a};return m(a,b)}function n(a){return function(){var b=r(a),c;if(q()){var d=r(this).constructor;c=Reflect.construct(b,arguments,d)}else{c=b.apply(this,arguments)}return o(this,c)}}function o(a,b){if(b&&("object"===f(b)||"function"==typeof b)){return b}return p(a)}function p(a){if(void 0===a){throw new ReferenceError("this hasn't been initialised - super() hasn't been called")}return a}function q(){if("undefined"==typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if("function"==typeof Proxy)return!0;try{Date.prototype.toString.call(Reflect.construct(Date,[],function(){}));return!0}catch(a){return!1}}function r(a){r=Object.setPrototypeOf?Object.getPrototypeOf:function(a){return a.__proto__||Object.getPrototypeOf(a)};return r(a)}d.default.prefetchTemplate("core_courseformat/local/courseindex/cmcompletion");var s=function(a){l(b,a);var d=n(b);function b(){i(this,b);return d.apply(this,arguments)}k(b,[{key:"create",value:function create(){this.name="courseindex_cm";this.selectors={CM_NAME:"[data-for='cm_name']",CM_COMPLETION:"[data-for='cm_completion']"};this.classes={CMHIDDEN:"dimmed",LOCKED:"editinprogress"};this.id=this.element.dataset.id}},{key:"stateReady",value:function stateReady(a){this.configDragDrop(this.id);this._refreshCompletion({state:a,element:a.cm.get(this.id)})}},{key:"getWatchers",value:function getWatchers(){return[{watch:"cm[".concat(this.id,"]:deleted"),handler:this.remove},{watch:"cm[".concat(this.id,"]:updated"),handler:this._refreshCm},{watch:"cm[".concat(this.id,"].completionstate:updated"),handler:this._refreshCompletion}]}},{key:"_refreshCm",value:function _refreshCm(a){var b,c,d=a.element;this.element.classList.toggle(this.classes.CMHIDDEN,!d.visible);this.getElement(this.selectors.CM_NAME).innerHTML=d.name;this.element.classList.toggle(this.classes.DRAGGING,null!==(b=d.dragging)&&void 0!==b?b:!1);this.element.classList.toggle(this.classes.LOCKED,null!==(c=d.locked)&&void 0!==c?c:!1);this.locked=d.locked}},{key:"_refreshCompletion",value:function(){var a=h(regeneratorRuntime.mark(function a(b){var d,e,f,g,h,i,j,k;return regeneratorRuntime.wrap(function(a){while(1){switch(a.prev=a.next){case 0:d=b.state,e=b.element;if(!(this.reactive.isEditing||!e.istrackeduser)){a.next=3;break}return a.abrupt("return");case 3:f=this.getElement(this.selectors.CM_COMPLETION);if(!(f.dataset.value==e.completionstate)){a.next=6;break}return a.abrupt("return");case 6:g=this.reactive.getExporter();h=g.cmCompletion(d,e);a.prev=8;a.next=11;return c.default.renderForPromise("core_courseformat/local/courseindex/cmcompletion",h);case 11:i=a.sent;j=i.html;k=i.js;c.default.replaceNode(f,j,k);a.next=20;break;case 17:a.prev=17;a.t0=a["catch"](8);throw a.t0;case 20:case"end":return a.stop();}}},a,this,[[8,17]])}));return function _refreshCompletion(){return a.apply(this,arguments)}}()}],[{key:"init",value:function init(a,c){return new b({element:document.getElementById(a),selectors:c})}}]);return b}(b.default);a.default=s;return a.default});
|
||||
//# sourceMappingURL=cm.min.js.map
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -30,6 +30,7 @@ import CmItem from 'core_courseformat/local/content/section/cmitem';
|
||||
// Course actions is needed for actions that are not migrated to components.
|
||||
import courseActions from 'core_course/actions';
|
||||
import DispatchActions from 'core_courseformat/local/content/actions';
|
||||
import * as CourseEvents from 'core_course/events';
|
||||
|
||||
export default class Component extends BaseComponent {
|
||||
|
||||
@@ -98,6 +99,13 @@ export default class Component extends BaseComponent {
|
||||
// Mark content as state ready.
|
||||
this.element.classList.add(this.classes.STATEDREADY);
|
||||
}
|
||||
|
||||
// Capture completion events.
|
||||
this.addEventListener(
|
||||
this.element,
|
||||
CourseEvents.manualCompletionToggled,
|
||||
this._completionHandler
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -211,6 +219,18 @@ export default class Component extends BaseComponent {
|
||||
this.dettachedSections = {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Activity manual completion listener.
|
||||
*
|
||||
* @param {Event} event the custom ecent
|
||||
*/
|
||||
_completionHandler({detail}) {
|
||||
if (detail === undefined) {
|
||||
return;
|
||||
}
|
||||
this.reactive.dispatch('cmCompletion', [detail.cmid], detail.completed);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a course section when the section number changes.
|
||||
*
|
||||
|
||||
@@ -31,6 +31,11 @@ export default class {
|
||||
*/
|
||||
constructor(reactive) {
|
||||
this.reactive = reactive;
|
||||
|
||||
// Completions states are defined in lib/completionlib.php. There are 4 different completion
|
||||
// state values, however, the course index uses the same state for complete and complete_pass.
|
||||
// This is the reason why completed appears twice in the array.
|
||||
this.COMPLETIONS = ['incomplete', 'complete', 'complete', 'fail'];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -150,4 +155,25 @@ export default class {
|
||||
number: sectioninfo.number,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a compoetion export data from the cm element.
|
||||
*
|
||||
* @param {Object} state the current state.
|
||||
* @param {Object} cminfo the course module state data.
|
||||
* @returns {Object}
|
||||
*/
|
||||
cmCompletion(state, cminfo) {
|
||||
const data = {
|
||||
statename: '',
|
||||
state: 'NaN',
|
||||
};
|
||||
if (cminfo.completionstate !== undefined) {
|
||||
data.state = cminfo.completionstate;
|
||||
data.hasstate = true;
|
||||
const statename = this.COMPLETIONS[cminfo.completionstate] ?? 'NaN';
|
||||
data[`is${statename}`] = true;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,6 +153,18 @@ export default class {
|
||||
this._setElementsValue(stateManager, 'section', sectionIds, 'dragging', dragValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark or unmark course modules as complete.
|
||||
*
|
||||
* @param {StateManager} stateManager the current state manager
|
||||
* @param {array} cmIds the list of course modules ids
|
||||
* @param {bool} complete the new completion value
|
||||
*/
|
||||
cmCompletion(stateManager, cmIds, complete) {
|
||||
const newValue = (complete) ? 1 : 0;
|
||||
this._setElementsValue(stateManager, 'cm', cmIds, 'completionstate', newValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Lock or unlock course modules.
|
||||
*
|
||||
|
||||
@@ -25,6 +25,12 @@
|
||||
*/
|
||||
|
||||
import DndCmItem from 'core_courseformat/local/courseeditor/dndcmitem';
|
||||
import Templates from 'core/templates';
|
||||
import Prefetch from 'core/prefetch';
|
||||
|
||||
// Prefetch the completion icons template.
|
||||
const completionTemplate = 'core_courseformat/local/courseindex/cmcompletion';
|
||||
Prefetch.prefetchTemplate(completionTemplate);
|
||||
|
||||
export default class Component extends DndCmItem {
|
||||
|
||||
@@ -37,6 +43,7 @@ export default class Component extends DndCmItem {
|
||||
// Default query selectors.
|
||||
this.selectors = {
|
||||
CM_NAME: `[data-for='cm_name']`,
|
||||
CM_COMPLETION: `[data-for='cm_completion']`,
|
||||
};
|
||||
// Default classes to toggle on refresh.
|
||||
this.classes = {
|
||||
@@ -63,9 +70,16 @@ export default class Component extends DndCmItem {
|
||||
|
||||
/**
|
||||
* Initial state ready method.
|
||||
*
|
||||
* @param {Object} state the course state.
|
||||
*/
|
||||
stateReady() {
|
||||
stateReady(state) {
|
||||
this.configDragDrop(this.id);
|
||||
// Refresh completion icon.
|
||||
this._refreshCompletion({
|
||||
state,
|
||||
element: state.cm.get(this.id),
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -77,6 +91,7 @@ export default class Component extends DndCmItem {
|
||||
return [
|
||||
{watch: `cm[${this.id}]:deleted`, handler: this.remove},
|
||||
{watch: `cm[${this.id}]:updated`, handler: this._refreshCm},
|
||||
{watch: `cm[${this.id}].completionstate:updated`, handler: this._refreshCompletion},
|
||||
];
|
||||
}
|
||||
|
||||
@@ -95,4 +110,34 @@ export default class Component extends DndCmItem {
|
||||
this.locked = element.locked;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the activity completion icon.
|
||||
*
|
||||
* @param {Object} details the update details
|
||||
* @param {Object} details.state the state data
|
||||
* @param {Object} details.element the element data
|
||||
*/
|
||||
async _refreshCompletion({state, element}) {
|
||||
// No completion icons are displayed in edit mode.
|
||||
if (this.reactive.isEditing || !element.istrackeduser) {
|
||||
return;
|
||||
}
|
||||
// Check if the completion value has changed.
|
||||
const completionElement = this.getElement(this.selectors.CM_COMPLETION);
|
||||
if (completionElement.dataset.value == element.completionstate) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Collect section information from the state.
|
||||
const exporter = this.reactive.getExporter();
|
||||
const data = exporter.cmCompletion(state, element);
|
||||
|
||||
try {
|
||||
const {html, js} = await Templates.renderForPromise(completionTemplate, data);
|
||||
Templates.replaceNode(completionElement, html, js);
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -17,12 +17,17 @@
|
||||
namespace core_courseformat\output\local\state;
|
||||
|
||||
use core_courseformat\base as course_format;
|
||||
use completion_info;
|
||||
use section_info;
|
||||
use cm_info;
|
||||
use renderable;
|
||||
use stdClass;
|
||||
use core_availability\info_module;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
require_once($CFG->libdir . '/completionlib.php');
|
||||
|
||||
/**
|
||||
* Contains the ajax update course module structure.
|
||||
*
|
||||
@@ -66,10 +71,12 @@ class cm implements renderable {
|
||||
* @return stdClass data context for a mustache template
|
||||
*/
|
||||
public function export_for_template(\renderer_base $output): stdClass {
|
||||
global $USER;
|
||||
|
||||
$format = $this->format;
|
||||
$section = $this->section;
|
||||
$cm = $this->cm;
|
||||
$course = $format->get_course();
|
||||
|
||||
$data = (object)[
|
||||
'id' => $cm->id,
|
||||
@@ -94,6 +101,14 @@ class cm implements renderable {
|
||||
$data->content = $output->course_section_updated_cm_item($format, $section, $cm);
|
||||
}
|
||||
|
||||
// Completion status.
|
||||
$completioninfo = new completion_info($course);
|
||||
$data->istrackeduser = $completioninfo->is_tracked_user($USER->id);
|
||||
if ($data->istrackeduser && $completioninfo->is_enabled($cm)) {
|
||||
$completiondata = $completioninfo->get_data($cm);
|
||||
$data->completionstate = $completiondata->completionstate;
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
"isactive": 1,
|
||||
"uniqid": "0",
|
||||
"isactive": 1,
|
||||
"accessvisible": 1,
|
||||
"accessvisible": 1
|
||||
}
|
||||
}}
|
||||
<li class="courseindex-item
|
||||
@@ -39,6 +39,7 @@
|
||||
data-for="cm"
|
||||
data-id="{{id}}"
|
||||
>
|
||||
<span class="completioninfo" data-for="cm_completion" data-value="NaN"></span>
|
||||
{{#url}}
|
||||
{{#uservisible}}
|
||||
<a class="courseindex-link text-truncate" href="{{{url}}}" data-for="cm_name">
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
{{!
|
||||
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/courseindex/cmcompletion
|
||||
|
||||
Displays a course index course-module entry.
|
||||
|
||||
Example context (json):
|
||||
{
|
||||
"state": 1,
|
||||
"iscomplete": true,
|
||||
"isincomplete": true,
|
||||
"isfail": true,
|
||||
"hasstate": true
|
||||
}
|
||||
}}
|
||||
{{#iscomplete}}
|
||||
<span class="completioninfo completion_complete" data-for="cm_completion" data-value="{{state}}">
|
||||
{{#pix}} t/completion_complete, moodle, {{#str}} done, completion {{/str}} {{/pix}}
|
||||
</span>
|
||||
{{/iscomplete}}
|
||||
{{#isincomplete}}
|
||||
<span class="completioninfo completion_incomplete" data-for="cm_completion" data-value="{{state}}">
|
||||
{{#pix}} t/completion_incomplete, moodle, {{#str}} todo, completion {{/str}} {{/pix}}
|
||||
</span>
|
||||
{{/isincomplete}}
|
||||
{{#isfail}}
|
||||
<span class="completioninfo completion_fail" data-for="cm_completion" data-value="{{state}}">
|
||||
{{#pix}} t/completion_fail, moodle, {{#str}} failed, completion {{/str}} {{/pix}}
|
||||
</span>
|
||||
{{/isfail}}
|
||||
{{^hasstate}}
|
||||
<span class="completioninfo completion_none" data-for="cm_completion" data-value="{{state}}"></span>
|
||||
{{/hasstate}}
|
||||
@@ -0,0 +1,121 @@
|
||||
@core @core_course @core_courseformat
|
||||
Feature: Course index completion icons
|
||||
In order to quickly check my activities completions
|
||||
As a student
|
||||
I need to see the activity completion in the course index.
|
||||
|
||||
Background:
|
||||
Given the following "users" exist:
|
||||
| username | firstname | lastname | email |
|
||||
| teacher1 | Teacher | 1 | teacher1@example.com |
|
||||
| student1 | Student | 1 | student1@example.com |
|
||||
And the following "course" exists:
|
||||
| fullname | Course 1 |
|
||||
| shortname | C1 |
|
||||
| category | 0 |
|
||||
| enablecompletion | 1 |
|
||||
| numsections | 4 |
|
||||
And the following "activities" exist:
|
||||
| activity | name | intro | course | idnumber | section | completion |
|
||||
| assign | Activity sample 1 | Test assignment description | C1 | sample1 | 1 | 1 |
|
||||
And the following "course enrolments" exist:
|
||||
| user | course | role |
|
||||
| student1 | C1 | student |
|
||||
| teacher1 | C1 | editingteacher |
|
||||
|
||||
@javascript
|
||||
Scenario: Teacher does not see completion icons.
|
||||
Given I am on the "C1" "Course" page logged in as "teacher1"
|
||||
And I click on "Side panel" "button"
|
||||
When I click on "Open course index drawer" "button"
|
||||
Then I should see "Topic 1" in the "courseindex-content" "region"
|
||||
And I should see "Activity sample 1" in the "courseindex-content" "region"
|
||||
And "To do" "icon" should not exist in the "courseindex-content" "region"
|
||||
|
||||
@javascript
|
||||
Scenario: User should see the completion icons
|
||||
Given I am on the "C1" "Course" page logged in as "student1"
|
||||
And I click on "Side panel" "button"
|
||||
When I click on "Open course index drawer" "button"
|
||||
Then I should see "Topic 1" in the "courseindex-content" "region"
|
||||
And I should see "Activity sample 1" in the "courseindex-content" "region"
|
||||
And "To do" "icon" should exist in the "courseindex-content" "region"
|
||||
|
||||
@javascript
|
||||
Scenario: Manual completion shoudl update the course index completion
|
||||
Given I am on the "C1" "Course" page logged in as "student1"
|
||||
And I click on "Side panel" "button"
|
||||
And I click on "Open course index drawer" "button"
|
||||
And "To do" "icon" should exist in the "courseindex-content" "region"
|
||||
When I press "Mark as done"
|
||||
And I wait until "Done" "button" exists
|
||||
Then "Done" "icon" should exist in the "courseindex-content" "region"
|
||||
And I press "Done"
|
||||
And I wait until "Mark as done" "button" exists
|
||||
And "To do" "icon" should exist in the "courseindex-content" "region"
|
||||
|
||||
@javascript
|
||||
Scenario: Refresh the page should keep the completion consistent
|
||||
Given I am on the "C1" "Course" page logged in as "student1"
|
||||
And I click on "Side panel" "button"
|
||||
And I click on "Open course index drawer" "button"
|
||||
And "To do" "icon" should exist in the "courseindex-content" "region"
|
||||
When I press "Mark as done"
|
||||
And I wait until "Done" "button" exists
|
||||
And I reload the page
|
||||
Then "Done" "icon" should exist in the "courseindex-content" "region"
|
||||
|
||||
@javascript
|
||||
Scenario: Auto completion should appear in the course index
|
||||
Given the following "activities" exist:
|
||||
| activity | name | intro | course | idnumber | section | completion | completionview |
|
||||
| assign | Activity sample 2 | Test assignment description | C1 | sample2 | 1 | 1 | 1 |
|
||||
When I am on the "sample2" "Activity" page logged in as "student1"
|
||||
And I am on the "C1" "Course" page
|
||||
And I click on "Side panel" "button"
|
||||
And I click on "Open course index drawer" "button"
|
||||
Then "Done" "icon" should exist in the "courseindex-content" "region"
|
||||
|
||||
@javascript
|
||||
Scenario: Completion failed should appear in the course index
|
||||
Given the following "question categories" exist:
|
||||
| contextlevel | reference | name |
|
||||
| Course | C1 | Test questions |
|
||||
And the following "questions" exist:
|
||||
| questioncategory | qtype | name | questiontext |
|
||||
| Test questions | truefalse | First question | Answer the first question |
|
||||
And the following "activities" exist:
|
||||
| activity | name | course | idnumber | attempts | gradepass | completion | completionusegrade | completionpass | completionattemptsexhausted |
|
||||
| quiz | Test quiz name | C1 | quiz1 | 1 | 5.00 | 2 | 1 | 1 | 1 |
|
||||
And quiz "Test quiz name" contains the following questions:
|
||||
| question | page |
|
||||
| First question | 1 |
|
||||
And user "student1" has attempted "Test quiz name" with responses:
|
||||
| slot | response |
|
||||
| 1 | False |
|
||||
When I am on the "C1" "Course" page logged in as "student1"
|
||||
And I click on "Side panel" "button"
|
||||
And I click on "Open course index drawer" "button"
|
||||
And "Failed" "icon" should exist in the "courseindex-content" "region"
|
||||
|
||||
@javascript
|
||||
Scenario: Completion passed should appear in the course index
|
||||
Given the following "question categories" exist:
|
||||
| contextlevel | reference | name |
|
||||
| Course | C1 | Test questions |
|
||||
And the following "questions" exist:
|
||||
| questioncategory | qtype | name | questiontext |
|
||||
| Test questions | truefalse | First question | Answer the first question |
|
||||
And the following "activities" exist:
|
||||
| activity | name | course | idnumber | attempts | gradepass | completion | completionusegrade | completionpass | completionattemptsexhausted |
|
||||
| quiz | Test quiz name | C1 | quiz1 | 1 | 5.00 | 2 | 1 | 1 | 1 |
|
||||
And quiz "Test quiz name" contains the following questions:
|
||||
| question | page |
|
||||
| First question | 1 |
|
||||
And user "student1" has attempted "Test quiz name" with responses:
|
||||
| slot | response |
|
||||
| 1 | True |
|
||||
When I am on the "C1" "Course" page logged in as "student1"
|
||||
And I click on "Side panel" "button"
|
||||
And I click on "Open course index drawer" "button"
|
||||
And "Done" "icon" should exist in the "courseindex-content" "region"
|
||||
@@ -148,6 +148,7 @@ $string['dependenciescompleted'] = 'Completion of other courses';
|
||||
$string['detail_desc:receivegrade'] = 'Receive a grade';
|
||||
$string['detail_desc:receivepassgrade'] = 'Receive a passing grade';
|
||||
$string['detail_desc:view'] = 'View';
|
||||
$string['done'] = 'Done';
|
||||
$string['hiddenrules'] = 'Some settings specific to <b>{$a}</b> have been hidden. To view unselect other activities';
|
||||
$string['editcoursecompletionsettings'] = 'Edit course completion settings';
|
||||
$string['enablecompletion'] = 'Enable completion tracking';
|
||||
@@ -166,6 +167,7 @@ $string['eventcoursecompletionupdated'] = 'Course completion updated';
|
||||
$string['eventcoursemodulecompletionupdated'] = 'Course activity completion updated';
|
||||
$string['eventdefaultcompletionupdated'] = 'Default for course activity completion updated';
|
||||
$string['excelcsvdownload'] = 'Download in Excel-compatible format (.csv)';
|
||||
$string['failed'] = 'Failed';
|
||||
$string['fraction'] = 'Fraction';
|
||||
$string['graderequired'] = 'Required course grade';
|
||||
$string['gradexrequired'] = '{$a} required';
|
||||
@@ -229,6 +231,7 @@ $string['selfcompletion'] = 'Self completion';
|
||||
$string['showcompletionconditions'] = 'Show activity completion conditions';
|
||||
$string['showcompletionconditions_help'] = 'Activity completion conditions are always shown on the activity page. This setting determines whether activity completion conditions are also shown below each activity on the course page.';
|
||||
$string['showinguser'] = 'Showing user';
|
||||
$string['todo'] = 'To do';
|
||||
$string['unenrolingfromcourse'] = 'Unenrolling from course';
|
||||
$string['unenrolment'] = 'Unenrolment';
|
||||
$string['unit'] = 'Unit';
|
||||
|
||||
@@ -368,6 +368,9 @@ class icon_system_fontawesome extends icon_system_font {
|
||||
'core:t/collapsed' => 'fa-caret-right',
|
||||
'core:t/collapsedcaret' => 'fa-caret-right',
|
||||
'core:t/collapsedchevron' => 'fa-chevron-right',
|
||||
'core:t/completion_complete' => 'fa-circle',
|
||||
'core:t/completion_fail' => 'fa-times',
|
||||
'core:t/completion_incomplete' => 'fa-circle-thin',
|
||||
'core:t/contextmenu' => 'fa-cog',
|
||||
'core:t/copy' => 'fa-copy',
|
||||
'core:t/delete' => 'fa-trash',
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 295 B |
@@ -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 6.0000002,2.4999999 C 4.0728255,2.4999999 2.5,4.0728255 2.5,6.0000001 c 0,1.9271748 1.5728255,3.5 3.5000002,3.5 C 7.927175,9.5000001 9.5,7.9271749 9.5,6.0000001 9.5,4.0728255 7.927175,2.4999999 6.0000002,2.4999999 Z"
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#387936;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.80178976;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers fill stroke;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.2 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 351 B |
@@ -0,0 +1,38 @@
|
||||
<?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" />
|
||||
<g
|
||||
style="overflow:visible"
|
||||
id="g870"
|
||||
transform="matrix(1.1075441,0,0,1.1075441,-0.6452645,-1.4759224)">
|
||||
<path
|
||||
id="path859"
|
||||
d="m 8.9412074,8.2774034 c 0.2106763,0.2106763 0.2106763,0.5266909 0,0.7373672 L 8.2038402,9.7521378 c -0.2106764,0.2106761 -0.5266909,0.2106761 -0.7373672,0 L 5.9917385,8.2774034 4.5170041,9.7521378 c -0.2106763,0.2106761 -0.5266908,0.2106761 -0.7373672,0 L 3.0422697,9.0147706 c -0.2106763,-0.2106763 -0.2106763,-0.5266909 0,-0.7373672 L 4.5170041,6.802669 3.0422697,5.2225964 c -0.2106763,-0.2106763 -0.2106763,-0.5266909 0,-0.7373672 L 3.7796369,3.747862 c 0.2106764,-0.2106764 0.5266909,-0.2106764 0.7373672,0 L 5.9917385,5.3279346 7.466473,3.8532002 C 7.6771493,3.6425238 8.098502,3.6425238 8.2038402,3.747862 l 0.7373672,0.7373672 c 0.2106763,0.2106763 0.2106763,0.632029 0.1053381,0.8427054 L 7.466473,6.802669 Z"
|
||||
style="fill:#c83328;fill-opacity:1;stroke-width:1.05338168" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 333 B |
@@ -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"
|
||||
width="12"
|
||||
height="12"
|
||||
viewBox="0 0 12 12"
|
||||
preserveAspectRatio="xMinYMid meet"
|
||||
overflow="visible"
|
||||
version="1.1"
|
||||
id="svg4">
|
||||
<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
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#999999;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.80178976;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers fill stroke;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
d="M 6.0000002,2.4999999 C 4.0728255,2.4999999 2.5,4.0728255 2.5,6.0000001 c 0,1.9271748 1.5728255,3.5 3.5000002,3.5 C 7.927175,9.5000001 9.5,7.9271749 9.5,6.0000001 9.5,4.0728255 7.927175,2.4999999 6.0000002,2.4999999 Z m 0,0.9756153 c 1.4006045,0 2.525951,1.1237806 2.525951,2.5243849 0,1.4006045 -1.1253465,2.5259511 -2.525951,2.525951 -1.4006044,0 -2.525951,-1.1253465 -2.525951,-2.525951 0,-1.4006043 1.1253466,-2.5243849 2.525951,-2.5243849 z"
|
||||
id="path817" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.5 KiB |
@@ -48,11 +48,24 @@ $courseindex-item-current: $primary !default;
|
||||
opacity: .7;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.completioninfo {
|
||||
min-width: 24px;
|
||||
transform: scale(0.5);
|
||||
|
||||
&.completion_complete {
|
||||
color: $success;
|
||||
}
|
||||
|
||||
&.completion_fail {
|
||||
color: $danger;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.courseindex-sectioncontent {
|
||||
.courseindex-item {
|
||||
padding-left: calc(#{$courseindex-item-padding-x} * 2 + 0.5rem);
|
||||
padding-left: $courseindex-item-padding-x;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20780,9 +20780,16 @@ div.editor_atto_toolbar button .icon {
|
||||
.courseindex .courseindex-item.dimmed {
|
||||
opacity: .7;
|
||||
font-style: italic; }
|
||||
.courseindex .courseindex-item .completioninfo {
|
||||
min-width: 24px;
|
||||
transform: scale(0.5); }
|
||||
.courseindex .courseindex-item .completioninfo.completion_complete {
|
||||
color: #357a32; }
|
||||
.courseindex .courseindex-item .completioninfo.completion_fail {
|
||||
color: #ca3120; }
|
||||
|
||||
.courseindex .courseindex-sectioncontent .courseindex-item {
|
||||
padding-left: calc(1rem * 2 + 0.5rem); }
|
||||
padding-left: 1rem; }
|
||||
|
||||
.courseindex .courseindex-section .current-badge {
|
||||
line-height: 1.5;
|
||||
|
||||
@@ -20726,9 +20726,16 @@ div.editor_atto_toolbar button .icon {
|
||||
.courseindex .courseindex-item.dimmed {
|
||||
opacity: .7;
|
||||
font-style: italic; }
|
||||
.courseindex .courseindex-item .completioninfo {
|
||||
min-width: 24px;
|
||||
transform: scale(0.5); }
|
||||
.courseindex .courseindex-item .completioninfo.completion_complete {
|
||||
color: #357a32; }
|
||||
.courseindex .courseindex-item .completioninfo.completion_fail {
|
||||
color: #ca3120; }
|
||||
|
||||
.courseindex .courseindex-sectioncontent .courseindex-item {
|
||||
padding-left: calc(1rem * 2 + 0.5rem); }
|
||||
padding-left: 1rem; }
|
||||
|
||||
.courseindex .courseindex-section .current-badge {
|
||||
line-height: 1.5;
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
"lib/tests/behat/action_menu.feature",
|
||||
"blocks/tests/behat/hide_blocks.feature",
|
||||
"blocks/tests/behat/move_blocks.feature",
|
||||
"course/format/tests/behat/course_courseindex.feature"
|
||||
"course/format/tests/behat/course_courseindex.feature",
|
||||
"course/format/tests/behat/courseindex_completion.feature"
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user