Merge branch 'MDL-79194-401' of https://github.com/laurentdavid/moodle into MOODLE_401_STABLE

This commit is contained in:
Jun Pataleta 2023-10-27 23:31:46 +08:00
commit e146a9d8cd
No known key found for this signature in database
GPG Key ID: F83510526D99E2C7
7 changed files with 128 additions and 16 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -23,6 +23,7 @@
*/
import {BaseComponent} from 'core/reactive';
import {debounce} from 'core/utils';
import {getCurrentCourseEditor} from 'core_courseformat/courseeditor';
import inplaceeditable from 'core/inplace_editable';
import Section from 'core_courseformat/local/content/section';
@ -33,6 +34,8 @@ import DispatchActions from 'core_courseformat/local/content/actions';
import * as CourseEvents from 'core_course/events';
// The jQuery module is only used for interacting with Boostrap 4. It can we removed when MDL-71979 is integrated.
import jQuery from 'jquery';
import Pending from 'core/pending';
import log from 'core/log';
export default class Component extends BaseComponent {
@ -75,6 +78,7 @@ export default class Component extends BaseComponent {
this.cms = {};
// The page section return.
this.sectionReturn = descriptor.sectionReturn ?? 0;
this.debouncedReloads = new Map();
}
/**
@ -230,6 +234,8 @@ export default class Component extends BaseComponent {
{watch: `transaction:start`, handler: this._startProcessing},
{watch: `course.sectionlist:updated`, handler: this._refreshCourseSectionlist},
{watch: `section.cmlist:updated`, handler: this._refreshSectionCmlist},
// Section visibility.
{watch: `section.visible:updated`, handler: this._reloadSection},
// Reindex sections and cms.
{watch: `state:updated`, handler: this._indexContents},
// State changes thaty require to reload course modules.
@ -506,14 +512,65 @@ export default class Component extends BaseComponent {
* @param {object} param0.element the state object
*/
_reloadCm({element}) {
const cmitem = this.getElement(this.selectors.CM, element.id);
if (cmitem) {
const promise = courseActions.refreshModule(cmitem, element.id);
if (!this.getElement(this.selectors.CM, element.id)) {
return;
}
const debouncedReload = this._getDebouncedReloadCm(element.id);
debouncedReload();
}
/**
* Generate or get a reload CM debounced function.
* @param {Number} cmId
* @returns {Function} the debounced reload function
*/
_getDebouncedReloadCm(cmId) {
const pendingKey = `courseformat/content:reloadCm_${cmId}`;
let debouncedReload = this.debouncedReloads.get(pendingKey);
if (debouncedReload) {
return debouncedReload;
}
const reload = () => {
const pendingReload = new Pending(pendingKey);
this.debouncedReloads.delete(pendingKey);
const cmitem = this.getElement(this.selectors.CM, cmId);
if (!cmitem) {
return pendingReload.resolve();
}
const promise = courseActions.refreshModule(cmitem, cmId);
promise.then(() => {
this._indexContents();
return;
}).catch();
return true;
}).catch((error) => {
log.debug(error);
}).finally(() => {
pendingReload.resolve();
});
return pendingReload;
};
debouncedReload = debounce(
reload,
200,
{
cancel: true, pending: true
}
);
this.debouncedReloads.set(pendingKey, debouncedReload);
return debouncedReload;
}
/**
* Cancel the active reload CM debounced function, if any.
* @param {Number} cmId
*/
_cancelDebouncedReloadCm(cmId) {
const pendingKey = `courseformat/content:reloadCm_${cmId}`;
const debouncedReload = this.debouncedReloads.get(pendingKey);
if (!debouncedReload) {
return;
}
debouncedReload.cancel();
this.debouncedReloads.delete(pendingKey);
}
/**
@ -526,13 +583,22 @@ export default class Component extends BaseComponent {
* @param {object} param0.element the state object
*/
_reloadSection({element}) {
const pendingReload = new Pending(`courseformat/content:reloadSection_${element.id}`);
const sectionitem = this.getElement(this.selectors.SECTION, element.id);
if (sectionitem) {
// Cancel any pending reload because the section will reload cms too.
for (const cmId of element.cmlist) {
this._cancelDebouncedReloadCm(cmId);
}
const promise = courseActions.refreshSection(sectionitem, element.id);
promise.then(() => {
this._indexContents();
return;
}).catch();
return true;
}).catch((error) => {
log.debug(error);
}).finally(() => {
pendingReload.resolve();
});
}
}

View File

@ -1,3 +1,3 @@
define("core/utils",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.throttle=_exports.debounce=void 0;_exports.throttle=(func,wait)=>{let onCooldown=!1,runAgain=null;const run=function(){for(var _len=arguments.length,args=new Array(_len),_key=0;_key<_len;_key++)args[_key]=arguments[_key];runAgain=null!==runAgain,onCooldown||(func.apply(this,args),onCooldown=!0,setTimeout((()=>{const recurse=runAgain;onCooldown=!1,runAgain=null,recurse&&run(args)}),wait))};return run};_exports.debounce=(func,wait)=>{let timeout=null;return function(){for(var _len2=arguments.length,args=new Array(_len2),_key2=0;_key2<_len2;_key2++)args[_key2]=arguments[_key2];clearTimeout(timeout),timeout=setTimeout((()=>{func.apply(this,args)}),wait)}}}));
define("core/utils",["exports","core/pending"],(function(_exports,_pending){var obj;Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.throttle=_exports.debounce=void 0,_pending=(obj=_pending)&&obj.__esModule?obj:{default:obj};_exports.throttle=(func,wait)=>{let onCooldown=!1,runAgain=null;const run=function(){for(var _len=arguments.length,args=new Array(_len),_key=0;_key<_len;_key++)args[_key]=arguments[_key];runAgain=null!==runAgain,onCooldown||(func.apply(this,args),onCooldown=!0,setTimeout((()=>{const recurse=runAgain;onCooldown=!1,runAgain=null,recurse&&run(args)}),wait))};return run};const debounceMap=new Map;_exports.debounce=function(func,wait){let{pending:pending=!1,cancel:cancel=!1}=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},timeout=null;const returnedFunction=function(){for(var _len2=arguments.length,args=new Array(_len2),_key2=0;_key2<_len2;_key2++)args[_key2]=arguments[_key2];pending&&!debounceMap.has(returnedFunction)&&debounceMap.set(returnedFunction,new _pending.default("core/utils:debounce")),clearTimeout(timeout),timeout=setTimeout((async()=>{const pendingPromise=debounceMap.get(returnedFunction);debounceMap.delete(returnedFunction),await func.apply(undefined,args),null==pendingPromise||pendingPromise.resolve()}),wait)};return cancel&&(returnedFunction.cancel=()=>{const pendingPromise=debounceMap.get(returnedFunction);null==pendingPromise||pendingPromise.resolve(),clearTimeout(timeout)}),returnedFunction}}));
//# sourceMappingURL=utils.min.js.map

File diff suppressed because one or more lines are too long

View File

@ -20,6 +20,7 @@
* @copyright 2019 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import Pending from 'core/pending';
/**
* Create a wrapper function to throttle the execution of the given
@ -70,6 +71,11 @@ export const throttle = (func, wait) => {
return run;
};
/**
* @property {Map} debounceMap A map of functions to their debounced pending promises.
*/
const debounceMap = new Map();
/**
* Create a wrapper function to debounce the execution of the given
* function. Each attempt to execute the function will reset the cooldown
@ -78,14 +84,49 @@ export const throttle = (func, wait) => {
* @method
* @param {Function} func The function to debounce
* @param {Number} wait The number of milliseconds to wait after the final attempt to execute
* @param {Object} [options]
* @param {boolean} [options.pending=false] Whether to wrap the debounced method in a pending promise
* @param {boolean} [options.cancel=false] Whether to add a cancel method to the debounced function
* @return {Function}
*/
export const debounce = (func, wait) => {
export const debounce = (
func,
wait,
{
pending = false,
cancel = false,
} = {},
) => {
let timeout = null;
return function(...args) {
const returnedFunction = (...args) => {
if (pending && !debounceMap.has(returnedFunction)) {
debounceMap.set(returnedFunction, new Pending('core/utils:debounce'));
}
clearTimeout(timeout);
timeout = setTimeout(() => {
func.apply(this, args);
timeout = setTimeout(async() => {
// Get the current pending promise and immediately empty it.
// This is important to allow the function to be debounced again as soon as possible.
// We do not resolve it until later - but that's fine because the promise is appropriately scoped.
const pendingPromise = debounceMap.get(returnedFunction);
debounceMap.delete(returnedFunction);
// Allow the debounced function to return a Promise.
// This ensures that Behat will not continue until the function has finished executing.
await func.apply(this, args);
// Resolve the pending promise if it exists.
pendingPromise?.resolve();
}, wait);
};
if (cancel) {
returnedFunction.cancel = () => {
const pendingPromise = debounceMap.get(returnedFunction);
pendingPromise?.resolve();
clearTimeout(timeout);
};
}
return returnedFunction;
};

View File

@ -1,6 +1,11 @@
This files describes API changes in core libraries and APIs,
information provided here is intended especially for developers.
=== 4.1.7 ===
* Add a new parameter to the debounce (core/utils) function allow it to create its own own Pending promise.
(via options.pending). This is a backport of patch MDL-78779.
* Add a new parameter to the debounce (core/utils) function to allow for cancellation.
=== 4.1.6 ===
* \moodle_page::set_title() has been updated to append the site name depending on the value of $CFG->sitenameintitle and whether
the site's fullname/shortname has been set. So there's no need to manually add the site name whenever calling $PAGE->set_title().