mirror of
https://github.com/moodle/moodle.git
synced 2025-08-05 00:46:50 +02:00
Merge branch 'MDL-71795-master' of git://github.com/ferranrecio/moodle
This commit is contained in:
commit
8b7fb0f7ab
11 changed files with 151 additions and 17 deletions
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/courseindex/placeholder",["exports","core/reactive","core/templates","core_courseformat/courseeditor"],function(a,b,c,d){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;c=function(a){return a&&a.__esModule?a:{default:a}}(c);function e(a){"@babel/helpers - typeof";if("function"==typeof Symbol&&"symbol"==typeof Symbol.iterator){e=function(a){return typeof a}}else{e=function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?"symbol":typeof a}}return e(a)}function f(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 g(a){return function(){var b=this,c=arguments;return new Promise(function(d,e){var i=a.apply(b,c);function g(a){f(i,d,e,g,h,"next",a)}function h(a){f(i,d,e,g,h,"throw",a)}g(void 0)})}}function h(a,b){if(!(a instanceof b)){throw new TypeError("Cannot call a class as a function")}}function i(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 j(a,b,c){if(b)i(a.prototype,b);if(c)i(a,c);return a}function k(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)l(a,b)}function l(a,b){l=Object.setPrototypeOf||function(a,b){a.__proto__=b;return a};return l(a,b)}function m(a){return function(){var b=q(a),c;if(p()){var d=q(this).constructor;c=Reflect.construct(b,arguments,d)}else{c=b.apply(this,arguments)}return n(this,c)}}function n(a,b){if(b&&("object"===e(b)||"function"==typeof b)){return b}return o(a)}function o(a){if(void 0===a){throw new ReferenceError("this hasn't been initialised - super() hasn't been called")}return a}function p(){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 q(a){q=Object.setPrototypeOf?Object.getPrototypeOf:function(a){return a.__proto__||Object.getPrototypeOf(a)};return q(a)}var r=function(a){k(b,a);var e=m(b);function b(){h(this,b);return e.apply(this,arguments)}j(b,[{key:"stateReady",value:function(){var a=g(regeneratorRuntime.mark(function a(b){var d,e,f,g,h;return regeneratorRuntime.wrap(function(a){while(1){switch(a.prev=a.next){case 0:d=this.reactive.getExporter();e=d.course(b);a.prev=2;a.next=5;return c.default.renderForPromise("core_courseformat/local/courseindex/courseindex",e);case 5:f=a.sent;g=f.html;h=f.js;c.default.replaceNode(this.element,g,h);a.next=14;break;case 11:a.prev=11;a.t0=a["catch"](2);throw a.t0;case 14:case"end":return a.stop();}}},a,this,[[2,11]])}));return function stateReady(){return a.apply(this,arguments)}}()}],[{key:"init",value:function init(a,c){return new b({element:document.getElementById(a),reactive:(0,d.getCurrentCourseEditor)(),selectors:c})}}]);return b}(b.BaseComponent);a.default=r;return a.default});
|
||||
define ("core_courseformat/local/courseindex/placeholder",["exports","core/reactive","core/templates","core_courseformat/courseeditor","core/pending"],function(a,b,c,d,e){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;c=f(c);e=f(e);function f(a){return a&&a.__esModule?a:{default:a}}function g(a){"@babel/helpers - typeof";if("function"==typeof Symbol&&"symbol"==typeof Symbol.iterator){g=function(a){return typeof a}}else{g=function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?"symbol":typeof a}}return g(a)}function h(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 i(a){return function(){var b=this,c=arguments;return new Promise(function(d,e){var i=a.apply(b,c);function f(a){h(i,d,e,f,g,"next",a)}function g(a){h(i,d,e,f,g,"throw",a)}f(void 0)})}}function j(a,b){if(!(a instanceof b)){throw new TypeError("Cannot call a class as a function")}}function k(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 l(a,b,c){if(b)k(a.prototype,b);if(c)k(a,c);return a}function m(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)n(a,b)}function n(a,b){n=Object.setPrototypeOf||function(a,b){a.__proto__=b;return a};return n(a,b)}function o(a){return function(){var b=s(a),c;if(r()){var d=s(this).constructor;c=Reflect.construct(b,arguments,d)}else{c=b.apply(this,arguments)}return p(this,c)}}function p(a,b){if(b&&("object"===g(b)||"function"==typeof b)){return b}return q(a)}function q(a){if(void 0===a){throw new ReferenceError("this hasn't been initialised - super() hasn't been called")}return a}function r(){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 s(a){s=Object.setPrototypeOf?Object.getPrototypeOf:function(a){return a.__proto__||Object.getPrototypeOf(a)};return s(a)}var t=function(a){m(b,a);var f=o(b);function b(){j(this,b);return f.apply(this,arguments)}l(b,[{key:"create",value:function create(){this.pendingContent=new e.default("core_courseformat/placeholder:loadcourseindex")}},{key:"stateReady",value:function(){var a=i(regeneratorRuntime.mark(function a(b){return regeneratorRuntime.wrap(function(a){while(1){switch(a.prev=a.next){case 0:if(this.loadStaticContent()){a.next=3;break}a.next=3;return this.loadTemplateContent(b);case 3:case"end":return a.stop();}}},a,this)}));return function stateReady(){return a.apply(this,arguments)}}()},{key:"loadStaticContent",value:function loadStaticContent(){var a=this.reactive.getStorageValue("courseIndex");if(a.html&&a.js){c.default.replaceNode(this.element,a.html,a.js);this.pendingContent.resolve();return!0}return!1}},{key:"loadTemplateContent",value:function(){var a=i(regeneratorRuntime.mark(function a(b){var d,e,f,g,h;return regeneratorRuntime.wrap(function(a){while(1){switch(a.prev=a.next){case 0:d=this.reactive.getExporter();e=d.course(b);a.prev=2;a.next=5;return c.default.renderForPromise("core_courseformat/local/courseindex/courseindex",e);case 5:f=a.sent;g=f.html;h=f.js;c.default.replaceNode(this.element,g,h);this.pendingContent.resolve();this.reactive.setStorageValue("courseIndex",{html:g,js:h});a.next=17;break;case 13:a.prev=13;a.t0=a["catch"](2);this.pendingContent.resolve(a.t0);throw a.t0;case 17:case"end":return a.stop();}}},a,this,[[2,13]])}));return function loadTemplateContent(){return a.apply(this,arguments)}}()}],[{key:"init",value:function init(a,c){return new b({element:document.getElementById(a),reactive:(0,d.getCurrentCourseEditor)(),selectors:c})}}]);return b}(b.BaseComponent);a.default=t;return a.default});
|
||||
//# sourceMappingURL=placeholder.min.js.map
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -18,6 +18,7 @@ import notification from 'core/notification';
|
|||
import Exporter from 'core_courseformat/local/courseeditor/exporter';
|
||||
import log from 'core/log';
|
||||
import ajax from 'core/ajax';
|
||||
import * as Storage from 'core/sessionstorage';
|
||||
|
||||
/**
|
||||
* Main course editor module.
|
||||
|
@ -32,6 +33,19 @@ import ajax from 'core/ajax';
|
|||
*/
|
||||
export default class extends Reactive {
|
||||
|
||||
/**
|
||||
* The current state cache key
|
||||
*
|
||||
* The state cache is considered dirty if the state changes from the last page or
|
||||
* if the page has editing mode on.
|
||||
*
|
||||
* @attribute stateKey
|
||||
* @type number|null
|
||||
* @default 1
|
||||
* @package
|
||||
*/
|
||||
stateKey = 1;
|
||||
|
||||
/**
|
||||
* Set up the course editor when the page is ready.
|
||||
*
|
||||
|
@ -62,6 +76,20 @@ export default class extends Reactive {
|
|||
}
|
||||
|
||||
this.setInitialState(stateData);
|
||||
|
||||
// In editing mode, the session cache is considered dirty always.
|
||||
if (this.isEditing) {
|
||||
this.stateKey = null;
|
||||
} else {
|
||||
// Check if the last state is the same as the cached one.
|
||||
const newState = JSON.stringify(stateData);
|
||||
const previousState = Storage.get(`course/${courseId}/staticState`);
|
||||
if (previousState !== newState) {
|
||||
Storage.set(`course/${courseId}/staticState`, newState);
|
||||
Storage.set(`course/${courseId}/stateKey`, Date.now());
|
||||
}
|
||||
this.stateKey = Storage.get(`course/${courseId}/stateKey`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -128,6 +156,56 @@ export default class extends Reactive {
|
|||
return this._supportscomponents ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a value from the course editor static storage if any.
|
||||
*
|
||||
* The course editor static storage uses the sessionStorage to store values from the
|
||||
* components. This is used to prevent unnecesary template loadings on every page. However,
|
||||
* the storage does not work if no sessionStorage can be used (in debug mode for example),
|
||||
* if the page is in editing mode or if the initial state change from the last page.
|
||||
*
|
||||
* @param {string} key the key to get
|
||||
* @return {boolean|string} the storage value or false if cannot be loaded
|
||||
*/
|
||||
getStorageValue(key) {
|
||||
if (this.isEditing || !this.stateKey) {
|
||||
return false;
|
||||
}
|
||||
const dataJson = Storage.get(`course/${this.courseId}/${key}`);
|
||||
if (!dataJson) {
|
||||
return false;
|
||||
}
|
||||
// Check the stateKey.
|
||||
try {
|
||||
const data = JSON.parse(dataJson);
|
||||
if (data?.stateKey !== this.stateKey) {
|
||||
return false;
|
||||
}
|
||||
return data.value;
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores a value into the course editor static storage if available
|
||||
*
|
||||
* @param {String} key the key to store
|
||||
* @param {*} value the value to store (must be compatible with JSON,stringify)
|
||||
* @returns {boolean} true if the value is stored
|
||||
*/
|
||||
setStorageValue(key, value) {
|
||||
// Values cannot be stored on edit mode.
|
||||
if (this.isEditing) {
|
||||
return false;
|
||||
}
|
||||
const data = {
|
||||
stateKey: this.stateKey,
|
||||
value,
|
||||
};
|
||||
return Storage.set(`course/${this.courseId}/${key}`, JSON.stringify(data));
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatch a change in the state.
|
||||
*
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
import {BaseComponent} from 'core/reactive';
|
||||
import Templates from 'core/templates';
|
||||
import {getCurrentCourseEditor} from 'core_courseformat/courseeditor';
|
||||
import Pending from 'core/pending';
|
||||
|
||||
export default class Component extends BaseComponent {
|
||||
|
||||
|
@ -43,6 +44,14 @@ export default class Component extends BaseComponent {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Component creation hook.
|
||||
*/
|
||||
create() {
|
||||
// Add a pending operation waiting for the initial content.
|
||||
this.pendingContent = new Pending(`core_courseformat/placeholder:loadcourseindex`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initial state ready method.
|
||||
*
|
||||
|
@ -51,10 +60,38 @@ export default class Component extends BaseComponent {
|
|||
* @param {object} state the initial state
|
||||
*/
|
||||
async stateReady(state) {
|
||||
|
||||
// Check if we have a static course index already loded from a previous page.
|
||||
if (!this.loadStaticContent()) {
|
||||
await this.loadTemplateContent(state);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the course index from the session storage if any.
|
||||
*
|
||||
* @return {boolean} true if the static version is loaded form the session
|
||||
*/
|
||||
loadStaticContent() {
|
||||
// Load the previous static course index from the session cache.
|
||||
const index = this.reactive.getStorageValue(`courseIndex`);
|
||||
if (index.html && index.js) {
|
||||
Templates.replaceNode(this.element, index.html, index.js);
|
||||
this.pendingContent.resolve();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the course index template.
|
||||
*
|
||||
* @param {Object} state the initial state
|
||||
*/
|
||||
async loadTemplateContent(state) {
|
||||
// Collect section information from the state.
|
||||
const exporter = this.reactive.getExporter();
|
||||
const data = exporter.course(state);
|
||||
|
||||
try {
|
||||
// To render an HTML into our component we just use the regular Templates module.
|
||||
const {html, js} = await Templates.renderForPromise(
|
||||
|
@ -62,7 +99,12 @@ export default class Component extends BaseComponent {
|
|||
data,
|
||||
);
|
||||
Templates.replaceNode(this.element, html, js);
|
||||
this.pendingContent.resolve();
|
||||
|
||||
// Save the rendered template into the session cache.
|
||||
this.reactive.setStorageValue(`courseIndex`, {html, js});
|
||||
} catch (error) {
|
||||
this.pendingContent.resolve(error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,10 +25,15 @@ Feature: Course index depending on role
|
|||
| student1 | C1 | student |
|
||||
| teacher1 | C1 | editingteacher |
|
||||
|
||||
@javascript
|
||||
Scenario: Course index is present on course and activities.
|
||||
Given I log in as "teacher1"
|
||||
When I am on "Course 1" course homepage
|
||||
Given I am on the "C1" "Course" page logged in as "teacher1"
|
||||
When I click on "Side panel" "button"
|
||||
Then I should see "Open course index drawer"
|
||||
And I am on the "Activity sample 1" "assign activity" page
|
||||
And I should see "Open course index drawer"
|
||||
And I click on "Open course index drawer" "button"
|
||||
And I should see "Activity sample 1" in the "courseindex-content" "region"
|
||||
|
||||
@javascript
|
||||
Scenario: Course index as a teacher
|
||||
|
|
|
@ -3867,15 +3867,17 @@ function core_course_core_calendar_get_valid_event_timestart_range(\calendar_eve
|
|||
*/
|
||||
function core_course_drawer(): string {
|
||||
global $PAGE;
|
||||
// Only course are able to render course index.
|
||||
if (!preg_match('/^(course).*/', $PAGE->pagetype)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$format = course_get_format($PAGE->course);
|
||||
|
||||
// Only course and modules are able to render course index.
|
||||
$ismod = strpos($PAGE->pagetype, 'mod-') === 0;
|
||||
if ($ismod || $PAGE->pagetype == 'course-view-' . $format->get_format()) {
|
||||
$renderer = $format->get_renderer($PAGE);
|
||||
$placeholder = $renderer->course_index_drawer($format);
|
||||
return $placeholder;
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -314,10 +314,15 @@ class page_requirements_manager {
|
|||
|
||||
// It is possible that the $page->context is null, so we can't use $page->context->id.
|
||||
$contextid = null;
|
||||
$contextinstanceid = null;
|
||||
if (!is_null($page->context)) {
|
||||
$contextid = $page->context->id;
|
||||
$contextinstanceid = $page->context->instanceid;
|
||||
}
|
||||
|
||||
$courseid = $page->course->id;
|
||||
$coursecontext = context_course::instance($courseid);
|
||||
|
||||
$this->M_cfg = array(
|
||||
'wwwroot' => $CFG->wwwroot,
|
||||
'sesskey' => sesskey(),
|
||||
|
@ -331,8 +336,10 @@ class page_requirements_manager {
|
|||
'admin' => $CFG->admin,
|
||||
'svgicons' => $page->theme->use_svg_icons(),
|
||||
'usertimezone' => usertimezone(),
|
||||
'courseId' => (int) $page->course->id,
|
||||
'courseId' => (int) $courseid,
|
||||
'courseContextId' => $coursecontext->id,
|
||||
'contextid' => $contextid,
|
||||
'contextInstanceId' => (int) $contextinstanceid,
|
||||
'langrev' => get_string_manager()->get_revision(),
|
||||
'templaterev' => $this->get_templaterev()
|
||||
);
|
||||
|
|
|
@ -1119,7 +1119,7 @@ class behat_navigation extends behat_base {
|
|||
"//nav[@aria-label='Navigation bar']/ol/li[last()][contains(normalize-space(.), '" . $pagename . "')]"
|
||||
);
|
||||
if (!$link) {
|
||||
$this->execute("behat_general::click_link", $pagename);
|
||||
$this->execute("behat_general::i_click_on_in_the", [$pagename, 'link', 'page', 'region']);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -87,7 +87,7 @@ $bulkoperations = has_capability('moodle/course:bulkmessaging', $context);
|
|||
|
||||
$PAGE->set_title("$course->shortname: ".get_string('participants'));
|
||||
$PAGE->set_heading($course->fullname);
|
||||
$PAGE->set_pagetype('course-view-' . $course->format);
|
||||
$PAGE->set_pagetype('course-view-participants');
|
||||
$PAGE->set_docs_path('enrol/users');
|
||||
$PAGE->add_body_class('path-user'); // So we can style it independently.
|
||||
$PAGE->set_other_editing_capability('moodle/course:manageactivities');
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue