MDL-76432 core: add process monitor UI component

Create a new UI compoment to queue, execute and display errors on batch
processing. The first use of this component is when the teacher drops a
file into the course page.
This commit is contained in:
Ferran Recio
2023-02-06 12:58:43 +01:00
parent ff5f669cf8
commit 2846751f2b
28 changed files with 1196 additions and 0 deletions
+1
View File
@@ -1745,6 +1745,7 @@ $string['private_files_handler_name'] = 'Email to Private files';
$string['proceed'] = 'Proceed';
$string['profile'] = 'Profile';
$string['profilenotshown'] = 'This profile description will not be shown until this person is enrolled in at least one course.';
$string['progress'] = 'Progress';
$string['publicprofile'] = 'Public profile';
$string['publicsitefileswarning'] = 'Note: files placed here can be accessed by anyone';
$string['publicsitefileswarning2'] = 'Note: Files placed here can be accessed by anyone who knows (or can guess) the URL. For security reasons, it is recommended that any backup files are deleted immediately after restoring them.';
+12
View File
@@ -0,0 +1,12 @@
define("core/local/process_monitor/events",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.dispatchStateChangedEvent=function(detail,target){void 0===target&&(target=document);target.dispatchEvent(new CustomEvent(eventTypes.processMonitorStateChange,{bubbles:!0,detail:detail}))},_exports.eventTypes=void 0;
/**
* Javascript events for the `process_monitor` module.
*
* @module core/local/process_monitor/events
* @copyright 2022 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since 4.2
*/
const eventTypes={processMonitorStateChange:"core_editor/contentRestored"};_exports.eventTypes=eventTypes}));
//# sourceMappingURL=events.min.js.map
@@ -0,0 +1 @@
{"version":3,"file":"events.min.js","sources":["../../../src/local/process_monitor/events.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/ //\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * Javascript events for the `process_monitor` module.\n *\n * @module core/local/process_monitor/events\n * @copyright 2022 Ferran Recio <[email protected]>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n * @since 4.2\n */\n\n/**\n * Events for the `core_editor` subsystem.\n *\n * @constant\n * @property {String} processMonitorStateChange See {@link event:processMonitorStateChange}\n */\nexport const eventTypes = {\n /**\n * An event triggered when the monitor state has changed.\n *\n * @event processMonitorStateChange\n */\n processMonitorStateChange: 'core_editor/contentRestored',\n};\n\n/**\n * Trigger a state changed event.\n *\n * @method dispatchStateChangedEvent\n * @param {Object} detail the full state\n * @param {Object} target the custom event target (document if none provided)\n * @param {Function} target.dispatchEvent the component dispatch event method.\n */\nexport function dispatchStateChangedEvent(detail, target) {\n if (target === undefined) {\n target = document;\n }\n target.dispatchEvent(new CustomEvent(\n eventTypes.processMonitorStateChange,\n {\n bubbles: true,\n detail: detail,\n }\n ));\n}\n"],"names":["detail","target","undefined","document","dispatchEvent","CustomEvent","eventTypes","processMonitorStateChange","bubbles"],"mappings":"+KA8C0CA,OAAQC,aAC/BC,IAAXD,SACAA,OAASE,UAEbF,OAAOG,cAAc,IAAIC,YACrBC,WAAWC,0BACX,CACIC,SAAS,EACTR,OAAQA;;;;;;;;;MAzBPM,WAAa,CAMtBC,0BAA2B"}
@@ -0,0 +1,3 @@
define("core/local/process_monitor/loadingprocess",["exports","core/log"],(function(_exports,_log){var obj;function _defineProperty(obj,key,value){return key in obj?Object.defineProperty(obj,key,{value:value,enumerable:!0,configurable:!0,writable:!0}):obj[key]=value,obj}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.LoadingProcess=void 0,_log=(obj=_log)&&obj.__esModule?obj:{default:obj};_exports.LoadingProcess=class{constructor(manager,definition){_defineProperty(this,"processData",null),_defineProperty(this,"extraData",null),_defineProperty(this,"manager",null),_defineProperty(this,"finishedCallback",null),_defineProperty(this,"removedCallback",null),_defineProperty(this,"errorCallback",null),this.manager=manager,this.processData={id:manager.generateProcessId(),name:"",percentage:0,url:null,error:null,finished:!1,...definition},this._dispatch("addProcess",this.processData)}_dispatch(action,params){this.manager.getInitialStatePromise().then((()=>{this.manager.dispatch(action,params)})).catch((()=>{_log.default.error("Cannot update process monitor.")}))}onFinish(callback){this.finishedCallback=callback}onRemove(callback){this.removedCallback=callback}onError(callback){this.errorCallback=callback}setPercentage(percentage){this.processData.percentage=percentage,this._dispatch("updateProcess",this.processData)}setExtraData(extraData){this.extraData=extraData}setError(error){this.processData.error=error,null!==this.errorCallback&&this.errorCallback(this),this.processData.finished=!0,null!==this.finishedCallback&&this.finishedCallback(this),this._dispatch("updateProcess",this.processData)}setName(name){this.processData.name=name,this._dispatch("updateProcess",this.processData)}finish(){this.processData.finished=!0,null!==this.finishedCallback&&this.finishedCallback(this),this._dispatch("updateProcess",this.processData)}remove(){null!==this.removedCallback&&this.removedCallback(this),this._dispatch("removeProcess",this.processData.id)}getData(){return{...this.processData}}get name(){return this.processData.name}get id(){return this.processData.id}get data(){return this.extraData}}}));
//# sourceMappingURL=loadingprocess.min.js.map
File diff suppressed because one or more lines are too long
+14
View File
@@ -0,0 +1,14 @@
define("core/local/process_monitor/manager",["exports","core/reactive","core/local/process_monitor/events"],(function(_exports,_reactive,_events){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.manager=void 0;
/**
* The reactive file uploader class.
*
* As all the upload queues are reactive, any plugin can implement its own upload monitor.
*
* @module core/local/process_monitor/manager
* @class ProcessMonitorManager
* @copyright 2021 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class ProcessMonitorManager extends _reactive.Reactive{constructor(){var obj,key,value;super(...arguments),value=1,(key="nextId")in(obj=this)?Object.defineProperty(obj,key,{value:value,enumerable:!0,configurable:!0,writable:!0}):obj[key]=value}generateProcessId(){return this.nextId++}}const mutations={addProcess:function(stateManager,processData){const state=stateManager.state;stateManager.setReadOnly(!1),state.queue.add({...processData}),state.display.show=!0,stateManager.setReadOnly(!0)},removeProcess:function(stateManager,processId){const state=stateManager.state;stateManager.setReadOnly(!1),state.queue.delete(processId),0===state.queue.size&&(state.display.show=!1),stateManager.setReadOnly(!0)},updateProcess:function(stateManager,processData){if(void 0===processData.id)throw Error("Missing process ID in process data");const state=stateManager.state;stateManager.setReadOnly(!1);const queueItem=state.queue.get(processData.id);if(!queueItem)throw Error("Unkown process with id ".concat(processData.id));for(const[prop,propValue]of Object.entries(processData))queueItem[prop]=propValue;stateManager.setReadOnly(!0)},setShow:function(stateManager,show){const state=stateManager.state;stateManager.setReadOnly(!1),state.display.show=show,show||this.cleanFinishedProcesses(stateManager),stateManager.setReadOnly(!0)},removeAllProcesses:function(stateManager){const state=stateManager.state;stateManager.setReadOnly(!1),state.queue.forEach((element=>{state.queue.delete(element.id)})),state.display.show=!1,stateManager.setReadOnly(!0)},cleanFinishedProcesses:function(stateManager){const state=stateManager.state;stateManager.setReadOnly(!1),state.queue.forEach((element=>{element.finished&&!element.error&&state.queue.delete(element.id)})),0===state.queue.size&&(state.display.show=!1),stateManager.setReadOnly(!0)}},manager=new ProcessMonitorManager({name:"ProcessMonitor",eventName:_events.eventTypes.processMonitorStateChange,eventDispatch:_events.dispatchStateChangedEvent,mutations:mutations,state:{display:{show:!1},queue:[]}});_exports.manager=manager}));
//# sourceMappingURL=manager.min.js.map
File diff suppressed because one or more lines are too long
+11
View File
@@ -0,0 +1,11 @@
define("core/local/process_monitor/monitor",["exports","core/templates","core/reactive","core/local/process_monitor/manager"],(function(_exports,_templates,_reactive,_manager){var obj;
/**
* The file upload monitor component.
*
* @module core/local/process_monitor/monitor
* @class core/local/process_monitor/monitor
* @copyright 2022 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_templates=(obj=_templates)&&obj.__esModule?obj:{default:obj};class _default extends _reactive.BaseComponent{create(){this.name="process_monitor",this.selectors={QUEUELIST:'[data-for="process-list"]',CLOSE:'[data-action="hide"]'},this.classes={HIDE:"d-none"}}static init(query,selectors){return new this({element:document.querySelector(query),reactive:_manager.manager,selectors:selectors})}stateReady(state){this._updateMonitor({state:state,element:state.display}),this.addEventListener(this.getElement(this.selectors.CLOSE),"click",this._closeMonitor),state.queue.forEach((element=>{this._createListItem({state:state,element:element})}))}getWatchers(){return[{watch:"queue:created",handler:this._createListItem},{watch:"display:updated",handler:this._updateMonitor}]}async _createListItem(_ref){let{element:element}=_ref;const{html:html,js:js}=await _templates.default.renderForPromise("core/local/process_monitor/process",{...element}),target=this.getElement(this.selectors.QUEUELIST);_templates.default.appendNodeContents(target,html,js)}_updateMonitor(_ref2){let{element:element}=_ref2;this.element.classList.toggle(this.classes.HIDE,!0!==element.show)}_closeMonitor(){this.reactive.dispatch("setShow",!1)}}return _exports.default=_default,_exports.default}));
//# sourceMappingURL=monitor.min.js.map
File diff suppressed because one or more lines are too long
+12
View File
@@ -0,0 +1,12 @@
define("core/local/process_monitor/process",["exports","core/reactive","core/local/process_monitor/manager"],(function(_exports,_reactive,_manager){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0;
/**
* The process motnitor's process reactive component.
*
* @module core/local/process_monitor/process
* @class core/local/process_monitor/process
* @copyright 2022 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class _default extends _reactive.BaseComponent{create(){this.name="process_monitor_process",this.selectors={CLOSE:'[data-action="closeProcess"]',ERROR:'[data-for="error"]',PROGRESSBAR:"progress",NAME:'[data-for="name"]'},this.classes={HIDE:"d-none"},this.id=this.element.dataset.id}static init(query,selectors){return new this({element:document.querySelector(query),reactive:_manager.manager,selectors:selectors})}stateReady(state){this._refreshItem({state:state,element:state.queue.get(this.id)}),this.addEventListener(this.getElement(this.selectors.CLOSE),"click",this._removeProcess)}getWatchers(){return[{watch:"queue[".concat(this.id,"]:updated"),handler:this._refreshItem},{watch:"queue[".concat(this.id,"]:deleted"),handler:this.remove}]}async _refreshItem(_ref){let{element:element}=_ref;this.getElement(this.selectors.NAME).innerHTML=element.name;const progressbar=this.getElement(this.selectors.PROGRESSBAR);progressbar.classList.toggle(this.classes.HIDE,element.finished),progressbar.value=element.percentage;this.getElement(this.selectors.CLOSE).classList.toggle(this.classes.HIDE,!element.error);const error=this.getElement(this.selectors.ERROR);error.innerHTML=element.error,error.classList.toggle(this.classes.HIDE,!element.error)}_removeProcess(){this.reactive.dispatch("removeProcess",this.id)}}return _exports.default=_default,_exports.default}));
//# sourceMappingURL=process.min.js.map
File diff suppressed because one or more lines are too long
+14
View File
@@ -0,0 +1,14 @@
define("core/local/process_monitor/processqueue",["exports","core/utils","core/local/process_monitor/loadingprocess","core/log"],(function(_exports,_utils,_loadingprocess,_log){var obj;function _defineProperty(obj,key,value){return key in obj?Object.defineProperty(obj,key,{value:value,enumerable:!0,configurable:!0,writable:!0}):obj[key]=value,obj}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.ProcessQueue=void 0,_log=(obj=_log)&&obj.__esModule?obj:{default:obj};_exports.ProcessQueue=
/**
* A process queue manager.
*
* Adding process to the queue will guarante process are executed in sequence.
*
* @module core/local/process_monitor/processqueue
* @class ProcessQueue
* @copyright 2022 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class{constructor(manager){_defineProperty(this,"pending",[]),_defineProperty(this,"currentProcess",null),this.manager=manager,this.cleanFinishedProcesses=(0,_utils.debounce)((()=>manager.dispatch("cleanFinishedProcesses")),3e3)}addPending(processName,processor){const process=new _loadingprocess.LoadingProcess(this.manager,{name:processName});process.setExtraData({processor:processor}),process.onFinish((uploadedFile=>{var _this$currentProcess;(null===(_this$currentProcess=this.currentProcess)||void 0===_this$currentProcess?void 0:_this$currentProcess.id)===uploadedFile.id&&this._discardCurrent()})),this.pending.push(process),this._continueProcessing()}addError(processName,errorMessage){new _loadingprocess.LoadingProcess(this.manager,{name:processName}).setError(errorMessage)}_discardCurrent(){this.currentProcess&&(this.currentProcess=null),this.cleanFinishedProcesses(),this._continueProcessing()}_currentProcessor(){return this.currentProcess.data.processor}async _continueProcessing(){if(null===this.currentProcess&&0!==this.pending.length){this.currentProcess=this.pending.shift();try{const processor=this._currentProcessor();await processor(this.currentProcess)}catch(error){this.currentProcess.setError(error.message),_log.default.error(error)}}}}}));
//# sourceMappingURL=processqueue.min.js.map
@@ -0,0 +1 @@
{"version":3,"file":"processqueue.min.js","sources":["../../../src/local/process_monitor/processqueue.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\nimport {debounce} from 'core/utils';\nimport {LoadingProcess} from 'core/local/process_monitor/loadingprocess';\nimport log from 'core/log';\n\nconst TOASTSTIMER = 3000;\n\n/**\n * A process queue manager.\n *\n * Adding process to the queue will guarante process are executed in sequence.\n *\n * @module core/local/process_monitor/processqueue\n * @class ProcessQueue\n * @copyright 2022 Ferran Recio <[email protected]>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nexport class ProcessQueue {\n /** @var {Array} pending the pending queue. */\n pending = [];\n\n /** @var {LoadingProcess} current the current uploading process. */\n currentProcess = null;\n\n /**\n * Class constructor.\n * @param {ProcessMonitorManager} manager the monitor manager\n */\n constructor(manager) {\n this.manager = manager;\n this.cleanFinishedProcesses = debounce(\n () => manager.dispatch('cleanFinishedProcesses'),\n TOASTSTIMER\n );\n }\n\n /**\n * Adds a new pending upload to the queue.\n * @param {String} processName the process name\n * @param {Function} processor the execution function\n */\n addPending(processName, processor) {\n const process = new LoadingProcess(this.manager, {name: processName});\n process.setExtraData({\n processor,\n });\n process.onFinish((uploadedFile) => {\n if (this.currentProcess?.id !== uploadedFile.id) {\n return;\n }\n this._discardCurrent();\n });\n this.pending.push(process);\n this._continueProcessing();\n }\n\n /**\n * Adds a new pending upload to the queue.\n * @param {String} processName the file info\n * @param {String} errorMessage the file processor\n */\n addError(processName, errorMessage) {\n const process = new LoadingProcess(this.manager, {name: processName});\n process.setError(errorMessage);\n }\n\n /**\n * Discard the current process and execute the next one if any.\n */\n _discardCurrent() {\n if (this.currentProcess) {\n this.currentProcess = null;\n }\n this.cleanFinishedProcesses();\n this._continueProcessing();\n }\n\n /**\n * Return the current file uploader.\n * @return {FileUploader}\n */\n _currentProcessor() {\n return this.currentProcess.data.processor;\n }\n\n /**\n * Continue the queue processing if no current process is defined.\n */\n async _continueProcessing() {\n if (this.currentProcess !== null || this.pending.length === 0) {\n return;\n }\n this.currentProcess = this.pending.shift();\n try {\n const processor = this._currentProcessor();\n await processor(this.currentProcess);\n } catch (error) {\n this.currentProcess.setError(error.message);\n log.error(error);\n }\n }\n}\n"],"names":["constructor","manager","cleanFinishedProcesses","dispatch","addPending","processName","processor","process","LoadingProcess","this","name","setExtraData","onFinish","uploadedFile","currentProcess","id","_discardCurrent","pending","push","_continueProcessing","addError","errorMessage","setError","_currentProcessor","data","length","shift","error","message"],"mappings":";;;;;;;;;;;MA0CIA,YAAYC,wCATF,0CAGO,WAORA,QAAUA,aACVC,wBAAyB,oBAC1B,IAAMD,QAAQE,SAAS,2BA1Bf,KAoChBC,WAAWC,YAAaC,iBACdC,QAAU,IAAIC,+BAAeC,KAAKR,QAAS,CAACS,KAAML,cACxDE,QAAQI,aAAa,CACjBL,UAAAA,YAEJC,QAAQK,UAAUC,2EACLC,2EAAgBC,MAAOF,aAAaE,SAGxCC,0BAEJC,QAAQC,KAAKX,cACbY,sBAQTC,SAASf,YAAagB,cACF,IAAIb,+BAAeC,KAAKR,QAAS,CAACS,KAAML,cAChDiB,SAASD,cAMrBL,kBACQP,KAAKK,sBACAA,eAAiB,WAErBZ,8BACAiB,sBAOTI,2BACWd,KAAKK,eAAeU,KAAKlB,yCAOJ,OAAxBG,KAAKK,gBAAmD,IAAxBL,KAAKQ,QAAQQ,aAG5CX,eAAiBL,KAAKQ,QAAQS,kBAEzBpB,UAAYG,KAAKc,0BACjBjB,UAAUG,KAAKK,gBACvB,MAAOa,YACAb,eAAeQ,SAASK,MAAMC,sBAC/BD,MAAMA"}
+10
View File
@@ -0,0 +1,10 @@
define("core/process_monitor",["exports","core/log","core/local/process_monitor/manager","core/local/process_monitor/loadingprocess","core/local/process_monitor/processqueue","core/templates"],(function(_exports,_log,_manager,_loadingprocess,_processqueue,_templates){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}
/**
* Process monitor includer.
*
* @module core/process_monitor
* @copyright 2022 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.processMonitor=void 0,_log=_interopRequireDefault(_log),_templates=_interopRequireDefault(_templates);let initialized=!1;const processMonitor={addLoadingProcess:function(definition){this.initProcessMonitor();return new _loadingprocess.LoadingProcess(_manager.manager,definition)},removeAllProcesses:function(){_manager.manager.getInitialStatePromise().then((()=>{_manager.manager.dispatch("removeAllProcesses")})).catch((()=>{_log.default.error("Cannot update process monitor.")}))},initProcessMonitor:async function(){if(initialized)return;initialized=!0;const container=null!==(_document$querySelect=document.querySelector("#page"))&&void 0!==_document$querySelect?_document$querySelect:document.body;var _document$querySelect;if(!document.getElementById("#processMonitor"))try{const{html:html,js:js}=await _templates.default.renderForPromise("core/local/process_monitor/monitor",{});_templates.default.appendNodeContents(container,html,js)}catch(error){_log.default.error("Cannot load the process monitor")}},getInitialStatePromise:function(){return _manager.manager.getInitialStatePromise()},createProcessQueue:async function(){processMonitor.initProcessMonitor();const processQueue=new _processqueue.ProcessQueue(_manager.manager);return await processMonitor.getInitialStatePromise(),processQueue}};_exports.processMonitor=processMonitor}));
//# sourceMappingURL=process_monitor.min.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"process_monitor.min.js","sources":["../src/process_monitor.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * Process monitor includer.\n *\n * @module core/process_monitor\n * @copyright 2022 Ferran Recio <[email protected]>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport log from 'core/log';\nimport {manager} from 'core/local/process_monitor/manager';\nimport {LoadingProcess} from 'core/local/process_monitor/loadingprocess';\nimport {ProcessQueue} from 'core/local/process_monitor/processqueue';\nimport Templates from 'core/templates';\n\nlet initialized = false;\n\n/**\n * Get the parent container.\n * @private\n * @return {HTMLelement} the process monitor container.\n */\nconst getParentContainer = () => {\n // The footer pop over depends on the theme.\n return document.querySelector(`#page`) ?? document.body;\n};\n\nexport const processMonitor = {\n /**\n * Adds a new process to the monitor.\n * @param {Object} definition the process definition\n * @param {String} definition.name the process name\n * @param {Number} definition.percentage the current percentage (0 - 100)\n * @param {String} definition.error the error message if any\n * @param {String} definition.url possible link url if any\n * @returns {LoadingProcess} the loading process\n */\n addLoadingProcess: function(definition) {\n this.initProcessMonitor();\n const process = new LoadingProcess(manager, definition);\n return process;\n },\n\n /**\n * Remove all processes form the current monitor.\n */\n removeAllProcesses: function() {\n manager.getInitialStatePromise().then(() => {\n manager.dispatch('removeAllProcesses');\n return;\n }).catch(() => {\n log.error(`Cannot update process monitor.`);\n });\n },\n\n /**\n * Initialize the process monitor.\n */\n initProcessMonitor: async function() {\n if (initialized) {\n return;\n }\n initialized = true;\n const container = getParentContainer();\n if (document.getElementById(`#processMonitor`)) {\n return;\n }\n try {\n const {html, js} = await Templates.renderForPromise('core/local/process_monitor/monitor', {});\n Templates.appendNodeContents(container, html, js);\n } catch (error) {\n log.error(`Cannot load the process monitor`);\n }\n },\n\n /**\n * Return the process monitor initial state promise.\n * @returns {Promise} Promise of the initial state fully loaded\n */\n getInitialStatePromise: function() {\n return manager.getInitialStatePromise();\n },\n\n /**\n * Load the load queue monitor.\n *\n * @return {Promise<ProcessQueue>} when the file uploader is ready to be used.\n */\n createProcessQueue: async function() {\n processMonitor.initProcessMonitor();\n const processQueue = new ProcessQueue(manager);\n await processMonitor.getInitialStatePromise();\n return processQueue;\n }\n};\n"],"names":["initialized","processMonitor","addLoadingProcess","definition","initProcessMonitor","LoadingProcess","manager","removeAllProcesses","getInitialStatePromise","then","dispatch","catch","error","async","container","document","querySelector","body","getElementById","html","js","Templates","renderForPromise","appendNodeContents","createProcessQueue","processQueue","ProcessQueue"],"mappings":";;;;;;;gLA6BIA,aAAc,QAYLC,eAAiB,CAU1BC,kBAAmB,SAASC,iBACnBC,4BACW,IAAIC,+BAAeC,iBAASH,aAOhDI,mBAAoB,4BACRC,yBAAyBC,MAAK,sBAC1BC,SAAS,yBAElBC,OAAM,kBACDC,4CAOZR,mBAAoBS,oBACZb,mBAGJA,aAAc,QACRc,wCAvCHC,SAASC,8EAA0BD,SAASE,KAF5B,8BA0CfF,SAASG,4CAIHC,KAACA,KAADC,GAAOA,UAAYC,mBAAUC,iBAAiB,qCAAsC,uBAChFC,mBAAmBT,UAAWK,KAAMC,IAChD,MAAOR,oBACDA,2CAQZJ,uBAAwB,kBACbF,iBAAQE,0BAQnBgB,mBAAoBX,iBAChBZ,eAAeG,2BACTqB,aAAe,IAAIC,2BAAapB,+BAChCL,eAAeO,yBACdiB"}
@@ -0,0 +1,58 @@
// 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/>.
/**
* Javascript events for the `process_monitor` module.
*
* @module core/local/process_monitor/events
* @copyright 2022 Ferran Recio <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since 4.2
*/
/**
* Events for the `core_editor` subsystem.
*
* @constant
* @property {String} processMonitorStateChange See {@link event:processMonitorStateChange}
*/
export const eventTypes = {
/**
* An event triggered when the monitor state has changed.
*
* @event processMonitorStateChange
*/
processMonitorStateChange: 'core_editor/contentRestored',
};
/**
* Trigger a state changed event.
*
* @method dispatchStateChangedEvent
* @param {Object} detail the full state
* @param {Object} target the custom event target (document if none provided)
* @param {Function} target.dispatchEvent the component dispatch event method.
*/
export function dispatchStateChangedEvent(detail, target) {
if (target === undefined) {
target = document;
}
target.dispatchEvent(new CustomEvent(
eventTypes.processMonitorStateChange,
{
bubbles: true,
detail: detail,
}
));
}
@@ -0,0 +1,211 @@
// 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/>.
/**
* The process wrapper class.
*
* This module is used to update a process in the process monitor.
*
* @module core/local/process_monitor/loadingprocess
* @class LoadingProcess
* @copyright 2022 Ferran Recio <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import log from 'core/log';
export class LoadingProcess {
/** @var {Map} editorUpdates the courses pending to be updated. */
processData = null;
/** @var {Object} extraData any extra process information to store. */
extraData = null;
/** @var {ProcessMonitorManager} manager the page monitor. */
manager = null;
/** @var {Function} finishedCallback the finished callback if any. */
finishedCallback = null;
/** @var {Function} removedCallback the removed callback if any. */
removedCallback = null;
/** @var {Function} errorCallback the error callback if any. */
errorCallback = null;
/**
* Class constructor
* @param {ProcessMonitorManager} manager the monitor manager
* @param {Object} definition the process definition data
*/
constructor(manager, definition) {
this.manager = manager;
// Add defaults.
this.processData = {
id: manager.generateProcessId(),
name: '',
percentage: 0,
url: null,
error: null,
finished: false,
...definition,
};
// Create a new entry.
this._dispatch('addProcess', this.processData);
}
/**
* Execute a monitor manager mutation when the state is ready.
*
* @private
* @param {String} action the mutation to dispatch
* @param {*} params the mutaiton params
*/
_dispatch(action, params) {
this.manager.getInitialStatePromise().then(() => {
this.manager.dispatch(action, params);
return;
}).catch(() => {
log.error(`Cannot update process monitor.`);
});
}
/**
* Define a finished process callback function.
* @param {Function} callback the callback function
*/
onFinish(callback) {
this.finishedCallback = callback;
}
/**
* Define a removed from monitor process callback function.
* @param {Function} callback the callback function
*/
onRemove(callback) {
this.removedCallback = callback;
}
/**
* Define a error process callback function.
* @param {Function} callback the callback function
*/
onError(callback) {
this.errorCallback = callback;
}
/**
* Set the process percentage.
* @param {Number} percentage
*/
setPercentage(percentage) {
this.processData.percentage = percentage;
this._dispatch('updateProcess', this.processData);
}
/**
* Stores extra information to the process.
*
* This method is used to add information like the course, the user
* or any other needed information.
*
* @param {Object} extraData any extra process information to store
*/
setExtraData(extraData) {
this.extraData = extraData;
}
/**
* Set the process error string.
*
* Note: set the error message will mark the process as finished.
*
* @param {String} error the string message
*/
setError(error) {
this.processData.error = error;
if (this.errorCallback !== null) {
this.errorCallback(this);
}
this.processData.finished = true;
if (this.finishedCallback !== null) {
this.finishedCallback(this);
}
this._dispatch('updateProcess', this.processData);
}
/**
* Rename the process
* @param {String} name the new process name
*/
setName(name) {
this.processData.name = name;
this._dispatch('updateProcess', this.processData);
}
/**
* Mark the process as finished.
*/
finish() {
this.processData.finished = true;
if (this.finishedCallback !== null) {
this.finishedCallback(this);
}
this._dispatch('updateProcess', this.processData);
}
/**
* Remove the process from the monitor.
*/
remove() {
if (this.removedCallback !== null) {
this.removedCallback(this);
}
this._dispatch('removeProcess', this.processData.id);
}
/**
* Returns the current rpocess data.
* @returns {Object} the process data
*/
getData() {
return {...this.processData};
}
/**
* Return the process name
* @return {String}
*/
get name() {
return this.processData.name;
}
/**
* Return the process internal id
* @return {Number}
*/
get id() {
return this.processData.id;
}
/**
* Return the process extra data.
* @return {*} whatever is in extra data
*/
get data() {
return this.extraData;
}
}
@@ -0,0 +1,182 @@
// 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/>.
/**
* The course file uploader.
*
* This module is used to upload files directly into the course.
*
* @module core/local/process_monitor/manager
* @copyright 2022 Ferran Recio <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import {Reactive} from 'core/reactive';
import {eventTypes, dispatchStateChangedEvent} from 'core/local/process_monitor/events';
const initialState = {
display: {
show: false,
},
queue: [],
};
/**
* The reactive file uploader class.
*
* As all the upload queues are reactive, any plugin can implement its own upload monitor.
*
* @module core/local/process_monitor/manager
* @class ProcessMonitorManager
* @copyright 2021 Ferran Recio <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class ProcessMonitorManager extends Reactive {
/**
* The next process id to use.
*
* @attribute nextId
* @type number
* @default 1
* @package
*/
nextId = 1;
/**
* Generate a unique process id.
* @return {number} a generated process Id
*/
generateProcessId() {
return this.nextId++;
}
}
/**
* @var {Object} mutations the monitor mutations.
*/
const mutations = {
/**
* Add a new process to the queue.
*
* @param {StateManager} stateManager the current state manager
* @param {Object} processData the upload id to finish
*/
addProcess: function(stateManager, processData) {
const state = stateManager.state;
stateManager.setReadOnly(false);
state.queue.add({...processData});
state.display.show = true;
stateManager.setReadOnly(true);
},
/**
* Remove a process from the queue.
*
* @param {StateManager} stateManager the current state manager
* @param {Number} processId the process id
*/
removeProcess: function(stateManager, processId) {
const state = stateManager.state;
stateManager.setReadOnly(false);
state.queue.delete(processId);
if (state.queue.size === 0) {
state.display.show = false;
}
stateManager.setReadOnly(true);
},
/**
* Update a process process to the queue.
*
* @param {StateManager} stateManager the current state manager
* @param {Object} processData the upload id to finish
* @param {Number} processData.id the process id
*/
updateProcess: function(stateManager, processData) {
if (processData.id === undefined) {
throw Error(`Missing process ID in process data`);
}
const state = stateManager.state;
stateManager.setReadOnly(false);
const queueItem = state.queue.get(processData.id);
if (!queueItem) {
throw Error(`Unkown process with id ${processData.id}`);
}
for (const [prop, propValue] of Object.entries(processData)) {
queueItem[prop] = propValue;
}
stateManager.setReadOnly(true);
},
/**
* Set the monitor show attribute.
*
* @param {StateManager} stateManager the current state manager
* @param {Boolean} show the show value
*/
setShow: function(stateManager, show) {
const state = stateManager.state;
stateManager.setReadOnly(false);
state.display.show = show;
if (!show) {
this.cleanFinishedProcesses(stateManager);
}
stateManager.setReadOnly(true);
},
/**
* Remove a processes from the queue.
*
* @param {StateManager} stateManager the current state manager
*/
removeAllProcesses: function(stateManager) {
const state = stateManager.state;
stateManager.setReadOnly(false);
state.queue.forEach((element) => {
state.queue.delete(element.id);
});
state.display.show = false;
stateManager.setReadOnly(true);
},
/**
* Clean all finished processes.
*
* @param {StateManager} stateManager the current state manager
*/
cleanFinishedProcesses: function(stateManager) {
const state = stateManager.state;
stateManager.setReadOnly(false);
state.queue.forEach((element) => {
if (element.finished && !element.error) {
state.queue.delete(element.id);
}
});
if (state.queue.size === 0) {
state.display.show = false;
}
stateManager.setReadOnly(true);
},
};
const manager = new ProcessMonitorManager({
name: `ProcessMonitor`,
eventName: eventTypes.processMonitorStateChange,
eventDispatch: dispatchStateChangedEvent,
mutations: mutations,
state: initialState,
});
export {manager};
@@ -0,0 +1,120 @@
// 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/>.
/**
* The file upload monitor component.
*
* @module core/local/process_monitor/monitor
* @class core/local/process_monitor/monitor
* @copyright 2022 Ferran Recio <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import Templates from 'core/templates';
import {BaseComponent} from 'core/reactive';
import {manager} from 'core/local/process_monitor/manager';
export default class extends BaseComponent {
/**
* Constructor hook.
*/
create() {
// Optional component name for debugging.
this.name = 'process_monitor';
// Default query selectors.
this.selectors = {
QUEUELIST: `[data-for="process-list"]`,
CLOSE: `[data-action="hide"]`,
};
// Default classes to toggle on refresh.
this.classes = {
HIDE: `d-none`,
};
}
/**
* Static method to create a component instance form the mustache template.
*
* @param {string} query the DOM main element query selector
* @param {object} selectors optional css selector overrides
* @return {this}
*/
static init(query, selectors) {
return new this({
element: document.querySelector(query),
reactive: manager,
selectors,
});
}
/**
* Initial state ready method.
*
* @param {Object} state the initial state
*/
stateReady(state) {
this._updateMonitor({state, element: state.display});
this.addEventListener(this.getElement(this.selectors.CLOSE), 'click', this._closeMonitor);
state.queue.forEach((element) => {
this._createListItem({state, element});
});
}
/**
* Return the component watchers.
*
* @returns {Array} of watchers
*/
getWatchers() {
return [
// State changes that require to reload some course modules.
{watch: `queue:created`, handler: this._createListItem},
{watch: `display:updated`, handler: this._updateMonitor},
];
}
/**
* Create a monitor item.
*
* @param {object} args the watcher arguments
* @param {object} args.element the item state data
*/
async _createListItem({element}) {
const {html, js} = await Templates.renderForPromise(
'core/local/process_monitor/process',
{...element}
);
const target = this.getElement(this.selectors.QUEUELIST);
Templates.appendNodeContents(target, html, js);
}
/**
* Create a monitor item.
*
* @param {object} args the watcher arguments
* @param {object} args.element the display state data
*/
_updateMonitor({element}) {
this.element.classList.toggle(this.classes.HIDE, element.show !== true);
}
/**
* Close the monitor.
*/
_closeMonitor() {
this.reactive.dispatch('setShow', false);
}
}
@@ -0,0 +1,115 @@
// 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/>.
/**
* The process motnitor's process reactive component.
*
* @module core/local/process_monitor/process
* @class core/local/process_monitor/process
* @copyright 2022 Ferran Recio <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import {BaseComponent} from 'core/reactive';
import {manager} from 'core/local/process_monitor/manager';
export default class extends BaseComponent {
/**
* Constructor hook.
*/
create() {
// Optional component name for debugging.
this.name = 'process_monitor_process';
// Default query selectors.
this.selectors = {
CLOSE: `[data-action="closeProcess"]`,
ERROR: `[data-for="error"]`,
PROGRESSBAR: `progress`,
NAME: `[data-for="name"]`,
};
// Default classes to toggle on refresh.
this.classes = {
HIDE: `d-none`,
};
this.id = this.element.dataset.id;
}
/**
* Static method to create a component instance form the mustache template.
*
* @param {string} query the DOM main element query selector
* @param {object} selectors optional css selector overrides
* @return {this}
*/
static init(query, selectors) {
return new this({
element: document.querySelector(query),
reactive: manager,
selectors,
});
}
/**
* Initial state ready method.
*
* @param {Object} state the initial state
*/
stateReady(state) {
this._refreshItem({state, element: state.queue.get(this.id)});
this.addEventListener(this.getElement(this.selectors.CLOSE), 'click', this._removeProcess);
}
/**
* Return the component watchers.
*
* @returns {Array} of watchers
*/
getWatchers() {
return [
{watch: `queue[${this.id}]:updated`, handler: this._refreshItem},
{watch: `queue[${this.id}]:deleted`, handler: this.remove},
];
}
/**
* Create a monitor item.
*
* @param {object} args the watcher arguments
* @param {object} args.element the item state data
*/
async _refreshItem({element}) {
const name = this.getElement(this.selectors.NAME);
name.innerHTML = element.name;
const progressbar = this.getElement(this.selectors.PROGRESSBAR);
progressbar.classList.toggle(this.classes.HIDE, element.finished);
progressbar.value = element.percentage;
const close = this.getElement(this.selectors.CLOSE);
close.classList.toggle(this.classes.HIDE, !element.error);
const error = this.getElement(this.selectors.ERROR);
error.innerHTML = element.error;
error.classList.toggle(this.classes.HIDE, !element.error);
}
/**
* Close the process.
*/
_removeProcess() {
this.reactive.dispatch('removeProcess', this.id);
}
}
@@ -0,0 +1,116 @@
// 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/>.
import {debounce} from 'core/utils';
import {LoadingProcess} from 'core/local/process_monitor/loadingprocess';
import log from 'core/log';
const TOASTSTIMER = 3000;
/**
* A process queue manager.
*
* Adding process to the queue will guarante process are executed in sequence.
*
* @module core/local/process_monitor/processqueue
* @class ProcessQueue
* @copyright 2022 Ferran Recio <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
export class ProcessQueue {
/** @var {Array} pending the pending queue. */
pending = [];
/** @var {LoadingProcess} current the current uploading process. */
currentProcess = null;
/**
* Class constructor.
* @param {ProcessMonitorManager} manager the monitor manager
*/
constructor(manager) {
this.manager = manager;
this.cleanFinishedProcesses = debounce(
() => manager.dispatch('cleanFinishedProcesses'),
TOASTSTIMER
);
}
/**
* Adds a new pending upload to the queue.
* @param {String} processName the process name
* @param {Function} processor the execution function
*/
addPending(processName, processor) {
const process = new LoadingProcess(this.manager, {name: processName});
process.setExtraData({
processor,
});
process.onFinish((uploadedFile) => {
if (this.currentProcess?.id !== uploadedFile.id) {
return;
}
this._discardCurrent();
});
this.pending.push(process);
this._continueProcessing();
}
/**
* Adds a new pending upload to the queue.
* @param {String} processName the file info
* @param {String} errorMessage the file processor
*/
addError(processName, errorMessage) {
const process = new LoadingProcess(this.manager, {name: processName});
process.setError(errorMessage);
}
/**
* Discard the current process and execute the next one if any.
*/
_discardCurrent() {
if (this.currentProcess) {
this.currentProcess = null;
}
this.cleanFinishedProcesses();
this._continueProcessing();
}
/**
* Return the current file uploader.
* @return {FileUploader}
*/
_currentProcessor() {
return this.currentProcess.data.processor;
}
/**
* Continue the queue processing if no current process is defined.
*/
async _continueProcessing() {
if (this.currentProcess !== null || this.pending.length === 0) {
return;
}
this.currentProcess = this.pending.shift();
try {
const processor = this._currentProcessor();
await processor(this.currentProcess);
} catch (error) {
this.currentProcess.setError(error.message);
log.error(error);
}
}
}
+109
View File
@@ -0,0 +1,109 @@
// 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/>.
/**
* Process monitor includer.
*
* @module core/process_monitor
* @copyright 2022 Ferran Recio <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import log from 'core/log';
import {manager} from 'core/local/process_monitor/manager';
import {LoadingProcess} from 'core/local/process_monitor/loadingprocess';
import {ProcessQueue} from 'core/local/process_monitor/processqueue';
import Templates from 'core/templates';
let initialized = false;
/**
* Get the parent container.
* @private
* @return {HTMLelement} the process monitor container.
*/
const getParentContainer = () => {
// The footer pop over depends on the theme.
return document.querySelector(`#page`) ?? document.body;
};
export const processMonitor = {
/**
* Adds a new process to the monitor.
* @param {Object} definition the process definition
* @param {String} definition.name the process name
* @param {Number} definition.percentage the current percentage (0 - 100)
* @param {String} definition.error the error message if any
* @param {String} definition.url possible link url if any
* @returns {LoadingProcess} the loading process
*/
addLoadingProcess: function(definition) {
this.initProcessMonitor();
const process = new LoadingProcess(manager, definition);
return process;
},
/**
* Remove all processes form the current monitor.
*/
removeAllProcesses: function() {
manager.getInitialStatePromise().then(() => {
manager.dispatch('removeAllProcesses');
return;
}).catch(() => {
log.error(`Cannot update process monitor.`);
});
},
/**
* Initialize the process monitor.
*/
initProcessMonitor: async function() {
if (initialized) {
return;
}
initialized = true;
const container = getParentContainer();
if (document.getElementById(`#processMonitor`)) {
return;
}
try {
const {html, js} = await Templates.renderForPromise('core/local/process_monitor/monitor', {});
Templates.appendNodeContents(container, html, js);
} catch (error) {
log.error(`Cannot load the process monitor`);
}
},
/**
* Return the process monitor initial state promise.
* @returns {Promise} Promise of the initial state fully loaded
*/
getInitialStatePromise: function() {
return manager.getInitialStatePromise();
},
/**
* Load the load queue monitor.
*
* @return {Promise<ProcessQueue>} when the file uploader is ready to be used.
*/
createProcessQueue: async function() {
processMonitor.initProcessMonitor();
const processQueue = new ProcessQueue(manager);
await processMonitor.getInitialStatePromise();
return processQueue;
}
};
@@ -0,0 +1,57 @@
{{!
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/local/process_monitor/monitor
Template to render the global reactive debug panel.
Classes required for JS:
* none
Data attributes required for JS:
* none
Example context (json):
{
"title": "Some title"
}
}}
<div
id="process-monitor-{{uniqid}}"
class="popover-process-monitor d-none shadow"
>
<div class="modal-header " data-region="header">
<h5 class="modal-title" data-region="title">
{{#title}} {{title}} {{/title}}
{{^title}} {{#str}} progress, core {{/str}} {{/title}}
</h5>
<button
type="button"
class="close"
data-action="hide"
aria-label="{{#str}}closebuttontitle, core{{/str}}"
>
<span aria-hidden="true">×</span>
</button>
</div>
<div data-for="process-list" class="process-list"></div>
</div>
{{#js}}
require(['core/local/process_monitor/monitor'], function(component) {
component.init('#process-monitor-{{uniqid}}');
});
{{/js}}
@@ -0,0 +1,57 @@
{{!
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/local/process_monitor/process
Template to render a process inside the process monitor.
Example context (json):
{
"id": 42,
"name": "Sample",
"percentage": 30,
"error": "Something goes wrong"
}
}}
<div
class="queue-process d-flex flex-column p-2"
data-for="queue-process"
data-id="{{id}}"
>
<div class="d-flex flex-row align-items-center">
<div class="p-2 uploadname text-truncate" data-for="name"> {{name}} </div>
<div class="ml-auto p-2 progressbar">
<progress value="{{percentage}}" max="100"></progress>
<button
type="button"
class="d-none close"
data-action="closeProcess"
aria-label="{{#str}}closebuttontitle, core{{/str}}"
>
<span aria-hidden="true">×</span>
</button>
</div>
</div>
<div class="d-none alert alert-danger" role="alert" data-for="error">
{{error}}
</div>
</div>
{{#js}}
require(['core/local/process_monitor/process'], function(component) {
component.init('[data-for="queue-process"][data-id="{{id}}"]');
});
{{/js}}
+1
View File
@@ -48,3 +48,4 @@ $breadcrumb-divider-rtl: "◀" !default;
@import "moodle/primarynavigation";
@import "moodle/secondarynavigation";
@import "moodle/tertiarynavigation";
@import "moodle/process-monitor";
@@ -0,0 +1,30 @@
// The popover process monitor.
$popover-process-monitor-right: 2rem !default;
$popover-process-monitor-bottom: 5rem !default;
$popover-process-monitor-max-height: 30vh !default;
$popover-process-monitor-width: 350px !default;
$popover-process-monitor-scroll-bg: $gray-100 !default;
.popover-process-monitor {
position: fixed;
right: $popover-process-monitor-right;
bottom: $popover-process-monitor-bottom;
width: $popover-process-monitor-width;
background-color: $white;
@include border-radius();
border: $border-width solid $border-color;
.process-list {
max-height: $popover-process-monitor-max-height;
overflow: auto;
@include thin-scrolls($popover-process-monitor-scroll-bg);
}
.queue-process {
border-bottom: 1px solid $gray-200;
}
.queue-process:last-child {
border-bottom: 0;
}
}
+28
View File
@@ -22024,6 +22024,34 @@ div.editor_atto_toolbar button .icon {
.tertiary-navigation {
display: none; } }
.popover-process-monitor {
position: fixed;
right: 2rem;
bottom: 5rem;
width: 350px;
background-color: #fff;
border-radius: 0.5rem;
border: 1px solid #dee2e6; }
.popover-process-monitor .process-list {
max-height: 30vh;
overflow: auto;
scrollbar-width: thin;
scrollbar-color: #6a737b #f8f9fa; }
.popover-process-monitor .process-list::-webkit-scrollbar {
width: 12px; }
.popover-process-monitor .process-list::-webkit-scrollbar-track {
background: #f8f9fa; }
.popover-process-monitor .process-list::-webkit-scrollbar-thumb {
background-color: #6a737b;
border-radius: 20px;
border: 3px solid #f8f9fa; }
.popover-process-monitor .process-list::-webkit-scrollbar-thumb:hover {
background-color: #495057; }
.popover-process-monitor .queue-process {
border-bottom: 1px solid #e9ecef; }
.popover-process-monitor .queue-process:last-child {
border-bottom: 0; }
body {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale; }
+28
View File
@@ -21970,6 +21970,34 @@ div.editor_atto_toolbar button .icon {
.tertiary-navigation {
display: none; } }
.popover-process-monitor {
position: fixed;
right: 2rem;
bottom: 5rem;
width: 350px;
background-color: #fff;
border-radius: 0.25rem;
border: 1px solid #dee2e6; }
.popover-process-monitor .process-list {
max-height: 30vh;
overflow: auto;
scrollbar-width: thin;
scrollbar-color: #6a737b #f8f9fa; }
.popover-process-monitor .process-list::-webkit-scrollbar {
width: 12px; }
.popover-process-monitor .process-list::-webkit-scrollbar-track {
background: #f8f9fa; }
.popover-process-monitor .process-list::-webkit-scrollbar-thumb {
background-color: #6a737b;
border-radius: 20px;
border: 3px solid #f8f9fa; }
.popover-process-monitor .process-list::-webkit-scrollbar-thumb:hover {
background-color: #495057; }
.popover-process-monitor .queue-process {
border-bottom: 1px solid #e9ecef; }
.popover-process-monitor .queue-process:last-child {
border-bottom: 0; }
body {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale; }